@vbarbarosh/node-helpers 3.66.1 → 3.68.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/README.md +2 -2
  2. package/dist/browser.js +603 -141
  3. package/package.json +3 -1
  4. package/src/HeartbeatServer.js +99 -0
  5. package/src/_finally.js +8 -0
  6. package/src/_finally.md +19 -0
  7. package/src/_perf.js +18 -0
  8. package/src/_perf.md +13 -0
  9. package/src/_restore.js +16 -0
  10. package/src/_restore.md +21 -0
  11. package/src/_try.js +11 -0
  12. package/src/_try.md +5 -0
  13. package/src/array_group.js +11 -7
  14. package/src/array_index.js +3 -1
  15. package/src/array_max.js +8 -14
  16. package/src/array_max.md +1 -0
  17. package/src/array_max.test.js +8 -0
  18. package/src/array_min.js +8 -14
  19. package/src/array_min.md +1 -0
  20. package/src/array_min.test.js +8 -0
  21. package/src/array_shuffle.md +5 -0
  22. package/src/array_sort.js +21 -12
  23. package/src/array_sort.test.js +78 -3
  24. package/src/array_sort_other.js +2 -2
  25. package/src/array_unique.js +9 -3
  26. package/src/array_unique.md +1 -0
  27. package/src/array_unique_last.js +7 -14
  28. package/src/array_unique_last.md +1 -0
  29. package/src/browser/index.js +15 -7
  30. package/src/countdown.js +1 -1
  31. package/src/fastdl.md +8 -0
  32. package/src/fcmp_default.js +12 -0
  33. package/src/fcmp_default_desc.js +12 -0
  34. package/src/fcmp_from_spec.js +2 -0
  35. package/src/fcmp_numbers.js +1 -1
  36. package/src/fcmp_tuples.js +15 -0
  37. package/src/fcmp_utf8_bin.js +13 -0
  38. package/src/fcmp_utf8_ci.js +7 -0
  39. package/src/fcmp_utf8_cs.js +7 -0
  40. package/src/fcmp_utf8_natural_ci.js +7 -0
  41. package/src/fcmp_utf8_natural_cs.js +7 -0
  42. package/src/fcmpx.js +120 -0
  43. package/src/fcmpx.md +17 -0
  44. package/src/fcmpx.test.js +173 -0
  45. package/src/filter1_from_spec.md +7 -0
  46. package/src/format_date_fs.js +13 -0
  47. package/src/format_date_human.js +13 -0
  48. package/src/format_hrtime.md +1 -0
  49. package/src/format_progress_bytes.js +6 -6
  50. package/src/format_progress_kilo.js +6 -6
  51. package/src/fs_fopen_update.md +5 -0
  52. package/src/fs_list.js +16 -0
  53. package/src/fs_list_deep.js +20 -0
  54. package/src/fs_ls.js +3 -0
  55. package/src/fs_path_extname.js +14 -0
  56. package/src/fs_path_extname.test.js +22 -0
  57. package/src/fs_size_enoent.md +3 -0
  58. package/src/fs_tempdir.md +6 -0
  59. package/src/now_atom.js +1 -1
  60. package/src/now_atom.md +1 -0
  61. package/src/now_fs.js +8 -0
  62. package/src/now_human.js +8 -0
  63. package/src/parallel.js +3 -3
  64. package/src/parallel.md +5 -0
  65. package/src/pid_exists.js +12 -0
  66. package/src/pid_kill_grace.js +28 -0
  67. package/src/ping_socket.js +32 -0
  68. package/src/ping_socket.md +3 -0
  69. package/src/plural.js +8 -2
  70. package/src/plural.test.js +5 -3
  71. package/src/sanitize_var_name.js +2 -2
  72. package/src/{camel_to_snake.js → str_camel_to_snake.js} +2 -2
  73. package/src/{camel_to_snake.test.js → str_camel_to_snake.test.js} +3 -3
  74. package/src/str_parse_kv.js +10 -0
  75. package/src/stream_chunk.md +1 -0
  76. package/src/stream_data_ln.md +1 -0
  77. package/src/stream_each.md +1 -0
  78. package/src/stream_filter.md +1 -0
  79. package/src/stream_lines.md +1 -0
  80. package/src/stream_map.md +1 -0
  81. package/src/stream_map_flatten.md +1 -0
  82. package/src/stream_map_parallel.md +1 -0
  83. package/src/stream_parse_csv.md +3 -0
  84. package/src/stream_progress.js +1 -1
  85. package/src/stream_progress.md +8 -0
  86. package/src/stream_skip.md +1 -0
  87. package/src/stream_strpbrk.md +1 -0
  88. package/src/stream_tap.md +1 -0
  89. package/src/stream_xml_analyze.md +1 -0
  90. package/src/stream_xml_parse.md +1 -0
  91. package/src/throttle.md +3 -0
  92. package/src/urlclean.js +1 -1
  93. package/src/urlclean.md +2 -0
  94. package/src/urlmod.md +7 -0
  95. package/src/user_friendly_status.md +4 -0
  96. package/src/wait_while.md +5 -0
  97. package/src/waitcb.md +12 -0
  98. package/src/fcmp_strings.js +0 -6
  99. /package/src/{stream-parse-csv.js → stream_parse_csv.js} +0 -0
@@ -0,0 +1,12 @@
1
+ function fcmp_default(a, b)
2
+ {
3
+ if (a < b) {
4
+ return -1;
5
+ }
6
+ if (a > b) {
7
+ return 1;
8
+ }
9
+ return 0;
10
+ }
11
+
12
+ module.exports = fcmp_default;
@@ -0,0 +1,12 @@
1
+ function fcmp_default_desc(b, a)
2
+ {
3
+ if (a < b) {
4
+ return -1;
5
+ }
6
+ if (a > b) {
7
+ return 1;
8
+ }
9
+ return 0;
10
+ }
11
+
12
+ module.exports = fcmp_default_desc;
@@ -1,6 +1,8 @@
1
1
  /**
2
2
  * Create fcmp from an array of props. For desc order a prop should be prefixed
3
3
  * with minus sign (e.g. -price).
4
+ *
5
+ * @deprecated Deprecated in favor of fcmpx
4
6
  */
5
7
  function fcmp_from_spec(props)
6
8
  {
@@ -1,6 +1,6 @@
1
1
  function fcmp_numbers(a, b)
2
2
  {
3
- return Number(a) - b;
3
+ return a - b;
4
4
  }
5
5
 
6
6
  module.exports = fcmp_numbers;
@@ -0,0 +1,15 @@
1
+ const fcmp_default = require('./fcmp_default');
2
+
3
+ function fcmp_tuples(a, b, fcmp = fcmp_default)
4
+ {
5
+ const end = Math.min(a.length, b.length);
6
+ for (let i = 0; i < end; ++i) {
7
+ const tmp = fcmp(a[i], b[i]);
8
+ if (tmp) {
9
+ return tmp;
10
+ }
11
+ }
12
+ return 0;
13
+ }
14
+
15
+ module.exports = fcmp_tuples;
@@ -0,0 +1,13 @@
1
+ // Strict binary (codepoint) comparison, like MySQL's utf8_bin collation
2
+ function fcmp_utf8_bin(a, b)
3
+ {
4
+ if (a < b) {
5
+ return -1;
6
+ }
7
+ if (a > b) {
8
+ return 1;
9
+ }
10
+ return 0;
11
+ }
12
+
13
+ module.exports = fcmp_utf8_bin;
@@ -0,0 +1,7 @@
1
+ // Locale-aware, case-insensitive, like utf8_ci or utf8mb4_ci
2
+ function fcmp_utf8_ci(a, b)
3
+ {
4
+ return a.localeCompare(b, undefined, {sensitivity: 'base'});
5
+ }
6
+
7
+ module.exports = fcmp_utf8_ci;
@@ -0,0 +1,7 @@
1
+ // Locale-aware, case-sensitive, like utf8_cs or utf8mb4_cs (but using system locale)
2
+ function fcmp_utf8_cs(a, b)
3
+ {
4
+ return a.localeCompare(b, undefined, {sensitivity: 'variant'});
5
+ }
6
+
7
+ module.exports = fcmp_utf8_cs;
@@ -0,0 +1,7 @@
1
+ // Natural sort (numeric), case-insensitive
2
+ function fcmp_utf8_natural_ci(a, b)
3
+ {
4
+ return a.localeCompare(b, undefined, {numeric: true, sensitivity: 'base'});
5
+ }
6
+
7
+ module.exports = fcmp_utf8_natural_ci;
@@ -0,0 +1,7 @@
1
+ // Natural sort (numeric), case-sensitive
2
+ function fcmp_utf8_natural_cs(a, b)
3
+ {
4
+ return a.localeCompare(b, undefined, {numeric: true, sensitivity: 'variant'});
5
+ }
6
+
7
+ module.exports = fcmp_utf8_natural_cs;
package/src/fcmpx.js ADDED
@@ -0,0 +1,120 @@
1
+ const fcmp_default = require('./fcmp_default');
2
+ const fcmp_default_desc = require('./fcmp_default_desc');
3
+ const identity = require('./identity');
4
+
5
+ const MISSING = Symbol('fcmpx.missing');
6
+
7
+ /**
8
+ * Comparator builder using compact expressions.
9
+ *
10
+ * fcmp expression — Creates an `fcmp` function from an expression, suitable for use with `[].sort()`.
11
+ *
12
+ * fcmpx('user.email')
13
+ * fcmpx('-user.age,user.email')
14
+ * fcmpx(v => v.user.email)
15
+ * fcmpx(['user.age', 'user.email'])
16
+ * fcmpx(['-user.age', 'user.email'])
17
+ */
18
+ function fcmpx(expr)
19
+ {
20
+ if (typeof expr === 'string') {
21
+ if (expr.includes(',')) {
22
+ return fcmpx(expr.split(','));
23
+ }
24
+ return fcmpx_compile(expr);
25
+ }
26
+ if (!Array.isArray(expr)) {
27
+ return fcmpx_compile(expr);
28
+ }
29
+ const fcmp_items = expr.map(fcmpx_parse);
30
+ return function (a, b) {
31
+ const tuple1 = fcmp_items.map(v => v.read(a));
32
+ const tuple2 = fcmp_items.map(v => v.read(b));
33
+ for (let i = 0; i < fcmp_items.length; ++i) {
34
+ const tmp = fcmp_items[i].fcmp(tuple1[i], tuple2[i]);
35
+ if (tmp) {
36
+ return tmp;
37
+ }
38
+ }
39
+ return 0;
40
+ };
41
+ }
42
+
43
+ function fcmpx_compile(expr)
44
+ {
45
+ const {read, fcmp} = fcmpx_parse(expr);
46
+ return function (a, b) {
47
+ return fcmp(read(a), read(b));
48
+ };
49
+ }
50
+
51
+ function fcmpx_parse(expr)
52
+ {
53
+ if (typeof expr === 'function') {
54
+ return {read: fcmpx_compile_read(expr), fcmp: fcmp_wrapper(fcmp_default)};
55
+ }
56
+ if (typeof expr === 'string') {
57
+ const desc = expr.startsWith('-');
58
+ const read = desc ? fcmpx_compile_read(expr.slice(1)) : fcmpx_compile_read(expr);
59
+ const fcmp = fcmp_wrapper(desc ? fcmp_default_desc : fcmp_default);
60
+ return {read, fcmp};
61
+ }
62
+ const fcmp_user = expr.fcmp;
63
+ const read = fcmpx_compile_read(expr.read);
64
+ const desc = expr.desc ?? false;
65
+ const fcmp = fcmp_wrapper(desc ? (fcmp_user ? (b, a) => fcmp_user(a, b) : fcmp_default_desc) : (fcmp_user ?? fcmp_default));
66
+ if (Array.isArray(expr.top)) {
67
+ const priority = new Map(expr.top.map((v, i) => [v, i]));
68
+ return {read: v => priority.get(read(v)) ?? priority.size, fcmp};
69
+ }
70
+ if (Array.isArray(expr.bottom)) {
71
+ const priority = new Map(expr.bottom.map((v, i) => [v, i]));
72
+ return {read: v => priority.get(read(v)) ?? -1, fcmp};
73
+ }
74
+ return {read, fcmp};
75
+ }
76
+
77
+ function fcmpx_compile_read(read)
78
+ {
79
+ if (read === undefined || read === '' || read === '.') {
80
+ return identity;
81
+ }
82
+ if (typeof read === 'function') {
83
+ return function (value) {
84
+ return read(value) ?? MISSING;
85
+ };
86
+ }
87
+ if (typeof read === 'string') {
88
+ const props = read.split('.');
89
+ const [a, b, c, d, e] = props;
90
+ switch (props.length) {
91
+ case 1: return v => v?.[a] ?? MISSING;
92
+ case 2: return v => v?.[a]?.[b] ?? MISSING;
93
+ case 3: return v => v?.[a]?.[b]?.[c] ?? MISSING;
94
+ case 4: return v => v?.[a]?.[b]?.[c]?.[d] ?? MISSING;
95
+ case 5: return v => v?.[a]?.[b]?.[c]?.[d]?.[e] ?? MISSING;
96
+ default: return v => props.reduce((a,p) => a?.[p], v) ?? MISSING;
97
+ }
98
+ }
99
+ return v => v?.[read] ?? MISSING;
100
+ }
101
+
102
+ // Sorting rule: Missing values are considered greater than any defined value
103
+ // and are therefore placed after all defined values.
104
+ function fcmp_wrapper(fcmp)
105
+ {
106
+ return function (a, b) {
107
+ if (a === MISSING) {
108
+ if (b === MISSING) {
109
+ return 0;
110
+ }
111
+ return 1;
112
+ }
113
+ if (b === MISSING) {
114
+ return -1;
115
+ }
116
+ return fcmp(a, b);
117
+ };
118
+ }
119
+
120
+ module.exports = fcmpx;
package/src/fcmpx.md ADDED
@@ -0,0 +1,17 @@
1
+ Comparator builder using compact expressions.
2
+
3
+ fcmp expression — Creates an `fcmp` function from an expression, suitable for use with `[].sort()`.
4
+
5
+ ```js
6
+ fcmpx('user.email')
7
+ fcmpx('-user.age,user.email')
8
+ fcmpx(v => v.user.email)
9
+ fcmpx(['user.age', 'user.email'])
10
+ fcmpx(['-user.age', 'user.email'])
11
+ ```
12
+
13
+ Sorting rule: Missing values are considered greater than any defined value
14
+ and are therefore placed after all defined values.
15
+
16
+ - YouTube: [Сортировка объектов в JavaScript](https://www.youtube.com/watch?v=rHEH_JfQjL4)
17
+ - https://github.com/vbarbarosh/speaking/tree/master/20250822-js-sort-fcmpx
@@ -0,0 +1,173 @@
1
+ const assert = require('assert');
2
+ const fcmpx = require('./fcmpx');
3
+
4
+ const users = [
5
+ {id: 'fred|48|100', user: {name: 'fred', age: 48, balance: 100}},
6
+ {id: 'barney|36|-200', user: {name: 'barney', age: 36, balance: -200}},
7
+ {id: 'wilma|-|-', user: {name: 'wilma', tag: {short: 'foo', long: 'foofoo'}}},
8
+ {id: 'betty|32|-', user: {name: 'betty', tag: {short: 'bar', long: 'barbar'}, age: 32}},
9
+ ];
10
+
11
+ // https://dummyjson.com/docs/products
12
+
13
+ const products = [
14
+ {id: 1, name: 'Yoga Mat', category: 'Yoga', price: 25.99, brand: 'BalanceFrom', rating: 4.6, discount: 0.1, availability: 'in_stock'},
15
+ {id: 2, name: 'Adjustable Dumbbells', category: 'Strength', price: 199.99, brand: 'Bowflex', rating: 4.8, availability: 'preorder'},
16
+ {id: 3, name: 'Resistance Bands Set', category: 'Strength', price: 32.5, brand: 'Fit Simplify', rating: 4.5, discount: 0.15, availability: 'in_stock'},
17
+ {id: 4, name: 'Treadmill', category: 'Cardio', price: 899, brand: 'NordicTrack', rating: 4.7, availability: 'out_of_stock'},
18
+ {id: 5, name: 'Kettlebell 20lb', category: 'Strength', price: 45, brand: 'CAP Barbell', rating: 4.4, discount: 0.05, availability: 'in_stock'},
19
+ {id: 6, name: 'Foam Roller', category: 'Recovery', price: 19.99, brand: 'TriggerPoint', rating: 4.6, availability: 'limited'},
20
+ {id: 7, name: 'Exercise Bike', category: 'Cardio', price: 599, brand: 'Peloton', rating: 4.9, discount: 0.2, availability: 'in_stock'},
21
+ {id: 8, name: 'Pull-Up Bar', category: 'Strength', price: 29.95, brand: 'Iron Gym', rating: 4.3, availability: 'in_stock'},
22
+ {id: 9, name: 'Jump Rope', category: 'Cardio', price: 12.99, brand: 'WOD Nation', rating: 4.2, discount: 0.25, availability: 'limited'},
23
+ {id: 10, name: 'Ab Roller', category: 'Core', price: 21.49, brand: 'Perfect Fitness', rating: 4.5, availability: 'in_stock'},
24
+ ];
25
+
26
+ const tests = [
27
+ {
28
+ description: 'missing: should move undefined values to the bottom',
29
+ items: [
30
+ {id: 'user1', user: {age: 48}},
31
+ {id: 'user2', user: {age: 36}},
32
+ {id: 'user3'},
33
+ {id: 'user4', user: {age: 32}},
34
+ ],
35
+ extract: v => v.id,
36
+ expressions: [
37
+ 'user.age',
38
+ v => v.user?.age,
39
+ {read: 'user.age'},
40
+ {read: v => v.user?.age},
41
+ ['user.age'],
42
+ [v => v.user?.age],
43
+ [{read: 'user.age'}],
44
+ [{read: v => v.user?.age}],
45
+ ],
46
+ expected: ['user4', 'user2', 'user1', 'user3'],
47
+ },
48
+ {
49
+ description: 'missing: should move undefined values to the bottom #2',
50
+ items: [
51
+ {id: 'user1', user: {age: 48}},
52
+ {id: 'user2', user: {age: 36}},
53
+ {id: 'user3', user: {tag: {short: 'foo'}}},
54
+ {id: 'user4', user: {tag: {short: 'bar'}, age: 32}},
55
+ ],
56
+ extract: v => v.id,
57
+ expressions: [
58
+ 'user.age,user.tag.short',
59
+ ['user.age', 'user.tag.short'],
60
+ [v => v.user.age, v => v.user.tag?.short],
61
+ [{read: 'user.age'}, {read: 'user.tag.short'}],
62
+ [{read: v => v.user.age}, {read: v => v.user.tag?.short}],
63
+ ],
64
+ expected: ['user4', 'user2', 'user1', 'user3'],
65
+ },
66
+ {
67
+ description: 'missing: should move undefined values to the bottom and keep their initial order',
68
+ items: [
69
+ {id: 'user1', user: {balance: 100}},
70
+ {id: 'user2', user: {balance: -200}},
71
+ {id: 'user3'},
72
+ {id: 'user4'},
73
+ ],
74
+ extract: v => v.id,
75
+ expressions: [
76
+ 'user.balance',
77
+ v => v.user?.balance,
78
+ [v => v.user?.balance],
79
+ ],
80
+ expected: ['user2', 'user1', 'user3', 'user4'],
81
+ },
82
+ {
83
+ description: 'move undefined value to the bottom, keep their initial order',
84
+ items: users,
85
+ extract: v => v.id,
86
+ expressions: [
87
+ 'user.balance,user.age',
88
+ [v => v.user.balance, v => v.user.age],
89
+ ],
90
+ expected: ['barney|36|-200', 'fred|48|100', 'betty|32|-', 'wilma|-|-'],
91
+ },
92
+ {
93
+ description: 'move undefined value to the bottom, keep their initial order',
94
+ items: users,
95
+ extract: v => v.id,
96
+ expressions: [
97
+ 'user.balance,user.age',
98
+ [v => v.user.balance, v => v.user.age],
99
+ ],
100
+ expected: ['barney|36|-200', 'fred|48|100', 'betty|32|-', 'wilma|-|-'],
101
+ },
102
+ {
103
+ description: 'user.tag.short',
104
+ items: [
105
+ {user: {name: 'fred', age: 48}},
106
+ {user: {name: 'barney', age: 36}},
107
+ {user: {name: 'wilma', tag: {short: 'foo'}}},
108
+ {user: {name: 'betty', tag: {short: 'bar'}, age: 32}},
109
+ ],
110
+ extract: v => `${v.user.name}|${v.user.tag?.short ?? '-'}`,
111
+ expressions: [
112
+ 'user.tag.short',
113
+ v => v.user.tag?.short,
114
+ [v => v.user.tag?.short],
115
+ ],
116
+ expected: ['betty|bar', 'wilma|foo', 'fred|-', 'barney|-'],
117
+ },
118
+ {
119
+ description: 'user.tag.short,user.age',
120
+ items: [
121
+ {user: {name: 'fred', age: 48}},
122
+ {user: {name: 'barney', age: 36}},
123
+ {user: {name: 'wilma', tag: {short: 'foo'}}},
124
+ {user: {name: 'betty', tag: {short: 'bar'}, age: 32}},
125
+ ],
126
+ extract: v => `${v.user.name}|${v.user.tag?.short ?? ''}|${v.user.age ?? ''}`,
127
+ expressions: [
128
+ 'user.tag.short,user.age',
129
+ ['user.tag.short', 'user.age'],
130
+ [v => v.user.tag?.short, v => v.user.age],
131
+ ],
132
+ expected: ['betty|bar|32', 'wilma|foo|', 'barney||36', 'fred||48'],
133
+ },
134
+ {
135
+ description: 'top: move specified values to the top',
136
+ items: [
137
+ {id: 'task1', priority: 'normal'},
138
+ {id: 'task2', priority: 'low'},
139
+ {id: 'task3', priority: 'high'},
140
+ {id: 'task4', priority: 'normal'},
141
+ ],
142
+ extract: v => `${v.id}:${v.priority}`,
143
+ expressions: [
144
+ {read: 'priority', top: ['high']},
145
+ ],
146
+ expected: ['task3:high', 'task1:normal', 'task2:low', 'task4:normal'],
147
+ },
148
+ {
149
+ description: 'top: move specified values to the top #2',
150
+ items: [
151
+ {id: 'task1', priority: 'normal'},
152
+ {id: 'task2', priority: 'low'},
153
+ {id: 'task3', priority: 'high'},
154
+ {id: 'task4', priority: 'normal'},
155
+ ],
156
+ extract: v => `${v.id}:${v.priority}`,
157
+ expressions: [
158
+ {read: 'priority', top: ['high', 'normal', 'low']},
159
+ ],
160
+ expected: ['task3:high', 'task1:normal', 'task4:normal', 'task2:low'],
161
+ },
162
+ ];
163
+
164
+ describe('fcmpx', function () {
165
+ tests.forEach(function (test) {
166
+ test.expressions.forEach(function (expr, i) {
167
+ it(`${test.description}: #${i + 1} | ${test.description}`, function () {
168
+ const actual = test.items.slice().sort(fcmpx(expr)).map(test.extract);
169
+ assert.deepStrictEqual(actual, test.expected);
170
+ });
171
+ });
172
+ });
173
+ });
@@ -0,0 +1,7 @@
1
+ Creates a filtering function based on the following spec:
2
+
3
+ 1. Substrings are separated by `/`.
4
+ 2. Each substring may have special characters:
5
+ - `^`: Substring must appear at the beginning.
6
+ - `$`: Substring must appear at the end.
7
+ - `!`: Negates the condition (substring must not appear).
@@ -0,0 +1,13 @@
1
+ function format_date_fs(d)
2
+ {
3
+ const ymd = d.getFullYear() + dd(d.getMonth() + 1) + dd(d.getDate());
4
+ const hms = dd(d.getHours()) + dd(d.getMinutes()) + dd(d.getSeconds());
5
+ return ymd + '_' + hms;
6
+ }
7
+
8
+ function dd(v)
9
+ {
10
+ return v > 9 ? `${v}` : `0${v}`;
11
+ }
12
+
13
+ module.exports = format_date_fs;
@@ -0,0 +1,13 @@
1
+ function format_date_human(d)
2
+ {
3
+ const ymd = d.getFullYear() + '/' + dd(d.getMonth() + 1) + '/' + dd(d.getDate());
4
+ const hms = dd(d.getHours()) + ':' + dd(d.getMinutes()) + ':' + dd(d.getSeconds());
5
+ return ymd + ' ' + hms;
6
+ }
7
+
8
+ function dd(v)
9
+ {
10
+ return v > 9 ? `${v}` : `0${v}`;
11
+ }
12
+
13
+ module.exports = format_date_human;
@@ -0,0 +1 @@
1
+ Return a human-readable representation of hrtime (the return value from `process.hrtime(hrtime0)`).
@@ -1,22 +1,22 @@
1
1
  const format_bytes = require('./format_bytes');
2
2
  const format_percents = require('./format_percents');
3
3
  const format_seconds = require('./format_seconds');
4
- const is_number_gt = require('@vbarbarosh/type-helpers/src/is_number_gt');
4
+ const is_num_gt = require('@vbarbarosh/type-helpers/src/is_num_gt');
5
5
 
6
6
  function format_progress_bytes({percents, total, done, rate, eta, duration})
7
7
  {
8
- const bps = is_number_gt(rate, 0) ? `${format_bytes(rate)}/s` : '~';
8
+ const bps = is_num_gt(rate, 0) ? `${format_bytes(rate)}/s` : '~';
9
9
  if (done > total) {
10
10
  return `${format_bytes(done)} at ${bps} duration=${format_seconds(duration)}`;
11
11
  }
12
- if (is_number_gt(total, 0)) {
13
- const eta_str = is_number_gt(eta, 0) ? format_seconds(eta) : '~';
12
+ if (is_num_gt(total, 0)) {
13
+ const eta_str = is_num_gt(eta, 0) ? format_seconds(eta) : '~';
14
14
  return `${format_percents(percents)} | ${format_bytes(done)} of ${format_bytes(total)} at ${bps} ETA ${eta_str} duration=${format_seconds(duration)}`;
15
15
  }
16
- if (is_number_gt(done, 0)) {
16
+ if (is_num_gt(done, 0)) {
17
17
  return `${format_bytes(done)} of ~ at ${bps} duration=${format_seconds(duration)}`;
18
18
  }
19
- if (is_number_gt(duration, 0)) {
19
+ if (is_num_gt(duration, 0)) {
20
20
  return `~ duration=${format_seconds(duration)}`;
21
21
  }
22
22
  return '~';
@@ -1,22 +1,22 @@
1
1
  const format_kilo = require('./format_kilo');
2
2
  const format_percents = require('./format_percents');
3
3
  const format_seconds = require('./format_seconds');
4
- const is_number_gt = require('@vbarbarosh/type-helpers/src/is_number_gt');
4
+ const is_num_gt = require('@vbarbarosh/type-helpers/src/is_num_gt');
5
5
 
6
6
  function format_progress_kilo({percents, total, done, rate, eta, duration})
7
7
  {
8
- const speed = is_number_gt(rate, 0) ? `${format_kilo(rate)}/s` : '~';
8
+ const speed = is_num_gt(rate, 0) ? `${format_kilo(rate)}/s` : '~';
9
9
  if (done > total) {
10
10
  return `${format_kilo(done)} at ${speed} duration=${format_seconds(duration)}`;
11
11
  }
12
- if (is_number_gt(total, 0)) {
13
- const eta_str = is_number_gt(eta, 0) ? format_seconds(eta) : '~';
12
+ if (is_num_gt(total, 0)) {
13
+ const eta_str = is_num_gt(eta, 0) ? format_seconds(eta) : '~';
14
14
  return `${format_percents(percents)} | ${format_kilo(done)} of ${format_kilo(total)} at ${speed} ETA ${eta_str} duration=${format_seconds(duration)}`;
15
15
  }
16
- if (is_number_gt(done, 0)) {
16
+ if (is_num_gt(done, 0)) {
17
17
  return `${format_kilo(done)} of ~ at ${speed} duration=${format_seconds(duration)}`;
18
18
  }
19
- if (is_number_gt(duration, 0)) {
19
+ if (is_num_gt(duration, 0)) {
20
20
  return `~ duration=${format_seconds(duration)}`;
21
21
  }
22
22
  return '~';
@@ -0,0 +1,5 @@
1
+ The main use case is uploading files in chunks.
2
+
3
+ Each time a chunk is uploaded, update the target file.
4
+
5
+ By the end, you will have a ready-to-use file.
package/src/fs_list.js ADDED
@@ -0,0 +1,16 @@
1
+ const Promise = require('bluebird');
2
+ const fs_fi = require('./fs_fi');
3
+ const fs_path_join = require('./fs_path_join');
4
+ const fs_readdir = require('./fs_readdir');
5
+
6
+ async function fs_list(pathname = '.')
7
+ {
8
+ const fi = await fs_fi(pathname);
9
+ if (fi.isDirectory()) {
10
+ const names = await fs_readdir(pathname);
11
+ return Promise.all(names.map(v => fs_fi(fs_path_join(pathname, v))));
12
+ }
13
+ return [fi];
14
+ }
15
+
16
+ module.exports = fs_list;
@@ -0,0 +1,20 @@
1
+ const fs_fi = require('./fs_fi');
2
+ const fs_path_join = require('./fs_path_join');
3
+ const fs_readdir = require('./fs_readdir');
4
+
5
+ async function fs_list_deep(path = '.')
6
+ {
7
+ const out = [];
8
+ const queue = [path];
9
+ while (queue.length) {
10
+ const fi = await fs_fi(queue.pop());
11
+ out.push(fi);
12
+ if (fi.isDirectory()) {
13
+ const names = await fs_readdir(fi.pathname);
14
+ queue.push(...names.map(v => fs_path_join(fi.pathname, v)));
15
+ }
16
+ }
17
+ return out;
18
+ }
19
+
20
+ module.exports = fs_list_deep;
package/src/fs_ls.js CHANGED
@@ -3,6 +3,9 @@ const fs_fi = require('./fs_fi');
3
3
  const fs_path_join = require('./fs_path_join');
4
4
  const fs_readdir = require('./fs_readdir');
5
5
 
6
+ /**
7
+ * @deprecated Deprecated in favor or fs_list
8
+ */
6
9
  async function fs_ls(pathname = '.')
7
10
  {
8
11
  const fi = await fs_fi(pathname);
@@ -0,0 +1,14 @@
1
+ const path = require('path');
2
+
3
+ function fs_path_extname(p)
4
+ {
5
+ return path.extname(p);
6
+ }
7
+
8
+ // function extname(s)
9
+ // {
10
+ // const i = s.lastIndexOf('.');
11
+ // return (i > 0) ? s.slice(i) : null;
12
+ // }
13
+
14
+ module.exports = fs_path_extname;
@@ -0,0 +1,22 @@
1
+ const assert = require('assert');
2
+ const fs_path_extname = require('./fs_path_extname');
3
+
4
+ const tests = [
5
+ ['.', ''],
6
+ ['..', ''],
7
+ ['.foo', ''],
8
+ ['..foo', '.foo'],
9
+ ['.foo.bar', '.bar'],
10
+ ['fs_path_extname.js', '.js'],
11
+ ['/path/to/file.js', '.js'],
12
+ ['/path/to/file.js/foo', ''],
13
+ ];
14
+
15
+ describe('fs_path_extname', function () {
16
+ tests.forEach(function ([input, expected]) {
17
+ it(`${input} -> ${expected}`, function () {
18
+ const actual = fs_path_extname(input);
19
+ assert.strictEqual(actual, expected);
20
+ });
21
+ });
22
+ });
@@ -0,0 +1,3 @@
1
+ Returns the size of a file, or 0 if the file is not present.
2
+
3
+ It was created for cases when you need to resume a download.
@@ -0,0 +1,6 @@
1
+ ```js
2
+ await fs_tempdir(async function (d) {
3
+ await fs_write(`${d}/input.txt`, 'hello');
4
+ await shell(['ls', '-alh'], {cwd: d});
5
+ });
6
+ ```
package/src/now_atom.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Return current date in ATOM format (e.g. "2021-07-17T23:27:01.030Z")
2
+ * Return the current date in ATOM format (e.g. "2021-07-17T23:27:01.030Z")
3
3
  *
4
4
  * @returns {string}
5
5
  * @link https://www.php.net/manual/en/class.datetimeinterface.php#datetime.constants.atom
@@ -0,0 +1 @@
1
+ Return the current date in ATOM format (e.g. "2021-07-17T23:27:01.030Z")
package/src/now_fs.js ADDED
@@ -0,0 +1,8 @@
1
+ const format_date_fs = require('./format_date_fs');
2
+
3
+ function now_fs()
4
+ {
5
+ return format_date_fs(new Date());
6
+ }
7
+
8
+ module.exports = now_fs;
@@ -0,0 +1,8 @@
1
+ const format_date_human = require('./format_date_human');
2
+
3
+ function now_human()
4
+ {
5
+ return format_date_human(new Date());
6
+ }
7
+
8
+ module.exports = now_human;