Compare commits
448 Commits
Author | SHA1 | Date |
---|---|---|
dependabot[bot] | 4f4015a4f7 | |
dependabot[bot] | b73ce7bd2e | |
David Humphrey | 3279c2d65a | |
Ben Heidemann | e135305f25 | |
David Humphrey | 0b26979839 | |
Ben Heidemann | 7efc4cdc1d | |
David Humphrey | 4112d072b5 | |
David Humphrey | ee26394ead | |
David Humphrey | 9f8804f716 | |
David Humphrey | 621b6567e0 | |
David Humphrey | 4acd064a2b | |
David Humphrey | b283023286 | |
Arun Bose | 24697a3ed9 | |
dependabot[bot] | 5f872f0f24 | |
bcheidemann | 77abca991a | |
bcheidemann | 49ace4b2fb | |
bcheidemann | d055d8042e | |
bcheidemann | 576831370f | |
bcheidemann | 594ab2621f | |
Ben Heidemann | 75a2cc86cf | |
Ben Heidemann | a4b3726520 | |
Ben Heidemann | 21e7a4750a | |
Ben Heidemann | aa2df3a17c | |
Ben Heidemann | bd15462f6e | |
Ben Heidemann | 78ae19fbad | |
Ben Heidemann | b3caddcfec | |
Ben Heidemann | 11e05e131b | |
Ben Heidemann | fb100b165a | |
Ben Heidemann | e94543471b | |
Ben Heidemann | 20d4d5e720 | |
Ben Heidemann | 0295d19a7f | |
Ben Heidemann | 365a7d23a3 | |
Ben Heidemann | ea5e48b48b | |
Ben Heidemann | e82374ae2b | |
Ben Heidemann | 5bd05287d4 | |
Ben Heidemann | 1c34abf009 | |
Ben Heidemann | 8b4b0a6140 | |
Ben Heidemann | e489409b9c | |
Ben Heidemann | 0db08e31bb | |
Ben Heidemann | 1f02edf5b3 | |
Ben Heidemann | 708c84fc63 | |
Ben Heidemann | 4e45701b51 | |
Ben Heidemann | fe9ed6a648 | |
Ben Heidemann | 81ab65b95c | |
Ben Heidemann | 582352f754 | |
Ben Heidemann | 3b9fafc53e | |
Ben Heidemann | bfb50bf608 | |
Ben Heidemann | ce56539190 | |
Ben Heidemann | 0f812fc6a0 | |
Ben Heidemann | 767c83706b | |
Ben Heidemann | 6a20ac2c3f | |
Ben Heidemann | 2bcf7e560b | |
Ben Heidemann | d040763a73 | |
Ben Heidemann | fd90f32d4c | |
dependabot[bot] | e18104a60d | |
Ben Heidemann | 7bd6e5f4e1 | |
Ben Heidemann | 90db749ee5 | |
Ben Heidemann | f9c5473c49 | |
Ben Heidemann | f5ad682fd7 | |
Ben Heidemann | 7b1c3e85ce | |
Ben Heidemann | 51afeeaf54 | |
Ben Heidemann | cef6adfc37 | |
Ben Heidemann | 6590cb79bd | |
Ben Heidemann | 3e88aeca92 | |
Ben Heidemann | f8c9732be9 | |
David Humphrey | 200171d2a5 | |
David Humphrey | 095f78498f | |
David Humphrey | 5cf2495079 | |
David Humphrey | aacc8061cf | |
Ben Heidemann | c378288819 | |
Ben Heidemann | d1054753a6 | |
bcheidemann | 887ed9318a | |
Ben Heidemann | ce076729f6 | |
bcheidemann | a2151cab25 | |
bcheidemann | 3a8a59d362 | |
bcheidemann | 15be384940 | |
bcheidemann | 6027376efa | |
bcheidemann | 8b57d299f4 | |
bcheidemann | d6b29226aa | |
bcheidemann | da65e06e3c | |
bcheidemann | aa152955b5 | |
bcheidemann | d264113e0d | |
bcheidemann | ce809c4ac9 | |
dependabot[bot] | 3061328459 | |
David Humphrey | c0a41d67ff | |
David Humphrey | 4941d10e13 | |
David Humphrey | 07a00a3f77 | |
bcheidemann | 0f5fe64ae8 | |
bcheidemann | 7ec1fed51f | |
bcheidemann | a2f7ee044b | |
bcheidemann | 7415e45867 | |
bcheidemann | 986ad37597 | |
bcheidemann | 9c669564b2 | |
bcheidemann | 9d03788c1c | |
Ben Heidemann | a60d2b4cfc | |
bcheidemann | 417a4e97dd | |
bcheidemann | 4d4b6bf3f3 | |
bcheidemann | f12f01dca0 | |
bcheidemann | 128e10dc13 | |
bcheidemann | ceed84f377 | |
bcheidemann | 2a581726c6 | |
David Humphrey | 75f2a70c7b | |
David Humphrey | 9db879374d | |
David Humphrey | 98683fe261 | |
SillyFreak | f1fc53d88f | |
Rachael Scotchmer | 4aae53839a | |
dcoull | 94d5ff8795 | |
Charles M Drani | ee56794601 | |
rscotchmer | 914ba8b473 | |
Oleksii Polovyi | 2a4fa0f0fd | |
ApolllonDev | 9d3f220d92 | |
Abdirahman Guled | 3d10d6431c | |
Adel El Masery | 7ab6f5e7c6 | |
kwchan19 | 9487e8ecc6 | |
hoaianhkhang | e6f8ef2251 | |
Paul Moon | f2201e7a74 | |
otkach | 3447ec9c8a | |
David Humphrey (:humph) david.humphrey@senecacollege.ca | fe17870a8f | |
David Humphrey | f4e0dce8d3 | |
Priyanka Dhiman | 34e5580a66 | |
Aqeel Parpia | c515865fb3 | |
Aqeel Parpia | 4887cc725f | |
Aqeel Parpia | 3471848b39 | |
David Humphrey | 9b3e567dd1 | |
David Humphrey | bf18093661 | |
David Humphrey | fa3186d322 | |
David Humphrey | 8a2e64f9ea | |
Alvin Valdez | 0ec51a198d | |
Oleksii Polovyi | 317cee5636 | |
cmchumak | 3c51bbb24f | |
Arash N | 9d4b264479 | |
Sahib Arora | b5e1d9a82b | |
Jacob Adach | da1aad5667 | |
Violet-XiaoWeiHuang | 05057c45d2 | |
Rachael Scotchmer | 4ba9eded4c | |
Woosle Park | 8eaaeeed17 | |
Brandon Wissmann | 3b4bf42604 | |
Harsh Patel | 701849eb5a | |
DavidLi119 | 87513997a2 | |
bonbon12 | 1e660a9d07 | |
Iryna Thompson | 3f619fdcb8 | |
Vincent Logozzo | 43bba422d9 | |
Priyam Brahmbhatt | 7e46fcde8f | |
OlenaVyshnevska | fc545ee3c7 | |
Yuansheng Lu | e5481efa9b | |
Vladimir Rozin | 5918cf6ae7 | |
jatinkumar | 0380a8153c | |
Nathaniel Ngo | 4a39dcc2f0 | |
andrewkoung | 4de0bbfafd | |
Priyanka Dhiman | 7cdef6d963 | |
David Humphrey | 26b47ee094 | |
David Humphrey | 5f10cc2fde | |
David Humphrey | 4e9593f5e8 | |
David Humphrey | 01d76ba0df | |
David Humphrey | dd7536827b | |
David Humphrey | c6e1d51612 | |
David Humphrey | c0acdb97d6 | |
David Humphrey | f4ff2e9ed9 | |
David Humphrey (:humph) david.humphrey@senecacollege.ca | 85a8c21dc1 | |
David Humphrey (:humph) david.humphrey@senecacollege.ca | 9c13a2d248 | |
David Humphrey | 1ad81f9bae | |
David Humphrey (:humph) david.humphrey@senecacollege.ca | bc861bfd4a | |
David Humphrey (:humph) david.humphrey@senecacollege.ca | 11c91acdcf | |
David Humphrey | ace4222a82 | |
David Humphrey | 7301024382 | |
David Humphrey | 6ec8cd6191 | |
David Humphrey | 52baa2523d | |
David Humphrey | d4bfcd71c2 | |
David Humphrey | 4f427ed8d7 | |
David Humphrey | 22731267a9 | |
David Humphrey | 9cb4ff7c0a | |
David Humphrey | 3dea503289 | |
David Humphrey | 909a66efd7 | |
David Humphrey | ea236cf43b | |
David Humphrey | f053c738b8 | |
David Humphrey | 3afbcca8b3 | |
David Humphrey (:humph) david.humphrey@senecacollege.ca | b4c3d1f1ae | |
David Humphrey (:humph) david.humphrey@senecacollege.ca | 3f400d17bf | |
David Humphrey (:humph) david.humphrey@senecacollege.ca | 1f3b9e79bf | |
David Humphrey (:humph) david.humphrey@senecacollege.ca | bfe4385a83 | |
David Humphrey (:humph) david.humphrey@senecacollege.ca | 5cc21c72fb | |
Julia Yatsenko | 9f7f93776e | |
David Humphrey (:humph) david.humphrey@senecacollege.ca | 69758613db | |
David Humphrey (:humph) david.humphrey@senecacollege.ca | 1ae7a220b4 | |
David Humphrey (:humph) david.humphrey@senecacollege.ca | b5fe85caaa | |
David Humphrey | d0178539f5 | |
David Humphrey (:humph) david.humphrey@senecacollege.ca | f738cbc17d | |
David Humphrey (:humph) david.humphrey@senecacollege.ca | b7312bfa1e | |
Vince | b0809272a1 | |
Vince | fb76c0ec1e | |
qwang135 | 9a7d3514d8 | |
Yuecheng Wu | 848cc7d3de | |
David Humphrey | ba188169d6 | |
mordax | 62cbe11a93 | |
mordax | ef80713c33 | |
Alexei Kozachenko | 0eb7066a86 | |
David Humphrey | 158f6241b6 | |
Dan Lin | 4a5d8c1222 | |
kwkofler | 443b582038 | |
David Humphrey | 83bd64fd58 | |
imedqq | 783e7a9e67 | |
imedqq | 2aa6cf2b5f | |
David Humphrey | acef2c93b0 | |
Deepanjali Gerangal | 94fd5ba829 | |
Deepanjali Gerangal | e3a285ae54 | |
Petr Bouianov | 1775c24d37 | |
chen yuzhou | 3d7ff3e08d | |
chen yuzhou | 1e1c704f7e | |
chen yuzhou | e1124993ba | |
David Humphrey | 97d2d1bfe3 | |
David Humphrey | 2841b2eba7 | |
Mera-Gangapersaud | 0352dad66c | |
David Humphrey | 307fc3e32c | |
David Humphrey | 33339b81d7 | |
Adam | 7e941bb304 | |
Adam | db237e617b | |
Adam | bb9dd2e67c | |
Pooch11 | 0a1aa0fecc | |
Pooch11 | d894a4cf16 | |
David Humphrey | 16e6b3e1c9 | |
Dragomegak | af3815c54b | |
Dragomegak | 0748951ca8 | |
Dragomegak | 265f0b1d0d | |
David Humphrey | 1c450ae8e7 | |
PopeSpaceous | 0395c4beae | |
jagmeetb | 1abcb0369b | |
David Humphrey | d1dd5fef61 | |
Josh Mayers | e85880dc8b | |
David Humphrey | 97a6004091 | |
chen yuzhou | 0e75661b4b | |
David Humphrey | 5218057d80 | |
David Humphrey | 61a1f5e020 | |
Deepanjali Gerangal | 0f93a04e40 | |
David Humphrey (:humph) david.humphrey@senecacollege.ca | 0f94c476e7 | |
David Humphrey (:humph) david.humphrey@senecacollege.ca | 0ed97864ec | |
David Humphrey (:humph) david.humphrey@senecacollege.ca | f5678ebe20 | |
kwkofler | 97fb82769a | |
David Humphrey (:humph) david.humphrey@senecacollege.ca | 7d196763f8 | |
kwkofler | 6a5d9073f3 | |
David Humphrey (:humph) david.humphrey@senecacollege.ca | a107fe21d7 | |
David Humphrey (:humph) david.humphrey@senecacollege.ca | d7945d745d | |
David Humphrey (:humph) david.humphrey@senecacollege.ca | 923b999e89 | |
David Humphrey | 9acedc2beb | |
David Humphrey | 395406609d | |
David Humphrey (:humph) david.humphrey@senecacollege.ca | cc6e3f7edf | |
David Humphrey | 43d2632069 | |
David Humphrey | 7ab229a64c | |
yatsenko-julia | f57c7330d3 | |
David Humphrey | 89cfef0f07 | |
David Humphrey | d1cf1286cb | |
David Humphrey | 2135ee17e9 | |
David Humphrey | 725bfbf30c | |
yuzhouChen | 89626107cc | |
Susan Truong | a25d71b524 | |
Alan K | d1afe9719d | |
Deepanjali Gerangal | 400b2c3c88 | |
David Humphrey | ea95badc5b | |
Rikku-x | a5fc0e699a | |
Alexander Ponomaroff | ce030b91fa | |
Deepanjali Gerangal | 5568c27bec | |
Volodymyr Klymenko | bfcb5a6a94 | |
ThomasNolte | 3bbabfcb4a | |
Shawn Pang | 1eab5f0ffd | |
yatsenko-julia | 73f0f19bb9 | |
bblarney | 707d404db0 | |
VictorKubrak | a447b2dd45 | |
AHKol | 8aa8dda4d6 | |
Stephen Ward | 08b0b3001e | |
Stephen Ward | 675773b92a | |
Janice | 41510e7da4 | |
jrkong | b1c4f04f83 | |
Stephen Ward | ee67cb39de | |
Stephen Ward | e11c101600 | |
Stephen Ward | 7a6a4160e6 | |
pynnl | c33f22b464 | |
Julia McGeoghan | 389dedd3c0 | |
yuzhouChen | 8504cc7e2a | |
yevseytsev | 27659d45da | |
Stephen Truong | 934ef8bfa7 | |
Ruihui Yan | c7ea45a18b | |
Sean | 009821290f | |
Thanh Nguyen | 2f3c384868 | |
giantpanpan | beea03dbae | |
ywpark1 | 3e0da99040 | |
dleung25 | 02bd6d8a62 | |
Jeffrey Espiritu | 7e27c8be2c | |
Casva | e77361107e | |
Daniel Bogomazov | 87230ce1e3 | |
Huda Al Dallal | 2e2e2f9d64 | |
rdittrich97 | 0354c7e13e | |
y2s82 | 1156f420c4 | |
Nick Skuybeda | 86b6b2a907 | |
Chaya Danzinger | a8759b1e38 | |
David Humphrey | c84dd14f5e | |
rhayes2 | 31b3c40d74 | |
rhayes2 | 7a716e033b | |
rhayes2 | 395de63751 | |
rhayes2 | 7f155a0f40 | |
rhayes2 | 294685d6cb | |
Stephen Ward | 62b85d1442 | |
Stephen Ward | 30752c9109 | |
Stephen Ward | f3a7170133 | |
Stephen Ward | 833ac62c45 | |
Stephen Ward | 78b3452d5d | |
Stephen Ward | e7811eb53b | |
rhayes2 | 546b4567d2 | |
Corey James | e3a1187ef9 | |
rhayes2 | 499c72daaf | |
Stephen Ward | add00ce563 | |
David Humphrey (:humph) david.humphrey@senecacollege.ca | 5e4de6b698 | |
0xazure | fd3de6be2c | |
Dmytro | 353290a08f | |
Alan K | de45918cbc | |
David Humphrey | 53f5b0c50b | |
David Humphrey | 7798b1cb87 | |
David Humphrey | 0da0e60194 | |
David Humphrey | dc8fa4cb96 | |
David Humphrey | 3a44c1756a | |
David Humphrey | 8d553b78e0 | |
David Humphrey | b92151cdc5 | |
David Humphrey | bcf4d9834a | |
David Humphrey | ed11b01234 | |
David Humphrey | 0980ec4608 | |
David Humphrey | a31044cc8d | |
David Humphrey | 454d5bc801 | |
David Humphrey | d41885e9d0 | |
David Humphrey | 0e00af661a | |
David Humphrey | f89b616fdc | |
David Humphrey | cbcddee3be | |
David Humphrey | 1a738dd2bb | |
David Humphrey | 9e08d96c96 | |
David Humphrey | 980d5d0917 | |
David Humphrey | a8e373e4b9 | |
David Humphrey | 86e57d60db | |
David Humphrey | 054094c852 | |
David Humphrey | cafff52c3a | |
David Humphrey | 1c7b40c895 | |
David Humphrey | 7258a2913e | |
David Humphrey | bb8743eda1 | |
David Humphrey | 94e6a98cb8 | |
David Humphrey | ee8e2a665a | |
David Humphrey | a93ae13c99 | |
David Humphrey | ab87c7f14a | |
David Humphrey | 49931722cc | |
David Humphrey | 3e1c3deb51 | |
David Humphrey | a8c3fa85db | |
David Humphrey | 51c81edb5b | |
David Humphrey | 8e900a3a88 | |
David Humphrey | 4a066064f4 | |
David Humphrey | e5b6026b91 | |
David Humphrey | a900d8df6d | |
David Humphrey | 2ad0d0eb8b | |
Alan K | 04f9c57cd8 | |
David Humphrey | e155c2d1a0 | |
David Humphrey | 9832807dd1 | |
David Humphrey (:humph) david.humphrey@senecacollege.ca | 65bbc17ccb | |
David Humphrey (:humph) david.humphrey@senecacollege.ca | c15145cc03 | |
David Humphrey (:humph) david.humphrey@senecacollege.ca | d61c61aab8 | |
Alan K | 2e90fdae0c | |
David Humphrey | 9ded3ea1eb | |
David Humphrey | 3da99f0133 | |
David Humphrey | 31cd579682 | |
David Humphrey | b7ecae4af1 | |
David Humphrey | 46698f30e2 | |
David Humphrey | c71a91f72f | |
David Humphrey | eee8200e23 | |
David Humphrey | 1087371fc4 | |
David Humphrey | 4e73ef8d9b | |
David Humphrey | 9508833b37 | |
David Humphrey | e77a8bacd3 | |
David Humphrey | bf1d0e41d6 | |
David Humphrey | c526445a43 | |
David Humphrey | 0aaaeacd1a | |
David Humphrey | ee412d4abe | |
David Humphrey | 9244e9be6e | |
David Humphrey | 2efb956411 | |
David Humphrey | 93633da622 | |
David Humphrey | 2e627cfe5b | |
Alan K | b026537f09 | |
Alan K | f771f48e8b | |
David Humphrey | 2a4674d11f | |
David Humphrey (:humph) david.humphrey@senecacollege.ca | 18a470e264 | |
David Humphrey (:humph) david.humphrey@senecacollege.ca | 3650b798ed | |
David Humphrey | 6d3cec89ee | |
Santiago Castro | 68cbd54524 | |
David Humphrey | c8f217fbcd | |
orthographic-pedant | 3ddb877276 | |
Alan K | 525e11f203 | |
Alan K | ddc41f49a5 | |
Alan K | 9f625c0d0f | |
Alan K | 8325b5a9d7 | |
Alan K | d66114e20c | |
Kieran Sedgwick | 9101db2344 | |
Alan K | 4de8bc4b81 | |
Alan K | 047536c013 | |
David Humphrey | 6549a98b6a | |
David Humphrey (:humph) david.humphrey@senecacollege.ca | 03896eef05 | |
David Humphrey (:humph) david.humphrey@senecacollege.ca | c85fa1851f | |
Kieran Sedgwick | f338991ace | |
Kieran Sedgwick | 1df35e1834 | |
David Humphrey (:humph) david.humphrey@senecacollege.ca | a13297449d | |
David Humphrey | f143ea0ce7 | |
David Humphrey (:humph) david.humphrey@senecacollege.ca | 17fb8993c3 | |
Gideon Thomas | e9c4cb6d7a | |
Alan K | 78f9ce824d | |
Kieran Sedgwick | 4dc2bce59d | |
Kieran Sedgwick | 0f63c0988c | |
Kieran Sedgwick | af0698a859 | |
Alan K | db4274f1d2 | |
Kieran Sedgwick | 4b01317189 | |
Alan K | 28e981c0c1 | |
Gideon Thomas | 67dba10d49 | |
Alan K | 841360de71 | |
Gideon Thomas | 8c4de99a98 | |
Alan K | 2347e12db2 | |
Alan K | fbb55c86e6 | |
Alan K | db23ae2bd2 | |
Alan K | ef9df89517 | |
Alan K | 4e31547270 | |
Alan K | 830c6adde6 | |
Alan K | 8c0f8eaac8 | |
Alan K | 2b6d54cbf4 | |
Alan K | 5798beb99f | |
Alan K | 9e3f48d783 | |
Alan K | f812ad406d | |
Alan K | 68070d0768 | |
gideonthomas | d28100c422 | |
yoavgurevich | 32265e92d7 | |
yoavgurevich | d720f65d23 | |
yoavgurevich | b4efa7d2a4 | |
Alan K | e39129a7f4 | |
Alan K | f160f540a0 | |
Alan K | d6c77abfa0 | |
David Humphrey (:humph) david.humphrey@senecacollege.ca | 21b602ef66 | |
Alan K | 76d256f744 | |
David Humphrey (:humph) david.humphrey@senecacollege.ca | 4cdcca1a8d | |
Gideon Thomas | 286074f519 | |
Alan K | b1cf2d7dfb | |
Gideon Thomas | 91f7bf0319 | |
Alan K | 1283535d75 | |
Kieran Sedgwick | 63f5fdeb6c | |
Kieran Sedgwick | 0d4110ff6f | |
Alan K | f8ca6e8208 | |
Alan K | 9612cbcbdc | |
Kieran Sedgwick | 483d3fef72 | |
Kieran Sedgwick | a05faf0b2a | |
Kieran Sedgwick | 3178ad9a45 | |
Kieran Sedgwick | c4c13c1535 |
|
@ -0,0 +1 @@
|
||||||
|
tests/dist/
|
|
@ -0,0 +1,36 @@
|
||||||
|
{
|
||||||
|
"env": {
|
||||||
|
"browser": true,
|
||||||
|
"commonjs": true,
|
||||||
|
"es6": true,
|
||||||
|
"mocha": true,
|
||||||
|
"node": true
|
||||||
|
},
|
||||||
|
"extends": "eslint:recommended",
|
||||||
|
"parserOptions": {
|
||||||
|
"ecmaVersion": 2017,
|
||||||
|
"sourceType": "module"
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"indent": [
|
||||||
|
"error",
|
||||||
|
2
|
||||||
|
],
|
||||||
|
"linebreak-style": [
|
||||||
|
"error",
|
||||||
|
"unix"
|
||||||
|
],
|
||||||
|
"quotes": [
|
||||||
|
"error",
|
||||||
|
"single"
|
||||||
|
],
|
||||||
|
"semi": [
|
||||||
|
"error",
|
||||||
|
"always"
|
||||||
|
],
|
||||||
|
"eqeqeq": [
|
||||||
|
"error",
|
||||||
|
"always"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
* text=auto eol=lf
|
|
@ -0,0 +1,27 @@
|
||||||
|
name: node-js-ci
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ${{matrix.os}}
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||||
|
node: ['14', '16']
|
||||||
|
|
||||||
|
name: Node ${{ matrix.node }} on ${{ matrix.os }}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Test
|
||||||
|
uses: actions/setup-node@v2.4.1
|
||||||
|
with:
|
||||||
|
node-version: ${{ matrix.node }}
|
||||||
|
- run: npm install
|
||||||
|
- run: npm test
|
|
@ -1,5 +1,13 @@
|
||||||
node_modules
|
node_modules
|
||||||
bower_components
|
|
||||||
.env
|
.env
|
||||||
*~
|
*~
|
||||||
dist/filer-issue225.js
|
.vscode
|
||||||
|
.idea
|
||||||
|
|
||||||
|
# Parcel build dirs
|
||||||
|
.cache
|
||||||
|
tests/dist
|
||||||
|
|
||||||
|
# nyc code coverage
|
||||||
|
.nyc_output
|
||||||
|
coverage
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
{
|
||||||
|
"hooks": {
|
||||||
|
"before:init": ["npm run test"],
|
||||||
|
"before:bump": ["npm run build"]
|
||||||
|
},
|
||||||
|
"git": {
|
||||||
|
"pushRepo": "git@github.com:filerjs/filer.git",
|
||||||
|
"tagName": "v${version}"
|
||||||
|
},
|
||||||
|
"npm": {
|
||||||
|
"publish": true
|
||||||
|
},
|
||||||
|
"github": {
|
||||||
|
"pushRepo": "git@github.com:filerjs/filer.git",
|
||||||
|
"release": true
|
||||||
|
}
|
||||||
|
}
|
31
.travis.yml
31
.travis.yml
|
@ -1,9 +1,32 @@
|
||||||
|
sudo: false
|
||||||
language: node_js
|
language: node_js
|
||||||
|
|
||||||
node_js:
|
node_js:
|
||||||
- "0.10"
|
- "lts/*"
|
||||||
before_install: npm install -g grunt-cli
|
|
||||||
|
cache:
|
||||||
|
directories:
|
||||||
|
- "node_modules"
|
||||||
|
|
||||||
|
os:
|
||||||
|
- linux
|
||||||
|
- osx
|
||||||
|
|
||||||
|
# Setup headless Firefox and Chrome support
|
||||||
|
# https://docs.travis-ci.com/user/gui-and-headless-browsers/#Using-the-Chrome-addon-in-the-headless-mode
|
||||||
|
env:
|
||||||
|
- MOZ_HEADLESS=1
|
||||||
|
addons:
|
||||||
|
chrome: stable
|
||||||
|
firefox: latest
|
||||||
|
before_install:
|
||||||
|
- google-chrome-stable --headless --disable-gpu --remote-debugging-port=9222 http://localhost &
|
||||||
|
|
||||||
|
after_success:
|
||||||
|
- npm install -g codecov
|
||||||
|
- npm run coverage
|
||||||
|
- codecov
|
||||||
|
|
||||||
notifications:
|
notifications:
|
||||||
email: false
|
email: false
|
||||||
irc: "irc.mozilla.org#filer"
|
irc: "irc.mozilla.org#filer"
|
||||||
env:
|
|
||||||
- "FILER_UPSTREAM_URI=\"default\" FILER_UPSTREAM_BRANCH=\"default\" FILER_UPSTREAM_REMOTE_NAME=\"default\""
|
|
||||||
|
|
2
AUTHORS
2
AUTHORS
|
@ -5,3 +5,5 @@ Barry Tulchinsky <barry.tulchinsky@gmail.com> (@btulchinsky)
|
||||||
Kieran Sedgwick <kieran.sedgwick@gmail.com> (@sedge)
|
Kieran Sedgwick <kieran.sedgwick@gmail.com> (@sedge)
|
||||||
Yoav Gurevich <ygurevich@ymail.com>
|
Yoav Gurevich <ygurevich@ymail.com>
|
||||||
Gideon Thomas <r.gideonthomas@gmail.com>
|
Gideon Thomas <r.gideonthomas@gmail.com>
|
||||||
|
Abdirahman Guled <aguled2@myseneca.ca>
|
||||||
|
Ben Heidemann <ben@heidemann.co.uk>
|
|
@ -9,78 +9,67 @@ message and I'll update it.
|
||||||
|
|
||||||
## Setup
|
## Setup
|
||||||
|
|
||||||
The Filer build system is based on [grunt](http://gruntjs.com/). To get a working build system
|
To get a working build system do the following:
|
||||||
do the following:
|
|
||||||
|
|
||||||
```
|
```
|
||||||
npm install
|
npm install
|
||||||
npm install -g grunt-cli
|
|
||||||
```
|
```
|
||||||
|
|
||||||
You can now run the following grunt tasks:
|
Next, make sure you have installed Chrome and Firefox, which are needed for
|
||||||
* `grunt jshint` will run [JSHint](http://www.jshint.com/) on your code (do this before submitting a pull request) to catch errors
|
running headless versions of the tests with `npm test`.
|
||||||
* `grunt develop` will create a single file version of the library for testing in `dist/idbfs.js`
|
|
||||||
* `grunt release` like `develop` but will also create a minified version of the library in `dist/idbfs.min.js`
|
|
||||||
* `grunt test` or `grunt test-node` will run [JSHint](http://www.jshint.com/) on your code and the test suite in the context of `nodejs`
|
|
||||||
* `grunt test-browser` will run [JSHint](http://www.jshint.com/) and start a localhost server on port `1234`. Navigating to `localhost:1234/tests/index.html` will run the test suite in the context of the browser. **NOTE:** When finished, you will have to manually shut off the server by pressing `cmd/ctrl`+`c` in the same terminal session you ran `grunt test-browser`.
|
|
||||||
|
|
||||||
Once you've done some hacking and you'd like to have your work merged, you'll need to
|
|
||||||
make a pull request. If you're patch includes code, make sure to check that all the
|
|
||||||
unit tests pass, including any new tests you wrote. Finally, make sure you add yourself
|
|
||||||
to the `AUTHORS` file.
|
|
||||||
|
|
||||||
=======
|
|
||||||
### Releasing a new version
|
|
||||||
=======
|
|
||||||
|
|
||||||
`grunt publish` will:
|
|
||||||
|
|
||||||
* Run the `grunt release` task
|
|
||||||
* Bump `bower.json` & `package.json` version numbers according to a [Semver](http://semver.org/) compatible scheme (see ["How to Publish"](#how-to-publish) below)
|
|
||||||
* Create a git tag at the new version number
|
|
||||||
* Create a release commit including `dist/filer.js`, `dist/filer.min.js`, `bower.json` and `package.json`
|
|
||||||
* Push tag & commit to `origin/develop`
|
|
||||||
* Update the `gh-pages` branch with the contents of the `develop` branch
|
|
||||||
* Force push the `gh-pages` branch to `origin/gh-pages`
|
|
||||||
* Publish the new version of the module to NPM
|
|
||||||
|
|
||||||
#### How to configure
|
|
||||||
1. Copy `env.sample` to `.env`
|
|
||||||
2. Modify as needed, or leave alone for defaults
|
|
||||||
|
|
||||||
#### How to Publish
|
|
||||||
`grunt publish` can be run in four ways:
|
|
||||||
|
|
||||||
1. `grunt publish` - does a patch (x.x.X) bump
|
|
||||||
2. `grunt publish:patch` - also does a patch (x.x.X) bump
|
|
||||||
3. `grunt publish:minor` - does a minor (x.X.x) bump
|
|
||||||
4. `grunt publish:major` - does a major (X.x.x) bump
|
|
||||||
|
|
||||||
The user *must* be on their local `develop` branch before running any form of `grunt publish`, or else the task will fail loudly.
|
|
||||||
|
|
||||||
=======
|
|
||||||
## Tests
|
## Tests
|
||||||
|
|
||||||
Tests are writting using [Mocha](http://visionmedia.github.io/mocha/) and [Chai](http://chaijs.com/api/bdd/).
|
Tests are written using [Mocha](https://mochajs.org/) and [Chai](http://chaijs.com/api/bdd/).
|
||||||
You can run the tests in your browser by running `grunt test-browser` and opening the `tests` directory @ `http://localhost:1234/tests`, or in a nodejs context by running `grunt test`.
|
There are a number of ways to run the tests. The preferred way is:
|
||||||
|
|
||||||
|
```
|
||||||
|
npm test
|
||||||
|
```
|
||||||
|
|
||||||
|
This will do a build, run the linting, start a server, and load the tests into
|
||||||
|
headless versions of Chrome and Firefox.
|
||||||
|
|
||||||
|
If you want more control over how tests are run, you can use other scripts:
|
||||||
|
|
||||||
|
* Linting is done via `npm run lint` or `npm run eslint`, both of which will run `eslint` on the `src` and `tests` directories. You can also use `npm run lint:fix` or `npm run eslint:fix`, which will run `eslint` with `--fix` on the `src` and `tests` directories, automatically fixing minor issues. Linting is run by default as part of `npm test`
|
||||||
|
|
||||||
|
* In headless versions of Chrome and Firefox using `npm test`. A report at the end will tell you what happened with each browser. Browser tests are preferred because they also test our providers (e.g., IndexedDB). They do take longer to run. You can also use `npm run karma-mocha-firefox` or `npm run karma-mocha-chrome` to run the tests in only one of the two headless browsers.
|
||||||
|
|
||||||
|
* In node.js using the Memory provider using `npm run test:node`. These run much faster, but don't run all tests (e.g., providers, watches).
|
||||||
|
|
||||||
|
* If you need to debug browser tests, or want to run them in a different browser, use `npm run test:manual`, which will start a server and you can point your browser to [http://localhost:1234](http://localhost:1234). Running the tests this way will also automatically watch your files, and hot-reload your code and tests, which is useful for debugging and trial/error testing.
|
||||||
|
|
||||||
|
* If you need to debug node.js test runs, you can do so using `npm run test:node-debug`. Then, open Chrome and browse to [chrome://inspect](chrome://inspect) and click on your tests in the inspector. The easiest way to get a breakpoint is to manually add a `debugger` keyword to your test code where you want the tests to stop.
|
||||||
|
|
||||||
|
> Tip: you can add `skip()` to any `it()` or `describe()` in Mocha to skip a test, or `only()` to have only that test run. For example: `describe.skip(...)` or `it.only(...)`.
|
||||||
|
|
||||||
|
* If you want to run migration tests separate from unit tests, use `npm run test:migrations`. Migration tests run at the end of a typical `npm test` run. If you need to create a new migration test, see [`tools/fs-image.js`](tools/fs-image.js) for details on how to generate a filesystem image, and [tests/filesystems/images/README.md](tests/filesystems/images/README.md) for more docs.
|
||||||
|
|
||||||
|
* If you want to manually generate coverage info for the tests, use `npm run coverage`. This is done automatically in Travis, so you shouldn't need to do it. You can see [https://codecov.io/gh/filerjs/filer](https://codecov.io/gh/filerjs/filer) for detailed reports.
|
||||||
|
|
||||||
There are a number of configurable options for the test suite, which are set via query string params.
|
There are a number of configurable options for the test suite, which are set via query string params.
|
||||||
First, you can choose which filer source to use (i.e., src/, dist/filer-test.js, dist/filer.js or dist/filer.min.js).
|
First, you can choose which filer source to use (i.e., src/, dist/filer-test.js, dist/filer.js or dist/filer.min.js). The default is to use what is in /dist/filer-test.js, and you can switch to other versions like so:
|
||||||
The default is to use what is in /dist/filer-test.js, and you can switch to other versions like so:
|
|
||||||
* tests/index.html?filer-dist/filer.js
|
* tests/index.html?filer-dist/filer.js
|
||||||
* tests/index.html?filer-dist/filer.min.js
|
* tests/index.html?filer-dist/filer.min.js
|
||||||
* tests/index.html?filer-src/filer.js (from src)
|
* tests/index.html?filer-src/filer.js (from src)
|
||||||
|
|
||||||
Second, you can specify which provider to use for all non-provider specific tests (i.e., most of the tests).
|
Second, you can specify which provider to use for all non-provider specific tests (i.e., most of the tests).
|
||||||
The default provider is `Memory`, and you can switch it like so:
|
The default provider is `Memory`, and you can switch it like so:
|
||||||
|
|
||||||
* tests/index.html?filer-provider=memory
|
* tests/index.html?filer-provider=memory
|
||||||
* tests/index.html?filer-provider=indexeddb
|
* tests/index.html?filer-provider=indexeddb
|
||||||
* tests/index.html?filer-provider=websql
|
|
||||||
|
|
||||||
If you're writing tests, make sure you write them in the same style as existing tests, which are
|
If you're writing tests, make sure you write them in the same style as existing tests, which are
|
||||||
provider agnostic. See `tests/lib/test-utils.js` and how it gets used in various tests as
|
provider agnostic. See [`tests/lib/test-utils.js`](tests/lib/test-utils.js) and how it gets used
|
||||||
an example.
|
in various tests as an example.
|
||||||
|
|
||||||
## Communication
|
## Releases
|
||||||
|
|
||||||
If you'd like to talk to someone about the project, you can reach us on irc.mozilla.org in the #filer or #mofodev channel. Look for "ack" or "humph".
|
In order to perform a release, you'll need commit access to the main Filer repo,
|
||||||
|
as well as access to publish to Filer's npm module. To do a release:
|
||||||
|
|
||||||
|
1. Make sure you have a .env file, with your `GITHUB_TOKEN` included. See [`env.sample`](env.sample) for more info on how to create one.
|
||||||
|
1. Login to the `npm` registry if you haven't already using `npm login`
|
||||||
|
1. Run `npm run release`. Releases are done interactively using [release-it](https://www.npmjs.com/package/release-it), and our config is defined in [`.release-it.json`](.release-it.json).
|
||||||
|
|
2
LICENSE
2
LICENSE
|
@ -1,4 +1,4 @@
|
||||||
Copyright (c) 2013, Alan Kligman
|
Copyright (c) 2013 - 2019 Alan Kligman and the Filer contributors
|
||||||
All rights reserved.
|
All rights reserved.
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||||
|
|
591
README.md
591
README.md
|
@ -1,26 +1,26 @@
|
||||||
[![NPM](https://nodei.co/npm/filer.png?downloads=true&stars=true)](https://nodei.co/npm/filer/)
|
[![NPM](https://nodei.co/npm/filer.png?downloads=true&stars=true)](https://nodei.co/npm/filer/)
|
||||||
|
|
||||||
[![Build Status](https://secure.travis-ci.org/filerjs/filer.png?branch=develop)](http://travis-ci.org/filerjs/filer)
|
[![Build Status](https://secure.travis-ci.org/filerjs/filer.png?branch=develop)](http://travis-ci.org/filerjs/filer) [![codecov](https://codecov.io/gh/filerjs/filer/branch/master/graph/badge.svg)](https://codecov.io/gh/filerjs/filer)
|
||||||
|
|
||||||
###Filer
|
### Filer
|
||||||
|
|
||||||
Filer is a POSIX-like file system interface for node.js and browser-based JavaScript.
|
Filer is a drop-in replacement for node's `fs` module, a POSIX-like file system
|
||||||
|
for browsers.
|
||||||
|
|
||||||
###Compatibility
|
### Compatibility
|
||||||
|
|
||||||
Filer is known to work in the following browsers/versions, with the specified [Storage Providers](#providers):
|
Filer uses [IndexedDB](https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API)
|
||||||
|
and is [known to work in the following browsers/versions](https://caniuse.com/#feat=indexeddb):
|
||||||
|
|
||||||
* node.js: v0.10.*+
|
* node.js: v0.10.*+
|
||||||
* IE: 10+ (IndexedDB)
|
* IE: 10+
|
||||||
* Firefox: 26+ (IndexedDB)
|
* Edge: 12+
|
||||||
* Chrome: 31+ (IndexedDB, WebSQL)
|
* Firefox: 10+
|
||||||
* Safari: 7.0+ (WebSQL)
|
* Chrome: 23+
|
||||||
* Opera: 19+ (IndexedDB, WebSQL)
|
* Safari: 10+
|
||||||
* iOS: 3.2+ (WebSQL)
|
* Opera: 15+
|
||||||
* Android Browser: 2.1-4.4 (WebSQL), 4.4+ (IndexedDB)
|
* iOS: 10+
|
||||||
|
* Android Browser: 4.4+
|
||||||
NOTE: if you're interested in maximum compatibility, use the `Fallback` provider instead of `Default`.
|
|
||||||
See the section on [Storage Providers](#providers).
|
|
||||||
|
|
||||||
### Contributing
|
### Contributing
|
||||||
|
|
||||||
|
@ -30,16 +30,15 @@ Want to join the fun? We'd love to have you! See [CONTRIBUTING](https://github.c
|
||||||
|
|
||||||
Filer can be obtained in a number of ways:
|
Filer can be obtained in a number of ways:
|
||||||
|
|
||||||
1. npm - `npm install filer`
|
1. Via npm: `npm install filer`
|
||||||
2. bower - `bower install filer`
|
1. Via unpkg: `<script src="https://unpkg.com/filer"></script>` or specify a version directly, for example: [https://unpkg.com/filer@1.0.1/dist/filer.min.js](https://unpkg.com/filer@1.0.1/dist/filer.min.js)
|
||||||
3. download pre-built versions: [filer.js](https://raw.github.com/filerjs/filer/develop/dist/filer.js), [filer.min.js](https://raw.github.com/filerjs/filer/develop/dist/filer.min.js)
|
|
||||||
|
|
||||||
### Loading and Usage
|
### Loading and Usage
|
||||||
|
|
||||||
Filer is built as a UMD module and can therefore be loaded as a CommonJS or AMD module, or used via the global.
|
Filer is built as a UMD module and can therefore be loaded as a CommonJS or AMD module, or used via the global.
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
// Option 1: Filer loaded via require() in node/browserify
|
// Option 1: Filer loaded via require()
|
||||||
var Filer = require('filer');
|
var Filer = require('filer');
|
||||||
|
|
||||||
// Option 2: Filer loaded via RequireJS
|
// Option 2: Filer loaded via RequireJS
|
||||||
|
@ -55,13 +54,84 @@ requirejs(['filer'], function(Filer) {...}
|
||||||
var Filer = window.Filer;
|
var Filer = window.Filer;
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Webpack Plugin
|
||||||
|
|
||||||
|
Filer can be used as a drop-in replacement for the node.js [fs](http://nodejs.org/api/fs.html) and
|
||||||
|
[path](http://nodejs.org/api/path.html) modules. For convenience, filer provides a webpack plugin which
|
||||||
|
will shim the desired node.js functionality. This plugin can be used by inserting the following into
|
||||||
|
your webpack config:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// webpack.config.js
|
||||||
|
var { FilerWebpackPlugin } = require('filer/webpack');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
plugins: [
|
||||||
|
new FilerWebpackPlugin(),
|
||||||
|
],
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
**NOTE**
|
||||||
|
|
||||||
|
Previously it was recommended to access the `FilerWebpackPlugin` class by importing the main filer module. This was depracated due [this issue](https://github.com/filerjs/filer/issues/790). For anyone using ***filer version 1.4.0 or earlier***, please import the plugin class like this:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
var FilerWebpackPlugin = require('filer/src/webpack-plugin');
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
You can then import the node.js [fs](http://nodejs.org/api/fs.html) and [path](http://nodejs.org/api/path.html)
|
||||||
|
modules as normal and `FilerWebpackPlugin` will ensure that webpack will resolve references to these modules to
|
||||||
|
the appropriate filer shims. You will then be able to use these modules as normal (with the exception of the
|
||||||
|
synchronous fs methods e.g. `mkdirSync()`).
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
import fs from 'fs';
|
||||||
|
import path from 'path';
|
||||||
|
```
|
||||||
|
|
||||||
|
The filer webpack plugin will, by default, shim the [fs](http://nodejs.org/api/fs.html) and
|
||||||
|
[path](http://nodejs.org/api/path.html) modules. However, it's behaviour can be customised by passing an
|
||||||
|
options object.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// webpack.config.js
|
||||||
|
module.exports = {
|
||||||
|
plugins: [
|
||||||
|
new FilerWebpackPlugin({
|
||||||
|
// Options
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The following options can be passed to the filer webpack plugin:
|
||||||
|
|
||||||
|
| Option | Type | Optional | Default | Description |
|
||||||
|
|---------------|---------|----------|--------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
|
| filerDir | string | yes | '\<rootDir\>/node_modules/filer' | The directory in which filer is installed. |
|
||||||
|
| shimsDir | string | yes | '\<rootDir\>/node_modules/filer/shims' | The directory in which the shims are installed. |
|
||||||
|
| fsProviderDir | string | yes | '\<rootDir\>/node_modules/filer/shims/providers' | The directory in which the shims are located. This option is required when using a custom provider. |
|
||||||
|
| shimFs | boolean | yes | true | Should the fs module be shimmed. |
|
||||||
|
| shimPath | boolean | yes | true | Should the path module be shimmed. |
|
||||||
|
| fsProvider | string | yes | 'default' | The file system provider to use. Should be one of 'default', 'indexeddb', 'memory', 'custom'. The 'default' option is equivalent to 'indexeddb'. |
|
||||||
|
|
||||||
|
NOTE: '\<rootDir\>' will be resolved to the current working directory.
|
||||||
|
|
||||||
|
Though filer also exposes the Buffer object, it is left up to the user to shim this as appropriate. This is because filer offers
|
||||||
|
no custom implementation. Currently, filer uses the [node-libs-browser](https://github.com/webpack/node-libs-browser) Buffer implementation
|
||||||
|
internally, though any faithful implementation of the [node.js Buffer object](http://nodejs.org/api/buffer.html) should play nicely
|
||||||
|
with filer.
|
||||||
|
|
||||||
### Getting Started
|
### Getting Started
|
||||||
|
|
||||||
Filer is as close to the node.js [fs module](http://nodejs.org/api/fs.html) as possible,
|
Filer is as close to the node.js [fs module](http://nodejs.org/api/fs.html) as possible,
|
||||||
with the following differences:
|
with the following differences:
|
||||||
|
|
||||||
* No synchronous versions of methods (e.g., `mkdir()` but not `mkdirSync()`).
|
* No synchronous versions of methods (e.g., `mkdir()` but not `mkdirSync()`).
|
||||||
* No permissions (e.g., no `chown()`, `chmod()`, etc.).
|
|
||||||
* No support for stream-based operations (e.g., `fs.ReadStream`, `fs.WriteStream`).
|
* No support for stream-based operations (e.g., `fs.ReadStream`, `fs.WriteStream`).
|
||||||
|
|
||||||
Filer has other features lacking in node.js (e.g., swappable backend
|
Filer has other features lacking in node.js (e.g., swappable backend
|
||||||
|
@ -78,18 +148,32 @@ they are invoked. Ensure proper ordering by chaining operations in callbacks.
|
||||||
To create a new file system or open an existing one, create a new `FileSystem`
|
To create a new file system or open an existing one, create a new `FileSystem`
|
||||||
instance. By default, a new [IndexedDB](https://developer.mozilla.org/en/docs/IndexedDB)
|
instance. By default, a new [IndexedDB](https://developer.mozilla.org/en/docs/IndexedDB)
|
||||||
database is created for each file system. The file system can also use other
|
database is created for each file system. The file system can also use other
|
||||||
backend storage providers, for example [WebSQL](http://en.wikipedia.org/wiki/Web_SQL_Database)
|
backend storage providers, for example `Memory`. See the section on [Storage Providers](#providers).
|
||||||
or even RAM (i.e., for temporary storage). See the section on [Storage Providers](#providers).
|
|
||||||
|
|
||||||
```javascript
|
<a name="overviewExample"></a>
|
||||||
var fs = new Filer.FileSystem();
|
|
||||||
fs.open('/myfile', 'w+', function(err, fd) {
|
```js
|
||||||
if (err) throw err;
|
const { fs, path } = require('filer');
|
||||||
fs.close(fd, function(err) {
|
|
||||||
if (err) throw err;
|
fs.mkdir('/docs', (err) => {
|
||||||
fs.stat('/myfile', function(err, stats) {
|
if (err) {
|
||||||
if (err) throw err;
|
return console.error('Unable to create /docs dir', err);
|
||||||
console.log('stats: ' + JSON.stringify(stats));
|
}
|
||||||
|
|
||||||
|
const filename = path.join('/docs', 'first.txt');
|
||||||
|
const data = 'Hello World!\n';
|
||||||
|
|
||||||
|
fs.writeFile(filename, data, (err) => {
|
||||||
|
if (err) {
|
||||||
|
return console.error('Unable to write /docs/first.txt', err);
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.stat(filename, (err, stats) => {
|
||||||
|
if (err) {
|
||||||
|
return console.error('Unable to stat /docs/first.txt', err);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Stats for /docs/first.txt:', stats);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -98,7 +182,7 @@ fs.open('/myfile', 'w+', function(err, fd) {
|
||||||
For a complete list of `FileSystem` methods and examples, see the [FileSystem Instance Methods](#FileSystemMethods)
|
For a complete list of `FileSystem` methods and examples, see the [FileSystem Instance Methods](#FileSystemMethods)
|
||||||
section below.
|
section below.
|
||||||
|
|
||||||
Filer also supports node's Path module. See the [Filer.Path](#FilerPath) section below.
|
Filer also includes node's `path` and `Buffer` modules. See the [Filer.Path](#FilerPath) and [Filer.Buffer](#FilerBuffer) sections below.
|
||||||
|
|
||||||
In addition, common shell operations (e.g., rm, touch, cat, etc.) are supported via the
|
In addition, common shell operations (e.g., rm, touch, cat, etc.) are supported via the
|
||||||
`FileSystemShell` object, which can be obtained from, and used with a `FileSystem`.
|
`FileSystemShell` object, which can be obtained from, and used with a `FileSystem`.
|
||||||
|
@ -110,11 +194,29 @@ Like node.js, callbacks for methods that accept them are optional but suggested
|
||||||
you omit the callback, errors will be thrown as exceptions). The first callback parameter is
|
you omit the callback, errors will be thrown as exceptions). The first callback parameter is
|
||||||
reserved for passing errors. It will be `null` if no errors occurred and should always be checked.
|
reserved for passing errors. It will be `null` if no errors occurred and should always be checked.
|
||||||
|
|
||||||
|
#### Support for Promises
|
||||||
|
|
||||||
|
The Promise based API mimics the way node [implements](https://nodejs.org/api/fs.html#fs_fs_promises_api) them. Both `Shell` and `FileSystem` now have a `promises` property, which gives access to Promise based versions of methods in addition to the regular callback style methods. Method names are identical to their callback counterparts with the difference that instead of receiving a final argument as a callback, they return a Promise that is resolved or rejected based on the success of method execution.
|
||||||
|
|
||||||
|
See example below:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const fs = new Filer.FileSystem().promises;
|
||||||
|
fs.writeFile('/myfile', 'some data')
|
||||||
|
.then(() => fs.stat('/myfile'))
|
||||||
|
.then(stats => { console.log(`stats: ${JSON.stringify(stats)}`); })
|
||||||
|
.catch(err => { console.error(err); });
|
||||||
|
```
|
||||||
|
|
||||||
#### Filer.FileSystem(options, callback) constructor
|
#### Filer.FileSystem(options, callback) constructor
|
||||||
|
|
||||||
File system constructor, invoked to open an existing file system or create a new one.
|
In most cases, using `Filer.fs` will be sufficient, and provide a working filesystem.
|
||||||
Accepts two arguments: an `options` object, and an optional `callback`. The `options`
|
However, if you need more control over the filesystem, you can also use the `FileSystem`
|
||||||
object can specify a number of optional arguments, including:
|
constructor, invoked to open an existing file system or create a new one.
|
||||||
|
|
||||||
|
`Filer.FileSystem()` It accepts two arguments: an `options` object, and an optional
|
||||||
|
`callback` function. The `options` object can specify a number of optional arguments,
|
||||||
|
including:
|
||||||
|
|
||||||
* `name`: the name of the file system, defaults to `'"local'`
|
* `name`: the name of the file system, defaults to `'"local'`
|
||||||
* `flags`: an Array of one or more flags to use when creating/opening the file system:
|
* `flags`: an Array of one or more flags to use when creating/opening the file system:
|
||||||
|
@ -147,17 +249,19 @@ NOTE: if the optional callback argument is not passed to the `FileSystem` constr
|
||||||
operations done on the resulting file system will be queued and run in sequence when
|
operations done on the resulting file system will be queued and run in sequence when
|
||||||
it becomes ready.
|
it becomes ready.
|
||||||
|
|
||||||
####Filer.FileSystem.providers - Storage Providers<a name="providers"></a>
|
#### Filer.FileSystem.providers - Storage Providers<a name="providers"></a>
|
||||||
|
|
||||||
Filer can be configured to use a number of different storage providers. The provider object encapsulates all aspects
|
Filer can be configured to use a number of different storage providers. The provider object encapsulates all aspects of data access, making it possible to swap in different backend storage options. There are currently 2 providers to choose from:
|
||||||
of data access, making it possible to swap in different backend storage options. There are currently 4 different
|
|
||||||
providers to choose from:
|
|
||||||
|
|
||||||
* `FileSystem.providers.IndexedDB()` - uses IndexedDB
|
* `FileSystem.providers.IndexedDB()` - uses IndexedDB
|
||||||
* `FileSystem.providers.WebSQL()` - uses WebSQL
|
if necessary
|
||||||
* `FileSystem.providers.Fallback()` - attempts to use IndexedDB if possible, falling-back to WebSQL if necessary
|
|
||||||
* `FileSystem.providers.Memory()` - uses memory (not suitable for data that needs to survive the current session)
|
* `FileSystem.providers.Memory()` - uses memory (not suitable for data that needs to survive the current session)
|
||||||
|
|
||||||
|
**NOTE**: previous versions of Filer also supported `FileSystem.providers.WebSQL()` and
|
||||||
|
`FileSystem.providers.Fallback()`, which could be used in browsers that supported
|
||||||
|
WebSQL but not IndexedDB. [WebSQL has been deprecated](https://www.w3.org/TR/webdatabase/),
|
||||||
|
and this functionality was removed in `v1.0.0`. If for some reason you still need it, use [`v0.0.44`](https://github.com/filerjs/filer/releases/tag/v0.0.44).
|
||||||
|
|
||||||
You can choose your provider when creating a `FileSystem`:
|
You can choose your provider when creating a `FileSystem`:
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
|
@ -167,45 +271,62 @@ var providers = FileSystem.providers;
|
||||||
// Example 1: Use the default provider (currently IndexedDB)
|
// Example 1: Use the default provider (currently IndexedDB)
|
||||||
var fs1 = new FileSystem();
|
var fs1 = new FileSystem();
|
||||||
|
|
||||||
// Example 2: Explicitly use IndexedDB
|
// Example 2: Use the Memory provider
|
||||||
var fs2 = new FileSystem({ provider: new providers.IndexedDB() });
|
var fs2 = new FileSystem({ provider: new providers.Memory() });
|
||||||
|
|
||||||
// Example 3: Use one of IndexedDB or WebSQL, whichever is supported
|
|
||||||
var fs3 = new FileSystem({ provider: new providers.Fallback() });
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Every provider has an `isSupported()` method, which returns `true` if the browser supports this provider:
|
Every provider has an `isSupported()` method, which returns `true` if the browser supports this provider:
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
if( Filer.FileSystem.providers.WebSQL.isSupported() ) {
|
if( Filer.FileSystem.providers.IndexedDB.isSupported() ) {
|
||||||
// WebSQL provider will work in current environment...
|
// IndexedDB provider will work in current environment...
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
You can also write your own provider if you need a different backend. See the code in `src/providers` for details.
|
You can also write your own provider if you need a different backend. See the code in `src/providers` for details.
|
||||||
|
|
||||||
A number of other providers have been written, including:
|
#### Filer.Buffer<a name="FilerBuffer"></a>
|
||||||
|
|
||||||
* node.js fs provider: https://github.com/humphd/filer-fs
|
|
||||||
* node.js Amazon S3 provider: https://github.com/alicoding/filer-s3
|
|
||||||
|
|
||||||
####Filer.Buffer<a name="FilerBuffer"></a>
|
|
||||||
|
|
||||||
When reading and writing data, Filer follows node.js and uses [`Buffer`](http://nodejs.org/api/buffer.html).
|
When reading and writing data, Filer follows node.js and uses [`Buffer`](http://nodejs.org/api/buffer.html).
|
||||||
When in a node.js environment, native `Buffer`s can be used, or Filer.Buffer, which is a shortcut
|
When in a node.js environment, native `Buffer`s can be used, or Filer.Buffer, which is a shortcut
|
||||||
to node's `Buffer`. In a browser, you can use also use `Filer.Buffer`.
|
to node's `Buffer`. In a browser, you can use also use `Filer.Buffer`.
|
||||||
|
|
||||||
NOTE: a `Filer.Buffer` in a browser is really an augmented `Uint8Array` (i.e., the node `Buffer` api
|
NOTE: a `Filer.Buffer` in a browser is really an augmented `Uint8Array` (i.e., the node `Buffer` api
|
||||||
methods are added to the instance). See https://github.com/feross/buffer for more details. Additionally, unlike native `Buffer`, `Filer.Buffer`'s constructor can accept `ArrayBuffer` objects, which will be interpreted as `Uint8Array`s.
|
methods are added to the instance). See https://github.com/feross/buffer for more details.
|
||||||
|
|
||||||
####Filer.Path<a name="FilerPath"></a>
|
NOTE: `Filer.Buffer` currently includes the older, deprecated [constructor functions](https://nodejs.org/api/buffer.html#buffer_new_buffer_array), but these will be removed
|
||||||
|
at some point. You are encouraged to switch to use the newer class methods `Buffer.from()`
|
||||||
|
and `Buffer.alloc()`. See the [node.js Buffer docs](https://nodejs.org/api/buffer.html).
|
||||||
|
|
||||||
The node.js [path module](http://nodejs.org/api/path.html) is available via the `Filer.Path` object. It is
|
```js
|
||||||
identical to the node.js version with the following differences:
|
/* Deprecated - see https://nodejs.org/api/buffer.html#buffer_new_buffer_array */
|
||||||
* No notion of a current working directory in `resolve` (the root dir is used instead)
|
new Buffer(array)
|
||||||
|
new Buffer(arrayBuffer[, byteOffset[, length]])
|
||||||
|
new Buffer(buffer)
|
||||||
|
new Buffer(string[, encoding])
|
||||||
|
new Buffer(size)
|
||||||
|
|
||||||
|
/* Use Instead */
|
||||||
|
Buffer.from(array)
|
||||||
|
Buffer.from(arrayBuffer[, byteOffset[, length]])
|
||||||
|
Buffer.from(buffer)
|
||||||
|
Buffer.from(string[, encoding])
|
||||||
|
Buffer.alloc(size)
|
||||||
|
Buffer.allocUnsafe(size)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Filer.Path<a name="FilerPath"></a>
|
||||||
|
|
||||||
|
The node.js [path module](http://nodejs.org/api/path.html) is available via `Filer.path` or
|
||||||
|
`Filer.Path` (both are supported for historical reasons, and to match node). The Filer `path`
|
||||||
|
module is identical to the node.js version (see [https://github.com/browserify/path-browserify](https://github.com/browserify/path-browserify)), with the following differences:
|
||||||
|
|
||||||
|
* The CWD always defaults to `/`
|
||||||
|
* No support for Windows style paths (assume you are on a POSIX system)
|
||||||
|
* Additional utility methods (see below)
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
var path = Filer.Path;
|
var path = Filer.path;
|
||||||
var dir = path.dirname('/foo/bar/baz/asdf/quux');
|
var dir = path.dirname('/foo/bar/baz/asdf/quux');
|
||||||
// dir is now '/foo/bar/baz/asdf'
|
// dir is now '/foo/bar/baz/asdf'
|
||||||
|
|
||||||
|
@ -220,17 +341,38 @@ var newpath = path.join('/foo', 'bar', 'baz/asdf', 'quux', '..');
|
||||||
```
|
```
|
||||||
|
|
||||||
For more info see the docs in the [path module](http://nodejs.org/api/path.html) for a particular method:
|
For more info see the docs in the [path module](http://nodejs.org/api/path.html) for a particular method:
|
||||||
* `path.normalize(p)`
|
* `path.normalize(p)` - NOTE: Filer.Path.normalize does *not* add a trailing slash
|
||||||
* `path.join([path1], [path2], [...])`
|
* `path.join([path1], [path2], [...])`
|
||||||
* `path.resolve([from ...], to)`
|
* `path.resolve([from ...], to)`
|
||||||
* `path.relative(from, to)`
|
* `path.relative(from, to)`
|
||||||
* `path.dirname(p)`
|
* `path.dirname(p)`
|
||||||
* `path.basename(p, [ext])`
|
* `path.basename(p, [ext])` - NOTE: Filer.Path.basename will return `'/'` vs. `''`
|
||||||
* `path.extname(p)`
|
* `path.extname(p)`
|
||||||
* `path.sep`
|
* `path.sep`
|
||||||
* `path.delimiter`
|
* `path.delimiter`
|
||||||
|
|
||||||
####Filer.Errors<a name="Errors"></a>
|
Filer.Path also includes the following extra methods:
|
||||||
|
|
||||||
|
* `isNull(p)` returns `true` or `false` if the path contains a null character (`'\u0000'`)
|
||||||
|
* `addTrailing(p)` returns the path `p` with a single trailing slash added
|
||||||
|
* `removeTrailing(p)` returns the path `p` with trailing slash(es) removed
|
||||||
|
|
||||||
|
[As with node.js](https://nodejs.org/api/fs.html#fs_file_paths), all methods below that
|
||||||
|
accept a `path` argument as a `String` can also take a [`file://` URL](https://nodejs.org/api/fs.html#fs_url_object_support)
|
||||||
|
or a `Buffer`. For example, all of the following cases will work the same way with Filer:
|
||||||
|
|
||||||
|
```js
|
||||||
|
// 1. path as a String
|
||||||
|
fs.writeFile('/dir/file.txt', 'data', function(err) {...});
|
||||||
|
|
||||||
|
// 2. path as a URL
|
||||||
|
fs.writeFile(new URL('file:///dir/file.txt'), 'data', function(err) {...});
|
||||||
|
|
||||||
|
// 3. path as a Buffer
|
||||||
|
fs.writeFile(Buffer.from('/dir/file.txt'), 'data', function(err) {...});
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Filer.Errors<a name="Errors"></a>
|
||||||
|
|
||||||
The error objects used internally by Filer are also exposed via the `Filer.Errors` object. As much as possible
|
The error objects used internally by Filer are also exposed via the `Filer.Errors` object. As much as possible
|
||||||
these match their node.js counterparts, with a few Filer-specifc additions.
|
these match their node.js counterparts, with a few Filer-specifc additions.
|
||||||
|
@ -266,13 +408,17 @@ function callback(err) {
|
||||||
console.log(err.message);
|
console.log(err.message);
|
||||||
```
|
```
|
||||||
|
|
||||||
###FileSystem Instance Methods<a name="FileSystemMethods"></a>
|
### FileSystem Instance Methods<a name="FileSystemMethods"></a>
|
||||||
|
|
||||||
Once a `FileSystem` is created, it has the following methods. NOTE: code examples below assume
|
Once a `FileSystem` is created, it has the following methods. NOTE: code examples below assume
|
||||||
a `FileSystem` instance named `fs` has been created like so:
|
a `FileSystem` instance named `fs` has been created like so:
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
var fs = new Filer.FileSystem();
|
// 1. Using Filer.fs for a default filesystem
|
||||||
|
const { fs } = require('filer');
|
||||||
|
|
||||||
|
// 2. Or via the FileSystem constructor with specified options
|
||||||
|
const fs = new Filer.FileSystem(options, callback);
|
||||||
```
|
```
|
||||||
|
|
||||||
* [fs.rename(oldPath, newPath, callback)](#rename)
|
* [fs.rename(oldPath, newPath, callback)](#rename)
|
||||||
|
@ -290,10 +436,16 @@ var fs = new Filer.FileSystem();
|
||||||
* [fs.mknod(path, mode, callback)](#mknod)
|
* [fs.mknod(path, mode, callback)](#mknod)
|
||||||
* [fs.rmdir(path, callback)](#rmdir)
|
* [fs.rmdir(path, callback)](#rmdir)
|
||||||
* [fs.mkdir(path, [mode], callback)](#mkdir)
|
* [fs.mkdir(path, [mode], callback)](#mkdir)
|
||||||
|
* [fs.access(path, [mode], callback)](#access)
|
||||||
|
* [fs.mkdtemp(path, [options], callback)](#mkdtemp)
|
||||||
* [fs.readdir(path, callback)](#readdir)
|
* [fs.readdir(path, callback)](#readdir)
|
||||||
* [fs.close(fd, callback)](#close)
|
* [fs.close(fd, callback)](#close)
|
||||||
* [fs.open(path, flags, [mode], callback)](#open)
|
* [fs.open(path, flags, [mode], callback)](#open)
|
||||||
* [fs.utimes(path, atime, mtime, callback)](#utimes)
|
* [fs.utimes(path, atime, mtime, callback)](#utimes)
|
||||||
|
* [fs.chown(path, uid, gid, callback)](#chown)
|
||||||
|
* [fs.fchown(fd, uid, gid, callback)](#fchown)
|
||||||
|
* [fs.chmod(path, mode, callback)](#chmod)
|
||||||
|
* [fs.fchmod(fd, mode, callback)](#fchmod)
|
||||||
* [fs.futimes(fd, atime, mtime, callback)](#fsutimes)
|
* [fs.futimes(fd, atime, mtime, callback)](#fsutimes)
|
||||||
* [fs.fsync(fd, callback)](#fsync)
|
* [fs.fsync(fd, callback)](#fsync)
|
||||||
* [fs.write(fd, buffer, offset, length, position, callback)](#write)
|
* [fs.write(fd, buffer, offset, length, position, callback)](#write)
|
||||||
|
@ -335,7 +487,7 @@ Example:
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
// Create a file, shrink it, expand it.
|
// Create a file, shrink it, expand it.
|
||||||
var buffer = new Filer.Buffer([1, 2, 3, 4, 5, 6, 7, 8]);
|
var buffer = Filer.Buffer.from([1, 2, 3, 4, 5, 6, 7, 8]);
|
||||||
|
|
||||||
fs.open('/myfile', 'w', function(err, fd) {
|
fs.open('/myfile', 'w', function(err, fd) {
|
||||||
if(err) throw error;
|
if(err) throw error;
|
||||||
|
@ -366,7 +518,7 @@ Example:
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
// Create a file, shrink it, expand it.
|
// Create a file, shrink it, expand it.
|
||||||
var buffer = new Filer.Buffer([1, 2, 3, 4, 5, 6, 7, 8]);
|
var buffer = Filer.Buffer.from([1, 2, 3, 4, 5, 6, 7, 8]);
|
||||||
|
|
||||||
fs.open('/myfile', 'w', function(err, fd) {
|
fs.open('/myfile', 'w', function(err, fd) {
|
||||||
if(err) throw error;
|
if(err) throw error;
|
||||||
|
@ -397,14 +549,22 @@ Callback gets `(error, stats)`, where `stats` is an object with the following pr
|
||||||
|
|
||||||
```
|
```
|
||||||
{
|
{
|
||||||
node: <string> // internal node id (unique)
|
node: <string> // internal node id (unique)
|
||||||
dev: <string> // file system name
|
dev: <string> // file system name
|
||||||
size: <number> // file size in bytes
|
name: <string> // the entry's name (basename)
|
||||||
nlinks: <number> // number of links
|
size: <number> // file size in bytes
|
||||||
atime: <number> // last access time
|
nlinks: <number> // number of links
|
||||||
mtime: <number> // last modified time
|
atime: <date> // last access time as JS Date Object
|
||||||
ctime: <number> // creation time
|
mtime: <date> // last modified time as JS Date Object
|
||||||
type: <string> // file type (FILE, DIRECTORY, SYMLINK)
|
ctime: <date> // creation time as JS Date Object
|
||||||
|
atimeMs: <number> // last access time as Unix Timestamp
|
||||||
|
mtimeMs: <number> // last modified time as Unix Timestamp
|
||||||
|
ctimeMs: <number> // creation time as Unix Timestamp
|
||||||
|
type: <string> // file type (FILE, DIRECTORY, SYMLINK),
|
||||||
|
gid: <number> // group name
|
||||||
|
uid: <number> // owner name
|
||||||
|
mode: <number> // permissions
|
||||||
|
version: <number> // version of the node
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -536,10 +696,12 @@ Create a symbolic link to the file at `dstPath` containing the path `srcPath`. A
|
||||||
Symbolic links are files that point to other paths.
|
Symbolic links are files that point to other paths.
|
||||||
|
|
||||||
NOTE: Filer allows for, but ignores the optional `type` parameter used in node.js.
|
NOTE: Filer allows for, but ignores the optional `type` parameter used in node.js.
|
||||||
|
The `srcPath` may be a relative path, which will be resolved relative to `dstPath`
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
|
// Absolute path
|
||||||
fs.symlink('/logs/august.log', '/logs/current', function(err) {
|
fs.symlink('/logs/august.log', '/logs/current', function(err) {
|
||||||
if(err) throw err;
|
if(err) throw err;
|
||||||
fs.readFile('/logs/current', 'utf8', function(err, data) {
|
fs.readFile('/logs/current', 'utf8', function(err, data) {
|
||||||
|
@ -547,11 +709,21 @@ fs.symlink('/logs/august.log', '/logs/current', function(err) {
|
||||||
var currentLog = data;
|
var currentLog = data;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Relative path
|
||||||
|
fs.symlink('../file', '/dir/symlink', function(err) {
|
||||||
|
if(err) throw err;
|
||||||
|
// The /dir/symlink file is now a symlink to /file
|
||||||
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
#### fs.readlink(path, callback)<a name="readlink"></a>
|
#### fs.readlink(path, callback)<a name="readlink"></a>
|
||||||
|
|
||||||
Reads the contents of a symbolic link. Asynchronous [readlink(2)](http://pubs.opengroup.org/onlinepubs/009695399/functions/readlink.html). Callback gets `(error, linkContents)`, where `linkContents` is a string containing the symbolic link's link path.
|
Reads the contents of a symbolic link. Asynchronous [readlink(2)](http://pubs.opengroup.org/onlinepubs/009695399/functions/readlink.html).
|
||||||
|
Callback gets `(error, linkContents)`, where `linkContents` is a string
|
||||||
|
containing the symbolic link's link path. If the original `srcPath` given
|
||||||
|
to `symlink()` was a relative path, it will be fully resolved relative
|
||||||
|
to `dstPath` when returned by `readlink()`.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
|
@ -648,7 +820,47 @@ fs.mkdir('/home', function(err) {
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
#### fs.readdir(path, callback)<a name="readdir"></a>
|
#### fs.access(path, [mode], callback)<a name="access"></a>
|
||||||
|
|
||||||
|
Tests a user's permissions for the file or directory supplied in `path` argument. Asynchronous [access(2)](http://pubs.opengroup.org/onlinepubs/009695399/functions/access.html). Callback gets no additional arguments. The `mode` argument can be one of the following (constants are available on `fs.constants` and `fs`):
|
||||||
|
|
||||||
|
* `F_OK`: Test for existence of file.
|
||||||
|
* `R_OK`: Test whether the file exists and grants read permission.
|
||||||
|
* `W_OK`: Test whether the file exists and grants write permission.
|
||||||
|
* `X_OK`: Test whether the file exists and grants execute permission.
|
||||||
|
|
||||||
|
NOTE: you can also create a mask consisting of the bitwise OR of two or more values (e.g. `fs.constants.W_OK | fs.constants.R_OK`).
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Check if the file exists in the current directory.
|
||||||
|
fs.access(file, fs.F_OK, function(err) {
|
||||||
|
console.log(`${file} ${err ? 'does not exist' : 'exists'}`);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
#### fs.mkdtemp(prefix, options, callback)<a name="mkdtemp"></a>
|
||||||
|
|
||||||
|
Makes a temporary directory with prefix supplied in `path` argument. Method will append six random characters directly to the prefix. Asynchronous. Callback gets `(error, path)`, where path is the path to the created directory.
|
||||||
|
|
||||||
|
NOTE: Filer allows for, but ignores the optional `options` argument used in node.js.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Create tmp directory with prefix foo
|
||||||
|
fs.mkdtemp("/foo-", function (error, path) {
|
||||||
|
// A new folder foo-xxxxxx will be created. Path contains a path to created folder.
|
||||||
|
});
|
||||||
|
|
||||||
|
fs.mkdtemp("/myDir/tmp", function (error, path) {
|
||||||
|
// Will create a new folder tmpxxxxxx inside myDir directory.
|
||||||
|
// Will throw error if myDir does not exist
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
#### fs.readdir(path, [options], callback)<a name="readdir"></a>
|
||||||
|
|
||||||
Reads the contents of a directory. Asynchronous [readdir(3)](http://pubs.opengroup.org/onlinepubs/009695399/functions/readdir.html).
|
Reads the contents of a directory. Asynchronous [readdir(3)](http://pubs.opengroup.org/onlinepubs/009695399/functions/readdir.html).
|
||||||
Callback gets `(error, files)`, where `files` is an array containing the names of each directory entry (i.e., file, directory, link) in the directory, excluding `.` and `..`.
|
Callback gets `(error, files)`, where `files` is an array containing the names of each directory entry (i.e., file, directory, link) in the directory, excluding `.` and `..`.
|
||||||
|
@ -669,6 +881,12 @@ fs.readdir('/docs', function(err, files) {
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Optionally accepts an options parameter, which can be either an encoding (e.g. "utf8") or an object with optional properties `encoding` and `withFileTypes`.
|
||||||
|
|
||||||
|
The `encoding` property is a `string` which will determine the character encoding to use for the names of each directory entry. The `withFileTypes` property is a `boolean` which defaults to `false`. If `true`, this method will return an array of [fs.Dirent](https://nodejs.org/api/fs.html#fs_class_fs_dirent) objects.
|
||||||
|
|
||||||
|
The `name` property on the [fs.Dirent](https://nodejs.org/api/fs.html#fs_class_fs_dirent) objects will be encoded using the specified character encoding.
|
||||||
|
|
||||||
#### fs.close(fd, callback)<a name="close"></a>
|
#### fs.close(fd, callback)<a name="close"></a>
|
||||||
|
|
||||||
Closes a file descriptor. Asynchronous [close(2)](http://pubs.opengroup.org/onlinepubs/009695399/functions/close.html).
|
Closes a file descriptor. Asynchronous [close(2)](http://pubs.opengroup.org/onlinepubs/009695399/functions/close.html).
|
||||||
|
@ -716,7 +934,7 @@ fs.open('/myfile', 'w', function(err, fd) {
|
||||||
|
|
||||||
#### fs.utimes(path, atime, mtime, callback)<a name="utimes"></a>
|
#### fs.utimes(path, atime, mtime, callback)<a name="utimes"></a>
|
||||||
|
|
||||||
Changes the file timestamps for the file given at path `path`. Asynchronous [utimes(2)](http://pubs.opengroup.org/onlinepubs/009695399/functions/utimes.html). Callback gets no additional arguments. Both `atime` (access time) and `mtime` (modified time) arguments should be a JavaScript Date.
|
Changes the file timestamps for the file given at path `path`. Asynchronous [utimes(2)](http://pubs.opengroup.org/onlinepubs/009695399/functions/utimes.html). Callback gets no additional arguments. Both `atime` (access time) and `mtime` (modified time) arguments should be a JavaScript Date or Number.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
|
@ -730,7 +948,7 @@ fs.utimes('/myfile.txt', now, now, function(err) {
|
||||||
|
|
||||||
#### fs.futimes(fd, atime, mtime, callback)<a name="futimes"></a>
|
#### fs.futimes(fd, atime, mtime, callback)<a name="futimes"></a>
|
||||||
|
|
||||||
Changes the file timestamps for the open file represented by the file descriptor `fd`. Asynchronous [utimes(2)](http://pubs.opengroup.org/onlinepubs/009695399/functions/utimes.html). Callback gets no additional arguments. Both `atime` (access time) and `mtime` (modified time) arguments should be a JavaScript Date.
|
Changes the file timestamps for the open file represented by the file descriptor `fd`. Asynchronous [utimes(2)](http://pubs.opengroup.org/onlinepubs/009695399/functions/utimes.html). Callback gets no additional arguments. Both `atime` (access time) and `mtime` (modified time) arguments should be a JavaScript Date or Number.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
|
@ -749,9 +967,107 @@ fs.open('/myfile.txt', function(err, fd) {
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### fs.chown(path, uid, gid, callback)<a name="chown"></a>
|
||||||
|
|
||||||
|
Changes the owner and group of a file. Asynchronous [chown(2)](http://pubs.opengroup.org/onlinepubs/009695399/functions/chown.html). Callback gets no additional arguments. Both `uid` (user id) and `gid` (group id) arguments should be a JavaScript Number. By default, `0x0` is used (i.e., `root:root` ownership).
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
fs.chown('/myfile.txt', 500, 500, function(err) {
|
||||||
|
if(err) throw err;
|
||||||
|
|
||||||
|
// /myfile.txt is now owned by user with id 500, group 500
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
#### fs.fchown(fd, uid, gid, callback)<a name="fchown"></a>
|
||||||
|
|
||||||
|
Changes the owner and group of a file. Asynchronous [chown(2)](http://pubs.opengroup.org/onlinepubs/009695399/functions/chown.html). Callback gets no additional arguments. Both `uid` (user id) and `gid` (group id) arguments should be a JavaScript Number. By default, `0x0` is used (i.e., `root:root` ownership).
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
fs.open('/myfile.txt', function(err, fd) {
|
||||||
|
if(err) throw err;
|
||||||
|
|
||||||
|
fs.fchown(fd, 500, 500, function(err) {
|
||||||
|
if(err) throw err;
|
||||||
|
|
||||||
|
// /myfile.txt is now owned by user with id 500, group 500
|
||||||
|
|
||||||
|
fs.close(fd);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
#### fs.chmod(path, mode, callback)<a name="chmod"></a>
|
||||||
|
|
||||||
|
Changes the mode of a file. Asynchronous [chmod(2)](http://pubs.opengroup.org/onlinepubs/009695399/functions/chmod.html). Callback gets no additional arguments. The `mode` argument should be a JavaScript Number, which combines file type and permission information. Here are a list of common values useful for setting the `mode`:
|
||||||
|
|
||||||
|
* File type `S_IFREG=0x8000`
|
||||||
|
* Dir type `S_IFDIR=0x4000`
|
||||||
|
* Link type `S_IFLNK=0xA000`
|
||||||
|
|
||||||
|
* Permissions `755=0x1ED`
|
||||||
|
* Permissions `644=0x1A4`
|
||||||
|
* Permissions `777=0x1FF`
|
||||||
|
* Permissions `666=0x1B6`
|
||||||
|
|
||||||
|
By default, directories use `(0x4000 | 0x1ED)` and files use `(0x8000 | 0x1A4)`.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// S_IFREG | 0o777
|
||||||
|
var mode = 0x8000 | 0x1FF
|
||||||
|
fs.chmod('/myfile.txt', mode, function(err) {
|
||||||
|
if(err) throw err;
|
||||||
|
|
||||||
|
// /myfile.txt is a regular file with permissions 777
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
#### fs.fchmod(fd, mode, callback)<a name="fchmod"></a>
|
||||||
|
|
||||||
|
Changes the mode of a file. Asynchronous [chmod(2)](http://pubs.opengroup.org/onlinepubs/009695399/functions/chmod.html). Callback gets no additional arguments. The `mode` argument should be a JavaScript Number, which combines file type and permission information. By default, `755` (dir) and `644` (file) are used.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
fs.open('/myfile.txt', function(err, fd) {
|
||||||
|
if(err) throw err;
|
||||||
|
|
||||||
|
// S_IFREG | 0o777
|
||||||
|
var mode = 0x8000 | 0x1FF
|
||||||
|
fs.fchmod(fd, mode, function(err) {
|
||||||
|
if(err) throw err;
|
||||||
|
|
||||||
|
// /myfile.txt is a regular file with permissions 777
|
||||||
|
|
||||||
|
fs.close(fd);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
#### fs.fsync(fd, callback)<a name="fsync"></a>
|
#### fs.fsync(fd, callback)<a name="fsync"></a>
|
||||||
|
|
||||||
NOTE: Not yet implemented, see https://github.com/filerjs/filer/issues/87
|
Synchronize the data and metadata for the file referred to by `fd` to disk.
|
||||||
|
Asynchronous [fsync(2)](http://man7.org/linux/man-pages/man2/fsync.2.html).
|
||||||
|
The callback gets `(error)`.
|
||||||
|
|
||||||
|
```js
|
||||||
|
fs.open('/myfile', 'r', function(error, fd) {
|
||||||
|
if(err) throw err;
|
||||||
|
|
||||||
|
// Use fd, then sync
|
||||||
|
|
||||||
|
fs.fsync(fd, function(error) {
|
||||||
|
if(err) throw err;
|
||||||
|
fs.close(fd, done);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
#### fs.write(fd, buffer, offset, length, position, callback)<a name="write"></a>
|
#### fs.write(fd, buffer, offset, length, position, callback)<a name="write"></a>
|
||||||
|
|
||||||
|
@ -763,7 +1079,7 @@ Example:
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
// Create a file with the following bytes.
|
// Create a file with the following bytes.
|
||||||
var buffer = new Filer.Buffer([1, 2, 3, 4, 5, 6, 7, 8]);
|
var buffer = Filer.Buffer.from([1, 2, 3, 4, 5, 6, 7, 8]);
|
||||||
|
|
||||||
fs.open('/myfile', 'w', function(err, fd) {
|
fs.open('/myfile', 'w', function(err, fd) {
|
||||||
if(err) throw error;
|
if(err) throw error;
|
||||||
|
@ -800,22 +1116,22 @@ Example:
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
fs.open('/myfile', 'r', function(err, fd) {
|
fs.open('/myfile', 'r', function(err, fd) {
|
||||||
if(err) throw error;
|
if(err) throw err;
|
||||||
|
|
||||||
// Determine size of file
|
// Determine size of file
|
||||||
fs.fstat(fd, function(err, stats) {
|
fs.fstat(fd, function(err, stats) {
|
||||||
if(err) throw error;
|
if(err) throw err;
|
||||||
|
|
||||||
// Create a buffer large enough to hold the file's contents
|
// Create a buffer large enough to hold the file's contents
|
||||||
var nbytes = expected = stats.size;
|
var nbytes = expected = stats.size;
|
||||||
var buffer = new Filer.Buffer(nbytes);
|
var buffer = Filer.Buffer.alloc(nbytes);
|
||||||
var read = 0;
|
var read = 0;
|
||||||
|
|
||||||
function readBytes(offset, position, length) {
|
function readBytes(offset, position, length) {
|
||||||
length = length || buffer.length - read;
|
length = length || buffer.length - read;
|
||||||
|
|
||||||
fs.read(fd, buffer, offset, length, position, function(err, nbytes) {
|
fs.read(fd, buffer, offset, length, position, function(err, nbytes) {
|
||||||
if(err) throw error;
|
if(err) throw err;
|
||||||
|
|
||||||
// nbytes is now the number of bytes read, between 0 and buffer.length.
|
// nbytes is now the number of bytes read, between 0 and buffer.length.
|
||||||
// See if we still have more bytes to read.
|
// See if we still have more bytes to read.
|
||||||
|
@ -866,7 +1182,7 @@ fs.writeFile('/myfile.txt', "...data...", function (err) {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Write binary file
|
// Write binary file
|
||||||
var buffer = new Filer.Buffer([1, 2, 3, 4, 5, 6, 7, 8]);
|
var buffer = Filer.Buffer.from([1, 2, 3, 4, 5, 6, 7, 8]);
|
||||||
fs.writeFile('/myfile', buffer, function (err) {
|
fs.writeFile('/myfile', buffer, function (err) {
|
||||||
if (err) throw err;
|
if (err) throw err;
|
||||||
});
|
});
|
||||||
|
@ -889,8 +1205,8 @@ fs.appendFile('/myfile.txt', "Data...", function (err) {
|
||||||
// '/myfile.txt' would now read out 'More...Data...'
|
// '/myfile.txt' would now read out 'More...Data...'
|
||||||
|
|
||||||
// Append binary file
|
// Append binary file
|
||||||
var data = new Filer.Buffer([1, 2, 3, 4]);
|
var data = Filer.Buffer.from([1, 2, 3, 4]);
|
||||||
var more = new Filer.Buffer([5, 6, 7, 8]);
|
var more = Filer.Buffer.from([5, 6, 7, 8]);
|
||||||
|
|
||||||
fs.writeFile('/myfile', data, function (err) {
|
fs.writeFile('/myfile', data, function (err) {
|
||||||
if (err) throw err;
|
if (err) throw err;
|
||||||
|
@ -1088,20 +1404,31 @@ fs.writeFile('/data/subdir/file', 'data');
|
||||||
### FileSystemShell<a name="FileSystemShell"></a>
|
### FileSystemShell<a name="FileSystemShell"></a>
|
||||||
|
|
||||||
Many common file system shell operations are available by using a `FileSystemShell` object.
|
Many common file system shell operations are available by using a `FileSystemShell` object.
|
||||||
The `FileSystemShell` is obtained from, and used in conjuction with a `FileSystem`,
|
The `FileSystemShell` is used in conjunction with a `FileSystem`,
|
||||||
and provides augmented features. Many separate `FileSystemShell` objects can exist per
|
and provides augmented features. Many separate `FileSystemShell` objects can exist per
|
||||||
`FileSystem`, but each `FileSystemShell` is bound to a single instance of a `FileSystem`
|
`FileSystem`, but each `FileSystemShell` is bound to a single instance of a `FileSystem`
|
||||||
for its lifetime.
|
for its lifetime.
|
||||||
|
|
||||||
A `FileSystemShell` is created using the `FileSystem.Shell()` function:
|
A `FileSystemShell` is created by instantiating `Filer.FileSystem().Shell`:
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
var fs = new Filer.FileSystem();
|
var fs = new Filer.FileSystem();
|
||||||
var sh = fs.Shell(options);
|
var sh = new fs.Shell(options);
|
||||||
var sh2 = fs.Shell(options);
|
var sh2 = new fs.Shell(options);
|
||||||
// sh and sh2 are two separate shells, each bound to fs
|
// sh and sh2 are two separate shells, each bound to fs
|
||||||
```
|
```
|
||||||
|
|
||||||
|
In addition, the constructor function can be accessed through `Filer`:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
var fs = new Filer.FileSystem();
|
||||||
|
var sh = new fs.Shell();
|
||||||
|
|
||||||
|
Filer.Shell.prototype.newFunction = ...;
|
||||||
|
|
||||||
|
sh.newFunction();
|
||||||
|
```
|
||||||
|
|
||||||
The `FileSystemShell` can take an optional `options` object. The `options` object
|
The `FileSystemShell` can take an optional `options` object. The `options` object
|
||||||
can include `env`, which is a set of environment variables. Currently supported variables
|
can include `env`, which is a set of environment variables. Currently supported variables
|
||||||
include `TMP` (the path to the temporary directory), and `PATH` (the list of known paths) and
|
include `TMP` (the path to the temporary directory), and `PATH` (the list of known paths) and
|
||||||
|
@ -1109,7 +1436,7 @@ others may be added in the future. You can also add your own, or update existing
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
var fs = new Filer.FileSystem();
|
var fs = new Filer.FileSystem();
|
||||||
var sh = fs.Shell({
|
var sh = new fs.Shell({
|
||||||
env: {
|
env: {
|
||||||
TMP: '/tempdir',
|
TMP: '/tempdir',
|
||||||
PATH: '/one:/two'
|
PATH: '/one:/two'
|
||||||
|
@ -1135,7 +1462,7 @@ Example:
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
var fs = new Filer.FileSystem();
|
var fs = new Filer.FileSystem();
|
||||||
var sh = fs.Shell();
|
var sh = new fs.Shell();
|
||||||
var p = sh.env.get('PATH');
|
var p = sh.env.get('PATH');
|
||||||
|
|
||||||
// Store the current location
|
// Store the current location
|
||||||
|
@ -1155,11 +1482,12 @@ examples below assume a `FileSystemShell` instance named `sh` has been created l
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
var fs = new Filer.FileSystem();
|
var fs = new Filer.FileSystem();
|
||||||
var sh = fs.Shell();
|
var sh = new fs.Shell();
|
||||||
```
|
```
|
||||||
|
|
||||||
* [sh.cd(path, callback)](#cd)
|
* [sh.cd(path, callback)](#cd)
|
||||||
* [sh.pwd()](#pwd)
|
* [sh.pwd()](#pwd)
|
||||||
|
* [sh.find(dir, [options], callback)](#find)
|
||||||
* [sh.ls(dir, [options], callback)](#ls)
|
* [sh.ls(dir, [options], callback)](#ls)
|
||||||
* [sh.exec(path, [args], callback)](#exec)
|
* [sh.exec(path, [args], callback)](#exec)
|
||||||
* [sh.touch(path, [options], callback)](#touch)
|
* [sh.touch(path, [options], callback)](#touch)
|
||||||
|
@ -1188,20 +1516,71 @@ sh.cd('/dir1', function(err) {
|
||||||
|
|
||||||
Returns the shell's current working directory. See [sh.cd()](#cd).
|
Returns the shell's current working directory. See [sh.cd()](#cd).
|
||||||
|
|
||||||
|
#### sh.find(dir, [options], callback)<a name="find"></a>
|
||||||
|
|
||||||
|
Recursively walk a directory tree, reporting back all paths that were
|
||||||
|
found along the way. Asynchronous [find(1)](http://pubs.opengroup.org/onlinepubs/9699919799/utilities/find.html
|
||||||
|
)
|
||||||
|
If given no options, `find` walks the given dir path
|
||||||
|
and the callback gives `function(err, found)`, where `found` is an array of
|
||||||
|
all paths discovered during a depth-first walk.
|
||||||
|
|
||||||
|
Valid options include a `regex` for pattern matching paths, allowing paths
|
||||||
|
to be ignored (e.g., ```regex: /\.bak$/``` to find all `.bak` files). You can
|
||||||
|
also use `name` and `path` to provide a [match pattern](https://github.com/isaacs/minimatch) for the basename and
|
||||||
|
dirname respectively (e.g., `{name: '*.js'}` to find all JavaScript files or
|
||||||
|
`{path: '*-modules'}` to only look in folders named `base-modules`, `foo-modules`, etc.).
|
||||||
|
Finally, you can also provide an `exec` function of the form `function(path, next)` where
|
||||||
|
`path` is the current path that was found and matches any provided `regex`
|
||||||
|
(NOTE: dir paths have an '/' appended), and `next` is a callback to call
|
||||||
|
when you are done processing the path.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
function processPath(path, next) {
|
||||||
|
// Process the path somehow, in this case we print it.
|
||||||
|
// Dir paths end with /
|
||||||
|
if(path.endsWith('/')) {
|
||||||
|
console.log('Found dir: ' + path);
|
||||||
|
} else {
|
||||||
|
console.log('Found file: ' + path);
|
||||||
|
}
|
||||||
|
|
||||||
|
// All done, let the process continue by invoking second arg:
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get every path (NOTE: no name or regex provided) below the root, depth first
|
||||||
|
sh.find('/', {exec: processPath}, function(err, found) {
|
||||||
|
/* find command is finished, `found` contains the flattened list as an Array */
|
||||||
|
});
|
||||||
|
|
||||||
|
// Find all files that look like map201.jpg, map202.jpg in the /data dir
|
||||||
|
sh.find('/data', {regex: /map20\d\.jpg$/, exec: processPath}, function(err) {
|
||||||
|
/* find command is finished */
|
||||||
|
});
|
||||||
|
|
||||||
|
// Find and delete all *.bak files under /app/user
|
||||||
|
sh.find('/app/user', {
|
||||||
|
name: '*.bak',
|
||||||
|
exec: function(path, next) {
|
||||||
|
sh.rm(path, next);
|
||||||
|
}
|
||||||
|
}, function callback(err, found) {
|
||||||
|
if(err) throw err;
|
||||||
|
|
||||||
|
if(found.length) {
|
||||||
|
console.log('Deleted the following ' + found.length + ' files: ', found);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
#### sh.ls(dir, [options], callback)<a name="ls"></a>
|
#### sh.ls(dir, [options], callback)<a name="ls"></a>
|
||||||
|
|
||||||
Get the listing of a directory, returning an array of directory entries
|
Get the listing of a directory, returning an array of directory entries
|
||||||
in the following form:
|
in the same form as [fs.stat()](#stat), with the exception that a new Array named
|
||||||
```
|
`contents` is added for directory entries, containing child entries.
|
||||||
{
|
|
||||||
path: <String> the basename of the directory entry
|
|
||||||
links: <Number> the number of links to the entry
|
|
||||||
size: <Number> the size in bytes of the entry
|
|
||||||
modified: <Number> the last modified date/time
|
|
||||||
type: <String> the type of the entry
|
|
||||||
contents: <Array> an optional array of child entries, if this entry is itself a directory
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
By default `sh.ls()` gives a shallow listing. If you want to follow
|
By default `sh.ls()` gives a shallow listing. If you want to follow
|
||||||
directories as they are encountered, use the `recursive=true` option. NOTE:
|
directories as they are encountered, use the `recursive=true` option. NOTE:
|
||||||
|
|
15
bower.json
15
bower.json
|
@ -1,15 +0,0 @@
|
||||||
{
|
|
||||||
"name": "filer",
|
|
||||||
"version": "0.0.35",
|
|
||||||
"main": "dist/filer.js",
|
|
||||||
"ignore": [
|
|
||||||
"build",
|
|
||||||
"examples",
|
|
||||||
"package.json",
|
|
||||||
"tests",
|
|
||||||
"gruntfile.js",
|
|
||||||
"node_modules",
|
|
||||||
"src",
|
|
||||||
"tools"
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -1,5 +0,0 @@
|
||||||
module.exports = (function() {
|
|
||||||
var habitat = require('habitat');
|
|
||||||
habitat.load();
|
|
||||||
return new habitat();
|
|
||||||
})();
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
13
env.sample
13
env.sample
|
@ -1,14 +1,9 @@
|
||||||
###
|
###
|
||||||
# Dev ENVIRONMENT file
|
# Dev ENVIRONMENT file
|
||||||
#
|
#
|
||||||
# Copy to .env to use defaults
|
# Copy to .env to use defaults when releasing via `npm release`
|
||||||
###
|
###
|
||||||
|
|
||||||
# GIT (upstream) url to publish to
|
# GitHub Personal Access Token (to push releases)
|
||||||
export FILER_UPSTREAM_URI="git@github.com:js-platform/filer.git"
|
# https://help.github.com/articles/creating-a-personal-access-token-for-the-command-line/
|
||||||
|
GITHUB_TOKEN=
|
||||||
# GIT (upstream) branch to publish to
|
|
||||||
export FILER_UPSTREAM_BRANCH="develop"
|
|
||||||
|
|
||||||
# Remote name for upstream repo
|
|
||||||
export FILER_UPSTREAM_REMOTE_NAME="origin"
|
|
||||||
|
|
231
gruntfile.js
231
gruntfile.js
|
@ -1,231 +0,0 @@
|
||||||
var semver = require('semver'),
|
|
||||||
fs = require('fs'),
|
|
||||||
currentVersion = JSON.parse(fs.readFileSync('./package.json', 'utf8')).version,
|
|
||||||
env = require('./config/environment');
|
|
||||||
|
|
||||||
// Globals
|
|
||||||
var PROMPT_CONFIRM_CONFIG = 'confirmation',
|
|
||||||
GIT_BRANCH = env.get('FILER_UPSTREAM_BRANCH'),
|
|
||||||
GIT_REMOTE = env.get('FILER_UPSTREAM_REMOTE_NAME'),
|
|
||||||
GIT_FULL_REMOTE = env.get('FILER_UPSTREAM_URI') + ' ' + GIT_BRANCH;
|
|
||||||
|
|
||||||
module.exports = function(grunt) {
|
|
||||||
|
|
||||||
// Project configuration.
|
|
||||||
grunt.initConfig({
|
|
||||||
pkg: grunt.file.readJSON('package.json'),
|
|
||||||
|
|
||||||
clean: ['dist/filer-test.js', 'dist/filer-issue225.js'],
|
|
||||||
|
|
||||||
uglify: {
|
|
||||||
options: {
|
|
||||||
banner: '/*! <%= pkg.name %> <%= pkg.version %> <%= grunt.template.today("yyyy-mm-dd") %> */\n'
|
|
||||||
},
|
|
||||||
develop: {
|
|
||||||
src: 'dist/filer.js',
|
|
||||||
dest: 'dist/filer.min.js'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
jshint: {
|
|
||||||
// Don't bother with src/path.js
|
|
||||||
all: [
|
|
||||||
'gruntfile.js',
|
|
||||||
'config/environment.js',
|
|
||||||
'src/constants.js',
|
|
||||||
'src/errors.js',
|
|
||||||
'src/fs.js',
|
|
||||||
'src/index.js',
|
|
||||||
'src/shared.js',
|
|
||||||
'src/shell.js',
|
|
||||||
'src/fswatcher.js',
|
|
||||||
'src/environment.js',
|
|
||||||
'src/providers/**/*.js',
|
|
||||||
'src/adapters/**/*.js',
|
|
||||||
'src/directory-entry.js',
|
|
||||||
'src/open-file-description.js',
|
|
||||||
'src/super-node.js',
|
|
||||||
'src/node.js',
|
|
||||||
'src/stats.js',
|
|
||||||
'src/filesystem/**/*.js'
|
|
||||||
]
|
|
||||||
},
|
|
||||||
|
|
||||||
browserify: {
|
|
||||||
filerDist: {
|
|
||||||
src: "./src/index.js",
|
|
||||||
dest: "./dist/filer.js",
|
|
||||||
options: {
|
|
||||||
browserifyOptions: {
|
|
||||||
commondir: false
|
|
||||||
},
|
|
||||||
bundleOptions: {
|
|
||||||
standalone: 'Filer'
|
|
||||||
},
|
|
||||||
exclude: ["./node_modules/request/index.js"]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
filerTest: {
|
|
||||||
src: "./tests/index.js",
|
|
||||||
dest: "./dist/filer-test.js"
|
|
||||||
},
|
|
||||||
// See tests/bugs/issue225.js
|
|
||||||
filerIssue225: {
|
|
||||||
src: "./src/index.js",
|
|
||||||
dest: "./dist/filer-issue225.js",
|
|
||||||
options: {
|
|
||||||
browserifyOptions: {
|
|
||||||
commondir: false
|
|
||||||
},
|
|
||||||
bundleOptions: {
|
|
||||||
standalone: 'Filer'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
shell: {
|
|
||||||
mocha: {
|
|
||||||
// Run all tests (e.g., tests require()'ed in tests/index.js) and also tests/bugs/issue225.js
|
|
||||||
// separately, since it can't be included in a browserify build.
|
|
||||||
command: '"./node_modules/.bin/mocha" --reporter list tests/index.js && "./node_modules/.bin/mocha" --reporter list tests/bugs/issue225.js'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
bump: {
|
|
||||||
options: {
|
|
||||||
files: ['package.json', 'bower.json'],
|
|
||||||
commit: true,
|
|
||||||
commitMessage: 'v%VERSION%',
|
|
||||||
commitFiles: ['package.json', 'bower.json', './dist/filer.js', './dist/filer.min.js', './dist/filer-test.js'],
|
|
||||||
createTag: true,
|
|
||||||
tagName: 'v%VERSION%',
|
|
||||||
tagMessage: 'v%VERSION%',
|
|
||||||
push: true,
|
|
||||||
pushTo: GIT_FULL_REMOTE
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
'npm-checkBranch': {
|
|
||||||
options: {
|
|
||||||
branch: GIT_BRANCH
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
'npm-publish': {
|
|
||||||
options: {
|
|
||||||
abortIfDirty: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
prompt: {
|
|
||||||
confirm: {
|
|
||||||
options: {
|
|
||||||
questions: [
|
|
||||||
{
|
|
||||||
config: PROMPT_CONFIRM_CONFIG,
|
|
||||||
type: 'confirm',
|
|
||||||
message: 'Bump version from ' + (currentVersion).cyan +
|
|
||||||
' to ' + semver.inc(currentVersion, "patch").yellow + '?',
|
|
||||||
default: false
|
|
||||||
}
|
|
||||||
],
|
|
||||||
then: function(results) {
|
|
||||||
if (!results[PROMPT_CONFIRM_CONFIG]) {
|
|
||||||
return grunt.fatal('User aborted...');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
gitcheckout: {
|
|
||||||
publish: {
|
|
||||||
options: {
|
|
||||||
branch: 'gh-pages',
|
|
||||||
overwrite: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
revert: {
|
|
||||||
options: {
|
|
||||||
branch: GIT_BRANCH
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
gitpush: {
|
|
||||||
publish: {
|
|
||||||
options: {
|
|
||||||
remote: GIT_REMOTE,
|
|
||||||
branch: 'gh-pages',
|
|
||||||
force: true
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
connect: {
|
|
||||||
serverForNode: {
|
|
||||||
options: {
|
|
||||||
port: 1234,
|
|
||||||
base: '.'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
serverForBrowser: {
|
|
||||||
options: {
|
|
||||||
port: 1234,
|
|
||||||
base: '.',
|
|
||||||
keepalive: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
grunt.loadNpmTasks('grunt-contrib-clean');
|
|
||||||
grunt.loadNpmTasks('grunt-contrib-uglify');
|
|
||||||
grunt.loadNpmTasks('grunt-contrib-jshint');
|
|
||||||
grunt.loadNpmTasks('grunt-contrib-connect');
|
|
||||||
grunt.loadNpmTasks('grunt-bump');
|
|
||||||
grunt.loadNpmTasks('grunt-npm');
|
|
||||||
grunt.loadNpmTasks('grunt-git');
|
|
||||||
grunt.loadNpmTasks('grunt-prompt');
|
|
||||||
grunt.loadNpmTasks('grunt-shell');
|
|
||||||
grunt.loadNpmTasks('grunt-contrib-connect');
|
|
||||||
grunt.loadNpmTasks('grunt-browserify');
|
|
||||||
|
|
||||||
grunt.registerTask('develop', ['clean', 'browserify']);
|
|
||||||
grunt.registerTask('build-tests', ['clean', 'browserify:filerTest']);
|
|
||||||
grunt.registerTask('release', ['test', 'develop', 'uglify']);
|
|
||||||
|
|
||||||
grunt.registerTask('publish', 'Publish filer as a new version to NPM, bower and github.', function(patchLevel) {
|
|
||||||
var allLevels = ['patch', 'minor', 'major'];
|
|
||||||
|
|
||||||
// No level specified defaults to 'patch'
|
|
||||||
patchLevel = (patchLevel || 'patch').toLowerCase();
|
|
||||||
|
|
||||||
// Fail out if the patch level isn't recognized
|
|
||||||
if (allLevels.filter(function(el) { return el == patchLevel; }).length === 0) {
|
|
||||||
return grunt.fatal('Patch level not recognized! "Patch", "minor" or "major" only.');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set prompt message
|
|
||||||
var promptOpts = grunt.config('prompt.confirm.options');
|
|
||||||
promptOpts.questions[0].message = 'Bump version from ' + (currentVersion).cyan +
|
|
||||||
' to ' + semver.inc(currentVersion, patchLevel).yellow + '?';
|
|
||||||
grunt.config('prompt.confirm.options', promptOpts);
|
|
||||||
|
|
||||||
grunt.task.run([
|
|
||||||
'prompt:confirm',
|
|
||||||
'checkBranch',
|
|
||||||
'release',
|
|
||||||
'bump:' + patchLevel,
|
|
||||||
'gitcheckout:publish',
|
|
||||||
'gitpush:publish',
|
|
||||||
'gitcheckout:revert',
|
|
||||||
'npm-publish'
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
grunt.registerTask('test-node', ['jshint', 'browserify:filerIssue225', 'connect:serverForNode', 'shell:mocha']);
|
|
||||||
grunt.registerTask('test-browser', ['jshint', 'build-tests', 'connect:serverForBrowser']);
|
|
||||||
grunt.registerTask('test', ['clean', 'test-node']);
|
|
||||||
|
|
||||||
grunt.registerTask('default', ['test']);
|
|
||||||
};
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
module.exports = function(config) {
|
||||||
|
config.set({
|
||||||
|
singleRun: true,
|
||||||
|
basePath: '',
|
||||||
|
files: [
|
||||||
|
'node_modules/regenerator-runtime/runtime.js',
|
||||||
|
'tests/dist/index.js'
|
||||||
|
],
|
||||||
|
frameworks: ['mocha', 'chai'],
|
||||||
|
reporters: ['mocha', 'summary'],
|
||||||
|
client: {
|
||||||
|
captureConsole: true,
|
||||||
|
mocha: {
|
||||||
|
ui: 'bdd',
|
||||||
|
timeout: 5000,
|
||||||
|
slow: 250
|
||||||
|
}
|
||||||
|
},
|
||||||
|
summaryReporter: {
|
||||||
|
// 'failed', 'skipped' or 'all'
|
||||||
|
show: 'failed',
|
||||||
|
// Limit the spec label to this length
|
||||||
|
specLength: 50,
|
||||||
|
// Show an 'all' column as a summary
|
||||||
|
overviewColumn: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
|
@ -1,97 +0,0 @@
|
||||||
// Cherry-picked bits of underscore.js, lodash.js
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Lo-Dash 2.4.0 <http://lodash.com/>
|
|
||||||
* Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
|
|
||||||
* Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
|
|
||||||
* Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
|
|
||||||
* Available under MIT license <http://lodash.com/license>
|
|
||||||
*/
|
|
||||||
var ArrayProto = Array.prototype;
|
|
||||||
var nativeForEach = ArrayProto.forEach;
|
|
||||||
var nativeIndexOf = ArrayProto.indexOf;
|
|
||||||
var nativeSome = ArrayProto.some;
|
|
||||||
|
|
||||||
var ObjProto = Object.prototype;
|
|
||||||
var hasOwnProperty = ObjProto.hasOwnProperty;
|
|
||||||
var nativeKeys = Object.keys;
|
|
||||||
|
|
||||||
var breaker = {};
|
|
||||||
|
|
||||||
function has(obj, key) {
|
|
||||||
return hasOwnProperty.call(obj, key);
|
|
||||||
}
|
|
||||||
|
|
||||||
var keys = nativeKeys || function(obj) {
|
|
||||||
if (obj !== Object(obj)) throw new TypeError('Invalid object');
|
|
||||||
var keys = [];
|
|
||||||
for (var key in obj) if (has(obj, key)) keys.push(key);
|
|
||||||
return keys;
|
|
||||||
};
|
|
||||||
|
|
||||||
function size(obj) {
|
|
||||||
if (obj == null) return 0;
|
|
||||||
return (obj.length === +obj.length) ? obj.length : keys(obj).length;
|
|
||||||
}
|
|
||||||
|
|
||||||
function identity(value) {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
function each(obj, iterator, context) {
|
|
||||||
var i, length;
|
|
||||||
if (obj == null) return;
|
|
||||||
if (nativeForEach && obj.forEach === nativeForEach) {
|
|
||||||
obj.forEach(iterator, context);
|
|
||||||
} else if (obj.length === +obj.length) {
|
|
||||||
for (i = 0, length = obj.length; i < length; i++) {
|
|
||||||
if (iterator.call(context, obj[i], i, obj) === breaker) return;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
var keys = keys(obj);
|
|
||||||
for (i = 0, length = keys.length; i < length; i++) {
|
|
||||||
if (iterator.call(context, obj[keys[i]], keys[i], obj) === breaker) return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
function any(obj, iterator, context) {
|
|
||||||
iterator || (iterator = identity);
|
|
||||||
var result = false;
|
|
||||||
if (obj == null) return result;
|
|
||||||
if (nativeSome && obj.some === nativeSome) return obj.some(iterator, context);
|
|
||||||
each(obj, function(value, index, list) {
|
|
||||||
if (result || (result = iterator.call(context, value, index, list))) return breaker;
|
|
||||||
});
|
|
||||||
return !!result;
|
|
||||||
};
|
|
||||||
|
|
||||||
function contains(obj, target) {
|
|
||||||
if (obj == null) return false;
|
|
||||||
if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1;
|
|
||||||
return any(obj, function(value) {
|
|
||||||
return value === target;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
function Wrapped(value) {
|
|
||||||
this.value = value;
|
|
||||||
}
|
|
||||||
Wrapped.prototype.has = function(key) {
|
|
||||||
return has(this.value, key);
|
|
||||||
};
|
|
||||||
Wrapped.prototype.contains = function(target) {
|
|
||||||
return contains(this.value, target);
|
|
||||||
};
|
|
||||||
Wrapped.prototype.size = function() {
|
|
||||||
return size(this.value);
|
|
||||||
};
|
|
||||||
|
|
||||||
function nodash(value) {
|
|
||||||
// don't wrap if already wrapped, even if wrapped by a different `lodash` constructor
|
|
||||||
return (value && typeof value == 'object' && !Array.isArray(value) && hasOwnProperty.call(value, '__wrapped__'))
|
|
||||||
? value
|
|
||||||
: new Wrapped(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = nodash;
|
|
File diff suppressed because it is too large
Load Diff
99
package.json
99
package.json
|
@ -5,48 +5,91 @@
|
||||||
"fs",
|
"fs",
|
||||||
"node",
|
"node",
|
||||||
"file",
|
"file",
|
||||||
"system",
|
"filesystem",
|
||||||
"browser",
|
"browser",
|
||||||
"indexeddb",
|
"indexeddb",
|
||||||
"idb",
|
"idb"
|
||||||
"websql"
|
|
||||||
],
|
],
|
||||||
"version": "0.0.35",
|
"version": "1.4.1",
|
||||||
"author": "Alan K <ack@modeswitch.org> (http://blog.modeswitch.org)",
|
"author": "Alan K <ack@modeswitch.org> (http://blog.modeswitch.org)",
|
||||||
"homepage": "http://filerjs.github.io/filer",
|
"homepage": "http://filerjs.github.io/filer",
|
||||||
"bugs": "https://github.com/filerjs/filer/issues",
|
"bugs": "https://github.com/filerjs/filer/issues",
|
||||||
"license": "BSD",
|
"license": "BSD-2-Clause",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "grunt test"
|
"eslint": "npm run lint",
|
||||||
|
"eslint:fix": "npm run lint:fix",
|
||||||
|
"lint": "eslint src tests",
|
||||||
|
"lint:fix": "eslint --fix src tests",
|
||||||
|
"test:node": "mocha --timeout 5000 tests",
|
||||||
|
"pretest:node-debug": "echo \"Open Chrome to chrome://inspect to debug tests...\"",
|
||||||
|
"test:node-debug": "mocha --timeout 5000 --inspect-brk tests",
|
||||||
|
"test:manual": "parcel tests/index.html --out-dir tests/dist",
|
||||||
|
"test:migrations": "mocha tests/filesystems/migrations",
|
||||||
|
"pretest": "npm run lint",
|
||||||
|
"test": "npm run karma-mocha",
|
||||||
|
"posttest": "npm run test:migrations",
|
||||||
|
"prebuild": "parcel build --global Filer src/index.js --no-minify --out-file filer.js",
|
||||||
|
"build": "parcel build --global Filer src/index.js --out-file filer.min.js --detailed-report",
|
||||||
|
"build-tests": "parcel build tests/index.js --no-source-maps --out-dir tests/dist",
|
||||||
|
"prekarma-mocha-firefox": "npm run build-tests",
|
||||||
|
"karma-mocha-firefox": "karma start karma.conf.js --browsers FirefoxHeadless",
|
||||||
|
"prekarma-mocha-chrome": "npm run build-tests",
|
||||||
|
"karma-mocha-chrome": "karma start karma.conf.js --browsers ChromeHeadless",
|
||||||
|
"prekarma-mocha": "npm run build-tests",
|
||||||
|
"karma-mocha": "karma start karma.conf.js --browsers ChromeHeadless,FirefoxHeadless",
|
||||||
|
"coverage": "nyc mocha tests/index.js",
|
||||||
|
"release": "run.env release-it"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/filerjs/filer.git"
|
"url": "https://github.com/filerjs/filer.git"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bower": "~1.3.8",
|
"es6-promisify": "^7.0.0",
|
||||||
"base64-arraybuffer": "^0.1.2"
|
"minimatch": "^3.0.4",
|
||||||
|
"schema-utils": "^3.1.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"chai": "~1.9.1",
|
"regenerator-runtime": "^0.13.9",
|
||||||
"grunt": "~0.4.0",
|
"chai": "^4.3.4",
|
||||||
"grunt-browserify": "^2.1.0",
|
"chai-datetime": "^1.8.0",
|
||||||
"grunt-bump": "0.0.13",
|
"eslint": "^7.32.0",
|
||||||
"grunt-contrib-clean": "~0.4.0",
|
"fake-indexeddb": "^3.1.7",
|
||||||
"grunt-contrib-compress": "~0.4.1",
|
"karma": "^6.3.8",
|
||||||
"grunt-contrib-concat": "~0.1.3",
|
"karma-chai": "^0.1.0",
|
||||||
"grunt-contrib-connect": "^0.7.1",
|
"karma-chrome-launcher": "^3.1.0",
|
||||||
"grunt-contrib-jshint": "~0.7.1",
|
"karma-firefox-launcher": "^2.1.2",
|
||||||
"grunt-contrib-uglify": "~0.1.2",
|
"karma-mocha": "^2.0.1",
|
||||||
"grunt-contrib-watch": "~0.3.1",
|
"karma-mocha-reporter": "^2.2.5",
|
||||||
"grunt-git": "0.2.10",
|
"karma-summary-reporter": "^3.0.0",
|
||||||
"grunt-npm": "git://github.com/sedge/grunt-npm.git#branchcheck",
|
"meow": "^10.0.1",
|
||||||
"grunt-prompt": "^1.1.0",
|
"mocha": "^9.1.3",
|
||||||
"grunt-shell": "~0.7.0",
|
"nyc": "^15.1.0",
|
||||||
"habitat": "^1.1.0",
|
"parcel-bundler": "^1.12.5",
|
||||||
"mocha": "~1.18.2",
|
"pretty-bytes": "^5.6.0",
|
||||||
"semver": "^2.3.0",
|
"release-it": "^14.11.6",
|
||||||
"requirejs": "^2.1.14"
|
"run.env": "^1.1.0",
|
||||||
|
"unused-filename": "^3.0.1",
|
||||||
|
"walk": "^2.3.15"
|
||||||
},
|
},
|
||||||
"main": "./src/index.js"
|
"main": "./src/index.js",
|
||||||
|
"browser": "./dist/filer.min.js",
|
||||||
|
"files": [
|
||||||
|
"src",
|
||||||
|
"lib",
|
||||||
|
"dist",
|
||||||
|
"shims",
|
||||||
|
"webpack"
|
||||||
|
],
|
||||||
|
"nyc": {
|
||||||
|
"exclude": [
|
||||||
|
"tests/**/*.js",
|
||||||
|
"lib/**/*.js",
|
||||||
|
"src/providers/**/*.js"
|
||||||
|
],
|
||||||
|
"reporter": [
|
||||||
|
"lcov",
|
||||||
|
"text"
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
<!DOCTYPE HTML>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<progress id="progress" value=0></progress>
|
||||||
|
<div id="output"></div>
|
||||||
|
<div id="stderr"></div>
|
||||||
|
</body>
|
||||||
|
<script type="text/javascript" src="simple-statistics/src/simple_statistics.js"></script>
|
||||||
|
<script src="../dist/filer-perf.js"></script>
|
||||||
|
</html>
|
|
@ -0,0 +1,119 @@
|
||||||
|
var util = require('../tests/lib/test-utils.js');
|
||||||
|
|
||||||
|
function setImmediate(cb) {
|
||||||
|
setTimeout(cb, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function parse_query() {
|
||||||
|
var query = window.location.search.substring(1);
|
||||||
|
var parsed = {};
|
||||||
|
query.split('&').forEach(function(pair) {
|
||||||
|
pair = pair.split('=');
|
||||||
|
var key = decodeURIComponent(pair[0]);
|
||||||
|
var value = decodeURIComponent(pair[1]);
|
||||||
|
parsed[key] = value;
|
||||||
|
});
|
||||||
|
return parsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
var query = parse_query();
|
||||||
|
|
||||||
|
function time(test, cb) {
|
||||||
|
var start = performance.now();
|
||||||
|
function done() {
|
||||||
|
var end = performance.now();
|
||||||
|
cb(end - start);
|
||||||
|
}
|
||||||
|
test(done);
|
||||||
|
}
|
||||||
|
|
||||||
|
var random_data = new Buffer(1024); // 1kB buffer
|
||||||
|
var read_buffer = new Buffer(1024);
|
||||||
|
|
||||||
|
function run(iter) {
|
||||||
|
iter = (undefined == iter) ? 0 : iter;
|
||||||
|
|
||||||
|
function before() {
|
||||||
|
util.setup(function() {
|
||||||
|
setImmediate(during);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function during() {
|
||||||
|
var fs = util.fs();
|
||||||
|
|
||||||
|
window.crypto.getRandomValues(random_data);
|
||||||
|
time(function(done) {
|
||||||
|
fs.mkdir('/tmp', function(err) {
|
||||||
|
fs.stat('/tmp', function(err, stats) {
|
||||||
|
fs.open('/tmp/test', 'w', function(err, fd) {
|
||||||
|
fs.write(fd, random_data, null, null, null, function(err, nbytes) {
|
||||||
|
fs.close(fd, function(err) {
|
||||||
|
fs.stat('/tmp/test', function(err, stats) {
|
||||||
|
fs.open('/tmp/test', 'r', function(err, fd) {
|
||||||
|
fs.read(fd, read_buffer, null, null, null, function(err, nbytes) {
|
||||||
|
fs.close(fd, function(err) {
|
||||||
|
fs.unlink('/tmp/test', function(err) {
|
||||||
|
done();
|
||||||
|
});});});});});});});});});});
|
||||||
|
}, after);
|
||||||
|
}
|
||||||
|
|
||||||
|
function after(dt) {
|
||||||
|
util.cleanup(complete.bind(null, iter, dt));
|
||||||
|
}
|
||||||
|
|
||||||
|
before();
|
||||||
|
}
|
||||||
|
|
||||||
|
var results = [];
|
||||||
|
function complete(iter, result) {
|
||||||
|
results.push(result);
|
||||||
|
|
||||||
|
if(++iter < iterations) {
|
||||||
|
setImmediate(run.bind(null, iter));
|
||||||
|
} else {
|
||||||
|
do_stats();
|
||||||
|
}
|
||||||
|
|
||||||
|
progress.value = iter;
|
||||||
|
}
|
||||||
|
|
||||||
|
function do_stats() {
|
||||||
|
var output = document.getElementById("output");
|
||||||
|
var stats = {
|
||||||
|
mean: ss.mean(results) + " ms",
|
||||||
|
min: ss.min(results),
|
||||||
|
max: ss.max(results),
|
||||||
|
med_abs_dev: ss.median_absolute_deviation(results),
|
||||||
|
};
|
||||||
|
|
||||||
|
var t = document.createElement("table");
|
||||||
|
var tbody = document.createElement("tbody");
|
||||||
|
var keys = Object.keys(stats);
|
||||||
|
keys.forEach(function(key) {
|
||||||
|
var row = document.createElement("tr");
|
||||||
|
|
||||||
|
var key_cell = document.createElement("td");
|
||||||
|
var key_cell_text = document.createTextNode(key);
|
||||||
|
key_cell.appendChild(key_cell_text);
|
||||||
|
row.appendChild(key_cell);
|
||||||
|
|
||||||
|
var val_cell = document.createElement("td");
|
||||||
|
var val_cell_text = document.createTextNode(stats[key]);
|
||||||
|
val_cell.appendChild(val_cell_text);
|
||||||
|
row.appendChild(val_cell);
|
||||||
|
|
||||||
|
tbody.appendChild(row);
|
||||||
|
});
|
||||||
|
|
||||||
|
t.appendChild(tbody);
|
||||||
|
output.appendChild(t);
|
||||||
|
}
|
||||||
|
|
||||||
|
var query = parse_query();
|
||||||
|
var iterations = query.iterations || 10;
|
||||||
|
var progress = document.getElementById("progress");
|
||||||
|
progress.max = iterations;
|
||||||
|
|
||||||
|
run();
|
|
@ -0,0 +1,3 @@
|
||||||
|
components
|
||||||
|
build
|
||||||
|
node_modules
|
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"indent": 4,
|
||||||
|
"undef": true,
|
||||||
|
"unused": true,
|
||||||
|
"globals": {
|
||||||
|
"require": true
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
language: node_js
|
||||||
|
node_js:
|
||||||
|
- 0.10
|
||||||
|
script:
|
||||||
|
- npm install
|
||||||
|
- npm test
|
||||||
|
- npm run cov
|
|
@ -0,0 +1,242 @@
|
||||||
|
Basic contracts of functions:
|
||||||
|
|
||||||
|
* Functions do not modify their arguments e.g. change their order
|
||||||
|
* Invalid input, like empty lists to functions that need 1+ items to work, will cause functions to return `null`.
|
||||||
|
|
||||||
|
# Basic Array Operations
|
||||||
|
|
||||||
|
### .mixin(array)
|
||||||
|
|
||||||
|
_Optionally_ mix in the following functions into the `Array` prototype. Otherwise
|
||||||
|
you can use them off of the simple-statistics object itself.
|
||||||
|
|
||||||
|
If given a particular array instance as an argument, this adds the functions
|
||||||
|
only to that array rather than the global `Array.prototype`. Without an argument,
|
||||||
|
it runs on the global `Array.prototype`.
|
||||||
|
|
||||||
|
### .mean(x)
|
||||||
|
|
||||||
|
Mean of a single-dimensional Array of numbers. _Also available as `.average(x)`_
|
||||||
|
|
||||||
|
### .sum(x)
|
||||||
|
|
||||||
|
Sum of a single-dimensional Array of numbers.
|
||||||
|
|
||||||
|
### .mode(x)
|
||||||
|
|
||||||
|
Returns the number that appears most frequently in a single-dimensional Array
|
||||||
|
of numbers. If there are multiple modes, the one that appears last
|
||||||
|
is returned.
|
||||||
|
|
||||||
|
### .variance(x)
|
||||||
|
|
||||||
|
[Variance](http://en.wikipedia.org/wiki/Variance) of a single-dimensional Array of numbers.
|
||||||
|
|
||||||
|
### .standard_deviation(x)
|
||||||
|
|
||||||
|
[Standard Deviation](http://en.wikipedia.org/wiki/Standard_deviation) of a single-dimensional Array of numbers.
|
||||||
|
|
||||||
|
### .sample(array, n)
|
||||||
|
|
||||||
|
Return a [simple random sample](http://en.wikipedia.org/wiki/Simple_random_sample)
|
||||||
|
of the given array. The sampling is _without replacement_, and uses a Fisher-Yates
|
||||||
|
sample to randomize.
|
||||||
|
|
||||||
|
### .median_absolute_deviation(x)
|
||||||
|
|
||||||
|
The Median Absolute Deviation (MAD) is a robust measure of statistical
|
||||||
|
dispersion. It is more resilient to outliers than the standard deviation.
|
||||||
|
Accepts a single-dimensional array of numbers and returns a dispersion value.
|
||||||
|
|
||||||
|
Also aliased to `.mad(x)` for brevity.
|
||||||
|
|
||||||
|
### .median(x)
|
||||||
|
|
||||||
|
[Median](http://en.wikipedia.org/wiki/Median) of a single-dimensional array of numbers.
|
||||||
|
|
||||||
|
### .geometric_mean(x)
|
||||||
|
|
||||||
|
[Geometric mean](http://en.wikipedia.org/wiki/Geometric_mean) of a single-dimensional array of **positive** numbers.
|
||||||
|
|
||||||
|
### .harmonic_mean(x)
|
||||||
|
|
||||||
|
[Harmonic mean](http://en.wikipedia.org/wiki/Harmonic_mean) of a single-dimensional array of **positive** numbers.
|
||||||
|
|
||||||
|
### .root_mean_square(x)
|
||||||
|
|
||||||
|
[Root mean square (RMS)](http://en.wikipedia.org/wiki/Root_mean_square) of a single-dimensional array of numbers.
|
||||||
|
|
||||||
|
Also aliased to `.rms(x)` for brevity.
|
||||||
|
|
||||||
|
### .min(x)
|
||||||
|
|
||||||
|
Finds the minimum of a single-dimensional array of numbers. This runs in linear `O(n)` time.
|
||||||
|
|
||||||
|
### .max(x)
|
||||||
|
|
||||||
|
Finds the maximum of a single-dimensional array of numbers. This runs in linear `O(n)` time.
|
||||||
|
|
||||||
|
### .t_test(sample, x)
|
||||||
|
|
||||||
|
Does a [student's t-test](http://en.wikipedia.org/wiki/Student's_t-test) of a dataset `sample`, represented by a single-dimensional array of numbers. `x` is the known value, and the result is a measure of [statistical significance](http://en.wikipedia.org/wiki/Statistical_significance).
|
||||||
|
|
||||||
|
### .t_test_two_sample(sample_x, sample_y, difference)
|
||||||
|
|
||||||
|
The two-sample t-test is used to compare samples from two populations or groups,
|
||||||
|
confirming or denying the suspicion (null hypothesis) that the populations are
|
||||||
|
the same. It returns a t-value that you can then look up to give certain
|
||||||
|
judgements of confidence based on a t distribution table.
|
||||||
|
|
||||||
|
This implementation expects the samples `sample_x` and `sample_y` to be given
|
||||||
|
as one-dimensional arrays of more than one number each.
|
||||||
|
|
||||||
|
### .sample_variance(x)
|
||||||
|
|
||||||
|
Produces [sample variance](http://mathworld.wolfram.com/SampleVariance.html)
|
||||||
|
of a single-dimensional array of numbers.
|
||||||
|
|
||||||
|
### .sample_covariance(a, b)
|
||||||
|
|
||||||
|
Produces [sample covariance](http://en.wikipedia.org/wiki/Sample_mean_and_sample_covariance)
|
||||||
|
of two single-dimensional arrays of numbers.
|
||||||
|
|
||||||
|
### .sample_correlation(a, b)
|
||||||
|
|
||||||
|
Produces [sample correlation](http://en.wikipedia.org/wiki/Correlation_and_dependence)
|
||||||
|
of two single-dimensional arrays of numbers.
|
||||||
|
|
||||||
|
### .quantile(sample, p)
|
||||||
|
|
||||||
|
Does a [quantile](http://en.wikipedia.org/wiki/Quantile) of a dataset `sample`,
|
||||||
|
at p. For those familiary with the `k/q` syntax, `p == k/q`. `sample` must
|
||||||
|
be a single-dimensional array of numbers. p must be a number greater than or equal to
|
||||||
|
than zero and less or equal to than one, or an array of numbers following that rule.
|
||||||
|
If an array is given, an array of results will be returned instead of a single
|
||||||
|
number.
|
||||||
|
|
||||||
|
### .chunk(sample, chunkSize)
|
||||||
|
|
||||||
|
Given a `sample` array, and a positive integer `chunkSize`, splits an array
|
||||||
|
into chunks of `chunkSize` size and returns an array of those chunks. This
|
||||||
|
does not change the input value. If the length of `sample` is not divisible
|
||||||
|
by `chunkSize`, the last array will be shorter than the rest.
|
||||||
|
|
||||||
|
### .shuffle(sample)
|
||||||
|
|
||||||
|
Given a `sample` array (with any type of contents), return a random permutation
|
||||||
|
of that array, using the [Fisher-Yates shuffle](http://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle)
|
||||||
|
algorithm.
|
||||||
|
|
||||||
|
### .shuffle_in_place(sample)
|
||||||
|
|
||||||
|
Given a `sample` array (with any type of contents), return a random permutation
|
||||||
|
of that array, using the [Fisher-Yates shuffle](http://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle)
|
||||||
|
algorithm.
|
||||||
|
|
||||||
|
This changes the input array in-place, as well as returns it - unlike `.shuffle()`,
|
||||||
|
it does not create a shallow copy of the array.
|
||||||
|
|
||||||
|
### .quantile_sorted(sample, p)
|
||||||
|
|
||||||
|
Does a [quantile](http://en.wikipedia.org/wiki/Quantile) of a dataset `sample`,
|
||||||
|
at p. `sample` must be a one-dimensional _sorted_ array of numbers, and
|
||||||
|
`p` must be a single number from zero to one.
|
||||||
|
|
||||||
|
### .iqr(sample)
|
||||||
|
|
||||||
|
Calculates the [Interquartile range](http://en.wikipedia.org/wiki/Interquartile_range) of
|
||||||
|
a sample - the difference between the upper and lower quartiles. Useful
|
||||||
|
as a measure of dispersion.
|
||||||
|
|
||||||
|
_Also available as `.interquartile_range(x)`_
|
||||||
|
|
||||||
|
### .sample_skewness(sample)
|
||||||
|
|
||||||
|
Calculates the [skewness](http://en.wikipedia.org/wiki/Skewness) of
|
||||||
|
a sample, a measure of the extent to which a probability distribution of a
|
||||||
|
real-valued random variable "leans" to one side of the mean.
|
||||||
|
The skewness value can be positive or negative, or even undefined.
|
||||||
|
|
||||||
|
This implementation uses the [Fisher-Pearson standardized moment coefficient](http://en.wikipedia.org/wiki/Skewness#Pearson.27s_skewness_coefficients),
|
||||||
|
which means that it behaves the same as Excel, Minitab, SAS, and SPSS.
|
||||||
|
|
||||||
|
Skewness is only valid for samples of over three values.
|
||||||
|
|
||||||
|
### .jenks(data, number_of_classes)
|
||||||
|
|
||||||
|
Find the [Jenks Natural Breaks](http://en.wikipedia.org/wiki/Jenks_natural_breaks_optimization) for
|
||||||
|
a single-dimensional array of numbers as input and a desired `number_of_classes`.
|
||||||
|
The result is a single-dimensional with class breaks, including the minimum
|
||||||
|
and maximum of the input array.
|
||||||
|
|
||||||
|
### .r_squared(data, function)
|
||||||
|
|
||||||
|
Find the [r-squared](http://en.wikipedia.org/wiki/Coefficient_of_determination) value of a particular dataset, expressed as a two-dimensional `Array` of numbers, against a `Function`.
|
||||||
|
|
||||||
|
var r_squared = ss.r_squared([[1, 1]], function(x) { return x * 2; });
|
||||||
|
|
||||||
|
### .cumulative_std_normal_probability(z)
|
||||||
|
|
||||||
|
Look up the given `z` value in a [standard normal table](http://en.wikipedia.org/wiki/Standard_normal_table)
|
||||||
|
to calculate the probability of a random variable appearing with a given value.
|
||||||
|
|
||||||
|
### .z_score(x, mean, standard_deviation)
|
||||||
|
|
||||||
|
The standard score is the number of standard deviations an observation
|
||||||
|
or datum is above or below the mean.
|
||||||
|
|
||||||
|
### .standard_normal_table
|
||||||
|
|
||||||
|
A [standard normal table](http://en.wikipedia.org/wiki/Standard_normal_table) from
|
||||||
|
which to pull values of Φ (phi).
|
||||||
|
|
||||||
|
## Regression
|
||||||
|
|
||||||
|
### .linear_regression()
|
||||||
|
|
||||||
|
Create a new linear regression solver.
|
||||||
|
|
||||||
|
#### .data([[1, 1], [2, 2]])
|
||||||
|
|
||||||
|
Set the data of a linear regression. The input is a two-dimensional array of numbers, which are treated as coordinates, like `[[x, y], [x1, y1]]`.
|
||||||
|
|
||||||
|
#### .line()
|
||||||
|
|
||||||
|
Get the linear regression line: this returns a function that you can
|
||||||
|
give `x` values and it will return `y` values. Internally, this uses the `m()`
|
||||||
|
and `b()` values and the classic `y = mx + b` equation.
|
||||||
|
|
||||||
|
var linear_regression_line = ss.linear_regression()
|
||||||
|
.data([[0, 1], [2, 2], [3, 3]]).line();
|
||||||
|
linear_regression_line(5);
|
||||||
|
|
||||||
|
#### .m()
|
||||||
|
|
||||||
|
Just get the slope of the fitted regression line, the `m` component of the full
|
||||||
|
line equation. Returns a number.
|
||||||
|
|
||||||
|
#### .b()
|
||||||
|
|
||||||
|
Just get the y-intercept of the fitted regression line, the `b` component
|
||||||
|
of the line equation. Returns a number.
|
||||||
|
|
||||||
|
## Classification
|
||||||
|
|
||||||
|
### .bayesian()
|
||||||
|
|
||||||
|
Create a naïve bayesian classifier.
|
||||||
|
|
||||||
|
### .train(item, category)
|
||||||
|
|
||||||
|
Train the classifier to classify a certain item, given as an object with keys,
|
||||||
|
to be in a certain category, given as a string.
|
||||||
|
|
||||||
|
### .score(item)
|
||||||
|
|
||||||
|
Get the classifications of a certain item, given as an object of
|
||||||
|
`category -> score` mappings.
|
||||||
|
|
||||||
|
var bayes = ss.bayesian();
|
||||||
|
bayes.train({ species: 'Cat' }, 'animal');
|
||||||
|
bayes.score({ species: 'Cat' });
|
||||||
|
// { animal: 1 }
|
|
@ -0,0 +1,60 @@
|
||||||
|
# CHANGELOG
|
||||||
|
|
||||||
|
## 0.9.0
|
||||||
|
|
||||||
|
* Adds `.sample` for simple random sampling
|
||||||
|
* Adds `.shuffle` and `.shuffle_in_place` for random permutations
|
||||||
|
* Adds `.chunk` for splitting arrays into chunked subsets
|
||||||
|
|
||||||
|
## 0.8.1
|
||||||
|
|
||||||
|
* fixes a bug in `mode` that favored the last new number
|
||||||
|
|
||||||
|
## 0.8.0
|
||||||
|
|
||||||
|
* `mixin` can now take an array in order to mixin functions into a single array
|
||||||
|
instance rather than the global Array prototype.
|
||||||
|
|
||||||
|
## 0.7.0
|
||||||
|
|
||||||
|
* Adds `simple_statistics.harmonic_mean` thanks to [jseppi](https://github.com/jseppi)
|
||||||
|
|
||||||
|
## 0.6.0
|
||||||
|
|
||||||
|
* Adds `simple_statistics.quantile_sorted` thanks to [rluta](http://github.com/rluta)
|
||||||
|
* `simple_statistics.quantile` now accepts a sorted list of quantiles as a second argument
|
||||||
|
* Improved test coverage
|
||||||
|
|
||||||
|
## 0.5.0
|
||||||
|
|
||||||
|
* Adds `simple_statistics.cumulative_std_normal_probability` by [doronlinder](https://github.com/doronlinder)
|
||||||
|
* Adds `simple_statistics.z_score` by doronlinder
|
||||||
|
* Adds `simple_statistics.standard_normal_table`
|
||||||
|
|
||||||
|
## 0.4.0
|
||||||
|
|
||||||
|
* Adds `simple_statistics.median_absolute_deviation()` by siculars
|
||||||
|
* Adds `simple_statistics.iqr()` by siculars
|
||||||
|
* Adds `simple_statistics.skewness()` by Doron Linder
|
||||||
|
* Lower-level accessors for linear regression allow users to do the line
|
||||||
|
equation themselves
|
||||||
|
|
||||||
|
## 0.3.0
|
||||||
|
|
||||||
|
* Adds `simple_statistics.jenks()`
|
||||||
|
* Adds `simple_statistics.jenksMatrices()`
|
||||||
|
* Improves test coverage and validation
|
||||||
|
|
||||||
|
## 0.2.0
|
||||||
|
|
||||||
|
* Adds `simple_statistics.quantile()`
|
||||||
|
* Adds `simple_statistics.mixin()`
|
||||||
|
* Adds `simple_statistics.geometric_mean()`
|
||||||
|
* Adds `simple_statistics.sample_variance()`
|
||||||
|
* Adds `simple_statistics.sample_covariance()`
|
||||||
|
|
||||||
|
## 0.1.0
|
||||||
|
|
||||||
|
* Adds `simple_statistics.t_test()`
|
||||||
|
* Adds `simple_statistics.min()`
|
||||||
|
* Adds `simple_statistics.max()`
|
|
@ -0,0 +1,99 @@
|
||||||
|
# Contributing to simple-statistics
|
||||||
|
|
||||||
|
Simple statistics is a statistics library that can be both used and read.
|
||||||
|
It should help programmers learn statistics and statisticians learn programming.
|
||||||
|
In order to achieve this goal, it must be **simple** and **explanatory**.
|
||||||
|
|
||||||
|
## Simple
|
||||||
|
|
||||||
|
`simple-statistics` is written in a subset of JavaScript. Unused features
|
||||||
|
include:
|
||||||
|
|
||||||
|
* [Conditional Operator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Conditional_Operator)
|
||||||
|
* [ES5 Array methods](http://ie.microsoft.com/TestDrive/HTML5/ECMAScript5Array/Default.html)
|
||||||
|
* `with`, `eval`, and other forms of `eval`
|
||||||
|
* Most micro-optimizations, like [alternative for loop forms](http://jsperf.com/loops/70)
|
||||||
|
* [Shortcut branching](http://javascriptweblog.wordpress.com/2010/07/26/no-more-ifs-alternatives-to-statement-branching-in-javascript/)
|
||||||
|
|
||||||
|
## Explanatory
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```js
|
||||||
|
// # harmonic mean
|
||||||
|
//
|
||||||
|
// a mean function typically used to find the average of rates
|
||||||
|
//
|
||||||
|
// this is the reciprocal of the arithmetic mean of the reciprocals
|
||||||
|
// of the input numbers
|
||||||
|
//
|
||||||
|
// This runs on `O(n)`, linear time in respect to the array
|
||||||
|
```
|
||||||
|
|
||||||
|
`simple-statistics` tries to stay away from speaking only in the language of math:
|
||||||
|
for instance, while JavaScript supports UTF8 characters like π, they are not used
|
||||||
|
in the source:
|
||||||
|
|
||||||
|
* UTF8 in JavaScript on pages without specific meta-tag or Content-Type encodings will fail
|
||||||
|
* UTF8 can be hard to type, since users need to memorize key combinations or code points
|
||||||
|
* Mathematical symbols have meanings that are often better communicated by words:
|
||||||
|
in the form of code, we do not run out of space on the paper, and can afford
|
||||||
|
to call a variable `reciprocal_sum` instead of `r`.
|
||||||
|
|
||||||
|
Every function has a comment that ideally includes:
|
||||||
|
|
||||||
|
* The English, long-form name of the method
|
||||||
|
* What the method does
|
||||||
|
* What purpose the method typically serves
|
||||||
|
* A link to a longer description on Wikipedia, Mathematica, or another
|
||||||
|
web-accessible, non-paywalled source
|
||||||
|
* The efficiency of the function in terms of Big-O notation, if appropriate
|
||||||
|
* If the function depends on another function in the library, a note of this, like
|
||||||
|
`depends on mean()`
|
||||||
|
|
||||||
|
## Tests
|
||||||
|
|
||||||
|
`simple-statistics` has a testsuite located in `test/spec/`. Each test file
|
||||||
|
covers a specific topic and tries to test against known values:
|
||||||
|
|
||||||
|
* Values produced by trusted statistics software like R or scipy
|
||||||
|
* Common-sense results
|
||||||
|
|
||||||
|
Tests can be run in [node.js](http://nodejs.org/) and are run on every commit
|
||||||
|
to GitHub by Travis-CI.
|
||||||
|
|
||||||
|
To run tests:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm install
|
||||||
|
npm test
|
||||||
|
```
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
While the code is meant to readable, it is not documentation. We maintain
|
||||||
|
documentation in `API.md`, which has the simple form:
|
||||||
|
|
||||||
|
```md
|
||||||
|
### .geometric_mean(x)
|
||||||
|
|
||||||
|
[Geometric mean](http://en.wikipedia.org/wiki/Geometric_mean) of a single-dimensional array of **positive** numbers.
|
||||||
|
```
|
||||||
|
|
||||||
|
This file is written in [Markdown](https://daringfireball.net/projects/markdown/) and
|
||||||
|
specifies which functions are available, what type of arguments they receive,
|
||||||
|
what they compute, and what type of answer they return.
|
||||||
|
|
||||||
|
## Code Style
|
||||||
|
|
||||||
|
We use the [Airbnb style for Javascript](https://github.com/airbnb/javascript) with
|
||||||
|
only one difference:
|
||||||
|
|
||||||
|
**4 space soft tabs always for Javascript, not 2.**
|
||||||
|
|
||||||
|
No aligned `=`, no aligned arguments, spaces are either indents or the 1
|
||||||
|
space between expressions. No hard tabs.
|
||||||
|
|
||||||
|
* All comparisons should be as strict and obvious as possible: prefer `(foo === 0)` to
|
||||||
|
`(!foo)`.
|
||||||
|
* Straightforward code is more important than most optimizations.
|
|
@ -0,0 +1,13 @@
|
||||||
|
Copyright (c) 2014, Tom MacWright
|
||||||
|
|
||||||
|
Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
purpose with or without fee is hereby granted, provided that the above
|
||||||
|
copyright notice and this permission notice appear in all copies.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||||
|
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||||
|
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||||
|
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||||
|
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||||
|
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
|
PERFORMANCE OF THIS SOFTWARE.
|
|
@ -0,0 +1,7 @@
|
||||||
|
docs:
|
||||||
|
docco src/*.js
|
||||||
|
|
||||||
|
test:
|
||||||
|
mocha -R spec test/spec/*.js
|
||||||
|
|
||||||
|
.PHONY: docs test
|
|
@ -0,0 +1,337 @@
|
||||||
|
[![Build Status](https://secure.travis-ci.org/tmcw/simple-statistics.png?branch=master)](http://travis-ci.org/tmcw/simple-statistics) [![Coverage Status](https://coveralls.io/repos/tmcw/simple-statistics/badge.png)](https://coveralls.io/r/tmcw/simple-statistics)
|
||||||
|
|
||||||
|
A JavaScript implementation of descriptive, regression, and inference statistics.
|
||||||
|
|
||||||
|
Implemented in literate JavaScript with no dependencies, designed to work
|
||||||
|
in all modern browsers (including IE) as well as in node.js.
|
||||||
|
|
||||||
|
## [API Documentation](API.md)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Basic contracts of functions:
|
||||||
|
|
||||||
|
* Functions do not modify their arguments e.g. change their order
|
||||||
|
* Invalid input, like empty lists to functions that need 1+ items to work, will cause functions to return `null`.
|
||||||
|
|
||||||
|
# Basic Array Operations
|
||||||
|
|
||||||
|
### .mixin(array)
|
||||||
|
|
||||||
|
_Optionally_ mix in the following functions into the `Array` prototype. Otherwise
|
||||||
|
you can use them off of the simple-statistics object itself.
|
||||||
|
|
||||||
|
If given a particular array instance as an argument, this adds the functions
|
||||||
|
only to that array rather than the global `Array.prototype`. Without an argument,
|
||||||
|
it runs on the global `Array.prototype`.
|
||||||
|
|
||||||
|
### .mean(x)
|
||||||
|
|
||||||
|
Mean of a single-dimensional Array of numbers. _Also available as `.average(x)`_
|
||||||
|
|
||||||
|
### .sum(x)
|
||||||
|
|
||||||
|
Sum of a single-dimensional Array of numbers.
|
||||||
|
|
||||||
|
### .mode(x)
|
||||||
|
|
||||||
|
Returns the number that appears most frequently in a single-dimensional Array
|
||||||
|
of numbers. If there are multiple modes, the one that appears last
|
||||||
|
is returned.
|
||||||
|
|
||||||
|
### .variance(x)
|
||||||
|
|
||||||
|
[Variance](http://en.wikipedia.org/wiki/Variance) of a single-dimensional Array of numbers.
|
||||||
|
|
||||||
|
### .standard_deviation(x)
|
||||||
|
|
||||||
|
[Standard Deviation](http://en.wikipedia.org/wiki/Standard_deviation) of a single-dimensional Array of numbers.
|
||||||
|
|
||||||
|
### .median_absolute_deviation(x)
|
||||||
|
|
||||||
|
The Median Absolute Deviation (MAD) is a robust measure of statistical
|
||||||
|
dispersion. It is more resilient to outliers than the standard deviation.
|
||||||
|
Accepts a single-dimensional array of numbers and returns a dispersion value.
|
||||||
|
|
||||||
|
Also aliased to `.mad(x)` for brevity.
|
||||||
|
|
||||||
|
### .median(x)
|
||||||
|
|
||||||
|
[Median](http://en.wikipedia.org/wiki/Median) of a single-dimensional array of numbers.
|
||||||
|
|
||||||
|
### .geometric_mean(x)
|
||||||
|
|
||||||
|
[Geometric mean](http://en.wikipedia.org/wiki/Geometric_mean) of a single-dimensional array of **positive** numbers.
|
||||||
|
|
||||||
|
### .harmonic_mean(x)
|
||||||
|
|
||||||
|
[Harmonic mean](http://en.wikipedia.org/wiki/Harmonic_mean) of a single-dimensional array of **positive** numbers.
|
||||||
|
|
||||||
|
### .root_mean_square(x)
|
||||||
|
|
||||||
|
[Root mean square (RMS)](http://en.wikipedia.org/wiki/Root_mean_square) of a single-dimensional array of numbers.
|
||||||
|
|
||||||
|
### .min(x)
|
||||||
|
|
||||||
|
Finds the minimum of a single-dimensional array of numbers. This runs in linear `O(n)` time.
|
||||||
|
|
||||||
|
### .max(x)
|
||||||
|
|
||||||
|
Finds the maximum of a single-dimensional array of numbers. This runs in linear `O(n)` time.
|
||||||
|
|
||||||
|
### .t_test(sample, x)
|
||||||
|
|
||||||
|
Does a [student's t-test](http://en.wikipedia.org/wiki/Student's_t-test) of a dataset `sample`, represented by a single-dimensional array of numbers. `x` is the known value, and the result is a measure of [statistical significance](http://en.wikipedia.org/wiki/Statistical_significance).
|
||||||
|
|
||||||
|
### .t_test_two_sample(sample_x, sample_y, difference)
|
||||||
|
|
||||||
|
The two-sample t-test is used to compare samples from two populations or groups,
|
||||||
|
confirming or denying the suspicion (null hypothesis) that the populations are
|
||||||
|
the same. It returns a t-value that you can then look up to give certain
|
||||||
|
judgements of confidence based on a t distribution table.
|
||||||
|
|
||||||
|
This implementation expects the samples `sample_x` and `sample_y` to be given
|
||||||
|
as one-dimensional arrays of more than one number each.
|
||||||
|
|
||||||
|
### .sample_variance(x)
|
||||||
|
|
||||||
|
Produces [sample variance](http://mathworld.wolfram.com/SampleVariance.html)
|
||||||
|
of a single-dimensional array of numbers.
|
||||||
|
|
||||||
|
### .sample_covariance(a, b)
|
||||||
|
|
||||||
|
Produces [sample covariance](http://en.wikipedia.org/wiki/Sample_mean_and_sample_covariance)
|
||||||
|
of two single-dimensional arrays of numbers.
|
||||||
|
|
||||||
|
### .sample_correlation(a, b)
|
||||||
|
|
||||||
|
Produces [sample correlation](http://en.wikipedia.org/wiki/Correlation_and_dependence)
|
||||||
|
of two single-dimensional arrays of numbers.
|
||||||
|
|
||||||
|
### .quantile(sample, p)
|
||||||
|
|
||||||
|
Does a [quantile](http://en.wikipedia.org/wiki/Quantile) of a dataset `sample`,
|
||||||
|
at p. For those familiary with the `k/q` syntax, `p == k/q`. `sample` must
|
||||||
|
be a single-dimensional array of numbers. p must be a number greater than or equal to zero and less than or equal to one, or an array of numbers following that rule.
|
||||||
|
If an array is given, an array of results will be returned instead of a single
|
||||||
|
number.
|
||||||
|
|
||||||
|
### .chunk(sample, chunkSize)
|
||||||
|
|
||||||
|
Given a `sample` array, and a positive integer `chunkSize`, splits an array
|
||||||
|
into chunks of `chunkSize` size and returns an array of those chunks. This
|
||||||
|
does not change the input value. If the length of `sample` is not divisible
|
||||||
|
by `chunkSize`, the last array will be shorter than the rest.
|
||||||
|
|
||||||
|
### .quantile_sorted(sample, p)
|
||||||
|
|
||||||
|
Does a [quantile](http://en.wikipedia.org/wiki/Quantile) of a dataset `sample`,
|
||||||
|
at p. `sample` must be a one-dimensional _sorted_ array of numbers, and
|
||||||
|
`p` must be a single number greater than or equal to zero and less than or equal to one.
|
||||||
|
|
||||||
|
### .iqr(sample)
|
||||||
|
|
||||||
|
Calculates the [Interquartile range](http://en.wikipedia.org/wiki/Interquartile_range) of
|
||||||
|
a sample - the difference between the upper and lower quartiles. Useful
|
||||||
|
as a measure of dispersion.
|
||||||
|
|
||||||
|
_Also available as `.interquartile_range(x)`_
|
||||||
|
|
||||||
|
### .sample_skewness(sample)
|
||||||
|
|
||||||
|
Calculates the [skewness](http://en.wikipedia.org/wiki/Skewness) of
|
||||||
|
a sample, a measure of the extent to which a probability distribution of a
|
||||||
|
real-valued random variable "leans" to one side of the mean.
|
||||||
|
The skewness value can be positive or negative, or even undefined.
|
||||||
|
|
||||||
|
This implementation uses the [Fisher-Pearson standardized moment coefficient](http://en.wikipedia.org/wiki/Skewness#Pearson.27s_skewness_coefficients),
|
||||||
|
which means that it behaves the same as Excel, Minitab, SAS, and SPSS.
|
||||||
|
|
||||||
|
Skewness is only valid for samples of over three values.
|
||||||
|
|
||||||
|
### .jenks(data, number_of_classes)
|
||||||
|
|
||||||
|
Find the [Jenks Natural Breaks](http://en.wikipedia.org/wiki/Jenks_natural_breaks_optimization) for
|
||||||
|
a single-dimensional array of numbers as input and a desired `number_of_classes`.
|
||||||
|
The result is a single-dimensional with class breaks, including the minimum
|
||||||
|
and maximum of the input array.
|
||||||
|
|
||||||
|
### .r_squared(data, function)
|
||||||
|
|
||||||
|
Find the [r-squared](http://en.wikipedia.org/wiki/Coefficient_of_determination) value of a particular dataset, expressed as a two-dimensional `Array` of numbers, against a `Function`.
|
||||||
|
|
||||||
|
var r_squared = ss.r_squared([[1, 1]], function(x) { return x * 2; });
|
||||||
|
|
||||||
|
### .cumulative_std_normal_probability(z)
|
||||||
|
|
||||||
|
Look up the given `z` value in a [standard normal table](http://en.wikipedia.org/wiki/Standard_normal_table)
|
||||||
|
to calculate the probability of a random variable appearing with a given value.
|
||||||
|
|
||||||
|
### .z_score(x, mean, standard_deviation)
|
||||||
|
|
||||||
|
The standard score is the number of standard deviations an observation
|
||||||
|
or datum is above or below the mean.
|
||||||
|
|
||||||
|
### .standard_normal_table
|
||||||
|
|
||||||
|
A [standard normal table](http://en.wikipedia.org/wiki/Standard_normal_table) from
|
||||||
|
which to pull values of Φ (phi).
|
||||||
|
|
||||||
|
## Regression
|
||||||
|
|
||||||
|
### .linear_regression()
|
||||||
|
|
||||||
|
Create a new linear regression solver.
|
||||||
|
|
||||||
|
#### .data([[1, 1], [2, 2]])
|
||||||
|
|
||||||
|
Set the data of a linear regression. The input is a two-dimensional array of numbers, which are treated as coordinates, like `[[x, y], [x1, y1]]`.
|
||||||
|
|
||||||
|
#### .line()
|
||||||
|
|
||||||
|
Get the linear regression line: this returns a function that you can
|
||||||
|
give `x` values and it will return `y` values. Internally, this uses the `m()`
|
||||||
|
and `b()` values and the classic `y = mx + b` equation.
|
||||||
|
|
||||||
|
var linear_regression_line = ss.linear_regression()
|
||||||
|
.data([[0, 1], [2, 2], [3, 3]]).line();
|
||||||
|
linear_regression_line(5);
|
||||||
|
|
||||||
|
#### .m()
|
||||||
|
|
||||||
|
Just get the slope of the fitted regression line, the `m` component of the full
|
||||||
|
line equation. Returns a number.
|
||||||
|
|
||||||
|
#### .b()
|
||||||
|
|
||||||
|
Just get the y-intercept of the fitted regression line, the `b` component
|
||||||
|
of the line equation. Returns a number.
|
||||||
|
|
||||||
|
## Classification
|
||||||
|
|
||||||
|
### .bayesian()
|
||||||
|
|
||||||
|
Create a naïve bayesian classifier.
|
||||||
|
|
||||||
|
### .train(item, category)
|
||||||
|
|
||||||
|
Train the classifier to classify a certain item, given as an object with keys,
|
||||||
|
to be in a certain category, given as a string.
|
||||||
|
|
||||||
|
### .score(item)
|
||||||
|
|
||||||
|
Get the classifications of a certain item, given as an object of
|
||||||
|
`category -> score` mappings.
|
||||||
|
|
||||||
|
var bayes = ss.bayesian();
|
||||||
|
bayes.train({ species: 'Cat' }, 'animal');
|
||||||
|
bayes.score({ species: 'Cat' });
|
||||||
|
// { animal: 1 }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [Literate Source](http://macwright.org/simple-statistics/)
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
To use it in browsers, grab [simple_statistics.js](https://raw.github.com/tmcw/simple-statistics/master/src/simple_statistics.js).
|
||||||
|
To use it in node, install it with [npm](https://npmjs.org/) or add it to your package.json.
|
||||||
|
|
||||||
|
npm install simple-statistics
|
||||||
|
|
||||||
|
To use it with [component](https://github.com/component/component),
|
||||||
|
|
||||||
|
component install tmcw/simple-statistics
|
||||||
|
|
||||||
|
To use it with [bower](http://bower.io/),
|
||||||
|
|
||||||
|
bower install simple-statistics
|
||||||
|
|
||||||
|
## Basic Descriptive Statistics
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Require simple statistics
|
||||||
|
var ss = require('simple-statistics');
|
||||||
|
|
||||||
|
// The input is a simple array
|
||||||
|
var list = [1, 2, 3];
|
||||||
|
|
||||||
|
// Many different descriptive statistics are supported
|
||||||
|
var sum = ss.sum(list),
|
||||||
|
mean = ss.mean(list),
|
||||||
|
min = ss.min(list),
|
||||||
|
geometric_mean = ss.geometric_mean(list),
|
||||||
|
max = ss.max(list),
|
||||||
|
quantile = ss.quantile(0.25);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Linear Regression
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// For a linear regression, it's a two-dimensional array
|
||||||
|
var data = [ [1, 2], [2, 3] ];
|
||||||
|
|
||||||
|
// simple-statistics can produce a linear regression and return
|
||||||
|
// a friendly javascript function for the line.
|
||||||
|
var line = ss.linear_regression()
|
||||||
|
.data(data)
|
||||||
|
.line();
|
||||||
|
|
||||||
|
// get a point along the line function
|
||||||
|
line(0);
|
||||||
|
|
||||||
|
var line = ss.linear_regression()
|
||||||
|
|
||||||
|
// Get the r-squared value of the line estimation
|
||||||
|
ss.r_squared(data, line);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Bayesian Classifier
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
var bayes = ss.bayesian();
|
||||||
|
bayes.train({ species: 'Cat' }, 'animal');
|
||||||
|
bayes.score({ species: 'Cat' });
|
||||||
|
// { animal: 1 }
|
||||||
|
```
|
||||||
|
|
||||||
|
### Mixin Style
|
||||||
|
|
||||||
|
_This is **optional** and not used by default. You can opt-in to mixins
|
||||||
|
with `ss.mixin()`._
|
||||||
|
|
||||||
|
This mixes `simple-statistics` methods into the Array prototype - note that
|
||||||
|
[extending native objects](http://perfectionkills.com/extending-native-builtins/) is a
|
||||||
|
tricky move.
|
||||||
|
|
||||||
|
This will _only work_ if `defineProperty` is available, which means modern browsers
|
||||||
|
and nodejs - on IE8 and below, calling `ss.mixin()` will throw an exception.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// mixin to Array class
|
||||||
|
ss.mixin();
|
||||||
|
|
||||||
|
// The input is a simple array
|
||||||
|
var list = [1, 2, 3];
|
||||||
|
|
||||||
|
// The same descriptive techniques as above, but in a simpler style
|
||||||
|
var sum = list.sum(),
|
||||||
|
mean = list.mean(),
|
||||||
|
min = list.min(),
|
||||||
|
max = list.max(),
|
||||||
|
quantile = list.quantile(0.25);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
* [Linear regression with simple-statistics and d3js](http://bl.ocks.org/3931800)
|
||||||
|
* [Jenks Natural Breaks with a choropleth map with d3js](http://bl.ocks.org/tmcw/4969184)
|
||||||
|
|
||||||
|
# Contributors
|
||||||
|
|
||||||
|
* Tom MacWright
|
||||||
|
* [Matt Sacks](https://github.com/mattsacks)
|
||||||
|
* Doron Linder
|
||||||
|
* [Alexander Sicular](https://github.com/siculars)
|
|
@ -0,0 +1,157 @@
|
||||||
|
[![Build Status](https://secure.travis-ci.org/tmcw/simple-statistics.png?branch=master)](http://travis-ci.org/tmcw/simple-statistics)
|
||||||
|
|
||||||
|
A JavaScript implementation of descriptive, regression, and inference statistics.
|
||||||
|
|
||||||
|
Implemented in literate JavaScript with no dependencies, designed to work
|
||||||
|
in all modern browsers (including IE) as well as in node.js.
|
||||||
|
|
||||||
|
# [API](API.md)
|
||||||
|
|
||||||
|
[Full documentation](API.md)
|
||||||
|
|
||||||
|
---
|
||||||
|
```
|
||||||
|
|
||||||
|
Basic Array Operations
|
||||||
|
.mixin()
|
||||||
|
.mean(x)
|
||||||
|
.sum(x)
|
||||||
|
.variance(x)
|
||||||
|
.standard_deviation(x)
|
||||||
|
.median_absolute_deviation(x)
|
||||||
|
.median(x)
|
||||||
|
.geometric_mean(x)
|
||||||
|
.harmonic_mean(x)
|
||||||
|
.root_mean_square(x)
|
||||||
|
.min(x)
|
||||||
|
.max(x)
|
||||||
|
.t_test(sample, x)
|
||||||
|
.t_test_two_sample(sample_x, sample_y, difference)
|
||||||
|
.sample_variance(x)
|
||||||
|
.sample_covariance(x)
|
||||||
|
.sample_correlation(x)
|
||||||
|
.quantile(sample, p)
|
||||||
|
.iqr(sample)
|
||||||
|
.sample_skewness(sample)
|
||||||
|
.jenks(data, number_of_classes)
|
||||||
|
.r_squared(data, function)
|
||||||
|
.cumulative_std_normal_probability(z)
|
||||||
|
.z_score(x, mean, standard_deviation)
|
||||||
|
.standard_normal_table
|
||||||
|
Regression
|
||||||
|
.linear_regression()
|
||||||
|
.data([[1, 1], [2, 2]])
|
||||||
|
.line()
|
||||||
|
.m()
|
||||||
|
.b()
|
||||||
|
Classification
|
||||||
|
.bayesian()
|
||||||
|
.train(item, category)
|
||||||
|
.score(item)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# [Literate Source](http://macwright.org/simple-statistics/)
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
To use it in browsers, grab [simple_statistics.js](https://raw.github.com/tmcw/simple-statistics/master/src/simple_statistics.js).
|
||||||
|
To use it in node, install it with [npm](https://npmjs.org/) or add it to your package.json.
|
||||||
|
|
||||||
|
npm install simple-statistics
|
||||||
|
|
||||||
|
To use it with [component](https://github.com/component/component),
|
||||||
|
|
||||||
|
component install tmcw/simple-statistics
|
||||||
|
|
||||||
|
To use it with [bower](http://bower.io/),
|
||||||
|
|
||||||
|
bower install simple-statistics
|
||||||
|
|
||||||
|
## Basic Descriptive Statistics
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Require simple statistics
|
||||||
|
var ss = require('simple-statistics');
|
||||||
|
|
||||||
|
// The input is a simple array
|
||||||
|
var list = [1, 2, 3];
|
||||||
|
|
||||||
|
// Many different descriptive statistics are supported
|
||||||
|
var sum = ss.sum(list),
|
||||||
|
mean = ss.mean(list),
|
||||||
|
min = ss.min(list),
|
||||||
|
geometric_mean = ss.geometric_mean(list),
|
||||||
|
max = ss.max(list),
|
||||||
|
quantile = ss.quantile(0.25);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Linear Regression
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// For a linear regression, it's a two-dimensional array
|
||||||
|
var data = [ [1, 2], [2, 3] ];
|
||||||
|
|
||||||
|
// simple-statistics can produce a linear regression and return
|
||||||
|
// a friendly javascript function for the line.
|
||||||
|
var line = ss.linear_regression()
|
||||||
|
.data(data)
|
||||||
|
.line();
|
||||||
|
|
||||||
|
// get a point along the line function
|
||||||
|
line(0);
|
||||||
|
|
||||||
|
var line = ss.linear_regression()
|
||||||
|
|
||||||
|
// Get the r-squared value of the line estimation
|
||||||
|
ss.r_squared(data, line);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Bayesian Classifier
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
var bayes = ss.bayesian();
|
||||||
|
bayes.train({ species: 'Cat' }, 'animal');
|
||||||
|
bayes.score({ species: 'Cat' });
|
||||||
|
// { animal: 1 }
|
||||||
|
```
|
||||||
|
|
||||||
|
### Mixin Style
|
||||||
|
|
||||||
|
_This is **optional** and not used by default. You can opt-in to mixins
|
||||||
|
with `ss.mixin()`._
|
||||||
|
|
||||||
|
This mixes `simple-statistics` methods into the Array prototype - note that
|
||||||
|
[extending native objects](http://perfectionkills.com/extending-built-in-native-objects-evil-or-not/) is a
|
||||||
|
tricky move.
|
||||||
|
|
||||||
|
This will _only work_ if `defineProperty` is available, which means modern browsers
|
||||||
|
and nodejs - on IE8 and below, calling `ss.mixin()` will throw an exception.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// mixin to Array class
|
||||||
|
ss.mixin();
|
||||||
|
|
||||||
|
// The input is a simple array
|
||||||
|
var list = [1, 2, 3];
|
||||||
|
|
||||||
|
// The same descriptive techniques as above, but in a simpler style
|
||||||
|
var sum = list.sum(),
|
||||||
|
mean = list.mean(),
|
||||||
|
min = list.min(),
|
||||||
|
max = list.max(),
|
||||||
|
quantile = list.quantile(0.25);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
* [Linear regression with simple-statistics and d3js](http://bl.ocks.org/3931800)
|
||||||
|
* [Jenks Natural Breaks with a choropleth map with d3js](http://bl.ocks.org/tmcw/4969184)
|
||||||
|
|
||||||
|
# Contributors
|
||||||
|
|
||||||
|
* Tom MacWright
|
||||||
|
* [Matt Sacks](https://github.com/mattsacks)
|
||||||
|
* Doron Linder
|
||||||
|
* [Alexander Sicular](https://github.com/siculars)
|
|
@ -0,0 +1,23 @@
|
||||||
|
## See Also
|
||||||
|
|
||||||
|
* [stream-statistics](https://github.com/tmcw/stream-statistics), a sister project that implements
|
||||||
|
many of the same measures for streaming data - as online algorithms
|
||||||
|
|
||||||
|
### Javascript
|
||||||
|
|
||||||
|
* [science.js](https://github.com/jasondavies/science.js)
|
||||||
|
* [atoll.js](https://github.com/nsfmc/atoll.js)
|
||||||
|
* [descriptive_statistics](https://github.com/thirtysixthspan/descriptive_statistics)
|
||||||
|
* [jStat](http://www.jstat.org/)
|
||||||
|
* [classifier](https://github.com/harthur/classifier) is a naive bayesian classifier (though specialized for the words-spam case)
|
||||||
|
* [underscore.math](https://github.com/syntagmatic/underscore.math/blob/master/underscore.math.js)
|
||||||
|
|
||||||
|
### Python
|
||||||
|
|
||||||
|
* [Pandas](http://pandas.pydata.org/)
|
||||||
|
* [SciPy](http://www.scipy.org/)
|
||||||
|
|
||||||
|
### Their Own Language
|
||||||
|
|
||||||
|
* [Julia Language](http://julialang.org/)
|
||||||
|
* [R language](http://www.r-project.org/)
|
|
@ -0,0 +1,20 @@
|
||||||
|
var fs = require('fs');
|
||||||
|
|
||||||
|
var readme = fs.readFileSync('README.md', 'utf8')
|
||||||
|
.split('\n');
|
||||||
|
|
||||||
|
var a = true, b = true;
|
||||||
|
|
||||||
|
fs.writeFileSync('README.md', readme.filter(function(f) {
|
||||||
|
if (f === '---') {
|
||||||
|
a = !a;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return a;
|
||||||
|
}).map(function(f) {
|
||||||
|
if (f === '---' && b) {
|
||||||
|
f = f + '\n\n' + fs.readFileSync('API.md', 'utf8') + '\n\n';
|
||||||
|
b = false;
|
||||||
|
}
|
||||||
|
return f;
|
||||||
|
}).join('\n'));
|
|
@ -0,0 +1,11 @@
|
||||||
|
{
|
||||||
|
"name": "simple-statistics",
|
||||||
|
"version": "0.9.0",
|
||||||
|
"description": "Simple Statistics",
|
||||||
|
"repo": "tmcw/simple-statistics",
|
||||||
|
"keywords": [],
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {},
|
||||||
|
"development": {},
|
||||||
|
"main": "src/simple_statistics.js"
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
{
|
||||||
|
"name": "simple-statistics",
|
||||||
|
"version": "0.9.0",
|
||||||
|
"description": "Simple Statistics",
|
||||||
|
"repo": "tmcw/simple-statistics",
|
||||||
|
"keywords": [],
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {},
|
||||||
|
"development": {},
|
||||||
|
"scripts": [
|
||||||
|
"src/simple_statistics.js"
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,506 @@
|
||||||
|
/*--------------------- Typography ----------------------------*/
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'aller-light';
|
||||||
|
src: url('public/fonts/aller-light.eot');
|
||||||
|
src: url('public/fonts/aller-light.eot?#iefix') format('embedded-opentype'),
|
||||||
|
url('public/fonts/aller-light.woff') format('woff'),
|
||||||
|
url('public/fonts/aller-light.ttf') format('truetype');
|
||||||
|
font-weight: normal;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'aller-bold';
|
||||||
|
src: url('public/fonts/aller-bold.eot');
|
||||||
|
src: url('public/fonts/aller-bold.eot?#iefix') format('embedded-opentype'),
|
||||||
|
url('public/fonts/aller-bold.woff') format('woff'),
|
||||||
|
url('public/fonts/aller-bold.ttf') format('truetype');
|
||||||
|
font-weight: normal;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'novecento-bold';
|
||||||
|
src: url('public/fonts/novecento-bold.eot');
|
||||||
|
src: url('public/fonts/novecento-bold.eot?#iefix') format('embedded-opentype'),
|
||||||
|
url('public/fonts/novecento-bold.woff') format('woff'),
|
||||||
|
url('public/fonts/novecento-bold.ttf') format('truetype');
|
||||||
|
font-weight: normal;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*--------------------- Layout ----------------------------*/
|
||||||
|
html { height: 100%; }
|
||||||
|
body {
|
||||||
|
font-family: "aller-light";
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 18px;
|
||||||
|
color: #30404f;
|
||||||
|
margin: 0; padding: 0;
|
||||||
|
height:100%;
|
||||||
|
}
|
||||||
|
#container { min-height: 100%; }
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
b, strong {
|
||||||
|
font-weight: normal;
|
||||||
|
font-family: "aller-bold";
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 15px 0 0px;
|
||||||
|
}
|
||||||
|
.annotation ul, .annotation ol {
|
||||||
|
margin: 25px 0;
|
||||||
|
}
|
||||||
|
.annotation ul li, .annotation ol li {
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 18px;
|
||||||
|
margin: 10px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1, h2, h3, h4, h5, h6 {
|
||||||
|
color: #112233;
|
||||||
|
line-height: 1em;
|
||||||
|
font-weight: normal;
|
||||||
|
font-family: "novecento-bold";
|
||||||
|
text-transform: uppercase;
|
||||||
|
margin: 30px 0 15px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
margin-top: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
hr {
|
||||||
|
border: 0;
|
||||||
|
background: 1px #ddd;
|
||||||
|
height: 1px;
|
||||||
|
margin: 20px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre, tt, code {
|
||||||
|
font-size: 12px; line-height: 16px;
|
||||||
|
font-family: Menlo, Monaco, Consolas, "Lucida Console", monospace;
|
||||||
|
margin: 0; padding: 0;
|
||||||
|
}
|
||||||
|
.annotation pre {
|
||||||
|
display: block;
|
||||||
|
margin: 0;
|
||||||
|
padding: 7px 10px;
|
||||||
|
background: #fcfcfc;
|
||||||
|
-moz-box-shadow: inset 0 0 10px rgba(0,0,0,0.1);
|
||||||
|
-webkit-box-shadow: inset 0 0 10px rgba(0,0,0,0.1);
|
||||||
|
box-shadow: inset 0 0 10px rgba(0,0,0,0.1);
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
.annotation pre code {
|
||||||
|
border: 0;
|
||||||
|
padding: 0;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
blockquote {
|
||||||
|
border-left: 5px solid #ccc;
|
||||||
|
margin: 0;
|
||||||
|
padding: 1px 0 1px 1em;
|
||||||
|
}
|
||||||
|
.sections blockquote p {
|
||||||
|
font-family: Menlo, Consolas, Monaco, monospace;
|
||||||
|
font-size: 12px; line-height: 16px;
|
||||||
|
color: #999;
|
||||||
|
margin: 10px 0 0;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.sections {
|
||||||
|
list-style: none;
|
||||||
|
padding:0 0 5px 0;;
|
||||||
|
margin:0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Force border-box so that % widths fit the parent
|
||||||
|
container without overlap because of margin/padding.
|
||||||
|
|
||||||
|
More Info : http://www.quirksmode.org/css/box.html
|
||||||
|
*/
|
||||||
|
ul.sections > li > div {
|
||||||
|
-moz-box-sizing: border-box; /* firefox */
|
||||||
|
-ms-box-sizing: border-box; /* ie */
|
||||||
|
-webkit-box-sizing: border-box; /* webkit */
|
||||||
|
-khtml-box-sizing: border-box; /* konqueror */
|
||||||
|
box-sizing: border-box; /* css3 */
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*---------------------- Jump Page -----------------------------*/
|
||||||
|
#jump_to, #jump_page {
|
||||||
|
margin: 0;
|
||||||
|
background: white;
|
||||||
|
-webkit-box-shadow: 0 0 25px #777; -moz-box-shadow: 0 0 25px #777;
|
||||||
|
-webkit-border-bottom-left-radius: 5px; -moz-border-radius-bottomleft: 5px;
|
||||||
|
font: 16px Arial;
|
||||||
|
cursor: pointer;
|
||||||
|
text-align: right;
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#jump_to a {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#jump_to a.large {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
#jump_to a.small {
|
||||||
|
font-size: 22px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #676767;
|
||||||
|
}
|
||||||
|
|
||||||
|
#jump_to, #jump_wrapper {
|
||||||
|
position: fixed;
|
||||||
|
right: 0; top: 0;
|
||||||
|
padding: 10px 15px;
|
||||||
|
margin:0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#jump_wrapper {
|
||||||
|
display: none;
|
||||||
|
padding:0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#jump_to:hover #jump_wrapper {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
#jump_page {
|
||||||
|
padding: 5px 0 3px;
|
||||||
|
margin: 0 0 25px 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#jump_page .source {
|
||||||
|
display: block;
|
||||||
|
padding: 15px;
|
||||||
|
text-decoration: none;
|
||||||
|
border-top: 1px solid #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
#jump_page .source:hover {
|
||||||
|
background: #f5f5ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
#jump_page .source:first-child {
|
||||||
|
}
|
||||||
|
|
||||||
|
/*---------------------- Low resolutions (> 320px) ---------------------*/
|
||||||
|
@media only screen and (min-width: 320px) {
|
||||||
|
.pilwrap { display: none; }
|
||||||
|
|
||||||
|
ul.sections > li > div {
|
||||||
|
display: block;
|
||||||
|
padding:5px 10px 0 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.sections > li > div.annotation ul, ul.sections > li > div.annotation ol {
|
||||||
|
padding-left: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.sections > li > div.content {
|
||||||
|
overflow-x:auto;
|
||||||
|
-webkit-box-shadow: inset 0 0 5px #e5e5ee;
|
||||||
|
box-shadow: inset 0 0 5px #e5e5ee;
|
||||||
|
border: 1px solid #dedede;
|
||||||
|
margin:5px 10px 5px 10px;
|
||||||
|
padding-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.sections > li > div.annotation pre {
|
||||||
|
margin: 7px 0 7px;
|
||||||
|
padding-left: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.sections > li > div.annotation p tt, .annotation code {
|
||||||
|
background: #f8f8ff;
|
||||||
|
border: 1px solid #dedede;
|
||||||
|
font-size: 12px;
|
||||||
|
padding: 0 0.2em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*---------------------- (> 481px) ---------------------*/
|
||||||
|
@media only screen and (min-width: 481px) {
|
||||||
|
#container {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
background-color: #F5F5FF;
|
||||||
|
font-size: 15px;
|
||||||
|
line-height: 21px;
|
||||||
|
}
|
||||||
|
pre, tt, code {
|
||||||
|
line-height: 18px;
|
||||||
|
}
|
||||||
|
p, ul, ol {
|
||||||
|
margin: 0 0 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#jump_to {
|
||||||
|
padding: 5px 10px;
|
||||||
|
}
|
||||||
|
#jump_wrapper {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
#jump_to, #jump_page {
|
||||||
|
font: 10px Arial;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
#jump_page .source {
|
||||||
|
padding: 5px 10px;
|
||||||
|
}
|
||||||
|
#jump_to a.large {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
#jump_to a.small {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#background {
|
||||||
|
position: absolute;
|
||||||
|
top: 0; bottom: 0;
|
||||||
|
width: 350px;
|
||||||
|
background: #fff;
|
||||||
|
border-right: 1px solid #e5e5ee;
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.sections > li > div.annotation ul, ul.sections > li > div.annotation ol {
|
||||||
|
padding-left: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.sections > li {
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.sections > li > div {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.sections > li > div.annotation {
|
||||||
|
max-width: 350px;
|
||||||
|
min-width: 350px;
|
||||||
|
min-height: 5px;
|
||||||
|
padding: 13px;
|
||||||
|
overflow-x: hidden;
|
||||||
|
white-space: normal;
|
||||||
|
vertical-align: top;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
ul.sections > li > div.annotation pre {
|
||||||
|
margin: 15px 0 15px;
|
||||||
|
padding-left: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.sections > li > div.content {
|
||||||
|
padding: 13px;
|
||||||
|
vertical-align: top;
|
||||||
|
border: none;
|
||||||
|
-webkit-box-shadow: none;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pilwrap {
|
||||||
|
position: relative;
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pilcrow {
|
||||||
|
font: 12px Arial;
|
||||||
|
text-decoration: none;
|
||||||
|
color: #454545;
|
||||||
|
position: absolute;
|
||||||
|
top: 3px; left: -20px;
|
||||||
|
padding: 1px 2px;
|
||||||
|
opacity: 0;
|
||||||
|
-webkit-transition: opacity 0.2s linear;
|
||||||
|
}
|
||||||
|
.for-h1 .pilcrow {
|
||||||
|
top: 47px;
|
||||||
|
}
|
||||||
|
.for-h2 .pilcrow, .for-h3 .pilcrow, .for-h4 .pilcrow {
|
||||||
|
top: 35px;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.sections > li > div.annotation:hover .pilcrow {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*---------------------- (> 1025px) ---------------------*/
|
||||||
|
@media only screen and (min-width: 1025px) {
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#background {
|
||||||
|
width: 525px;
|
||||||
|
}
|
||||||
|
ul.sections > li > div.annotation {
|
||||||
|
max-width: 525px;
|
||||||
|
min-width: 525px;
|
||||||
|
padding: 10px 25px 1px 50px;
|
||||||
|
}
|
||||||
|
ul.sections > li > div.content {
|
||||||
|
padding: 9px 15px 16px 25px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*---------------------- Syntax Highlighting -----------------------------*/
|
||||||
|
|
||||||
|
td.linenos { background-color: #f0f0f0; padding-right: 10px; }
|
||||||
|
span.lineno { background-color: #f0f0f0; padding: 0 5px 0 5px; }
|
||||||
|
/*
|
||||||
|
|
||||||
|
github.com style (c) Vasily Polovnyov <vast@whiteants.net>
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
pre code {
|
||||||
|
display: block; padding: 0.5em;
|
||||||
|
color: #000;
|
||||||
|
background: #f8f8ff
|
||||||
|
}
|
||||||
|
|
||||||
|
pre .hljs-comment,
|
||||||
|
pre .hljs-template_comment,
|
||||||
|
pre .hljs-diff .hljs-header,
|
||||||
|
pre .hljs-javadoc {
|
||||||
|
color: #408080;
|
||||||
|
font-style: italic
|
||||||
|
}
|
||||||
|
|
||||||
|
pre .hljs-keyword,
|
||||||
|
pre .hljs-assignment,
|
||||||
|
pre .hljs-literal,
|
||||||
|
pre .hljs-css .hljs-rule .hljs-keyword,
|
||||||
|
pre .hljs-winutils,
|
||||||
|
pre .hljs-javascript .hljs-title,
|
||||||
|
pre .hljs-lisp .hljs-title,
|
||||||
|
pre .hljs-subst {
|
||||||
|
color: #954121;
|
||||||
|
/*font-weight: bold*/
|
||||||
|
}
|
||||||
|
|
||||||
|
pre .hljs-number,
|
||||||
|
pre .hljs-hexcolor {
|
||||||
|
color: #40a070
|
||||||
|
}
|
||||||
|
|
||||||
|
pre .hljs-string,
|
||||||
|
pre .hljs-tag .hljs-value,
|
||||||
|
pre .hljs-phpdoc,
|
||||||
|
pre .hljs-tex .hljs-formula {
|
||||||
|
color: #219161;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre .hljs-title,
|
||||||
|
pre .hljs-id {
|
||||||
|
color: #19469D;
|
||||||
|
}
|
||||||
|
pre .hljs-params {
|
||||||
|
color: #00F;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre .hljs-javascript .hljs-title,
|
||||||
|
pre .hljs-lisp .hljs-title,
|
||||||
|
pre .hljs-subst {
|
||||||
|
font-weight: normal
|
||||||
|
}
|
||||||
|
|
||||||
|
pre .hljs-class .hljs-title,
|
||||||
|
pre .hljs-haskell .hljs-label,
|
||||||
|
pre .hljs-tex .hljs-command {
|
||||||
|
color: #458;
|
||||||
|
font-weight: bold
|
||||||
|
}
|
||||||
|
|
||||||
|
pre .hljs-tag,
|
||||||
|
pre .hljs-tag .hljs-title,
|
||||||
|
pre .hljs-rules .hljs-property,
|
||||||
|
pre .hljs-django .hljs-tag .hljs-keyword {
|
||||||
|
color: #000080;
|
||||||
|
font-weight: normal
|
||||||
|
}
|
||||||
|
|
||||||
|
pre .hljs-attribute,
|
||||||
|
pre .hljs-variable,
|
||||||
|
pre .hljs-instancevar,
|
||||||
|
pre .hljs-lisp .hljs-body {
|
||||||
|
color: #008080
|
||||||
|
}
|
||||||
|
|
||||||
|
pre .hljs-regexp {
|
||||||
|
color: #B68
|
||||||
|
}
|
||||||
|
|
||||||
|
pre .hljs-class {
|
||||||
|
color: #458;
|
||||||
|
font-weight: bold
|
||||||
|
}
|
||||||
|
|
||||||
|
pre .hljs-symbol,
|
||||||
|
pre .hljs-ruby .hljs-symbol .hljs-string,
|
||||||
|
pre .hljs-ruby .hljs-symbol .hljs-keyword,
|
||||||
|
pre .hljs-ruby .hljs-symbol .hljs-keymethods,
|
||||||
|
pre .hljs-lisp .hljs-keyword,
|
||||||
|
pre .hljs-tex .hljs-special,
|
||||||
|
pre .hljs-input_number {
|
||||||
|
color: #990073
|
||||||
|
}
|
||||||
|
|
||||||
|
pre .hljs-builtin,
|
||||||
|
pre .hljs-constructor,
|
||||||
|
pre .hljs-built_in,
|
||||||
|
pre .hljs-lisp .hljs-title {
|
||||||
|
color: #0086b3
|
||||||
|
}
|
||||||
|
|
||||||
|
pre .hljs-preprocessor,
|
||||||
|
pre .hljs-pi,
|
||||||
|
pre .hljs-doctype,
|
||||||
|
pre .hljs-shebang,
|
||||||
|
pre .hljs-cdata {
|
||||||
|
color: #999;
|
||||||
|
font-weight: bold
|
||||||
|
}
|
||||||
|
|
||||||
|
pre .hljs-deletion {
|
||||||
|
background: #fdd
|
||||||
|
}
|
||||||
|
|
||||||
|
pre .hljs-addition {
|
||||||
|
background: #dfd
|
||||||
|
}
|
||||||
|
|
||||||
|
pre .hljs-diff .hljs-change {
|
||||||
|
background: #0086b3
|
||||||
|
}
|
||||||
|
|
||||||
|
pre .hljs-chunk {
|
||||||
|
color: #aaa
|
||||||
|
}
|
||||||
|
|
||||||
|
pre .hljs-tex .hljs-formula {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1 @@
|
||||||
|
<meta http-equiv="refresh" content="0;URL='docs/simple_statistics.html'">
|
|
@ -0,0 +1,28 @@
|
||||||
|
{
|
||||||
|
"name": "simple-statistics",
|
||||||
|
"version": "0.9.0",
|
||||||
|
"description": "Simple Statistics",
|
||||||
|
"author": "Tom MacWright <tom@macwright.org> (http://macwright.org/)",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git://github.com/tmcw/simple-statistics.git"
|
||||||
|
},
|
||||||
|
"dependencies": {},
|
||||||
|
"devDependencies": {
|
||||||
|
"jshint": "2.5.3",
|
||||||
|
"coveralls": "~2.11.1",
|
||||||
|
"istanbul": "~0.3.0",
|
||||||
|
"tape": "~2.14.0",
|
||||||
|
"random-js": "~1.0.4"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"test": "tape test/*.js",
|
||||||
|
"cov": "istanbul cover ./node_modules/.bin/tape test/*.js && coveralls < ./coverage/lcov.info",
|
||||||
|
"api": "node api.js"
|
||||||
|
},
|
||||||
|
"main": "src/simple_statistics.js",
|
||||||
|
"engines": {
|
||||||
|
"node": "*"
|
||||||
|
},
|
||||||
|
"license": "ISC"
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,114 @@
|
||||||
|
var ss = require('../');
|
||||||
|
var test = require('tape');
|
||||||
|
|
||||||
|
test('bayes', function(t) {
|
||||||
|
test('makes an easy call with one training round', function(t) {
|
||||||
|
var bayes = ss.bayesian();
|
||||||
|
bayes.train({
|
||||||
|
species: 'Cat'
|
||||||
|
}, 'animal');
|
||||||
|
t.deepEqual(bayes.score({
|
||||||
|
species: 'Cat'
|
||||||
|
}), {
|
||||||
|
animal: 1
|
||||||
|
});
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('makes fify-fifty call', function(t) {
|
||||||
|
var bayes = ss.bayesian();
|
||||||
|
bayes.train({
|
||||||
|
species: 'Cat'
|
||||||
|
}, 'animal');
|
||||||
|
bayes.train({
|
||||||
|
species: 'Cat'
|
||||||
|
}, 'chair');
|
||||||
|
t.deepEqual(bayes.score({
|
||||||
|
species: 'Cat'
|
||||||
|
}), {
|
||||||
|
animal: 0.5,
|
||||||
|
chair: 0.5
|
||||||
|
});
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('makes seventy-five/twenty-five call', function(t) {
|
||||||
|
var bayes = ss.bayesian();
|
||||||
|
bayes.train({
|
||||||
|
species: 'Cat'
|
||||||
|
}, 'animal');
|
||||||
|
bayes.train({
|
||||||
|
species: 'Cat'
|
||||||
|
}, 'animal');
|
||||||
|
bayes.train({
|
||||||
|
species: 'Cat'
|
||||||
|
}, 'animal');
|
||||||
|
bayes.train({
|
||||||
|
species: 'Cat'
|
||||||
|
}, 'chair');
|
||||||
|
t.deepEqual(bayes.score({
|
||||||
|
species: 'Cat'
|
||||||
|
}), {
|
||||||
|
animal: 0.75,
|
||||||
|
chair: 0.25
|
||||||
|
});
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('tests multiple properties', function(t) {
|
||||||
|
var bayes = ss.bayesian();
|
||||||
|
bayes.train({
|
||||||
|
species: 'Cat'
|
||||||
|
}, 'animal');
|
||||||
|
bayes.train({
|
||||||
|
species: 'Cat'
|
||||||
|
}, 'animal');
|
||||||
|
bayes.train({
|
||||||
|
species: 'Cat'
|
||||||
|
}, 'animal');
|
||||||
|
bayes.train({
|
||||||
|
species: 'Cat'
|
||||||
|
}, 'chair');
|
||||||
|
bayes.train({
|
||||||
|
species: 'Cat',
|
||||||
|
color: 'white'
|
||||||
|
}, 'chair');
|
||||||
|
t.deepEqual(bayes.score({
|
||||||
|
color: 'white'
|
||||||
|
}), {
|
||||||
|
animal: 0,
|
||||||
|
chair: 0.2
|
||||||
|
});
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('classifies multiple things', function(t) {
|
||||||
|
var bayes = ss.bayesian();
|
||||||
|
bayes.train({
|
||||||
|
species: 'Cat'
|
||||||
|
}, 'animal');
|
||||||
|
bayes.train({
|
||||||
|
species: 'Dog'
|
||||||
|
}, 'animal');
|
||||||
|
bayes.train({
|
||||||
|
species: 'Dog'
|
||||||
|
}, 'animal');
|
||||||
|
bayes.train({
|
||||||
|
species: 'Cat'
|
||||||
|
}, 'chair');
|
||||||
|
t.deepEqual(bayes.score({
|
||||||
|
species: 'Cat'
|
||||||
|
}), {
|
||||||
|
animal: 0.25,
|
||||||
|
chair: 0.25
|
||||||
|
});
|
||||||
|
t.deepEqual(bayes.score({
|
||||||
|
species: 'Dog'
|
||||||
|
}), {
|
||||||
|
animal: 0.5,
|
||||||
|
chair: 0
|
||||||
|
});
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
t.end();
|
||||||
|
});
|
|
@ -0,0 +1,17 @@
|
||||||
|
var test = require('tape');
|
||||||
|
var ss = require('../');
|
||||||
|
|
||||||
|
test('bernoulli_distribution', function(t) {
|
||||||
|
test('can return generate probability and cumulative probability distributions for p = 0.3', function(t) {
|
||||||
|
t.equal('object', typeof ss.bernoulli_distribution(0.3));
|
||||||
|
t.equal(ss.bernoulli_distribution(0.3)[0], 0.7, ss.epsilon);
|
||||||
|
t.equal(ss.bernoulli_distribution(0.3)[1], 0.3, ss.epsilon);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
test('can return null when p is not a valid probability', function(t) {
|
||||||
|
t.equal(null, ss.bernoulli_distribution(-0.01), 'p should be greater than 0.0');
|
||||||
|
t.equal(null, ss.bernoulli_distribution(1.5), 'p should be less than 1.0');
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
t.end();
|
||||||
|
});
|
|
@ -0,0 +1,31 @@
|
||||||
|
var test = require('tape');
|
||||||
|
var ss = require('../');
|
||||||
|
|
||||||
|
function rnd(n) {
|
||||||
|
return parseFloat(n.toFixed(4));
|
||||||
|
}
|
||||||
|
|
||||||
|
test('binomial_distribution', function(t) {
|
||||||
|
// Data given in the [Wikipedia example](http://en.wikipedia.org/wiki/Binomial_distribution#Example) retrieved 29 Mar 2014
|
||||||
|
// Cumulative probabilities worked by hand to mitigate accumulated rounding errors.
|
||||||
|
test('can return generate probability and cumulative probability distributions for n = 6, p = 0.3', function(t) {
|
||||||
|
t.equal('object', typeof ss.binomial_distribution(6, 0.3));
|
||||||
|
t.equal(rnd(ss.binomial_distribution(6, 0.3)[0]), 0.1176, ss.epsilon);
|
||||||
|
t.equal(rnd(ss.binomial_distribution(6, 0.3)[1]), 0.3025, ss.epsilon);
|
||||||
|
t.equal(rnd(ss.binomial_distribution(6, 0.3)[2]), 0.3241, ss.epsilon);
|
||||||
|
t.equal(rnd(ss.binomial_distribution(6, 0.3)[3]), 0.1852, ss.epsilon);
|
||||||
|
t.equal(rnd(ss.binomial_distribution(6, 0.3)[4]), 0.0595, ss.epsilon);
|
||||||
|
t.equal(rnd(ss.binomial_distribution(6, 0.3)[5]), 0.0102, ss.epsilon);
|
||||||
|
t.equal(rnd(ss.binomial_distribution(6, 0.3)[6]), 0.0007, ss.epsilon);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('can return null when p or n are not valid parameters', function(t) {
|
||||||
|
t.equal(null, ss.binomial_distribution(0, 0.5), 'n should be strictly positive');
|
||||||
|
t.equal(null, ss.binomial_distribution(1.5, 0.5), 'n should be an integer');
|
||||||
|
t.equal(null, ss.binomial_distribution(2, -0.01), 'p should be greater than 0.0');
|
||||||
|
t.equal(null, ss.binomial_distribution(2, 1.5), 'p should be less than 1.0');
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
t.end();
|
||||||
|
});
|
|
@ -0,0 +1,23 @@
|
||||||
|
var test = require('tape');
|
||||||
|
var ss = require('../');
|
||||||
|
|
||||||
|
// Data from Poisson goodness-of-fit example 10-19 in William W. Hines & Douglas C. Montgomery,
|
||||||
|
// "Probability and Statistics in Engineering and Management Science", Wiley (1980).
|
||||||
|
var data_10_19 = [
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||||
|
2, 2, 2, 2, 2, 2, 2, 2, 2,
|
||||||
|
3, 3, 3, 3
|
||||||
|
];
|
||||||
|
|
||||||
|
test('chi_squared_goodness_of_fit', function(t) {
|
||||||
|
test('can reject the null hypothesis with level of confidence specified at 0.05', function(t) {
|
||||||
|
t.equal(false, ss.chi_squared_goodness_of_fit(data_10_19, ss.poisson_distribution, 0.05));
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
test('can accept the null hypothesis with level of confidence specified at 0.10', function(t) {
|
||||||
|
t.equal(true, ss.chi_squared_goodness_of_fit(data_10_19, ss.poisson_distribution, 0.10));
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
t.end();
|
||||||
|
});
|
|
@ -0,0 +1,18 @@
|
||||||
|
var test = require('tape');
|
||||||
|
var ss = require('../');
|
||||||
|
|
||||||
|
test('chunks', function(t) {
|
||||||
|
test('can get chunks of an array', function(t) {
|
||||||
|
t.deepEqual(ss.chunk([1, 2], 1), [[1], [2]]);
|
||||||
|
t.deepEqual(ss.chunk([1, 2], 2), [[1, 2]]);
|
||||||
|
t.deepEqual(ss.chunk([1, 2, 3, 4], 4), [[1, 2, 3, 4]]);
|
||||||
|
t.deepEqual(ss.chunk([1, 2, 3, 4], 2), [[1, 2], [3, 4]]);
|
||||||
|
t.deepEqual(ss.chunk([1, 2, 3, 4], 3), [[1, 2, 3], [4]]);
|
||||||
|
t.deepEqual(ss.chunk([1, 2, 3, 4, 5, 6, 7], 2), [[1, 2], [3, 4], [5, 6], [7]]);
|
||||||
|
t.deepEqual(ss.chunk([], 2), []);
|
||||||
|
t.deepEqual(ss.chunk([], 0), null);
|
||||||
|
t.deepEqual(ss.chunk([1, 2], 0), null);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
t.end();
|
||||||
|
});
|
|
@ -0,0 +1,13 @@
|
||||||
|
var test = require('tape');
|
||||||
|
var ss = require('../');
|
||||||
|
|
||||||
|
test('cumulative_std_normal_probability', function(t) {
|
||||||
|
// https://en.wikipedia.org/wiki/Standard_normal_table#Examples_of_use
|
||||||
|
test('wikipedia test example works', function(t) {
|
||||||
|
for (var i = 0; i < ss.standard_normal_table.length; i++) {
|
||||||
|
t.equal(ss.cumulative_std_normal_probability(0.4), 0.6554);
|
||||||
|
}
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
t.end();
|
||||||
|
});
|
|
@ -0,0 +1,22 @@
|
||||||
|
var test = require('tape');
|
||||||
|
var ss = require('../');
|
||||||
|
|
||||||
|
test('factorial', function(t) {
|
||||||
|
test('can return null given a negative number', function(t) {
|
||||||
|
t.equal(null, ss.factorial(-1));
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
test('can calculate 0! = 1', function(t) {
|
||||||
|
t.equal(ss.factorial(0), 1);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
test('can calculate 1! = 1', function(t) {
|
||||||
|
t.equal(ss.factorial(1), 1);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
test('can calculate 100! = 1', function(t) {
|
||||||
|
t.equal(ss.factorial(100), 9.33262154439441e+157);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
t.end();
|
||||||
|
});
|
|
@ -0,0 +1,23 @@
|
||||||
|
var test = require('tape');
|
||||||
|
var ss = require('../');
|
||||||
|
|
||||||
|
test('geometric mean', function(t) {
|
||||||
|
// From http://en.wikipedia.org/wiki/Geometric_mean
|
||||||
|
test('can get the mean of two numbers', function(t) {
|
||||||
|
t.equal(ss.geometric_mean([2, 8]), 4);
|
||||||
|
t.equal(ss.geometric_mean([4, 1, 1 / 32]), 0.5);
|
||||||
|
t.equal(Math.round(ss.geometric_mean([2, 32, 1])), 4);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('returns null for empty lists', function(t) {
|
||||||
|
t.equal(ss.geometric_mean([]), null);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('returns null for lists with negative numbers', function(t) {
|
||||||
|
t.equal(ss.geometric_mean([-1]), null);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
t.end();
|
||||||
|
});
|
|
@ -0,0 +1,27 @@
|
||||||
|
var test = require('tape');
|
||||||
|
var ss = require('../');
|
||||||
|
|
||||||
|
function rnd(x) {
|
||||||
|
return Math.round(x * 1000) / 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
test('harmonic_mean', function(t) {
|
||||||
|
// From http://en.wikipedia.org/wiki/Harmonic_mean
|
||||||
|
test('can get the mean of two or more numbers', function(t) {
|
||||||
|
t.equal(ss.harmonic_mean([1, 1]), 1);
|
||||||
|
t.equal(rnd(ss.harmonic_mean([2, 3])), 2.4);
|
||||||
|
t.equal(ss.harmonic_mean([1, 2, 4]), 12 / 7);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('returns null for empty lists', function(t) {
|
||||||
|
t.equal(ss.harmonic_mean([]), null);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('returns null for lists with negative numbers', function(t) {
|
||||||
|
t.equal(ss.harmonic_mean([-1]), null);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
t.end();
|
||||||
|
});
|
|
@ -0,0 +1,24 @@
|
||||||
|
var test = require('tape');
|
||||||
|
var ss = require('../');
|
||||||
|
|
||||||
|
test('interquartile range (iqr)', function(t) {
|
||||||
|
// Data and results from
|
||||||
|
// [Wikipedia](http://en.wikipedia.org/wiki/Quantile#Quantiles_of_a_population)
|
||||||
|
test('can get proper iqr of an even-length list', function(t) {
|
||||||
|
var even = [3, 6, 7, 8, 8, 10, 13, 15, 16, 20];
|
||||||
|
t.equal(ss.quantile(even, 0.75) - ss.quantile(even, 0.25), ss.iqr(even));
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('can get proper iqr of an odd-length list', function(t) {
|
||||||
|
var odd = [3, 6, 7, 8, 8, 9, 10, 13, 15, 16, 20];
|
||||||
|
t.equal(ss.quantile(odd, 0.75) - ss.quantile(odd, 0.25), ss.iqr(odd));
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('an iqr of a zero-length list produces null', function(t) {
|
||||||
|
t.equal(ss.iqr([]), null);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
t.end();
|
||||||
|
});
|
|
@ -0,0 +1,14 @@
|
||||||
|
var test = require('tape');
|
||||||
|
var ss = require('../');
|
||||||
|
|
||||||
|
test('jenks', function(t) {
|
||||||
|
test('will not try to assign more classes than datapoints', function(t) {
|
||||||
|
t.equal(ss.jenks([1, 2], 3), null);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
test('assigns correct breaks', function(t) {
|
||||||
|
t.deepEqual(ss.jenks([1, 2, 4, 5, 7, 9, 10, 20], 3), [1, 2, 5, 20]);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
t.end();
|
||||||
|
});
|
|
@ -0,0 +1,54 @@
|
||||||
|
var test = require('tape');
|
||||||
|
var ss = require('../');
|
||||||
|
|
||||||
|
test('linear regression', function(t) {
|
||||||
|
test('correctly generates a line for a 0, 0 to 1, 1 dataset', function(t) {
|
||||||
|
var l = ss.linear_regression().data([[0, 0], [1, 1]]);
|
||||||
|
t.equal(l.line()(0), 0);
|
||||||
|
t.equal(l.line()(0.5), 0.5);
|
||||||
|
t.equal(l.line()(1), 1);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('correctly generates a line for a 0, 0 to 1, 0 dataset', function(t) {
|
||||||
|
var l = ss.linear_regression().data([[0, 0], [1, 0]]);
|
||||||
|
t.equal(l.line()(0), 0);
|
||||||
|
t.equal(l.line()(0.5), 0);
|
||||||
|
t.equal(l.line()(1), 0);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('returns the data assigned to it', function(t) {
|
||||||
|
var l = ss.linear_regression().data([[0, 0], [1, 0]]);
|
||||||
|
t.deepEqual(l.data(), [[0, 0], [1, 0]]);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('handles a single-point sample', function(t) {
|
||||||
|
var l = ss.linear_regression().data([[0, 0]]).line();
|
||||||
|
t.deepEqual(l(10), 0);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('a straight line will have a slope of 0', function(t) {
|
||||||
|
var l = ss.linear_regression().data([[0, 0], [1, 0]]);
|
||||||
|
t.equal(l.m(), 0);
|
||||||
|
t.equal(l.b(), 0);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('a line at 50% grade', function(t) {
|
||||||
|
var l = ss.linear_regression().data([[0, 0], [1, 0.5]]);
|
||||||
|
t.equal(l.m(), 0.5);
|
||||||
|
t.equal(l.b(), 0);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('a line with a high y-intercept', function(t) {
|
||||||
|
var l = ss.linear_regression().data([[0, 20], [1, 10]]);
|
||||||
|
t.equal(l.m(), -10);
|
||||||
|
t.equal(l.b(), 20);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
t.end();
|
||||||
|
});
|
|
@ -0,0 +1,26 @@
|
||||||
|
var test = require('tape');
|
||||||
|
var ss = require('../');
|
||||||
|
|
||||||
|
test('median absolute deviation (mad)', function(t) {
|
||||||
|
test('median absolute deviation of an example on wikipedia', function(t) {
|
||||||
|
t.equal(ss.mad([1, 1, 2, 2, 4, 6, 9]), 1);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
// wolfram alpha: median absolute deviation {0,1,2,3,4,5,6,7,8,9,10}
|
||||||
|
test('median absolute deviation of 0-10', function(t) {
|
||||||
|
t.equal(ss.mad([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]), 3);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('median absolute deviation of one number is zero', function(t) {
|
||||||
|
t.equal(ss.mad([1]), 0);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('zero-length corner case', function(t) {
|
||||||
|
t.equal(ss.mad([]), null);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
t.end();
|
||||||
|
});
|
|
@ -0,0 +1,18 @@
|
||||||
|
var test = require('tape');
|
||||||
|
var ss = require('../');
|
||||||
|
|
||||||
|
test('mean', function(t) {
|
||||||
|
test('can get the mean of two numbers', function(t) {
|
||||||
|
t.equal(ss.mean([1, 2]), 1.5);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
test('can get the mean of one number', function(t) {
|
||||||
|
t.equal(ss.mean([1]), 1);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
test('an empty list has no average', function(t) {
|
||||||
|
t.equal(ss.mean([]), null);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
t.end();
|
||||||
|
});
|
|
@ -0,0 +1,38 @@
|
||||||
|
var test = require('tape');
|
||||||
|
var ss = require('../');
|
||||||
|
|
||||||
|
test('median', function(t) {
|
||||||
|
test('can get the median of three numbers', function(t) {
|
||||||
|
t.equal(ss.median([1, 2, 3]), 2);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('can get the median of two numbers', function(t) {
|
||||||
|
t.equal(ss.median([1, 2]), 1.5);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('can get the median of four numbers', function(t) {
|
||||||
|
t.equal(ss.median([1, 2, 3, 4]), 2.5);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('gives null for the median of an empty list', function(t) {
|
||||||
|
t.equal(ss.median([]), null);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('sorts numbers numerically', function(t) {
|
||||||
|
t.equal(ss.median([8, 9, 10]), 9);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('does not change the sorting order of its input', function(t) {
|
||||||
|
var x = [1, 0];
|
||||||
|
t.equal(ss.median(x), 0.5);
|
||||||
|
t.equal(x[0], 1);
|
||||||
|
t.equal(x[1], 0);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
t.end();
|
||||||
|
});
|
|
@ -0,0 +1,23 @@
|
||||||
|
var test = require('tape');
|
||||||
|
var ss = require('../');
|
||||||
|
|
||||||
|
test('min', function(t) {
|
||||||
|
test('can get the minimum of one number', function(t) {
|
||||||
|
t.equal(ss.min([1]), 1);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('can get the minimum of three numbers', function(t) {
|
||||||
|
t.equal(ss.min([1, 7, -1000]), -1000);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('max', function(t) {
|
||||||
|
test('can get the maximum of three numbers', function(t) {
|
||||||
|
t.equal(ss.max([1, 7, -1000]), 7);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
t.end();
|
||||||
|
});
|
|
@ -0,0 +1,34 @@
|
||||||
|
var test = require('tape');
|
||||||
|
var ss = require('../');
|
||||||
|
|
||||||
|
test('mixin', function(t) {
|
||||||
|
test('can mix into a single array', function(t) {
|
||||||
|
var even = ss.mixin([2, 4, 6, 8]);
|
||||||
|
t.equal(even.sum(), 20);
|
||||||
|
t.equal(even.mean(), 5);
|
||||||
|
t.equal(even.max(), 8);
|
||||||
|
t.equal(even.min(), 2);
|
||||||
|
t.equal(even.sample_skewness(), 0);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('can mix into Array.prototype', function(t) {
|
||||||
|
ss.mixin();
|
||||||
|
var even = [2, 4, 6, 8];
|
||||||
|
t.equal(even.sum(), 20);
|
||||||
|
t.equal(even.mean(), 5);
|
||||||
|
t.equal(even.max(), 8);
|
||||||
|
t.equal(even.min(), 2);
|
||||||
|
t.equal(even.sample_skewness(), 0);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('mixins can take arguments', function(t) {
|
||||||
|
ss.mixin();
|
||||||
|
var even = [2, 4, 6, 8];
|
||||||
|
t.equal(even.quantile(0.2), 2);
|
||||||
|
t.equal(even.quantile(0.8), 8);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
t.end();
|
||||||
|
});
|
|
@ -0,0 +1,37 @@
|
||||||
|
var test = require('tape');
|
||||||
|
var ss = require('../');
|
||||||
|
|
||||||
|
test('mode', function(t) {
|
||||||
|
test('the mode of a single-number array is that one number', function(t) {
|
||||||
|
t.equal(ss.mode([1]), 1);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('the mode of a two-number array is that one number', function(t) {
|
||||||
|
t.equal(ss.mode([1, 1]), 1);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('other cases', function(t) {
|
||||||
|
t.equal(ss.mode([1, 1, 2]), 1);
|
||||||
|
t.equal(ss.mode([1, 1, 2, 3]), 1);
|
||||||
|
t.equal(ss.mode([1, 1, 2, 3, 3]), 1);
|
||||||
|
t.equal(ss.mode([1, 1, 2, 3, 3, 3]), 3);
|
||||||
|
t.equal(ss.mode([1, 1, 2, 2, 2, 2, 3, 3, 3]), 2);
|
||||||
|
t.equal(ss.mode([1, 2, 3, 4, 5]), 1);
|
||||||
|
t.equal(ss.mode([1, 2, 3, 4, 5, 5]), 5);
|
||||||
|
t.equal(ss.mode([1, 1, 1, 2, 2, 3, 3, 4, 4]), 1);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('the mode of an empty array is null', function(t) {
|
||||||
|
t.equal(ss.mode([]), null);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('the mode of a three-number array with two same numbers is the repeated one', function(t) {
|
||||||
|
t.equal(ss.mode([1, 2, 2]), 2);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
t.end();
|
||||||
|
});
|
|
@ -0,0 +1,60 @@
|
||||||
|
var test = require('tape');
|
||||||
|
var ss = require('../');
|
||||||
|
|
||||||
|
test('natural distribution and z-score', function(t) {
|
||||||
|
|
||||||
|
test('normal table is exposed in the API', function(t) {
|
||||||
|
t.equal(ss.standard_normal_table.length, 310);
|
||||||
|
t.equal(ss.standard_normal_table[0], 0.5);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('P(Z <= 0.4) is 0.6554', function(t) {
|
||||||
|
// Taken from the examples of use in http://en.wikipedia.org/wiki/Standard_normal_table
|
||||||
|
t.equal(ss.cumulative_std_normal_probability(0.4), 0.6554);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('P(Z <= -1.20) is 0.1151', function(t) {
|
||||||
|
// Taken from the examples of use in http://en.wikipedia.org/wiki/Standard_normal_table
|
||||||
|
t.equal(ss.cumulative_std_normal_probability(-1.20), 0.1151);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('P(X <= 82) when X ~ N (80, 25) is 0.6554', function(t) {
|
||||||
|
// Taken from the examples of use in http://en.wikipedia.org/wiki/Standard_normal_table
|
||||||
|
// A professor's exam scores are approximately distributed normally with mean 80 and standard deviation 5.
|
||||||
|
// What is the probability that a student scores an 82 or less?
|
||||||
|
t.equal(ss.cumulative_std_normal_probability(ss.z_score(82, 80, 5)), 0.6554);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('P(X >= 90) when X ~ N (80, 25) is 0.0228', function(t) {
|
||||||
|
// Taken from the examples of use in http://en.wikipedia.org/wiki/Standard_normal_table
|
||||||
|
// A professor's exam scores are approximately distributed normally with mean 80 and standard deviation 5.
|
||||||
|
// What is the probability that a student scores a 90 or more?
|
||||||
|
t.equal(+(1 - ss.cumulative_std_normal_probability(ss.z_score(90, 80, 5))).toPrecision(5), 0.0228);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('P(X <= 74) when X ~ N (80, 25) is 0.1151', function(t) {
|
||||||
|
// Taken from the examples of use in http://en.wikipedia.org/wiki/Standard_normal_table
|
||||||
|
// A professor's exam scores are approximately distributed normally with mean 80 and standard deviation 5.
|
||||||
|
// What is the probability that a student scores a 74 or less?
|
||||||
|
t.equal(ss.cumulative_std_normal_probability(ss.z_score(74, 80, 5)), 0.1151);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('P(78 <= X <= 88) when X ~ N (80, 25) is 0.6006', function(t) {
|
||||||
|
// Taken from the examples of use in http://en.wikipedia.org/wiki/Standard_normal_table
|
||||||
|
// A professor's exam scores are approximately distributed normally with mean 80 and standard deviation 5.
|
||||||
|
// What is the probability that a student scores between 78 and 88?
|
||||||
|
var prob88 = ss.cumulative_std_normal_probability(ss.z_score(88, 80, 5)),
|
||||||
|
prob78 = ss.cumulative_std_normal_probability(ss.z_score(78, 80, 5));
|
||||||
|
|
||||||
|
t.equal(+(prob88 - prob78).toPrecision(5), 0.6006);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
t.end();
|
||||||
|
});
|
|
@ -0,0 +1,37 @@
|
||||||
|
var test = require('tape');
|
||||||
|
var ss = require('../');
|
||||||
|
|
||||||
|
function rnd(n) {
|
||||||
|
return parseFloat(n.toFixed(4));
|
||||||
|
}
|
||||||
|
|
||||||
|
// expected cumulative probabilities taken from Appendix 1, Table I of William W. Hines & Douglas C.
|
||||||
|
// Montgomery, "Probability and Statistics in Engineering and Management Science", Wiley (1980).
|
||||||
|
test('poisson_distribution', function(t) {
|
||||||
|
test('can return generate probability and cumulative probability distributions for lambda = 3.0', function(t) {
|
||||||
|
t.equal('object', typeof ss.poisson_distribution(3.0));
|
||||||
|
t.equal(rnd(ss.poisson_distribution(3.0)[3]), 0.2240, ss.epsilon);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
test('can generate probability and cumulative probability distributions for lambda = 4.0', function(t) {
|
||||||
|
t.equal('object', typeof ss.poisson_distribution(4.0));
|
||||||
|
t.equal(rnd(ss.poisson_distribution(4.0)[2]), 0.1465, ss.epsilon);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
test('can generate probability and cumulative probability distributions for lambda = 5.5', function(t) {
|
||||||
|
t.equal('object', typeof ss.poisson_distribution(5.5));
|
||||||
|
t.equal(rnd(ss.poisson_distribution(5.5)[7]), 0.1234, ss.epsilon);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
test('can generate probability and cumulative probability distributions for lambda = 9.5', function(t) {
|
||||||
|
t.equal('object', typeof ss.poisson_distribution(9.5));
|
||||||
|
t.equal(rnd(ss.poisson_distribution(9.5)[17]), 0.0088, ss.epsilon);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
test('can return null when lambda <= 0', function(t) {
|
||||||
|
t.equal(null, ss.poisson_distribution(0));
|
||||||
|
t.equal(null, ss.poisson_distribution(-10));
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
t.end();
|
||||||
|
});
|
|
@ -0,0 +1,64 @@
|
||||||
|
var test = require('tape');
|
||||||
|
var ss = require('../');
|
||||||
|
|
||||||
|
test('quantile', function(t) {
|
||||||
|
// Data and results from
|
||||||
|
// [Wikipedia](http://en.wikipedia.org/wiki/Quantile#Quantiles_of_a_population)
|
||||||
|
test('can get proper quantiles of an even-length list', function(t) {
|
||||||
|
var even = [3, 6, 7, 8, 8, 10, 13, 15, 16, 20];
|
||||||
|
t.equal(ss.quantile(even, 0.25), 7);
|
||||||
|
t.equal(ss.quantile(even, 0.5), 9);
|
||||||
|
t.equal(ss.quantile(even, 0.75), 15);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('can get proper quantiles of an odd-length list', function(t) {
|
||||||
|
var odd = [3, 6, 7, 8, 8, 9, 10, 13, 15, 16, 20];
|
||||||
|
t.equal(ss.quantile(odd, 0.25), 7);
|
||||||
|
t.equal(ss.quantile(odd, 0.5), 9);
|
||||||
|
t.equal(ss.quantile(odd, 0.75), 15);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('the median quantile is equal to the median', function(t) {
|
||||||
|
var rand = [1, 4, 5, 8];
|
||||||
|
t.equal(ss.quantile(rand, 0.5), ss.median(rand));
|
||||||
|
var rand2 = [10, 50, 2, 4, 4, 5, 8];
|
||||||
|
t.equal(ss.quantile(rand2, 0.5), ss.median(rand2));
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('a zero-length list produces null', function(t) {
|
||||||
|
t.equal(ss.quantile([], 0.5), null);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('test odd-value case', function(t) {
|
||||||
|
t.equal(ss.quantile([0, 1, 2, 3, 4], 0.2), 1);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('bad bounds produce null', function(t) {
|
||||||
|
t.equal(ss.quantile([1, 2, 3], 1.1), null);
|
||||||
|
t.equal(ss.quantile([1, 2, 3], -0.5), null);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('max quantile is equal to the max', function(t) {
|
||||||
|
t.equal(ss.quantile([1, 2, 3], 1), ss.max([1, 2, 3]));
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('min quantile is equal to the min', function(t) {
|
||||||
|
t.equal(ss.quantile([1, 2, 3], 0), ss.min([1, 2, 3]));
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('if quantile arg is an array, response is an array of quantiles', function(t) {
|
||||||
|
var odd = [3, 6, 7, 8, 8, 9, 10, 13, 15, 16, 20];
|
||||||
|
t.deepEqual(ss.quantile(odd, [0, 0.25, 0.5, 0.75, 1]), [3, 7, 9, 15, 20]);
|
||||||
|
t.deepEqual(ss.quantile(odd, [0.75, 0.5]), [15, 9]);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
t.end();
|
||||||
|
});
|
|
@ -0,0 +1,15 @@
|
||||||
|
var test = require('tape');
|
||||||
|
var ss = require('../');
|
||||||
|
|
||||||
|
test('quantile_sorted', function(t) {
|
||||||
|
// Data and results from
|
||||||
|
// [Wikipedia](http://en.wikipedia.org/wiki/Quantile#Quantiles_of_a_population)
|
||||||
|
test('can get proper quantiles of an even-length list', function(t) {
|
||||||
|
var even = [3, 6, 7, 8, 8, 10, 13, 15, 16, 20];
|
||||||
|
t.equal(ss.quantile_sorted(even, 0.25), 7);
|
||||||
|
t.equal(ss.quantile_sorted(even, 0.5), 9);
|
||||||
|
t.equal(ss.quantile_sorted(even, 0.75), 15);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
t.end();
|
||||||
|
});
|
|
@ -0,0 +1,26 @@
|
||||||
|
var test = require('tape');
|
||||||
|
var ss = require('../');
|
||||||
|
|
||||||
|
test('r-squared', function(t) {
|
||||||
|
test('says that the r squared of a two-point line is perfect', function(t) {
|
||||||
|
var d = [[0, 0], [1, 1]];
|
||||||
|
var l = ss.linear_regression().data(d);
|
||||||
|
t.equal(ss.r_squared(d, l.line()), 1);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('says that the r squared of a three-point line is not perfect', function(t) {
|
||||||
|
var d = [[0, 0], [0.5, 0.2], [1, 1]];
|
||||||
|
var l = ss.linear_regression().data(d);
|
||||||
|
t.notEqual(ss.r_squared(d, l.line()), 1);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('r-squared of single sample is 1', function(t) {
|
||||||
|
var d = [[0, 0]];
|
||||||
|
var l = ss.linear_regression().data(d);
|
||||||
|
t.equal(ss.r_squared(d, l.line()), 1);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
t.end();
|
||||||
|
});
|
|
@ -0,0 +1,23 @@
|
||||||
|
var test = require('tape');
|
||||||
|
var ss = require('../');
|
||||||
|
|
||||||
|
function rnd(x) {
|
||||||
|
return Math.round(x * 1000) / 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
test('root_mean_square', function(t) {
|
||||||
|
// From http://en.wikipedia.org/wiki/Root_mean_square
|
||||||
|
test('can get the RMS of two or more numbers', function(t) {
|
||||||
|
t.equal(ss.root_mean_square([1, 1]), 1);
|
||||||
|
t.equal(rnd(ss.root_mean_square([3, 4, 5])), 4.082);
|
||||||
|
t.equal(rnd(ss.root_mean_square([-0.1, 5, -2, 10])), 5.679);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('returns null for empty lists', function(t) {
|
||||||
|
t.equal(ss.root_mean_square([]), null);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
t.end();
|
||||||
|
});
|
|
@ -0,0 +1,19 @@
|
||||||
|
var test = require('tape');
|
||||||
|
var Random = require('random-js');
|
||||||
|
var random = new Random(Random.engines.mt19937().seed(0));
|
||||||
|
var ss = require('../');
|
||||||
|
|
||||||
|
function rng() { return random.real(0, 1); }
|
||||||
|
|
||||||
|
test('sample', function(t) {
|
||||||
|
t.deepEqual(ss.sample([], 0, rng), [], 'edge case - zero array');
|
||||||
|
t.deepEqual(ss.sample([], 2, rng), [], 'edge case - zero array');
|
||||||
|
t.deepEqual(ss.sample([1,2,3], 0, rng, 0), [], 'edge case - zero array');
|
||||||
|
t.deepEqual(ss.sample([1,2,3], 1, rng), [1], 'edge case - sample of 1');
|
||||||
|
t.deepEqual(ss.sample([1,2,3], 1, rng), [2]);
|
||||||
|
t.deepEqual(ss.sample([1,2,3], 3, rng), [2,3,1]);
|
||||||
|
t.deepEqual(ss.sample([1,2,3,4], 2, rng), [3,1]);
|
||||||
|
t.deepEqual(ss.sample([1,2,3,4,6,7,8], 2, rng), [8,7]);
|
||||||
|
t.deepEqual(ss.sample(['foo', 'bar'], 1, rng), ['foo'], 'non-number contents');
|
||||||
|
t.end();
|
||||||
|
});
|
|
@ -0,0 +1,29 @@
|
||||||
|
var test = require('tape');
|
||||||
|
var ss = require('../');
|
||||||
|
|
||||||
|
function rnd(x) {
|
||||||
|
return Math.round(x * 1000) / 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
test('sample correlation', function(t) {
|
||||||
|
|
||||||
|
test('can get the sample correlation of identical arrays', function(t) {
|
||||||
|
var data = [1, 2, 3, 4, 5, 6];
|
||||||
|
t.equal(rnd(ss.sample_correlation(data, data)), 1);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('can get the sample correlation of different arrays', function(t) {
|
||||||
|
var a = [1, 2, 3, 4, 5, 6];
|
||||||
|
var b = [2, 2, 3, 4, 5, 60];
|
||||||
|
t.equal(rnd(ss.sample_correlation(a, b)), 0.691);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('zero-length corner case', function(t) {
|
||||||
|
t.equal(rnd(ss.sample_correlation([], [])), 0);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
t.end();
|
||||||
|
});
|
|
@ -0,0 +1,34 @@
|
||||||
|
var test = require('tape');
|
||||||
|
var ss = require('../');
|
||||||
|
|
||||||
|
function rnd(x) {
|
||||||
|
return Math.round(x * 1000) / 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
test('sample covariance', function(t) {
|
||||||
|
test('can get perfect negative covariance', function(t) {
|
||||||
|
var x = [1, 2, 3, 4, 5, 6];
|
||||||
|
var y = [6, 5, 4, 3, 2, 1];
|
||||||
|
t.equal(rnd(ss.sample_covariance(x, y)), -3.5);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('covariance of something with itself is its variance', function(t) {
|
||||||
|
var x = [1, 2, 3, 4, 5, 6];
|
||||||
|
t.equal(rnd(ss.sample_covariance(x, x)), 3.5);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('covariance is zero for something with no correlation', function(t) {
|
||||||
|
var x = [1, 2, 3, 4, 5, 6];
|
||||||
|
var y = [1, 1, 2, 2, 1, 1];
|
||||||
|
t.equal(rnd(ss.sample_covariance(x, y)), 0);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('zero-length corner case', function(t) {
|
||||||
|
t.equal(rnd(ss.sample_covariance([], [])), 0);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
t.end();
|
||||||
|
});
|
|
@ -0,0 +1,48 @@
|
||||||
|
var test = require('tape');
|
||||||
|
var ss = require('../');
|
||||||
|
|
||||||
|
test('sample skewness', function(t) {
|
||||||
|
|
||||||
|
test('the skewness of an empty sample is null', function(t) {
|
||||||
|
var data = [];
|
||||||
|
t.equal(ss.sample_skewness(data), null);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('the skewness of an sample with one number is null', function(t) {
|
||||||
|
var data = [1];
|
||||||
|
t.equal(ss.sample_skewness(data), null);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('the skewness of an sample with two numbers is null', function(t) {
|
||||||
|
var data = [1, 2];
|
||||||
|
t.equal(ss.sample_skewness(data), null);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('can calculate the skewness of SAS example 1', function(t) {
|
||||||
|
// Data and answer taken from SKEWNESS function documentation at
|
||||||
|
// http://support.sas.com/documentation/c../lrdict/64316/HTML/default/viewer.htm#a000245947.htm
|
||||||
|
var data = [0, 1, 1];
|
||||||
|
t.equal(+ss.sample_skewness(data).toPrecision(10), -1.732050808);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('can calculate the skewness of SAS example 2', function(t) {
|
||||||
|
// Data and answer taken from SKEWNESS function documentation at
|
||||||
|
// http://support.sas.com/documentation/c../lrdict/64316/HTML/default/viewer.htm#a000245947.htm
|
||||||
|
var data = [2, 4, 6, 3, 1];
|
||||||
|
t.equal(+ss.sample_skewness(data).toPrecision(10), 0.5901286564);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('can calculate the skewness of SAS example 3', function(t) {
|
||||||
|
// Data and answer taken from SKEWNESS function documentation at
|
||||||
|
// http://support.sas.com/documentation/c../lrdict/64316/HTML/default/viewer.htm#a000245947.htm
|
||||||
|
var data = [2, 0, 0];
|
||||||
|
t.equal(+ss.sample_skewness(data).toPrecision(10), 1.732050808);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
t.end();
|
||||||
|
});
|
|
@ -0,0 +1,19 @@
|
||||||
|
var test = require('tape');
|
||||||
|
var ss = require('../');
|
||||||
|
|
||||||
|
function rnd(x) {
|
||||||
|
return Math.round(x * 1000) / 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
test('sample_standard_deviation', function(t) {
|
||||||
|
test('can get the standard deviation of an example on wikipedia', function(t) {
|
||||||
|
t.equal(rnd(ss.sample_standard_deviation([2, 4, 4, 4, 5, 5, 7, 9])), 2.138);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('zero-length corner case', function(t) {
|
||||||
|
t.equal(rnd(ss.sample_standard_deviation([])), 0);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
t.end();
|
||||||
|
});
|
|
@ -0,0 +1,38 @@
|
||||||
|
var test = require('tape');
|
||||||
|
var ss = require('../');
|
||||||
|
|
||||||
|
function rnd(x) {
|
||||||
|
return Math.round(x * 1000) / 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
test('sample variance', function(t) {
|
||||||
|
test('can get the sample variance of a six-sided die', function(t) {
|
||||||
|
t.equal(rnd(ss.sample_variance([1, 2, 3, 4, 5, 6])), 3.5);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
// confirmed in R
|
||||||
|
//
|
||||||
|
// > var(1:10)
|
||||||
|
// [1] 9.166667
|
||||||
|
test('can get the sample variance of numbers 1-10', function(t) {
|
||||||
|
t.equal(rnd(ss.sample_variance([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])), 9.167);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('the sample variance of two numbers that are the same is 0', function(t) {
|
||||||
|
t.equal(rnd(ss.sample_variance([1, 1])), 0);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('the sample variance of one number is null', function(t) {
|
||||||
|
t.equal(ss.sample_variance([1]), null);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('the sample variance of no numbers is null', function(t) {
|
||||||
|
t.equal(ss.sample_variance([]), null);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
t.end();
|
||||||
|
});
|
|
@ -0,0 +1,24 @@
|
||||||
|
var test = require('tape');
|
||||||
|
var Random = require('random-js');
|
||||||
|
var random = new Random(Random.engines.mt19937().seed(0));
|
||||||
|
var ss = require('../');
|
||||||
|
|
||||||
|
function rng() { return random.real(0, 1); }
|
||||||
|
|
||||||
|
test('shuffle', function(t) {
|
||||||
|
var input = [1, 2, 3, 4, 5, 6];
|
||||||
|
t.deepEqual(ss.shuffle([], rng), []);
|
||||||
|
t.deepEqual(ss.shuffle(input, rng), [1, 5, 3, 2, 4, 6]);
|
||||||
|
t.deepEqual(input, [1, 2, 3, 4, 5, 6], 'does not change original array');
|
||||||
|
t.deepEqual(ss.shuffle(input, rng), [5, 4, 1, 3, 6, 2]);
|
||||||
|
t.deepEqual(input, [1, 2, 3, 4, 5, 6], 'does not change original array');
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('shuffle_in_place', function(t) {
|
||||||
|
var input = [1, 2, 3, 4, 5, 6];
|
||||||
|
t.deepEqual(ss.shuffle_in_place([], rng), []);
|
||||||
|
t.deepEqual(ss.shuffle_in_place(input, rng), [6, 1, 5, 2, 4, 3]);
|
||||||
|
t.deepEqual(input, [6, 1, 5, 2, 4, 3], 'changes original array');
|
||||||
|
t.end();
|
||||||
|
});
|
|
@ -0,0 +1,39 @@
|
||||||
|
var test = require('tape');
|
||||||
|
var ss = require('../');
|
||||||
|
|
||||||
|
function rnd(x) {
|
||||||
|
return Math.round(x * 1000) / 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
test('standard_deviation', function(t) {
|
||||||
|
test('can get the standard deviation of an example on wikipedia', function(t) {
|
||||||
|
t.equal(rnd(ss.standard_deviation([2, 4, 4, 4, 5, 5, 7, 9])), 2);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
// confirmed with numpy
|
||||||
|
// In [4]: numpy.std([1,2,3])
|
||||||
|
// Out[4]: 0.81649658092772603
|
||||||
|
test('can get the standard deviation of 1-3', function(t) {
|
||||||
|
t.equal(rnd(ss.standard_deviation([1, 2, 3])), 0.816);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('zero-length array corner case', function(t) {
|
||||||
|
t.equal(rnd(ss.standard_deviation([])), 0);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
// In [6]: numpy.std([0,1,2,3,4,5,6,7,8,9,10])
|
||||||
|
// Out[6]: 3.1622776601683795
|
||||||
|
test('can get the standard deviation of 1-10', function(t) {
|
||||||
|
t.equal(rnd(ss.standard_deviation([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10])), 3.162);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('the standard deviation of one number is zero', function(t) {
|
||||||
|
t.equal(rnd(ss.standard_deviation([1])), 0);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
t.end();
|
||||||
|
});
|
|
@ -0,0 +1,14 @@
|
||||||
|
var test = require('tape');
|
||||||
|
var ss = require('../');
|
||||||
|
|
||||||
|
test('standard_normal_table', function(t) {
|
||||||
|
test('all entries are numeric', function(t) {
|
||||||
|
for (var i = 0; i < ss.standard_normal_table.length; i++) {
|
||||||
|
t.equal(typeof ss.standard_normal_table[i], 'number');
|
||||||
|
t.ok(ss.standard_normal_table[i] >= 0);
|
||||||
|
t.ok(ss.standard_normal_table[i] <= 1);
|
||||||
|
}
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
t.end();
|
||||||
|
});
|
|
@ -0,0 +1,15 @@
|
||||||
|
var test = require('tape');
|
||||||
|
var ss = require('../');
|
||||||
|
|
||||||
|
test('sum', function(t) {
|
||||||
|
test('can get the sum of two numbers', function(t) {
|
||||||
|
t.equal(ss.sum([1, 2]), 3);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('the sum of no numbers is zero', function(t) {
|
||||||
|
t.equal(ss.sum([]), 0);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
t.end();
|
||||||
|
});
|
|
@ -0,0 +1,38 @@
|
||||||
|
var test = require('tape'),
|
||||||
|
ss = require('../');
|
||||||
|
|
||||||
|
test('t test', function(t) {
|
||||||
|
|
||||||
|
test('can compare a known value to the mean of samples', function(t) {
|
||||||
|
var res = ss.t_test([1, 2, 3, 4, 5, 6], 3.385);
|
||||||
|
t.equal(res, 0.1649415480881466);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('can test independency of two samples', function(t) {
|
||||||
|
var res = ss.t_test_two_sample([1, 2, 3, 4], [3, 4, 5, 6], 0);
|
||||||
|
t.equal(res, -2.1908902300206643);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('can test independency of two samples (mu == -2)', function(t) {
|
||||||
|
var res = ss.t_test_two_sample([1, 2, 3, 4], [3, 4, 5, 6], -2);
|
||||||
|
t.equal(res, 0);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('can test independency of two samples of different lengths', function(t) {
|
||||||
|
var res = ss.t_test_two_sample([1, 2, 3, 4], [3, 4, 5, 6, 1, 2, 0]);
|
||||||
|
t.equal(res, -0.4165977904505309);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('has an edge case for one sample being of size zero', function(t) {
|
||||||
|
t.equal(ss.t_test_two_sample([1, 2, 3, 4], []), null);
|
||||||
|
t.equal(ss.t_test_two_sample([], [1, 2, 3, 4]), null);
|
||||||
|
t.equal(ss.t_test_two_sample([], []), null);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
t.end();
|
||||||
|
});
|
|
@ -0,0 +1,24 @@
|
||||||
|
var test = require('tape');
|
||||||
|
var ss = require('../');
|
||||||
|
|
||||||
|
function rnd(x) {
|
||||||
|
return Math.round(x * 1000) / 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
test('variance', function(t) {
|
||||||
|
test('can get the variance of a six-sided die', function(t) {
|
||||||
|
t.equal(rnd(ss.variance([1, 2, 3, 4, 5, 6])), 2.917);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('the variance of one number is zero', function(t) {
|
||||||
|
t.equal(rnd(ss.variance([1])), 0);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('the variance of no numbers is null', function(t) {
|
||||||
|
t.equal(ss.variance([]), null);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
t.end();
|
||||||
|
});
|
|
@ -0,0 +1,65 @@
|
||||||
|
const { FileSystem } = require('../src/index');
|
||||||
|
|
||||||
|
let Provider;
|
||||||
|
try {
|
||||||
|
Provider = require('fsProvider');
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
Provider = require('./providers/default');
|
||||||
|
}
|
||||||
|
|
||||||
|
const provider = new Provider();
|
||||||
|
|
||||||
|
let onFsReady;
|
||||||
|
let onFsError;
|
||||||
|
|
||||||
|
let fsReady = new Promise((resolve, reject) => {
|
||||||
|
onFsReady = resolve;
|
||||||
|
onFsError = reject;
|
||||||
|
});
|
||||||
|
|
||||||
|
var fsInstance = new FileSystem({ provider }, (err) => {
|
||||||
|
if (err) {
|
||||||
|
onFsError(err);
|
||||||
|
} else {
|
||||||
|
onFsReady(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function proxyHasProp(target, prop) {
|
||||||
|
return prop in target;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fsPromises = new Proxy(fsInstance.promises, {
|
||||||
|
get(target, prop) {
|
||||||
|
if (!proxyHasProp(target, prop)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return async (...args) => {
|
||||||
|
await fsReady;
|
||||||
|
return await target[prop](...args);
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const fs = new Proxy(fsInstance, {
|
||||||
|
get(target, prop) {
|
||||||
|
if (!proxyHasProp(target, prop)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prop === 'promises') {
|
||||||
|
return fsPromises;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (...args) => {
|
||||||
|
(async () => {
|
||||||
|
await fsReady;
|
||||||
|
target[prop](...args);
|
||||||
|
})();
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = fs;
|
|
@ -0,0 +1,3 @@
|
||||||
|
const { path } = require('../src/index');
|
||||||
|
|
||||||
|
module.exports = path;
|
|
@ -0,0 +1,2 @@
|
||||||
|
const { Default } = require('../../src/providers/index');
|
||||||
|
module.exports = Default;
|
|
@ -0,0 +1,2 @@
|
||||||
|
const IndexedDB = require('../../src/providers/indexeddb');
|
||||||
|
module.exports = IndexedDB;
|
|
@ -0,0 +1,2 @@
|
||||||
|
const Memory = require('../../src/providers/memory');
|
||||||
|
module.exports = Memory;
|
|
@ -1,23 +0,0 @@
|
||||||
function FilerBuffer (subject, encoding, nonZero) {
|
|
||||||
|
|
||||||
// Automatically turn ArrayBuffer into Uint8Array so that underlying
|
|
||||||
// Buffer code doesn't just throw away and ignore ArrayBuffer data.
|
|
||||||
if (subject instanceof ArrayBuffer) {
|
|
||||||
subject = new Uint8Array(subject);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Buffer(subject, encoding, nonZero);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Inherit prototype from Buffer
|
|
||||||
FilerBuffer.prototype = Object.create(Buffer.prototype);
|
|
||||||
FilerBuffer.prototype.constructor = FilerBuffer;
|
|
||||||
|
|
||||||
// Also copy static methods onto FilerBuffer ctor
|
|
||||||
Object.keys(Buffer).forEach(function (p) {
|
|
||||||
if (Buffer.hasOwnProperty(p)) {
|
|
||||||
FilerBuffer[p] = Buffer[p];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = FilerBuffer;
|
|
|
@ -15,14 +15,20 @@ module.exports = {
|
||||||
IDB_RO: 'readonly',
|
IDB_RO: 'readonly',
|
||||||
IDB_RW: 'readwrite',
|
IDB_RW: 'readwrite',
|
||||||
|
|
||||||
WSQL_VERSION: "1",
|
WSQL_VERSION: '1',
|
||||||
WSQL_SIZE: 5 * 1024 * 1024,
|
WSQL_SIZE: 5 * 1024 * 1024,
|
||||||
WSQL_DESC: "FileSystem Storage",
|
WSQL_DESC: 'FileSystem Storage',
|
||||||
|
|
||||||
MODE_FILE: 'FILE',
|
NODE_TYPE_FILE: 'FILE',
|
||||||
MODE_DIRECTORY: 'DIRECTORY',
|
NODE_TYPE_DIRECTORY: 'DIRECTORY',
|
||||||
MODE_SYMBOLIC_LINK: 'SYMLINK',
|
NODE_TYPE_SYMBOLIC_LINK: 'SYMLINK',
|
||||||
MODE_META: 'META',
|
NODE_TYPE_META: 'META',
|
||||||
|
|
||||||
|
|
||||||
|
DEFAULT_DIR_PERMISSIONS: 0x1ED, // 755
|
||||||
|
DEFAULT_FILE_PERMISSIONS: 0x1A4, // 644
|
||||||
|
FULL_READ_WRITE_EXEC_PERMISSIONS: 0x1FF, // 777
|
||||||
|
READ_WRITE_PERMISSIONS: 0x1B6, /// 666
|
||||||
|
|
||||||
SYMLOOP_MAX: 10,
|
SYMLOOP_MAX: 10,
|
||||||
|
|
||||||
|
@ -76,5 +82,49 @@ module.exports = {
|
||||||
ENVIRONMENT: {
|
ENVIRONMENT: {
|
||||||
TMP: '/tmp',
|
TMP: '/tmp',
|
||||||
PATH: ''
|
PATH: ''
|
||||||
|
},
|
||||||
|
|
||||||
|
// Duplicate Node's fs.constants
|
||||||
|
fsConstants: {
|
||||||
|
O_RDONLY: 0,
|
||||||
|
O_WRONLY: 1,
|
||||||
|
O_RDWR: 2,
|
||||||
|
S_IFMT: 61440,
|
||||||
|
S_IFREG: 32768,
|
||||||
|
S_IFDIR: 16384,
|
||||||
|
S_IFCHR: 8192,
|
||||||
|
S_IFBLK: 24576,
|
||||||
|
S_IFIFO: 4096,
|
||||||
|
S_IFLNK: 40960,
|
||||||
|
S_IFSOCK: 49152,
|
||||||
|
O_CREAT: 512,
|
||||||
|
O_EXCL: 2048,
|
||||||
|
O_NOCTTY: 131072,
|
||||||
|
O_TRUNC: 1024,
|
||||||
|
O_APPEND: 8,
|
||||||
|
O_DIRECTORY: 1048576,
|
||||||
|
O_NOFOLLOW: 256,
|
||||||
|
O_SYNC: 128,
|
||||||
|
O_DSYNC: 4194304,
|
||||||
|
O_SYMLINK: 2097152,
|
||||||
|
O_NONBLOCK: 4,
|
||||||
|
S_IRWXU: 448,
|
||||||
|
S_IRUSR: 256,
|
||||||
|
S_IWUSR: 128,
|
||||||
|
S_IXUSR: 64,
|
||||||
|
S_IRWXG: 56,
|
||||||
|
S_IRGRP: 32,
|
||||||
|
S_IWGRP: 16,
|
||||||
|
S_IXGRP: 8,
|
||||||
|
S_IRWXO: 7,
|
||||||
|
S_IROTH: 4,
|
||||||
|
S_IWOTH: 2,
|
||||||
|
S_IXOTH: 1,
|
||||||
|
F_OK: 0,
|
||||||
|
R_OK: 4,
|
||||||
|
W_OK: 2,
|
||||||
|
X_OK: 1,
|
||||||
|
UV_FS_COPYFILE_EXCL: 1,
|
||||||
|
COPYFILE_EXCL: 1
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
var MODE_FILE = require('./constants.js').MODE_FILE;
|
var NODE_TYPE_FILE = require('./constants.js').NODE_TYPE_FILE;
|
||||||
|
|
||||||
module.exports = function DirectoryEntry(id, type) {
|
module.exports = function DirectoryEntry(id, type) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.type = type || MODE_FILE;
|
this.type = type || NODE_TYPE_FILE;
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const Stats = require('./stats.js');
|
||||||
|
|
||||||
|
function Dirent(path, fileNode, devName) {
|
||||||
|
this.constructor = Dirent;
|
||||||
|
Stats.call(this, path, fileNode, devName);
|
||||||
|
}
|
||||||
|
|
||||||
|
Dirent.prototype = Stats.prototype;
|
||||||
|
|
||||||
|
module.exports = Dirent;
|
|
@ -1,13 +0,0 @@
|
||||||
// Adapt encodings to work with Buffer or Uint8Array, they expect the latter
|
|
||||||
function decode(buf) {
|
|
||||||
return buf.toString('utf8');
|
|
||||||
}
|
|
||||||
|
|
||||||
function encode(string) {
|
|
||||||
return new Buffer(string, 'utf8');
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
encode: encode,
|
|
||||||
decode: decode
|
|
||||||
};
|
|
|
@ -7,7 +7,7 @@ var errors = {};
|
||||||
//'0:OK:success',
|
//'0:OK:success',
|
||||||
//'1:EOF:end of file',
|
//'1:EOF:end of file',
|
||||||
//'2:EADDRINFO:getaddrinfo error',
|
//'2:EADDRINFO:getaddrinfo error',
|
||||||
//'3:EACCES:permission denied',
|
'3:EACCES:permission denied',
|
||||||
//'4:EAGAIN:resource temporarily unavailable',
|
//'4:EAGAIN:resource temporarily unavailable',
|
||||||
//'5:EADDRINUSE:address already in use',
|
//'5:EADDRINUSE:address already in use',
|
||||||
//'6:EADDRNOTAVAIL:address not available',
|
//'6:EADDRNOTAVAIL:address not available',
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,44 +1,111 @@
|
||||||
var _ = require('../../lib/nodash.js');
|
'use strict';
|
||||||
|
|
||||||
var isNullPath = require('../path.js').isNull;
|
const { promisify } = require('es6-promisify');
|
||||||
var nop = require('../shared.js').nop;
|
|
||||||
|
|
||||||
var Constants = require('../constants.js');
|
const Path = require('../path.js');
|
||||||
var FILE_SYSTEM_NAME = Constants.FILE_SYSTEM_NAME;
|
|
||||||
var FS_FORMAT = Constants.FS_FORMAT;
|
|
||||||
var FS_READY = Constants.FS_READY;
|
|
||||||
var FS_PENDING = Constants.FS_PENDING;
|
|
||||||
var FS_ERROR = Constants.FS_ERROR;
|
|
||||||
var FS_NODUPEIDCHECK = Constants.FS_NODUPEIDCHECK;
|
|
||||||
|
|
||||||
var providers = require('../providers/index.js');
|
const providers = require('../providers/index.js');
|
||||||
|
|
||||||
var Shell = require('../shell/shell.js');
|
const Shell = require('../shell/shell.js');
|
||||||
var Intercom = require('../../lib/intercom.js');
|
const Intercom = require('../../lib/intercom.js');
|
||||||
var FSWatcher = require('../fs-watcher.js');
|
const FSWatcher = require('../fs-watcher.js');
|
||||||
var Errors = require('../errors.js');
|
const Errors = require('../errors.js');
|
||||||
var defaultGuidFn = require('../shared.js').guid;
|
const {
|
||||||
|
nop,
|
||||||
|
guid: defaultGuidFn
|
||||||
|
} = require('../shared.js');
|
||||||
|
|
||||||
var STDIN = Constants.STDIN;
|
const {
|
||||||
var STDOUT = Constants.STDOUT;
|
fsConstants,
|
||||||
var STDERR = Constants.STDERR;
|
FILE_SYSTEM_NAME,
|
||||||
var FIRST_DESCRIPTOR = Constants.FIRST_DESCRIPTOR;
|
FS_FORMAT,
|
||||||
|
FS_READY,
|
||||||
|
FS_PENDING,
|
||||||
|
FS_ERROR,
|
||||||
|
FS_NODUPEIDCHECK,
|
||||||
|
STDIN,
|
||||||
|
STDOUT,
|
||||||
|
STDERR
|
||||||
|
} = require('../constants.js');
|
||||||
|
|
||||||
// The core fs operations live on impl
|
// The core fs operations live on impl
|
||||||
var impl = require('./implementation.js');
|
const impl = require('./implementation.js');
|
||||||
|
|
||||||
// node.js supports a calling pattern that leaves off a callback.
|
// node.js supports a calling pattern that leaves off a callback.
|
||||||
function maybeCallback(callback) {
|
function maybeCallback(callback) {
|
||||||
if(typeof callback === "function") {
|
if (typeof callback === 'function') {
|
||||||
return callback;
|
return callback;
|
||||||
}
|
}
|
||||||
return function(err) {
|
return function (err) {
|
||||||
if(err) {
|
if (err) {
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Default callback that logs an error if passed in
|
||||||
|
function defaultCallback(err) {
|
||||||
|
if (err) {
|
||||||
|
/* eslint no-console: 0 */
|
||||||
|
console.error('Filer error: ', err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Get a path (String) from a file:// URL. Support URL() like objects
|
||||||
|
// https://github.com/nodejs/node/blob/968e901aff38a343b1de4addebf79fd8fa991c59/lib/internal/url.js#L1381
|
||||||
|
function toPathIfFileURL(fileURLOrPath) {
|
||||||
|
if (!(fileURLOrPath &&
|
||||||
|
fileURLOrPath.protocol &&
|
||||||
|
fileURLOrPath.pathname)) {
|
||||||
|
return fileURLOrPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fileURLOrPath.protocol !== 'file:') {
|
||||||
|
throw new Errors.EINVAL('only file: URLs are supported for paths', fileURLOrPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
const pathname = fileURLOrPath.pathname;
|
||||||
|
for (let n = 0; n < pathname.length; n++) {
|
||||||
|
if (pathname[n] === '%') {
|
||||||
|
const third = pathname.codePointAt(n + 2) | 0x20;
|
||||||
|
if (pathname[n + 1] === '2' && third === 102) {
|
||||||
|
throw new Errors.EINVAL('file: URLs must not include encoded / characters', fileURLOrPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return decodeURIComponent(pathname);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allow Buffers for paths. Assumes we want UTF8.
|
||||||
|
function toPathIfBuffer(bufferOrPath) {
|
||||||
|
return Buffer.isBuffer(bufferOrPath) ? bufferOrPath.toString() : bufferOrPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
function validatePath(path, allowRelative) {
|
||||||
|
if (!path) {
|
||||||
|
return new Errors.EINVAL('Path must be a string', path);
|
||||||
|
} else if (Path.isNull(path)) {
|
||||||
|
return new Errors.EINVAL('Path must be a string without null bytes.', path);
|
||||||
|
} else if (!allowRelative && !Path.isAbsolute(path)) {
|
||||||
|
return new Errors.EINVAL('Path must be absolute.', path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function processPathArg(args, idx, allowRelative) {
|
||||||
|
let path = args[idx];
|
||||||
|
path = toPathIfFileURL(path);
|
||||||
|
path = toPathIfBuffer(path);
|
||||||
|
|
||||||
|
// Some methods specifically allow for rel paths (eg symlink with srcPath)
|
||||||
|
let err = validatePath(path, allowRelative);
|
||||||
|
if (err) {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Overwrite path arg with converted and validated path
|
||||||
|
args[idx] = path;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* FileSystem
|
* FileSystem
|
||||||
*
|
*
|
||||||
|
@ -69,16 +136,16 @@ function maybeCallback(callback) {
|
||||||
*/
|
*/
|
||||||
function FileSystem(options, callback) {
|
function FileSystem(options, callback) {
|
||||||
options = options || {};
|
options = options || {};
|
||||||
callback = callback || nop;
|
callback = callback || defaultCallback;
|
||||||
|
|
||||||
var flags = options.flags;
|
const flags = options.flags || [];
|
||||||
var guid = options.guid ? options.guid : defaultGuidFn;
|
const guid = options.guid ? options.guid : defaultGuidFn;
|
||||||
var provider = options.provider || new providers.Default(options.name || FILE_SYSTEM_NAME);
|
const provider = options.provider || new providers.Default(options.name || FILE_SYSTEM_NAME);
|
||||||
// If we're given a provider, match its name unless we get an explicit name
|
// If we're given a provider, match its name unless we get an explicit name
|
||||||
var name = options.name || provider.name;
|
const name = options.name || provider.name;
|
||||||
var forceFormatting = _(flags).contains(FS_FORMAT);
|
const forceFormatting = flags.includes(FS_FORMAT);
|
||||||
|
|
||||||
var fs = this;
|
const fs = this;
|
||||||
fs.readyState = FS_PENDING;
|
fs.readyState = FS_PENDING;
|
||||||
fs.name = name;
|
fs.name = name;
|
||||||
fs.error = null;
|
fs.error = null;
|
||||||
|
@ -87,30 +154,25 @@ function FileSystem(options, callback) {
|
||||||
fs.stdout = STDOUT;
|
fs.stdout = STDOUT;
|
||||||
fs.stderr = STDERR;
|
fs.stderr = STDERR;
|
||||||
|
|
||||||
// Safely expose the list of open files and file
|
// Expose Node's fs.constants to users
|
||||||
// descriptor management functions
|
fs.constants = fsConstants;
|
||||||
var openFiles = {};
|
// Node also forwards the access mode flags onto fs
|
||||||
var nextDescriptor = FIRST_DESCRIPTOR;
|
fs.F_OK = fsConstants.F_OK;
|
||||||
Object.defineProperty(this, "openFiles", {
|
fs.R_OK = fsConstants.R_OK;
|
||||||
get: function() { return openFiles; }
|
fs.W_OK = fsConstants.W_OK;
|
||||||
});
|
fs.X_OK = fsConstants.X_OK;
|
||||||
this.allocDescriptor = function(openFileDescription) {
|
|
||||||
var fd = nextDescriptor ++;
|
// Expose Shell constructor
|
||||||
openFiles[fd] = openFileDescription;
|
this.Shell = Shell.bind(undefined, this);
|
||||||
return fd;
|
|
||||||
};
|
|
||||||
this.releaseDescriptor = function(fd) {
|
|
||||||
delete openFiles[fd];
|
|
||||||
};
|
|
||||||
|
|
||||||
// Safely expose the operation queue
|
// Safely expose the operation queue
|
||||||
var queue = [];
|
let queue = [];
|
||||||
this.queueOrRun = function(operation) {
|
this.queueOrRun = function (operation) {
|
||||||
var error;
|
let error;
|
||||||
|
|
||||||
if(FS_READY == fs.readyState) {
|
if (FS_READY === fs.readyState) {
|
||||||
operation.call(fs);
|
operation.call(fs);
|
||||||
} else if(FS_ERROR == fs.readyState) {
|
} else if (FS_ERROR === fs.readyState) {
|
||||||
error = new Errors.EFILESYSTEMERROR('unknown error');
|
error = new Errors.EFILESYSTEMERROR('unknown error');
|
||||||
} else {
|
} else {
|
||||||
queue.push(operation);
|
queue.push(operation);
|
||||||
|
@ -119,25 +181,25 @@ function FileSystem(options, callback) {
|
||||||
return error;
|
return error;
|
||||||
};
|
};
|
||||||
function runQueued() {
|
function runQueued() {
|
||||||
queue.forEach(function(operation) {
|
queue.forEach(function (operation) {
|
||||||
operation.call(this);
|
operation.call(this);
|
||||||
}.bind(fs));
|
}.bind(fs));
|
||||||
queue = null;
|
queue = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// We support the optional `options` arg from node, but ignore it
|
// We support the optional `options` arg from node, but ignore it
|
||||||
this.watch = function(filename, options, listener) {
|
this.watch = function (filename, options, listener) {
|
||||||
if(isNullPath(filename)) {
|
if (Path.isNull(filename)) {
|
||||||
throw new Error('Path must be a string without null bytes.');
|
throw new Error('Path must be a string without null bytes.');
|
||||||
}
|
}
|
||||||
if(typeof options === 'function') {
|
if (typeof options === 'function') {
|
||||||
listener = options;
|
listener = options;
|
||||||
options = {};
|
options = {};
|
||||||
}
|
}
|
||||||
options = options || {};
|
options = options || {};
|
||||||
listener = listener || nop;
|
listener = listener || nop;
|
||||||
|
|
||||||
var watcher = new FSWatcher();
|
const watcher = new FSWatcher();
|
||||||
watcher.start(filename, false, options.recursive);
|
watcher.start(filename, false, options.recursive);
|
||||||
watcher.on('change', listener);
|
watcher.on('change', listener);
|
||||||
|
|
||||||
|
@ -146,24 +208,24 @@ function FileSystem(options, callback) {
|
||||||
|
|
||||||
// Deal with various approaches to node ID creation
|
// Deal with various approaches to node ID creation
|
||||||
function wrappedGuidFn(context) {
|
function wrappedGuidFn(context) {
|
||||||
return function(callback) {
|
return function (callback) {
|
||||||
// Skip the duplicate ID check if asked to
|
// Skip the duplicate ID check if asked to
|
||||||
if(_(flags).contains(FS_NODUPEIDCHECK)) {
|
if (flags.includes(FS_NODUPEIDCHECK)) {
|
||||||
callback(null, guid());
|
callback(null, guid());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise (default) make sure this id is unused first
|
// Otherwise (default) make sure this id is unused first
|
||||||
function guidWithCheck(callback) {
|
function guidWithCheck(callback) {
|
||||||
var id = guid();
|
const id = guid();
|
||||||
context.getObject(id, function(err, value) {
|
context.getObject(id, function (err, value) {
|
||||||
if(err) {
|
if (err) {
|
||||||
callback(err);
|
callback(err);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If this id is unused, use it, otherwise find another
|
// If this id is unused, use it, otherwise find another
|
||||||
if(!value) {
|
if (!value) {
|
||||||
callback(null, id);
|
callback(null, id);
|
||||||
} else {
|
} else {
|
||||||
guidWithCheck(callback);
|
guidWithCheck(callback);
|
||||||
|
@ -177,27 +239,28 @@ function FileSystem(options, callback) {
|
||||||
// Let other instances (in this or other windows) know about
|
// Let other instances (in this or other windows) know about
|
||||||
// any changes to this fs instance.
|
// any changes to this fs instance.
|
||||||
function broadcastChanges(changes) {
|
function broadcastChanges(changes) {
|
||||||
if(!changes.length) {
|
if (!changes.length) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var intercom = Intercom.getInstance();
|
const intercom = Intercom.getInstance();
|
||||||
changes.forEach(function(change) {
|
changes.forEach(function (change) {
|
||||||
intercom.emit(change.event, change.path);
|
intercom.emit(change.event, change.path);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open file system storage provider
|
// Open file system storage provider
|
||||||
provider.open(function(err) {
|
provider.open(function (err) {
|
||||||
function complete(error) {
|
function complete(error) {
|
||||||
function wrappedContext(methodName) {
|
function wrappedContext(methodName) {
|
||||||
var context = provider[methodName]();
|
let context = provider[methodName]();
|
||||||
|
context.name = name;
|
||||||
context.flags = flags;
|
context.flags = flags;
|
||||||
context.changes = [];
|
context.changes = [];
|
||||||
context.guid = wrappedGuidFn(context);
|
context.guid = wrappedGuidFn(context);
|
||||||
|
|
||||||
// When the context is finished, let the fs deal with any change events
|
// When the context is finished, let the fs deal with any change events
|
||||||
context.close = function() {
|
context.close = function () {
|
||||||
var changes = context.changes;
|
let changes = context.changes;
|
||||||
broadcastChanges(changes);
|
broadcastChanges(changes);
|
||||||
changes.length = 0;
|
changes.length = 0;
|
||||||
};
|
};
|
||||||
|
@ -210,15 +273,15 @@ function FileSystem(options, callback) {
|
||||||
// for paths updated during the lifetime of the context). From this
|
// for paths updated during the lifetime of the context). From this
|
||||||
// point forward we won't call open again, so it's safe to drop it.
|
// point forward we won't call open again, so it's safe to drop it.
|
||||||
fs.provider = {
|
fs.provider = {
|
||||||
openReadWriteContext: function() {
|
openReadWriteContext: function () {
|
||||||
return wrappedContext('getReadWriteContext');
|
return wrappedContext('getReadWriteContext');
|
||||||
},
|
},
|
||||||
openReadOnlyContext: function() {
|
openReadOnlyContext: function () {
|
||||||
return wrappedContext('getReadOnlyContext');
|
return wrappedContext('getReadOnlyContext');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if(error) {
|
if (error) {
|
||||||
fs.readyState = FS_ERROR;
|
fs.readyState = FS_ERROR;
|
||||||
} else {
|
} else {
|
||||||
fs.readyState = FS_READY;
|
fs.readyState = FS_READY;
|
||||||
|
@ -227,18 +290,18 @@ function FileSystem(options, callback) {
|
||||||
callback(error, fs);
|
callback(error, fs);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(err) {
|
if (err) {
|
||||||
return complete(err);
|
return complete(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
var context = provider.getReadWriteContext();
|
const context = provider.getReadWriteContext();
|
||||||
context.guid = wrappedGuidFn(context);
|
context.guid = wrappedGuidFn(context);
|
||||||
|
|
||||||
// Mount the filesystem, formatting if necessary
|
// Mount the filesystem, formatting if necessary
|
||||||
if(forceFormatting) {
|
if (forceFormatting) {
|
||||||
// Wipe the storage provider, then write root block
|
// Wipe the storage provider, then write root block
|
||||||
context.clear(function(err) {
|
context.clear(function (err) {
|
||||||
if(err) {
|
if (err) {
|
||||||
return complete(err);
|
return complete(err);
|
||||||
}
|
}
|
||||||
impl.ensureRootDirectory(context, complete);
|
impl.ensureRootDirectory(context, complete);
|
||||||
|
@ -248,94 +311,128 @@ function FileSystem(options, callback) {
|
||||||
impl.ensureRootDirectory(context, complete);
|
impl.ensureRootDirectory(context, complete);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
FileSystem.prototype.promises = {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Public API for FileSystem. All node.js methods that are exposed on fs.promises
|
||||||
|
* include `promise: true`. We also include our own extra methods, but skip the
|
||||||
|
* fd versions to match node.js, which puts these on a `FileHandle` object.
|
||||||
|
* Any method that deals with path argument(s) also includes the position of
|
||||||
|
* those args in one of `absPathArgs: [...]` or `relPathArgs: [...]`, so they
|
||||||
|
* can be processed and validated before being passed on to the method.
|
||||||
|
*/
|
||||||
|
[
|
||||||
|
{ name: 'appendFile', promises: true, absPathArgs: [0] },
|
||||||
|
{ name: 'access', promises: true, absPathArgs: [0] },
|
||||||
|
{ name: 'chown', promises: true, absPathArgs: [0] },
|
||||||
|
{ name: 'chmod', promises: true, absPathArgs: [0] },
|
||||||
|
{ name: 'close' },
|
||||||
|
// copyFile - https://github.com/filerjs/filer/issues/436
|
||||||
|
{ name: 'exists', absPathArgs: [0] },
|
||||||
|
{ name: 'fchown' },
|
||||||
|
{ name: 'fchmod' },
|
||||||
|
// fdatasync - https://github.com/filerjs/filer/issues/653
|
||||||
|
{ name: 'fgetxattr' },
|
||||||
|
{ name: 'fremovexattr' },
|
||||||
|
{ name: 'fsetxattr' },
|
||||||
|
{ name: 'fstat' },
|
||||||
|
{ name: 'fsync' },
|
||||||
|
{ name: 'ftruncate' },
|
||||||
|
{ name: 'futimes' },
|
||||||
|
{ name: 'getxattr', promises: true, absPathArgs: [0] },
|
||||||
|
// lchown - https://github.com/filerjs/filer/issues/620
|
||||||
|
// lchmod - https://github.com/filerjs/filer/issues/619
|
||||||
|
{ name: 'link', promises: true, absPathArgs: [0, 1] },
|
||||||
|
{ name: 'lseek' },
|
||||||
|
{ name: 'lstat', promises: true },
|
||||||
|
{ name: 'mkdir', promises: true, absPathArgs: [0] },
|
||||||
|
{ name: 'mkdtemp', promises: true },
|
||||||
|
{ name: 'mknod', promises: true, absPathArgs: [0] },
|
||||||
|
{ name: 'open', promises: true, absPathArgs: [0] },
|
||||||
|
{ name: 'readdir', promises: true, absPathArgs: [0] },
|
||||||
|
{ name: 'read' },
|
||||||
|
{ name: 'readFile', promises: true, absPathArgs: [0] },
|
||||||
|
{ name: 'readlink', promises: true, absPathArgs: [0] },
|
||||||
|
// realpath - https://github.com/filerjs/filer/issues/85
|
||||||
|
{ name: 'removexattr', promises: true, absPathArgs: [0] },
|
||||||
|
{ name: 'rename', promises: true, absPathArgs: [0, 1] },
|
||||||
|
{ name: 'rmdir', promises: true, absPathArgs: [0] },
|
||||||
|
{ name: 'setxattr', promises: true, absPathArgs: [0] },
|
||||||
|
{ name: 'stat', promises: true, absPathArgs: [0] },
|
||||||
|
{ name: 'symlink', promises: true, relPathArgs: [0], absPathArgs: [1] },
|
||||||
|
{ name: 'truncate', promises: true, absPathArgs: [0] },
|
||||||
|
// unwatchFile - https://github.com/filerjs/filer/pull/553
|
||||||
|
{ name: 'unlink', promises: true, absPathArgs: [0] },
|
||||||
|
{ name: 'utimes', promises: true, absPathArgs: [0] },
|
||||||
|
// watch - implemented above in `this.watch`
|
||||||
|
// watchFile - https://github.com/filerjs/filer/issues/654
|
||||||
|
{ name: 'writeFile', promises: true, absPathArgs: [0] },
|
||||||
|
{ name: 'write' }
|
||||||
|
].forEach(function (method) {
|
||||||
|
const methodName = method.name;
|
||||||
|
const shouldPromisify = method.promises === true;
|
||||||
|
|
||||||
|
FileSystem.prototype[methodName] = function () {
|
||||||
|
const fs = this;
|
||||||
|
const args = Array.prototype.slice.call(arguments, 0);
|
||||||
|
const lastArgIndex = args.length - 1;
|
||||||
|
|
||||||
|
// We may or may not get a callback, and since node.js supports
|
||||||
|
// fire-and-forget style fs operations, we have to dance a bit here.
|
||||||
|
const missingCallback = typeof args[lastArgIndex] !== 'function';
|
||||||
|
const callback = maybeCallback(args[lastArgIndex]);
|
||||||
|
|
||||||
|
// Deal with path arguments, validating and normalizing Buffer and file:// URLs
|
||||||
|
if (method.absPathArgs) {
|
||||||
|
method.absPathArgs.forEach(pathArg => processPathArg(args, pathArg, false));
|
||||||
|
}
|
||||||
|
if (method.relPathArgs) {
|
||||||
|
method.relPathArgs.forEach(pathArg => processPathArg(args, pathArg, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
const error = fs.queueOrRun(function () {
|
||||||
|
const context = fs.provider.openReadWriteContext();
|
||||||
|
|
||||||
|
// Fail early if the filesystem is in an error state (e.g.,
|
||||||
|
// provider failed to open.
|
||||||
|
if (FS_ERROR === fs.readyState) {
|
||||||
|
const err = new Errors.EFILESYSTEMERROR('filesystem unavailable, operation canceled');
|
||||||
|
return callback.call(fs, err);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wrap the callback so we can explicitly close the context
|
||||||
|
function complete() {
|
||||||
|
context.close();
|
||||||
|
callback.apply(fs, arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Either add or replace the callback with our wrapper complete()
|
||||||
|
if (missingCallback) {
|
||||||
|
args.push(complete);
|
||||||
|
} else {
|
||||||
|
args[lastArgIndex] = complete;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Forward this call to the impl's version, using the following
|
||||||
|
// call signature, with complete() as the callback/last-arg now:
|
||||||
|
// fn(fs, context, arg0, arg1, ... , complete);
|
||||||
|
const fnArgs = [context].concat(args);
|
||||||
|
impl[methodName].apply(null, fnArgs);
|
||||||
|
});
|
||||||
|
if (error) {
|
||||||
|
callback(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add to fs.promises if appropriate
|
||||||
|
if (shouldPromisify) {
|
||||||
|
FileSystem.prototype.promises[methodName] = promisify(FileSystem.prototype[methodName].bind(fs));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Expose storage providers on FileSystem constructor
|
// Expose storage providers on FileSystem constructor
|
||||||
FileSystem.providers = providers;
|
FileSystem.providers = providers;
|
||||||
|
|
||||||
/**
|
|
||||||
* Public API for FileSystem
|
|
||||||
*/
|
|
||||||
[
|
|
||||||
'open',
|
|
||||||
'close',
|
|
||||||
'mknod',
|
|
||||||
'mkdir',
|
|
||||||
'rmdir',
|
|
||||||
'stat',
|
|
||||||
'fstat',
|
|
||||||
'link',
|
|
||||||
'unlink',
|
|
||||||
'read',
|
|
||||||
'readFile',
|
|
||||||
'write',
|
|
||||||
'writeFile',
|
|
||||||
'appendFile',
|
|
||||||
'exists',
|
|
||||||
'lseek',
|
|
||||||
'readdir',
|
|
||||||
'rename',
|
|
||||||
'readlink',
|
|
||||||
'symlink',
|
|
||||||
'lstat',
|
|
||||||
'truncate',
|
|
||||||
'ftruncate',
|
|
||||||
'utimes',
|
|
||||||
'futimes',
|
|
||||||
'setxattr',
|
|
||||||
'getxattr',
|
|
||||||
'fsetxattr',
|
|
||||||
'fgetxattr',
|
|
||||||
'removexattr',
|
|
||||||
'fremovexattr'
|
|
||||||
].forEach(function(methodName) {
|
|
||||||
FileSystem.prototype[methodName] = function() {
|
|
||||||
var fs = this;
|
|
||||||
var args = Array.prototype.slice.call(arguments, 0);
|
|
||||||
var lastArgIndex = args.length - 1;
|
|
||||||
|
|
||||||
// We may or may not get a callback, and since node.js supports
|
|
||||||
// fire-and-forget style fs operations, we have to dance a bit here.
|
|
||||||
var missingCallback = typeof args[lastArgIndex] !== 'function';
|
|
||||||
var callback = maybeCallback(args[lastArgIndex]);
|
|
||||||
|
|
||||||
var error = fs.queueOrRun(function() {
|
|
||||||
var context = fs.provider.openReadWriteContext();
|
|
||||||
|
|
||||||
// Fail early if the filesystem is in an error state (e.g.,
|
|
||||||
// provider failed to open.
|
|
||||||
if(FS_ERROR === fs.readyState) {
|
|
||||||
var err = new Errors.EFILESYSTEMERROR('filesystem unavailable, operation canceled');
|
|
||||||
return callback.call(fs, err);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wrap the callback so we can explicitly close the context
|
|
||||||
function complete() {
|
|
||||||
context.close();
|
|
||||||
callback.apply(fs, arguments);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Either add or replace the callback with our wrapper complete()
|
|
||||||
if(missingCallback) {
|
|
||||||
args.push(complete);
|
|
||||||
} else {
|
|
||||||
args[lastArgIndex] = complete;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Forward this call to the impl's version, using the following
|
|
||||||
// call signature, with complete() as the callback/last-arg now:
|
|
||||||
// fn(fs, context, arg0, arg1, ... , complete);
|
|
||||||
var fnArgs = [fs, context].concat(args);
|
|
||||||
impl[methodName].apply(null, fnArgs);
|
|
||||||
});
|
|
||||||
if(error) {
|
|
||||||
callback(error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
FileSystem.prototype.Shell = function(options) {
|
|
||||||
return new Shell(this, options);
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = FileSystem;
|
module.exports = FileSystem;
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
var EventEmitter = require('../lib/eventemitter.js');
|
'using strict';
|
||||||
var Path = require('./path.js');
|
|
||||||
var Intercom = require('../lib/intercom.js');
|
const EventEmitter = require('../lib/eventemitter.js');
|
||||||
|
const Path = require('./path.js');
|
||||||
|
const Intercom = require('../lib/intercom.js');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* FSWatcher based on node.js' FSWatcher
|
* FSWatcher based on node.js' FSWatcher
|
||||||
|
@ -8,10 +10,10 @@ var Intercom = require('../lib/intercom.js');
|
||||||
*/
|
*/
|
||||||
function FSWatcher() {
|
function FSWatcher() {
|
||||||
EventEmitter.call(this);
|
EventEmitter.call(this);
|
||||||
var self = this;
|
const self = this;
|
||||||
var recursive = false;
|
let recursive = false;
|
||||||
var recursivePathPrefix;
|
let recursivePathPrefix;
|
||||||
var filename;
|
let filename;
|
||||||
|
|
||||||
function onchange(path) {
|
function onchange(path) {
|
||||||
// Watch for exact filename, or parent path when recursive is true.
|
// Watch for exact filename, or parent path when recursive is true.
|
||||||
|
@ -46,12 +48,12 @@ function FSWatcher() {
|
||||||
recursivePathPrefix = filename === '/' ? '/' : filename + '/';
|
recursivePathPrefix = filename === '/' ? '/' : filename + '/';
|
||||||
}
|
}
|
||||||
|
|
||||||
var intercom = Intercom.getInstance();
|
const intercom = Intercom.getInstance();
|
||||||
intercom.on('change', onchange);
|
intercom.on('change', onchange);
|
||||||
};
|
};
|
||||||
|
|
||||||
self.close = function() {
|
self.close = function() {
|
||||||
var intercom = Intercom.getInstance();
|
const intercom = Intercom.getInstance();
|
||||||
intercom.off('change', onchange);
|
intercom.off('change', onchange);
|
||||||
self.removeAllListeners('change');
|
self.removeAllListeners('change');
|
||||||
};
|
};
|
||||||
|
|
34
src/index.js
34
src/index.js
|
@ -1,6 +1,34 @@
|
||||||
module.exports = {
|
let fs = null;
|
||||||
|
let Filer = null;
|
||||||
|
|
||||||
|
module.exports = Filer = {
|
||||||
FileSystem: require('./filesystem/interface.js'),
|
FileSystem: require('./filesystem/interface.js'),
|
||||||
Buffer: require('./buffer.js'),
|
Buffer: Buffer,
|
||||||
|
// We previously called this Path, but node calls it path. Do both
|
||||||
Path: require('./path.js'),
|
Path: require('./path.js'),
|
||||||
Errors: require('./errors.js')
|
path: require('./path.js'),
|
||||||
|
Errors: require('./errors.js'),
|
||||||
|
Shell: require('./shell/shell.js'),
|
||||||
|
/**
|
||||||
|
* @deprecated Importing filer from your webpack config is not recommended.
|
||||||
|
*
|
||||||
|
* The filer `FilerWebpackPlugin` class is exposed directly.
|
||||||
|
*
|
||||||
|
* ```
|
||||||
|
* const { FilerWebpackPlugin } = require('filer/webpack');
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
FilerWebpackPlugin: require('./webpack-plugin'),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Add a getter for the `fs` instance, which returns
|
||||||
|
// a Filer FileSystem instance, using the default provider/flags.
|
||||||
|
Object.defineProperty(Filer, 'fs', {
|
||||||
|
enumerable: true,
|
||||||
|
get() {
|
||||||
|
if(!fs) {
|
||||||
|
fs = new Filer.FileSystem();
|
||||||
|
}
|
||||||
|
return fs;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
150
src/node.js
150
src/node.js
|
@ -1,53 +1,135 @@
|
||||||
var MODE_FILE = require('./constants.js').MODE_FILE;
|
const {
|
||||||
|
NODE_TYPE_FILE,
|
||||||
|
NODE_TYPE_DIRECTORY,
|
||||||
|
NODE_TYPE_SYMBOLIC_LINK,
|
||||||
|
DEFAULT_FILE_PERMISSIONS,
|
||||||
|
DEFAULT_DIR_PERMISSIONS
|
||||||
|
} = require('./constants');
|
||||||
|
const {
|
||||||
|
S_IFREG,
|
||||||
|
S_IFDIR,
|
||||||
|
S_IFLNK
|
||||||
|
} = require('./constants').fsConstants;
|
||||||
|
|
||||||
function Node(options) {
|
/**
|
||||||
var now = Date.now();
|
* Make sure the options object has an id on property,
|
||||||
|
* either from caller or one we generate using supplied guid fn.
|
||||||
this.id = options.id;
|
*/
|
||||||
this.mode = options.mode || MODE_FILE; // node type (file, directory, etc)
|
|
||||||
this.size = options.size || 0; // size (bytes for files, entries for directories)
|
|
||||||
this.atime = options.atime || now; // access time (will mirror ctime after creation)
|
|
||||||
this.ctime = options.ctime || now; // creation/change time
|
|
||||||
this.mtime = options.mtime || now; // modified time
|
|
||||||
this.flags = options.flags || []; // file flags
|
|
||||||
this.xattrs = options.xattrs || {}; // extended attributes
|
|
||||||
this.nlinks = options.nlinks || 0; // links count
|
|
||||||
this.version = options.version || 0; // node version
|
|
||||||
this.blksize = undefined; // block size
|
|
||||||
this.nblocks = 1; // blocks count
|
|
||||||
this.data = options.data; // id for data object
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make sure the options object has an id on property,
|
|
||||||
// either from caller or one we generate using supplied guid fn.
|
|
||||||
function ensureID(options, prop, callback) {
|
function ensureID(options, prop, callback) {
|
||||||
if(options[prop]) {
|
if(options[prop]) {
|
||||||
callback(null);
|
return callback();
|
||||||
} else {
|
}
|
||||||
options.guid(function(err, id) {
|
|
||||||
options[prop] = id;
|
options.guid(function(err, id) {
|
||||||
callback(err);
|
if(err) {
|
||||||
});
|
return callback(err);
|
||||||
|
}
|
||||||
|
options[prop] = id;
|
||||||
|
callback();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a POSIX mode (integer) for the node type and permissions.
|
||||||
|
* Use default permissions if we aren't passed any.
|
||||||
|
*/
|
||||||
|
function generateMode(nodeType, modePermissions) {
|
||||||
|
switch(nodeType) {
|
||||||
|
case NODE_TYPE_DIRECTORY:
|
||||||
|
return (modePermissions || DEFAULT_DIR_PERMISSIONS) | S_IFDIR;
|
||||||
|
case NODE_TYPE_SYMBOLIC_LINK:
|
||||||
|
return (modePermissions || DEFAULT_FILE_PERMISSIONS) | S_IFLNK;
|
||||||
|
case NODE_TYPE_FILE:
|
||||||
|
// falls through
|
||||||
|
default:
|
||||||
|
return (modePermissions || DEFAULT_FILE_PERMISSIONS) | S_IFREG;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Node.create = function(options, callback) {
|
/**
|
||||||
|
* Common properties for the layout of a Node
|
||||||
|
*/
|
||||||
|
class Node {
|
||||||
|
constructor(options) {
|
||||||
|
var now = Date.now();
|
||||||
|
|
||||||
|
this.id = options.id;
|
||||||
|
this.data = options.data; // id for data object
|
||||||
|
this.size = options.size || 0; // size (bytes for files, entries for directories)
|
||||||
|
this.atime = options.atime || now; // access time (will mirror ctime after creation)
|
||||||
|
this.ctime = options.ctime || now; // creation/change time
|
||||||
|
this.mtime = options.mtime || now; // modified time
|
||||||
|
this.flags = options.flags || []; // file flags
|
||||||
|
this.xattrs = options.xattrs || {}; // extended attributes
|
||||||
|
this.nlinks = options.nlinks || 0; // links count
|
||||||
|
|
||||||
|
// Historically, Filer's node layout has referred to the
|
||||||
|
// node type as `mode`, and done so using a String. In
|
||||||
|
// a POSIX filesystem, the mode is a number that combines
|
||||||
|
// both node type and permission bits. Internal we use `type`,
|
||||||
|
// but store it in the database as `mode` for backward
|
||||||
|
// compatibility.
|
||||||
|
if(typeof options.type === 'string') {
|
||||||
|
this.type = options.type;
|
||||||
|
} else if(typeof options.mode === 'string') {
|
||||||
|
this.type = options.mode;
|
||||||
|
} else {
|
||||||
|
this.type = NODE_TYPE_FILE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extra mode permissions and ownership info
|
||||||
|
this.permissions = options.permissions || generateMode(this.type);
|
||||||
|
this.uid = options.uid || 0x0; // owner name
|
||||||
|
this.gid = options.gid || 0x0; // group name
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Serialize a Node to JSON. Everything is as expected except
|
||||||
|
* that we use `mode` for `type` to maintain backward compatibility.
|
||||||
|
*/
|
||||||
|
toJSON() {
|
||||||
|
return {
|
||||||
|
id: this.id,
|
||||||
|
data: this.data,
|
||||||
|
size: this.size,
|
||||||
|
atime: this.atime,
|
||||||
|
ctime: this.ctime,
|
||||||
|
mtime: this.ctime,
|
||||||
|
flags: this.flags,
|
||||||
|
xattrs: this.xattrs,
|
||||||
|
nlinks: this.nlinks,
|
||||||
|
// Use `mode` for `type` to keep backward compatibility
|
||||||
|
mode: this.type,
|
||||||
|
permissions: this.permissions,
|
||||||
|
uid: this.uid,
|
||||||
|
gid: this.gid
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return complete POSIX `mode` for node type + permissions. See:
|
||||||
|
// http://man7.org/linux/man-pages/man2/chmod.2.html
|
||||||
|
get mode() {
|
||||||
|
return generateMode(this.type, this.permissions);
|
||||||
|
}
|
||||||
|
// When setting the `mode` we assume permissions bits only (not changing type)
|
||||||
|
set mode(value) {
|
||||||
|
this.permissions = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports.create = function create(options, callback) {
|
||||||
// We expect both options.id and options.data to be provided/generated.
|
// We expect both options.id and options.data to be provided/generated.
|
||||||
ensureID(options, 'id', function(err) {
|
ensureID(options, 'id', function(err) {
|
||||||
if(err) {
|
if(err) {
|
||||||
callback(err);
|
return callback(err);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ensureID(options, 'data', function(err) {
|
ensureID(options, 'data', function(err) {
|
||||||
if(err) {
|
if(err) {
|
||||||
callback(err);
|
return callback(err);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
callback(null, new Node(options));
|
callback(null, new Node(options));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = Node;
|
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue