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.
Files changed (216) hide show
  1. package/README.md +397 -91
  2. package/dist/array/index.d.ts +9 -0
  3. package/dist/array/index.js +9 -0
  4. package/dist/array/list.d.ts +99 -0
  5. package/dist/array/list.js +133 -0
  6. package/dist/array/sets.d.ts +130 -0
  7. package/dist/array/sets.js +213 -0
  8. package/dist/async/index.d.ts +6 -0
  9. package/dist/async/index.js +6 -0
  10. package/dist/async/retry.d.ts +38 -0
  11. package/dist/async/retry.js +56 -0
  12. package/dist/async/sleep.d.ts +42 -0
  13. package/dist/async/sleep.js +76 -0
  14. package/dist/collections/bisect.d.ts +103 -0
  15. package/dist/collections/bisect.js +157 -0
  16. package/dist/collections/deque.d.ts +67 -97
  17. package/dist/collections/deque.js +197 -256
  18. package/dist/collections/entries.d.ts +89 -0
  19. package/dist/collections/entries.js +131 -0
  20. package/dist/collections/frozen-set.d.ts +42 -0
  21. package/dist/collections/frozen-set.js +64 -0
  22. package/dist/collections/group.d.ts +141 -0
  23. package/dist/collections/group.js +241 -0
  24. package/dist/collections/heap.d.ts +64 -0
  25. package/dist/collections/heap.js +205 -0
  26. package/dist/collections/index.d.ts +15 -623
  27. package/dist/collections/index.js +14 -844
  28. package/dist/collections/internal/arrayKeys.d.ts +19 -0
  29. package/dist/collections/internal/arrayKeys.js +83 -0
  30. package/dist/collections/internal/invariants.d.ts +4 -0
  31. package/dist/collections/internal/invariants.js +12 -0
  32. package/dist/collections/internal/iterable.d.ts +25 -0
  33. package/dist/collections/internal/iterable.js +62 -0
  34. package/dist/collections/iterate.d.ts +162 -0
  35. package/dist/collections/iterate.js +188 -0
  36. package/dist/collections/lru-cache.d.ts +58 -0
  37. package/dist/collections/lru-cache.js +164 -0
  38. package/dist/collections/order.d.ts +19 -0
  39. package/dist/collections/order.js +38 -0
  40. package/dist/collections/shape.d.ts +32 -0
  41. package/dist/collections/shape.js +86 -0
  42. package/dist/collections/sorted-map.d.ts +171 -0
  43. package/dist/collections/sorted-map.js +845 -0
  44. package/dist/collections/sorted-set.d.ts +103 -0
  45. package/dist/collections/sorted-set.js +147 -0
  46. package/dist/collections/splay-tree.d.ts +82 -0
  47. package/dist/collections/splay-tree.js +354 -0
  48. package/dist/collections/trie.d.ts +58 -0
  49. package/dist/collections/trie.js +196 -0
  50. package/dist/decimal/currency.d.ts +211 -0
  51. package/dist/decimal/currency.js +321 -0
  52. package/dist/decimal/decimal.d.ts +299 -0
  53. package/dist/decimal/decimal.js +660 -0
  54. package/dist/decimal/index.d.ts +11 -148
  55. package/dist/decimal/index.js +10 -410
  56. package/dist/decimal/internal/bits.d.ts +2 -0
  57. package/dist/decimal/internal/bits.js +7 -0
  58. package/dist/decimal/internal/echo.d.ts +4 -0
  59. package/dist/decimal/internal/echo.js +6 -0
  60. package/dist/decimal/internal/isqrt.d.ts +9 -0
  61. package/dist/decimal/internal/isqrt.js +34 -0
  62. package/dist/decimal/internal/locale.d.ts +7 -0
  63. package/dist/decimal/internal/locale.js +24 -0
  64. package/dist/decimal/internal/minor-units.d.ts +2 -0
  65. package/dist/decimal/internal/minor-units.js +33 -0
  66. package/dist/decimal/internal/pow10.d.ts +24 -0
  67. package/dist/decimal/internal/pow10.js +96 -0
  68. package/dist/encoding/base64.d.ts +39 -0
  69. package/dist/encoding/base64.js +53 -0
  70. package/dist/encoding/hex.d.ts +23 -0
  71. package/dist/encoding/hex.js +34 -0
  72. package/dist/encoding/index.d.ts +6 -0
  73. package/dist/encoding/index.js +6 -0
  74. package/dist/function/debounce.d.ts +68 -0
  75. package/dist/function/debounce.js +203 -0
  76. package/dist/function/index.d.ts +6 -0
  77. package/dist/function/index.js +6 -0
  78. package/dist/function/shared.d.ts +2 -0
  79. package/dist/function/shared.js +5 -0
  80. package/dist/function/wrap.d.ts +130 -0
  81. package/dist/function/wrap.js +113 -0
  82. package/dist/hash/digest.d.ts +18 -0
  83. package/dist/hash/digest.js +26 -0
  84. package/dist/hash/hash.d.ts +22 -0
  85. package/dist/hash/hash.js +252 -0
  86. package/dist/hash/hashString.d.ts +13 -0
  87. package/dist/hash/hashString.js +53 -0
  88. package/dist/hash/index.d.ts +8 -0
  89. package/dist/hash/index.js +8 -0
  90. package/dist/hash/stableStringify.d.ts +19 -0
  91. package/dist/hash/stableStringify.js +102 -0
  92. package/dist/id/index.d.ts +7 -0
  93. package/dist/id/index.js +7 -0
  94. package/dist/id/nanoid.d.ts +16 -0
  95. package/dist/id/nanoid.js +65 -0
  96. package/dist/id/uniqueId.d.ts +14 -0
  97. package/dist/id/uniqueId.js +20 -0
  98. package/dist/id/uuid.d.ts +25 -0
  99. package/dist/id/uuid.js +64 -0
  100. package/dist/index.d.ts +15 -8
  101. package/dist/index.js +15 -8
  102. package/dist/internal/bytes.d.ts +11 -0
  103. package/dist/internal/bytes.js +33 -0
  104. package/dist/internal/codec.d.ts +11 -0
  105. package/dist/internal/codec.js +84 -0
  106. package/dist/internal/compare.d.ts +10 -0
  107. package/dist/internal/compare.js +26 -0
  108. package/dist/internal/crypto.d.ts +5 -0
  109. package/dist/internal/crypto.js +11 -0
  110. package/dist/internal/keys.d.ts +12 -0
  111. package/dist/internal/keys.js +23 -0
  112. package/dist/internal/kind.d.ts +44 -0
  113. package/dist/internal/kind.js +139 -0
  114. package/dist/internal/sparse.d.ts +51 -0
  115. package/dist/internal/sparse.js +104 -0
  116. package/dist/internal/text.d.ts +7 -0
  117. package/dist/internal/text.js +15 -0
  118. package/dist/internal/timer.d.ts +10 -0
  119. package/dist/internal/timer.js +17 -0
  120. package/dist/internal/truncate.d.ts +10 -0
  121. package/dist/internal/truncate.js +22 -0
  122. package/dist/internal/walk.d.ts +47 -0
  123. package/dist/internal/walk.js +100 -0
  124. package/dist/is/index.d.ts +8 -0
  125. package/dist/is/index.js +8 -0
  126. package/dist/is/isArrayLike.d.ts +18 -0
  127. package/dist/is/isArrayLike.js +25 -0
  128. package/dist/is/isError.d.ts +13 -0
  129. package/dist/is/isError.js +18 -0
  130. package/dist/is/isNil.d.ts +12 -0
  131. package/dist/is/isNil.js +14 -0
  132. package/dist/is/isPlainObject.d.ts +15 -0
  133. package/dist/is/isPlainObject.js +34 -0
  134. package/dist/number/index.d.ts +7 -0
  135. package/dist/number/index.js +7 -0
  136. package/dist/number/round.d.ts +86 -0
  137. package/dist/number/round.js +130 -0
  138. package/dist/number/stats.d.ts +52 -0
  139. package/dist/number/stats.js +115 -0
  140. package/dist/object/cloneDeep.d.ts +28 -0
  141. package/dist/object/cloneDeep.js +200 -0
  142. package/dist/object/index.d.ts +14 -0
  143. package/dist/object/index.js +13 -0
  144. package/dist/object/isEqual.d.ts +64 -0
  145. package/dist/object/isEqual.js +289 -0
  146. package/dist/object/matches.d.ts +40 -0
  147. package/dist/object/matches.js +284 -0
  148. package/dist/object/merge.d.ts +66 -0
  149. package/dist/object/merge.js +272 -0
  150. package/dist/object/parsePath.d.ts +22 -0
  151. package/dist/object/parsePath.js +111 -0
  152. package/dist/object/path.d.ts +85 -0
  153. package/dist/object/path.js +208 -0
  154. package/dist/object/pick.d.ts +91 -0
  155. package/dist/object/pick.js +180 -0
  156. package/dist/object/reshape.d.ts +73 -0
  157. package/dist/object/reshape.js +144 -0
  158. package/dist/random/index.d.ts +96 -32
  159. package/dist/random/index.js +215 -53
  160. package/dist/string/case.d.ts +71 -0
  161. package/dist/string/case.js +152 -0
  162. package/dist/string/fold.d.ts +35 -0
  163. package/dist/string/fold.js +100 -0
  164. package/dist/string/index.d.ts +6 -187
  165. package/dist/string/index.js +6 -224
  166. package/dist/string/truncate.d.ts +25 -0
  167. package/dist/string/truncate.js +44 -0
  168. package/dist/string/words.d.ts +27 -0
  169. package/dist/string/words.js +241 -0
  170. package/package.json +99 -51
  171. package/dist/collections/BTree.d.ts +0 -230
  172. package/dist/collections/BTree.js +0 -1143
  173. package/dist/collections/Heap.d.ts +0 -69
  174. package/dist/collections/Heap.js +0 -181
  175. package/dist/collections/LRUCache.d.ts +0 -83
  176. package/dist/collections/LRUCache.js +0 -139
  177. package/dist/collections/SortedMap.d.ts +0 -92
  178. package/dist/collections/SortedMap.js +0 -131
  179. package/dist/collections/SortedSet.d.ts +0 -69
  180. package/dist/collections/SortedSet.js +0 -99
  181. package/dist/collections/SortedTree.d.ts +0 -103
  182. package/dist/collections/SortedTree.js +0 -142
  183. package/dist/collections/SplayTree.d.ts +0 -107
  184. package/dist/collections/SplayTree.js +0 -351
  185. package/dist/collections/Trie.d.ts +0 -42
  186. package/dist/collections/Trie.js +0 -104
  187. package/dist/collections/abc.d.ts +0 -152
  188. package/dist/collections/abc.js +0 -39
  189. package/dist/collections/frozenset.d.ts +0 -19
  190. package/dist/collections/frozenset.js +0 -33
  191. package/dist/functools/index.d.ts +0 -106
  192. package/dist/functools/index.js +0 -203
  193. package/dist/globals/index.d.ts +0 -268
  194. package/dist/globals/index.js +0 -447
  195. package/dist/i18n/currency.d.ts +0 -27
  196. package/dist/i18n/currency.js +0 -100
  197. package/dist/i18n/date/index.d.ts +0 -210
  198. package/dist/i18n/date/index.js +0 -552
  199. package/dist/i18n/date/utils.d.ts +0 -64
  200. package/dist/i18n/date/utils.js +0 -192
  201. package/dist/i18n/duration.d.ts +0 -113
  202. package/dist/i18n/duration.js +0 -398
  203. package/dist/i18n/format.d.ts +0 -34
  204. package/dist/i18n/format.js +0 -378
  205. package/dist/i18n/index.d.ts +0 -9
  206. package/dist/i18n/index.js +0 -8
  207. package/dist/itertools/index.d.ts +0 -251
  208. package/dist/itertools/index.js +0 -434
  209. package/dist/math/index.d.ts +0 -79
  210. package/dist/math/index.js +0 -81
  211. package/dist/mod.d.ts +0 -1
  212. package/dist/mod.js +0 -1
  213. package/dist/operators/index.d.ts +0 -148
  214. package/dist/operators/index.js +0 -325
  215. package/dist/types.d.ts +0 -19
  216. package/dist/types.js +0 -1
@@ -1,227 +1,9 @@
1
1
  /**
2
- * This module includes utilities for working with strings.
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
- const WORDS_REGEX = /[A-Z\xc0-\xd6\xd8-\xde]?[a-z\xdf-\xf6\xf8-\xff]+(?:['’](?:d|ll|m|re|s|t|ve))?(?=[\xac\xb1\xd7\xf7\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\xbf\u2000-\u206f \t\x0b\f\xa0\ufeff\n\r\u2028\u2029\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000]|[A-Z\xc0-\xd6\xd8-\xde]|$)|(?:[A-Z\xc0-\xd6\xd8-\xde]|[^\ud800-\udfff\xac\xb1\xd7\xf7\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\xbf\u2000-\u206f \t\x0b\f\xa0\ufeff\n\r\u2028\u2029\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\d+\u2700-\u27bfa-z\xdf-\xf6\xf8-\xffA-Z\xc0-\xd6\xd8-\xde])+(?:['’](?:D|LL|M|RE|S|T|VE))?(?=[\xac\xb1\xd7\xf7\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\xbf\u2000-\u206f \t\x0b\f\xa0\ufeff\n\r\u2028\u2029\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000]|[A-Z\xc0-\xd6\xd8-\xde](?:[a-z\xdf-\xf6\xf8-\xff]|[^\ud800-\udfff\xac\xb1\xd7\xf7\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\xbf\u2000-\u206f \t\x0b\f\xa0\ufeff\n\r\u2028\u2029\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\d+\u2700-\u27bfa-z\xdf-\xf6\xf8-\xffA-Z\xc0-\xd6\xd8-\xde])|$)|[A-Z\xc0-\xd6\xd8-\xde]?(?:[a-z\xdf-\xf6\xf8-\xff]|[^\ud800-\udfff\xac\xb1\xd7\xf7\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\xbf\u2000-\u206f \t\x0b\f\xa0\ufeff\n\r\u2028\u2029\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\d+\u2700-\u27bfa-z\xdf-\xf6\xf8-\xffA-Z\xc0-\xd6\xd8-\xde])+(?:['’](?:d|ll|m|re|s|t|ve))?|[A-Z\xc0-\xd6\xd8-\xde]+(?:['’](?:D|LL|M|RE|S|T|VE))?|\d*(?:1ST|2ND|3RD|(?![123])\dTH)(?=\b|[a-z_])|\d*(?:1st|2nd|3rd|(?![123])\dth)(?=\b|[A-Z_])|\d+|(?:[\u2700-\u27bf]|(?:\ud83c[\udde6-\uddff]){2}|[\ud800-\udbff][\udc00-\udfff])[\ufe0e\ufe0f]?(?:[\u0300-\u036f\ufe20-\ufe2f\u20d0-\u20ff]|\ud83c[\udffb-\udfff])?(?:\u200d(?:[^\ud800-\udfff]|(?:\ud83c[\udde6-\uddff]){2}|[\ud800-\udbff][\udc00-\udfff])[\ufe0e\ufe0f]?(?:[\u0300-\u036f\ufe20-\ufe2f\u20d0-\u20ff]|\ud83c[\udffb-\udfff])?)*/g;
7
- /**
8
- * Converts only the first character of the given string to upper case. Note that the rest of the characters are preserved.
9
- * @param {string} s The string
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
+ }