atomic-fns 1.2.0 → 2.0.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.
- package/README.md +397 -91
- package/dist/array/index.d.ts +9 -0
- package/dist/array/index.js +9 -0
- package/dist/array/list.d.ts +99 -0
- package/dist/array/list.js +133 -0
- package/dist/array/sets.d.ts +130 -0
- package/dist/array/sets.js +213 -0
- package/dist/async/index.d.ts +6 -0
- package/dist/async/index.js +6 -0
- package/dist/async/retry.d.ts +38 -0
- package/dist/async/retry.js +56 -0
- package/dist/async/sleep.d.ts +42 -0
- package/dist/async/sleep.js +76 -0
- package/dist/collections/bisect.d.ts +103 -0
- package/dist/collections/bisect.js +157 -0
- package/dist/collections/deque.d.ts +67 -97
- package/dist/collections/deque.js +197 -256
- package/dist/collections/entries.d.ts +89 -0
- package/dist/collections/entries.js +131 -0
- package/dist/collections/frozen-set.d.ts +42 -0
- package/dist/collections/frozen-set.js +64 -0
- package/dist/collections/group.d.ts +141 -0
- package/dist/collections/group.js +241 -0
- package/dist/collections/heap.d.ts +64 -0
- package/dist/collections/heap.js +205 -0
- package/dist/collections/index.d.ts +15 -623
- package/dist/collections/index.js +14 -844
- package/dist/collections/internal/arrayKeys.d.ts +19 -0
- package/dist/collections/internal/arrayKeys.js +83 -0
- package/dist/collections/internal/invariants.d.ts +4 -0
- package/dist/collections/internal/invariants.js +12 -0
- package/dist/collections/internal/iterable.d.ts +25 -0
- package/dist/collections/internal/iterable.js +62 -0
- package/dist/collections/iterate.d.ts +162 -0
- package/dist/collections/iterate.js +188 -0
- package/dist/collections/lru-cache.d.ts +58 -0
- package/dist/collections/lru-cache.js +164 -0
- package/dist/collections/order.d.ts +19 -0
- package/dist/collections/order.js +38 -0
- package/dist/collections/shape.d.ts +32 -0
- package/dist/collections/shape.js +86 -0
- package/dist/collections/sorted-map.d.ts +171 -0
- package/dist/collections/sorted-map.js +845 -0
- package/dist/collections/sorted-set.d.ts +103 -0
- package/dist/collections/sorted-set.js +147 -0
- package/dist/collections/splay-tree.d.ts +82 -0
- package/dist/collections/splay-tree.js +354 -0
- package/dist/collections/trie.d.ts +58 -0
- package/dist/collections/trie.js +196 -0
- package/dist/decimal/currency.d.ts +211 -0
- package/dist/decimal/currency.js +321 -0
- package/dist/decimal/decimal.d.ts +299 -0
- package/dist/decimal/decimal.js +660 -0
- package/dist/decimal/index.d.ts +11 -148
- package/dist/decimal/index.js +10 -410
- package/dist/decimal/internal/bits.d.ts +2 -0
- package/dist/decimal/internal/bits.js +7 -0
- package/dist/decimal/internal/echo.d.ts +4 -0
- package/dist/decimal/internal/echo.js +6 -0
- package/dist/decimal/internal/isqrt.d.ts +9 -0
- package/dist/decimal/internal/isqrt.js +34 -0
- package/dist/decimal/internal/locale.d.ts +7 -0
- package/dist/decimal/internal/locale.js +24 -0
- package/dist/decimal/internal/minor-units.d.ts +2 -0
- package/dist/decimal/internal/minor-units.js +33 -0
- package/dist/decimal/internal/pow10.d.ts +24 -0
- package/dist/decimal/internal/pow10.js +96 -0
- package/dist/encoding/base64.d.ts +39 -0
- package/dist/encoding/base64.js +53 -0
- package/dist/encoding/hex.d.ts +23 -0
- package/dist/encoding/hex.js +34 -0
- package/dist/encoding/index.d.ts +6 -0
- package/dist/encoding/index.js +6 -0
- package/dist/function/debounce.d.ts +68 -0
- package/dist/function/debounce.js +203 -0
- package/dist/function/index.d.ts +6 -0
- package/dist/function/index.js +6 -0
- package/dist/function/shared.d.ts +2 -0
- package/dist/function/shared.js +5 -0
- package/dist/function/wrap.d.ts +130 -0
- package/dist/function/wrap.js +113 -0
- package/dist/hash/digest.d.ts +18 -0
- package/dist/hash/digest.js +26 -0
- package/dist/hash/hash.d.ts +22 -0
- package/dist/hash/hash.js +252 -0
- package/dist/hash/hashString.d.ts +13 -0
- package/dist/hash/hashString.js +53 -0
- package/dist/hash/index.d.ts +8 -0
- package/dist/hash/index.js +8 -0
- package/dist/hash/stableStringify.d.ts +19 -0
- package/dist/hash/stableStringify.js +102 -0
- package/dist/id/index.d.ts +7 -0
- package/dist/id/index.js +7 -0
- package/dist/id/nanoid.d.ts +16 -0
- package/dist/id/nanoid.js +65 -0
- package/dist/id/uniqueId.d.ts +14 -0
- package/dist/id/uniqueId.js +20 -0
- package/dist/id/uuid.d.ts +25 -0
- package/dist/id/uuid.js +64 -0
- package/dist/index.d.ts +15 -8
- package/dist/index.js +15 -8
- package/dist/internal/bytes.d.ts +11 -0
- package/dist/internal/bytes.js +33 -0
- package/dist/internal/codec.d.ts +11 -0
- package/dist/internal/codec.js +84 -0
- package/dist/internal/compare.d.ts +10 -0
- package/dist/internal/compare.js +26 -0
- package/dist/internal/crypto.d.ts +5 -0
- package/dist/internal/crypto.js +11 -0
- package/dist/internal/keys.d.ts +12 -0
- package/dist/internal/keys.js +23 -0
- package/dist/internal/kind.d.ts +44 -0
- package/dist/internal/kind.js +139 -0
- package/dist/internal/sparse.d.ts +51 -0
- package/dist/internal/sparse.js +104 -0
- package/dist/internal/text.d.ts +7 -0
- package/dist/internal/text.js +15 -0
- package/dist/internal/timer.d.ts +10 -0
- package/dist/internal/timer.js +17 -0
- package/dist/internal/truncate.d.ts +10 -0
- package/dist/internal/truncate.js +22 -0
- package/dist/internal/walk.d.ts +47 -0
- package/dist/internal/walk.js +100 -0
- package/dist/is/index.d.ts +8 -0
- package/dist/is/index.js +8 -0
- package/dist/is/isArrayLike.d.ts +18 -0
- package/dist/is/isArrayLike.js +25 -0
- package/dist/is/isError.d.ts +13 -0
- package/dist/is/isError.js +18 -0
- package/dist/is/isNil.d.ts +12 -0
- package/dist/is/isNil.js +14 -0
- package/dist/is/isPlainObject.d.ts +15 -0
- package/dist/is/isPlainObject.js +34 -0
- package/dist/number/index.d.ts +7 -0
- package/dist/number/index.js +7 -0
- package/dist/number/round.d.ts +86 -0
- package/dist/number/round.js +130 -0
- package/dist/number/stats.d.ts +52 -0
- package/dist/number/stats.js +115 -0
- package/dist/object/cloneDeep.d.ts +28 -0
- package/dist/object/cloneDeep.js +200 -0
- package/dist/object/index.d.ts +14 -0
- package/dist/object/index.js +13 -0
- package/dist/object/isEqual.d.ts +64 -0
- package/dist/object/isEqual.js +289 -0
- package/dist/object/matches.d.ts +40 -0
- package/dist/object/matches.js +284 -0
- package/dist/object/merge.d.ts +66 -0
- package/dist/object/merge.js +272 -0
- package/dist/object/parsePath.d.ts +22 -0
- package/dist/object/parsePath.js +111 -0
- package/dist/object/path.d.ts +85 -0
- package/dist/object/path.js +208 -0
- package/dist/object/pick.d.ts +91 -0
- package/dist/object/pick.js +180 -0
- package/dist/object/reshape.d.ts +73 -0
- package/dist/object/reshape.js +144 -0
- package/dist/random/index.d.ts +96 -32
- package/dist/random/index.js +215 -53
- package/dist/string/case.d.ts +71 -0
- package/dist/string/case.js +152 -0
- package/dist/string/fold.d.ts +35 -0
- package/dist/string/fold.js +100 -0
- package/dist/string/index.d.ts +6 -187
- package/dist/string/index.js +6 -224
- package/dist/string/truncate.d.ts +25 -0
- package/dist/string/truncate.js +44 -0
- package/dist/string/words.d.ts +27 -0
- package/dist/string/words.js +241 -0
- package/package.json +99 -51
- package/dist/collections/BTree.d.ts +0 -230
- package/dist/collections/BTree.js +0 -1143
- package/dist/collections/Heap.d.ts +0 -69
- package/dist/collections/Heap.js +0 -181
- package/dist/collections/LRUCache.d.ts +0 -83
- package/dist/collections/LRUCache.js +0 -139
- package/dist/collections/SortedMap.d.ts +0 -92
- package/dist/collections/SortedMap.js +0 -131
- package/dist/collections/SortedSet.d.ts +0 -69
- package/dist/collections/SortedSet.js +0 -99
- package/dist/collections/SortedTree.d.ts +0 -103
- package/dist/collections/SortedTree.js +0 -142
- package/dist/collections/SplayTree.d.ts +0 -107
- package/dist/collections/SplayTree.js +0 -351
- package/dist/collections/Trie.d.ts +0 -42
- package/dist/collections/Trie.js +0 -104
- package/dist/collections/abc.d.ts +0 -152
- package/dist/collections/abc.js +0 -39
- package/dist/collections/frozenset.d.ts +0 -19
- package/dist/collections/frozenset.js +0 -33
- package/dist/functools/index.d.ts +0 -106
- package/dist/functools/index.js +0 -203
- package/dist/globals/index.d.ts +0 -268
- package/dist/globals/index.js +0 -447
- package/dist/i18n/currency.d.ts +0 -27
- package/dist/i18n/currency.js +0 -100
- package/dist/i18n/date/index.d.ts +0 -210
- package/dist/i18n/date/index.js +0 -552
- package/dist/i18n/date/utils.d.ts +0 -64
- package/dist/i18n/date/utils.js +0 -192
- package/dist/i18n/duration.d.ts +0 -113
- package/dist/i18n/duration.js +0 -398
- package/dist/i18n/format.d.ts +0 -34
- package/dist/i18n/format.js +0 -378
- package/dist/i18n/index.d.ts +0 -9
- package/dist/i18n/index.js +0 -8
- package/dist/itertools/index.d.ts +0 -251
- package/dist/itertools/index.js +0 -434
- package/dist/math/index.d.ts +0 -79
- package/dist/math/index.js +0 -81
- package/dist/mod.d.ts +0 -1
- package/dist/mod.js +0 -1
- package/dist/operators/index.d.ts +0 -148
- package/dist/operators/index.js +0 -325
- package/dist/types.d.ts +0 -19
- package/dist/types.js +0 -1
package/dist/string/index.js
CHANGED
|
@@ -1,227 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
2
|
+
* String helpers: `words`, the case converters `camelCase`, `pascalCase`, `snakeCase`,
|
|
3
|
+
* `kebabCase`, `startCase` and `capitalize`, and `deburr`, `slugify` and `truncate`.
|
|
4
4
|
* @module string
|
|
5
5
|
*/
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
* @returns {string} Returns the sentence string.
|
|
11
|
-
* @example
|
|
12
|
-
```js
|
|
13
|
-
sentenceCase('a test')
|
|
14
|
-
// 'A test'
|
|
15
|
-
|
|
16
|
-
sentenceCase('TEST')
|
|
17
|
-
// 'TEST'
|
|
18
|
-
```
|
|
19
|
-
* @see {@link capitalize}
|
|
20
|
-
*/
|
|
21
|
-
export function sentenceCase(s) {
|
|
22
|
-
return `${(s[0] || '').toLocaleUpperCase()}${s.substring(1) || ''}`;
|
|
23
|
-
}
|
|
24
|
-
/**
|
|
25
|
-
* Converts the first character of the given string to upper case and the rest to lower case.
|
|
26
|
-
* @param {string} s The string to capitalize
|
|
27
|
-
* @returns {string} Returns the capitalized string.
|
|
28
|
-
* @example
|
|
29
|
-
```js
|
|
30
|
-
capitalize('TEST')
|
|
31
|
-
// 'Test'
|
|
32
|
-
```
|
|
33
|
-
* @see {@link titleCase}
|
|
34
|
-
*/
|
|
35
|
-
export function capitalize(s) {
|
|
36
|
-
return `${(s[0] || '').toLocaleUpperCase()}${(s.substring(1) || '').toLocaleLowerCase()}`;
|
|
37
|
-
}
|
|
38
|
-
/**
|
|
39
|
-
* Split a string into an array of its words.
|
|
40
|
-
* @param {string} s The string to split
|
|
41
|
-
* @param {RegExp | string} [pattern] The separator to split on, instead of matching words
|
|
42
|
-
* @returns {string[]} Returns the words array.
|
|
43
|
-
* @example
|
|
44
|
-
```js
|
|
45
|
-
words('lord of the rings')
|
|
46
|
-
// ['lord', 'of', 'the', 'rings']
|
|
47
|
-
```
|
|
48
|
-
*/
|
|
49
|
-
export function words(s, pattern) {
|
|
50
|
-
if (pattern != null)
|
|
51
|
-
return s.split(pattern);
|
|
52
|
-
return s.match(WORDS_REGEX) || [];
|
|
53
|
-
}
|
|
54
|
-
/**
|
|
55
|
-
* Create a new string which is the concatenation of the words in the given input.
|
|
56
|
-
* @param {string} s The string to join
|
|
57
|
-
* @param {string} [sep=' '] The separator to use
|
|
58
|
-
* @returns {string} Returns the joined string.
|
|
59
|
-
* @example
|
|
60
|
-
```js
|
|
61
|
-
join('Lord Of The Rings', '-')
|
|
62
|
-
// 'Lord-Of-The-Rings'
|
|
63
|
-
```
|
|
64
|
-
*/
|
|
65
|
-
export function join(s, sep = ' ') {
|
|
66
|
-
return words(s).join(sep);
|
|
67
|
-
}
|
|
68
|
-
/**
|
|
69
|
-
* Checks if a string contains only alphanumeric characters.
|
|
70
|
-
* @param {string} s The string
|
|
71
|
-
* @returns {boolean} Returns `true` if the string contains only alphanumeric.
|
|
72
|
-
* @example
|
|
73
|
-
```js
|
|
74
|
-
isAlnum('alpha1234')
|
|
75
|
-
// true
|
|
76
|
-
|
|
77
|
-
isAlnum('oops!')
|
|
78
|
-
// false
|
|
79
|
-
```
|
|
80
|
-
*/
|
|
81
|
-
export function isAlnum(s) {
|
|
82
|
-
return /^[\p{L}\p{N}]+$/gu.test(s);
|
|
83
|
-
}
|
|
84
|
-
/**
|
|
85
|
-
* Checks if a string contains only alphabetic characters.
|
|
86
|
-
* @param {string} s The string
|
|
87
|
-
* @returns {boolean} Returns `true` if the string contains only alphabetic.
|
|
88
|
-
* @example
|
|
89
|
-
```js
|
|
90
|
-
isAlpha('alpha1234')
|
|
91
|
-
// false
|
|
92
|
-
|
|
93
|
-
isAlpha('letters')
|
|
94
|
-
// true
|
|
95
|
-
```
|
|
96
|
-
*/
|
|
97
|
-
export function isAlpha(s) {
|
|
98
|
-
return /^[\p{L}]+$/gu.test(s);
|
|
99
|
-
}
|
|
100
|
-
/**
|
|
101
|
-
* Checks if a string contains only numeric characters.
|
|
102
|
-
* @param {string} s The string
|
|
103
|
-
* @returns {boolean} Returns `true` if the string contains only numeric.
|
|
104
|
-
* @example
|
|
105
|
-
```js
|
|
106
|
-
isNumeric('alpha1234')
|
|
107
|
-
// false
|
|
108
|
-
|
|
109
|
-
isNumeric('1234')
|
|
110
|
-
// true
|
|
111
|
-
```
|
|
112
|
-
*/
|
|
113
|
-
export function isNumeric(s) {
|
|
114
|
-
return /^[\p{N}]+$/gu.test(s);
|
|
115
|
-
}
|
|
116
|
-
/**
|
|
117
|
-
* Returns a new string with only valid ASCII characters
|
|
118
|
-
* @param {string} s The string
|
|
119
|
-
* @returns {string} The ASCII characters in the given string
|
|
120
|
-
* @example
|
|
121
|
-
```js
|
|
122
|
-
asciify('äÄçÇéÉêPHP-MySQLöÖÐþúÚ')
|
|
123
|
-
// 'PHP-MySQL'
|
|
124
|
-
```
|
|
125
|
-
*/
|
|
126
|
-
export function asciify(s) {
|
|
127
|
-
return s.replace(/[^\x00-\x7F]/g, '');
|
|
128
|
-
}
|
|
129
|
-
/**
|
|
130
|
-
* Replaces any accent characters in the string with non accents.
|
|
131
|
-
* @param {string} s The string
|
|
132
|
-
* @returns {string} The new string without accents
|
|
133
|
-
* @example
|
|
134
|
-
```js
|
|
135
|
-
removeAccents('Antoine de Saint-Exupéry')
|
|
136
|
-
// 'Antoine de Saint-Exupery'
|
|
137
|
-
```
|
|
138
|
-
*/
|
|
139
|
-
export function removeAccents(s) {
|
|
140
|
-
return s.normalize('NFD').replace(/[\u0300-\u036f]/g, '');
|
|
141
|
-
}
|
|
142
|
-
/**
|
|
143
|
-
* Capitalizes the first letter of every word in the string.
|
|
144
|
-
* @param {string} s The string to capitalize
|
|
145
|
-
* @returns {string} Returns the title cased string.
|
|
146
|
-
* @example
|
|
147
|
-
```js
|
|
148
|
-
titleCase('lord of the rings')
|
|
149
|
-
// 'Lord Of The Rings'
|
|
150
|
-
```
|
|
151
|
-
@see {@link capitalize}
|
|
152
|
-
*/
|
|
153
|
-
export function titleCase(s) {
|
|
154
|
-
return words(s)
|
|
155
|
-
.map((word) => capitalize(word))
|
|
156
|
-
.join(' ');
|
|
157
|
-
}
|
|
158
|
-
/**
|
|
159
|
-
* Converts the string to camel case.
|
|
160
|
-
* @param {string} s The string to convert
|
|
161
|
-
* @returns {string} Returns the camel cased string.
|
|
162
|
-
* @example
|
|
163
|
-
```js
|
|
164
|
-
camelCase('Equipment class name')
|
|
165
|
-
// 'equipmentClassName'
|
|
166
|
-
```
|
|
167
|
-
*/
|
|
168
|
-
export function camelCase(s) {
|
|
169
|
-
return words(s).reduce((acc, next) => `${acc}${!acc ? next.toLowerCase() : next[0].toUpperCase() + next.slice(1).toLowerCase()}`, '');
|
|
170
|
-
}
|
|
171
|
-
/**
|
|
172
|
-
* Converts the string to Pascal case.
|
|
173
|
-
* @param {string} s The string to convert
|
|
174
|
-
* @returns {string} Returns the Pascal cased string.
|
|
175
|
-
* @example
|
|
176
|
-
```js
|
|
177
|
-
pascalCase('equipment class name')
|
|
178
|
-
// 'EquipmentClassName'
|
|
179
|
-
```
|
|
180
|
-
*/
|
|
181
|
-
export function pascalCase(s) {
|
|
182
|
-
return sentenceCase(camelCase(s));
|
|
183
|
-
}
|
|
184
|
-
/**
|
|
185
|
-
* Converts the string to snake case.
|
|
186
|
-
* @param {string} s The string to convert
|
|
187
|
-
* @returns {string} Returns the snake cased string.
|
|
188
|
-
* @example
|
|
189
|
-
```js
|
|
190
|
-
snakeCase('Equipment Class Name')
|
|
191
|
-
// 'equipment_class_name'
|
|
192
|
-
```
|
|
193
|
-
*/
|
|
194
|
-
export function snakeCase(s) {
|
|
195
|
-
return join(s, '_').toLowerCase();
|
|
196
|
-
}
|
|
197
|
-
/**
|
|
198
|
-
* Converts a string to kebab case.
|
|
199
|
-
* @param {string} s The string
|
|
200
|
-
* @returns {string} Returns the kebab cased string.
|
|
201
|
-
* @example
|
|
202
|
-
```js
|
|
203
|
-
kebabCase('Equipment Class Name')
|
|
204
|
-
// 'equipment-class-name'
|
|
205
|
-
|
|
206
|
-
kebabCase('equipment_class_name')
|
|
207
|
-
// 'equipment-class-name'
|
|
208
|
-
```
|
|
209
|
-
*/
|
|
210
|
-
export function kebabCase(s) {
|
|
211
|
-
return join(s, '-').toLowerCase();
|
|
212
|
-
}
|
|
213
|
-
/**
|
|
214
|
-
* Returns a url-safe slug for the given string. Note that by default it will preserve any capitalization in the given string.
|
|
215
|
-
* @param {string} s The string
|
|
216
|
-
* @returns {string} A url-safe slug version of the string
|
|
217
|
-
* @example
|
|
218
|
-
```js
|
|
219
|
-
slugify('Antoine de Saint-Exupéry')
|
|
220
|
-
// 'Antoine-de-Saint-Exupery'
|
|
221
|
-
```
|
|
222
|
-
*/
|
|
223
|
-
export function slugify(s) {
|
|
224
|
-
const slug = join(removeAccents(s), '-');
|
|
225
|
-
const urlSafe = slug.replace(/[^a-zA-Z0-9-_]/g, '');
|
|
226
|
-
return urlSafe;
|
|
227
|
-
}
|
|
6
|
+
export { camelCase, capitalize, kebabCase, pascalCase, snakeCase, startCase } from './case.js';
|
|
7
|
+
export { deburr, slugify } from './fold.js';
|
|
8
|
+
export { truncate } from './truncate.js';
|
|
9
|
+
export { words } from './words.js';
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/** Options for {@link truncate}. */
|
|
2
|
+
export interface TruncateOptions {
|
|
3
|
+
/**
|
|
4
|
+
* Most graphemes in the result, `omission` included (default 30), unless `omission` alone is
|
|
5
|
+
* at least as long, in which case the result is `omission`.
|
|
6
|
+
*/
|
|
7
|
+
length?: number;
|
|
8
|
+
/** What marks the cut (default `'...'`). */
|
|
9
|
+
omission?: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Shortens a string to at most `length` graphemes, ending with `omission` when it cuts. Lengths
|
|
13
|
+
* count user-perceived characters, so an emoji family or a letter with its marks is never split.
|
|
14
|
+
* When `omission` alone is at least as long as `length`, the result is `omission`, as in lodash.
|
|
15
|
+
* @param s The string to shorten.
|
|
16
|
+
* @param options `length` and `omission`.
|
|
17
|
+
* @returns The shortened string.
|
|
18
|
+
* @throws {RangeError} when `length` is not a non-negative integer.
|
|
19
|
+
* @example
|
|
20
|
+
* ```ts
|
|
21
|
+
* truncate('hi-diddly-ho there, neighborino', { length: 24 }) // 'hi-diddly-ho there, n...'
|
|
22
|
+
* truncate('👨👩👧👨👩👧👨👩👧', { length: 2, omission: '' }) // '👨👩👧👨👩👧'
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
export declare function truncate(s: unknown, options?: TruncateOptions): string;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Grapheme-safe truncation on `Intl.Segmenter`.
|
|
3
|
+
* @module
|
|
4
|
+
*/
|
|
5
|
+
import { toText } from '../internal/text.js';
|
|
6
|
+
/** Lazy: Intl.Segmenter at module load fails where Intl is incomplete. */
|
|
7
|
+
let graphemeSegmenter;
|
|
8
|
+
function getGraphemeSegmenter() {
|
|
9
|
+
graphemeSegmenter ??= new Intl.Segmenter(undefined, { granularity: 'grapheme' });
|
|
10
|
+
return graphemeSegmenter;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Shortens a string to at most `length` graphemes, ending with `omission` when it cuts. Lengths
|
|
14
|
+
* count user-perceived characters, so an emoji family or a letter with its marks is never split.
|
|
15
|
+
* When `omission` alone is at least as long as `length`, the result is `omission`, as in lodash.
|
|
16
|
+
* @param s The string to shorten.
|
|
17
|
+
* @param options `length` and `omission`.
|
|
18
|
+
* @returns The shortened string.
|
|
19
|
+
* @throws {RangeError} when `length` is not a non-negative integer.
|
|
20
|
+
* @example
|
|
21
|
+
* ```ts
|
|
22
|
+
* truncate('hi-diddly-ho there, neighborino', { length: 24 }) // 'hi-diddly-ho there, n...'
|
|
23
|
+
* truncate('👨👩👧👨👩👧👨👩👧', { length: 2, omission: '' }) // '👨👩👧👨👩👧'
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
export function truncate(s, options) {
|
|
27
|
+
const text = toText(s);
|
|
28
|
+
const length = options?.length ?? 30;
|
|
29
|
+
const omission = toText(options?.omission ?? '...');
|
|
30
|
+
if (!Number.isSafeInteger(length) || length < 0) {
|
|
31
|
+
throw new RangeError('length must be a non-negative integer');
|
|
32
|
+
}
|
|
33
|
+
const segmenter = getGraphemeSegmenter();
|
|
34
|
+
const keep = Math.max(length - [...segmenter.segment(omission)].length, 0);
|
|
35
|
+
let count = 0;
|
|
36
|
+
let cut = 0;
|
|
37
|
+
for (const { index } of segmenter.segment(text)) {
|
|
38
|
+
if (count === keep)
|
|
39
|
+
cut = index;
|
|
40
|
+
if (++count > length)
|
|
41
|
+
return text.slice(0, cut) + omission;
|
|
42
|
+
}
|
|
43
|
+
return text;
|
|
44
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unicode-aware word splitting on `Intl.Segmenter` with identifier case rules.
|
|
3
|
+
* @module
|
|
4
|
+
*/
|
|
5
|
+
declare function fromSegments(s: string): string[];
|
|
6
|
+
/** Word extraction forced through Intl.Segmenter, for path-equivalence tests. */
|
|
7
|
+
export { fromSegments as segmenterWords };
|
|
8
|
+
/**
|
|
9
|
+
* Splits a string into words. Without `pattern`, it uses `Intl.Segmenter` word boundaries plus
|
|
10
|
+
* identifier case rules: `fooBar` and `HTTPServer` split at case changes, ordinals (`11th`) and
|
|
11
|
+
* plural acronyms (`IDs`) stay whole, digits split from letters, emoji stay whole, and bidi
|
|
12
|
+
* controls separate words. With a `pattern`, every match of it is a word, even when the pattern
|
|
13
|
+
* is not global, and capture groups are ignored; lodash returns only the first match and its
|
|
14
|
+
* groups for a non-global pattern. A `null` pattern means no pattern, where lodash matches
|
|
15
|
+
* `'null'`. `null` and `undefined` read as `''`, and other values go through `String`.
|
|
16
|
+
* @param s The string to split.
|
|
17
|
+
* @param pattern A regular expression that matches each word.
|
|
18
|
+
* @throws {TypeError} When `pattern` is neither a `RegExp` nor `null` or `undefined`.
|
|
19
|
+
* @returns The words, in order.
|
|
20
|
+
* @example
|
|
21
|
+
* ```ts
|
|
22
|
+
* words('fooBar_baz qux') // ['foo', 'Bar', 'baz', 'qux']
|
|
23
|
+
* words('HTTPServer2xx') // ['HTTP', 'Server', '2', 'xx']
|
|
24
|
+
* words('fred, barney, & pebbles', /[^, ]+/g) // ['fred', 'barney', '&', 'pebbles']
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export declare function words(s: unknown, pattern?: RegExp | null): string[];
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unicode-aware word splitting on `Intl.Segmenter` with identifier case rules.
|
|
3
|
+
* @module
|
|
4
|
+
*/
|
|
5
|
+
import { isAsciiString, toText } from '../internal/text.js';
|
|
6
|
+
/** Lazy: Intl.Segmenter at module load fails where Intl is incomplete. */
|
|
7
|
+
let wordSegmenter;
|
|
8
|
+
function getWordSegmenter() {
|
|
9
|
+
wordSegmenter ??= new Intl.Segmenter(undefined, { granularity: 'word' });
|
|
10
|
+
return wordSegmenter;
|
|
11
|
+
}
|
|
12
|
+
const IS_UPPER = /^[\p{Lu}\p{Lt}]$/u;
|
|
13
|
+
const IS_LOWER = /^\p{Ll}$/u;
|
|
14
|
+
const IS_OTHER_LETTER = /^[\p{Lo}\p{Lm}]$/u;
|
|
15
|
+
const IS_NUMBER = /^\p{N}$/u;
|
|
16
|
+
/** Format characters the segmenter keeps inside a word, but that must not reach a slug or key. */
|
|
17
|
+
const IS_BIDI_CONTROL = /^[\u061C\u200E\u200F\u202A-\u202E\u2066-\u2069]$/u;
|
|
18
|
+
const IS_MARK_OR_FORMAT = /^[\p{M}\p{Cf}]$/u;
|
|
19
|
+
const HAS_LETTER_OR_NUMBER = /[\p{L}\p{N}]/u;
|
|
20
|
+
const ZWJ_EMOJI = /\u200D\p{ExtPict}/u;
|
|
21
|
+
const RGI_EMOJI_AT = /\p{RGI_Emoji}/vy;
|
|
22
|
+
const COMBINING_ENCLOSING_KEYCAP = 0x20e3;
|
|
23
|
+
const ORDINAL_LOWER = new Set(['st', 'nd', 'rd', 'th']);
|
|
24
|
+
const ORDINAL_UPPER = new Set(['ST', 'ND', 'RD', 'TH']);
|
|
25
|
+
function isAsciiDigit(code) {
|
|
26
|
+
return code >= 0x30 && code <= 0x39;
|
|
27
|
+
}
|
|
28
|
+
function classifyAscii(cp) {
|
|
29
|
+
if (cp >= 0x41 && cp <= 0x5a)
|
|
30
|
+
return 'U';
|
|
31
|
+
if (cp >= 0x61 && cp <= 0x7a)
|
|
32
|
+
return 'L';
|
|
33
|
+
if (isAsciiDigit(cp))
|
|
34
|
+
return 'D';
|
|
35
|
+
if (cp === 0x27)
|
|
36
|
+
return 'A';
|
|
37
|
+
return 'X';
|
|
38
|
+
}
|
|
39
|
+
function classifyNonAscii(ch) {
|
|
40
|
+
if (ch === '\u2019')
|
|
41
|
+
return 'A';
|
|
42
|
+
if (IS_UPPER.test(ch))
|
|
43
|
+
return 'U';
|
|
44
|
+
if (IS_LOWER.test(ch))
|
|
45
|
+
return 'L';
|
|
46
|
+
if (IS_OTHER_LETTER.test(ch))
|
|
47
|
+
return 'O';
|
|
48
|
+
if (IS_NUMBER.test(ch))
|
|
49
|
+
return 'D';
|
|
50
|
+
if (IS_BIDI_CONTROL.test(ch))
|
|
51
|
+
return 'X';
|
|
52
|
+
if (IS_MARK_OR_FORMAT.test(ch))
|
|
53
|
+
return 'K';
|
|
54
|
+
return 'X';
|
|
55
|
+
}
|
|
56
|
+
/** ASCII takes a few compares; only the regexp-based non-ASCII classes are worth caching. */
|
|
57
|
+
function classifyCp(cp, cache) {
|
|
58
|
+
if (cp < 0x80)
|
|
59
|
+
return classifyAscii(cp);
|
|
60
|
+
let cls = cache.get(cp);
|
|
61
|
+
if (cls === undefined) {
|
|
62
|
+
cls = classifyNonAscii(String.fromCodePoint(cp));
|
|
63
|
+
cache.set(cp, cls);
|
|
64
|
+
}
|
|
65
|
+
return cls;
|
|
66
|
+
}
|
|
67
|
+
/** The code point at UTF-16 index `i`, which the caller keeps below `s.length`. */
|
|
68
|
+
function codePointAt(s, i) {
|
|
69
|
+
return s.codePointAt(i);
|
|
70
|
+
}
|
|
71
|
+
/** The class of the code point at UTF-16 index `i`, or `undefined` past the end. */
|
|
72
|
+
function classAt(s, i, cache) {
|
|
73
|
+
return i < s.length ? classifyCp(codePointAt(s, i), cache) : undefined;
|
|
74
|
+
}
|
|
75
|
+
function isLetter(cls) {
|
|
76
|
+
return cls === 'U' || cls === 'L' || cls === 'O';
|
|
77
|
+
}
|
|
78
|
+
/** `IDs`, `APIs`: an `s` at `sIndex`, then the end, a non-letter or a new capital. */
|
|
79
|
+
function isPluralAcronym(s, sIndex, cache) {
|
|
80
|
+
if (s[sIndex] !== 's')
|
|
81
|
+
return false;
|
|
82
|
+
const afterCls = classAt(s, sIndex + 1, cache);
|
|
83
|
+
return afterCls === undefined || !isLetter(afterCls) || afterCls === 'U';
|
|
84
|
+
}
|
|
85
|
+
/** `11th`, `1stPlace`, `1ST`: an ordinal suffix at `start`, then the end, a non-letter or a capital. */
|
|
86
|
+
function isOrdinalSuffix(s, start, cache) {
|
|
87
|
+
const two = s.slice(start, start + 2);
|
|
88
|
+
const lower = ORDINAL_LOWER.has(two);
|
|
89
|
+
if (!lower && !ORDINAL_UPPER.has(two))
|
|
90
|
+
return false;
|
|
91
|
+
const afterCls = classAt(s, start + 2, cache);
|
|
92
|
+
return afterCls === undefined || !isLetter(afterCls) || (afterCls === 'U' && lower);
|
|
93
|
+
}
|
|
94
|
+
/** A camel boundary right after a mid-word apostrophe strands it; it belongs to neither word. */
|
|
95
|
+
function endBeforeApostrophe(s, end) {
|
|
96
|
+
const last = s[end - 1];
|
|
97
|
+
return last === "'" || last === '\u2019' ? end - 1 : end;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Splits one word-like run at case and letter-digit changes, keeping ordinals, plural acronyms
|
|
101
|
+
* and digit keycaps whole. Walks UTF-16 indices, so it allocates only the words.
|
|
102
|
+
*/
|
|
103
|
+
function splitRun(s, cache) {
|
|
104
|
+
const out = [];
|
|
105
|
+
let wordStart = -1;
|
|
106
|
+
let prev;
|
|
107
|
+
let prevStart = -1;
|
|
108
|
+
let i = 0;
|
|
109
|
+
const flush = (end) => {
|
|
110
|
+
if (wordStart >= 0 && end > wordStart)
|
|
111
|
+
out.push(s.slice(wordStart, end));
|
|
112
|
+
wordStart = -1;
|
|
113
|
+
prev = undefined;
|
|
114
|
+
};
|
|
115
|
+
while (i < s.length) {
|
|
116
|
+
const cp = codePointAt(s, i);
|
|
117
|
+
const end = i + (cp > 0xffff ? 2 : 1);
|
|
118
|
+
const cls = classifyCp(cp, cache);
|
|
119
|
+
if (cls === 'K') {
|
|
120
|
+
if (cp === COMBINING_ENCLOSING_KEYCAP &&
|
|
121
|
+
prev === 'D' &&
|
|
122
|
+
isAsciiDigit(s.charCodeAt(prevStart))) {
|
|
123
|
+
// The segmenter puts `1️⃣2️⃣` in one Numeric segment; each keycap is its own word.
|
|
124
|
+
flush(prevStart);
|
|
125
|
+
out.push(s.slice(prevStart, end));
|
|
126
|
+
}
|
|
127
|
+
// A mark extends the current word; after a flush it has no word to attach to and is dropped.
|
|
128
|
+
i = end;
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
if (cls === 'X') {
|
|
132
|
+
flush(i);
|
|
133
|
+
i = end;
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
if (cls === 'A') {
|
|
137
|
+
const nextCls = classAt(s, end, cache);
|
|
138
|
+
// Mid-word apostrophe: wordStart is already set whenever prev is a letter.
|
|
139
|
+
if (!(prev !== undefined && isLetter(prev) && nextCls !== undefined && isLetter(nextCls))) {
|
|
140
|
+
flush(i);
|
|
141
|
+
}
|
|
142
|
+
i = end;
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
let boundary = false;
|
|
146
|
+
if (prev !== undefined) {
|
|
147
|
+
if ((prev === 'L' || prev === 'O') && cls === 'U') {
|
|
148
|
+
boundary = true;
|
|
149
|
+
}
|
|
150
|
+
else if (prev === 'U' && cls === 'U') {
|
|
151
|
+
boundary = classAt(s, end, cache) === 'L' && !isPluralAcronym(s, end, cache);
|
|
152
|
+
}
|
|
153
|
+
else if (isLetter(prev) && cls === 'D') {
|
|
154
|
+
boundary = true;
|
|
155
|
+
}
|
|
156
|
+
else if (prev === 'D' && isLetter(cls)) {
|
|
157
|
+
boundary = !isOrdinalSuffix(s, i, cache);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
if (boundary)
|
|
161
|
+
flush(endBeforeApostrophe(s, i));
|
|
162
|
+
if (wordStart < 0)
|
|
163
|
+
wordStart = i;
|
|
164
|
+
prev = cls;
|
|
165
|
+
prevStart = i;
|
|
166
|
+
i = end;
|
|
167
|
+
}
|
|
168
|
+
flush(s.length);
|
|
169
|
+
return out;
|
|
170
|
+
}
|
|
171
|
+
/** Steps one emoji at a time: `\p{RGI_Emoji}+` overflows the regexp stack on a long segment. */
|
|
172
|
+
function isRgiEmojiRun(s) {
|
|
173
|
+
RGI_EMOJI_AT.lastIndex = 0;
|
|
174
|
+
while (RGI_EMOJI_AT.lastIndex < s.length) {
|
|
175
|
+
if (!RGI_EMOJI_AT.test(s))
|
|
176
|
+
return false;
|
|
177
|
+
}
|
|
178
|
+
return true;
|
|
179
|
+
}
|
|
180
|
+
function fromSegments(s) {
|
|
181
|
+
const out = [];
|
|
182
|
+
const cache = new Map();
|
|
183
|
+
const pushWords = (run) => {
|
|
184
|
+
for (const part of splitRun(run, cache))
|
|
185
|
+
out.push(part);
|
|
186
|
+
};
|
|
187
|
+
const pushEmoji = (run) => {
|
|
188
|
+
if (isRgiEmojiRun(run))
|
|
189
|
+
out.push(run);
|
|
190
|
+
};
|
|
191
|
+
const pushNonWord = (segment) => {
|
|
192
|
+
if (!HAS_LETTER_OR_NUMBER.test(segment)) {
|
|
193
|
+
pushEmoji(segment);
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
// ICU glues `word ZWJ emoji` into one segment and calls it non-word-like.
|
|
197
|
+
const glue = segment.search(ZWJ_EMOJI);
|
|
198
|
+
pushWords(glue < 0 ? segment : segment.slice(0, glue));
|
|
199
|
+
if (glue >= 0)
|
|
200
|
+
pushEmoji(segment.slice(glue + 1));
|
|
201
|
+
};
|
|
202
|
+
for (const { segment, isWordLike } of getWordSegmenter().segment(s)) {
|
|
203
|
+
if (isWordLike)
|
|
204
|
+
pushWords(segment);
|
|
205
|
+
else if (segment.length > 1 || segment.charCodeAt(0) > 0x7f)
|
|
206
|
+
pushNonWord(segment);
|
|
207
|
+
}
|
|
208
|
+
return out;
|
|
209
|
+
}
|
|
210
|
+
/** Word extraction forced through Intl.Segmenter, for path-equivalence tests. */
|
|
211
|
+
export { fromSegments as segmenterWords };
|
|
212
|
+
/**
|
|
213
|
+
* Splits a string into words. Without `pattern`, it uses `Intl.Segmenter` word boundaries plus
|
|
214
|
+
* identifier case rules: `fooBar` and `HTTPServer` split at case changes, ordinals (`11th`) and
|
|
215
|
+
* plural acronyms (`IDs`) stay whole, digits split from letters, emoji stay whole, and bidi
|
|
216
|
+
* controls separate words. With a `pattern`, every match of it is a word, even when the pattern
|
|
217
|
+
* is not global, and capture groups are ignored; lodash returns only the first match and its
|
|
218
|
+
* groups for a non-global pattern. A `null` pattern means no pattern, where lodash matches
|
|
219
|
+
* `'null'`. `null` and `undefined` read as `''`, and other values go through `String`.
|
|
220
|
+
* @param s The string to split.
|
|
221
|
+
* @param pattern A regular expression that matches each word.
|
|
222
|
+
* @throws {TypeError} When `pattern` is neither a `RegExp` nor `null` or `undefined`.
|
|
223
|
+
* @returns The words, in order.
|
|
224
|
+
* @example
|
|
225
|
+
* ```ts
|
|
226
|
+
* words('fooBar_baz qux') // ['foo', 'Bar', 'baz', 'qux']
|
|
227
|
+
* words('HTTPServer2xx') // ['HTTP', 'Server', '2', 'xx']
|
|
228
|
+
* words('fred, barney, & pebbles', /[^, ]+/g) // ['fred', 'barney', '&', 'pebbles']
|
|
229
|
+
* ```
|
|
230
|
+
*/
|
|
231
|
+
export function words(s, pattern) {
|
|
232
|
+
const text = toText(s);
|
|
233
|
+
if (pattern != null) {
|
|
234
|
+
if (!(pattern instanceof RegExp))
|
|
235
|
+
throw new TypeError('words expects pattern to be a RegExp');
|
|
236
|
+
const flags = pattern.flags.includes('g') ? pattern.flags : `${pattern.flags}g`;
|
|
237
|
+
return Array.from(text.matchAll(new RegExp(pattern.source, flags)), (match) => match[0]);
|
|
238
|
+
}
|
|
239
|
+
// On ASCII the segmenter breaks only where splitRun splits too, so ASCII text skips it.
|
|
240
|
+
return isAsciiString(text) ? splitRun(text, new Map()) : fromSegments(text);
|
|
241
|
+
}
|