atomic-fns 1.1.7 → 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 +405 -60
  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 -249
  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 -642
  27. package/dist/collections/index.js +14 -716
  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 -145
  55. package/dist/decimal/index.js +10 -374
  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 -50
  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 -226
  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 +101 -58
  171. package/dist/collections/BTree.d.ts +0 -229
  172. package/dist/collections/BTree.js +0 -1165
  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 -80
  176. package/dist/collections/LRUCache.js +0 -126
  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 -141
  183. package/dist/collections/SplayTree.d.ts +0 -105
  184. package/dist/collections/SplayTree.js +0 -338
  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 -30
  191. package/dist/functools/index.d.ts +0 -130
  192. package/dist/functools/index.js +0 -235
  193. package/dist/globals/index.d.ts +0 -285
  194. package/dist/globals/index.js +0 -463
  195. package/dist/i18n/currency.d.ts +0 -24
  196. package/dist/i18n/currency.js +0 -85
  197. package/dist/i18n/date/index.d.ts +0 -203
  198. package/dist/i18n/date/index.js +0 -531
  199. package/dist/i18n/date/utils.d.ts +0 -65
  200. package/dist/i18n/date/utils.js +0 -290
  201. package/dist/i18n/duration.d.ts +0 -112
  202. package/dist/i18n/duration.js +0 -392
  203. package/dist/i18n/format.d.ts +0 -31
  204. package/dist/i18n/format.js +0 -506
  205. package/dist/i18n/index.d.ts +0 -8
  206. package/dist/i18n/index.js +0 -8
  207. package/dist/itertools/index.d.ts +0 -255
  208. package/dist/itertools/index.js +0 -437
  209. package/dist/math/index.d.ts +0 -96
  210. package/dist/math/index.js +0 -98
  211. package/dist/mod.d.ts +0 -1
  212. package/dist/mod.js +0 -1
  213. package/dist/operators/index.d.ts +0 -152
  214. package/dist/operators/index.js +0 -305
  215. package/dist/types.d.ts +0 -19
  216. package/dist/types.js +0 -1
@@ -1,229 +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 = // eslint-disable-next-line no-control-regex
7
- /[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;
8
- /**
9
- * Converts only the first character of the given string to upper case. Note that the rest of the characters are preserved.
10
- * @param {string} s The string
11
- * @returns {string} Returns the sentence string.
12
- * @example
13
- ```js
14
- sentenceCase('a test')
15
- // 'A test'
16
-
17
- sentenceCase('TEST')
18
- // 'TEST'
19
- ```
20
- * @see {@link capitalize}
21
- */
22
- export function sentenceCase(s) {
23
- return `${(s[0] || '').toLocaleUpperCase()}${s.substring(1) || ''}`;
24
- }
25
- /**
26
- * Converts the first character of the given string to upper case and the rest to lower case.
27
- * @param {string} s The string to capitalize
28
- * @returns {string} Returns the capitalized string.
29
- * @example
30
- ```js
31
- capitalize('TEST')
32
- // 'Test'
33
- ```
34
- * @see {@link titleCase}
35
- */
36
- export function capitalize(s) {
37
- return `${(s[0] || '').toLocaleUpperCase()}${(s.substring(1) || '').toLocaleLowerCase()}`;
38
- }
39
- /**
40
- * Split a string into an array of its words.
41
- * @param {string} s The string to split
42
- * @param {RegExp | string} [pattern] The pattern to match words
43
- * @returns {string[]} Returns the words array.
44
- * @example
45
- ```js
46
- words('lord of the rings')
47
- // ['lord', 'of', 'the', 'rings']
48
- ```
49
- */
50
- export function words(s, pattern) {
51
- if (pattern != null)
52
- return s.split(pattern);
53
- return s.match(WORDS_REGEX) || [];
54
- }
55
- /**
56
- * Create a new string which is the concatenation of the words in the given input.
57
- * @param {string} s The string to join
58
- * @param {string} [sep=' '] The separator to use
59
- * @returns {string} Returns the joined string.
60
- * @example
61
- ```js
62
- join('Lord Of The Rings', '-')
63
- // 'Lord-Of-The-Rings'
64
- ```
65
- */
66
- export function join(s, sep = ' ') {
67
- return words(s).join(sep);
68
- }
69
- /**
70
- * Checks if a string contains only alphanumeric characters.
71
- * @param {string} s The string
72
- * @returns {boolean} Returns `true` if the string contains only alphanumeric.
73
- * @example
74
- ```js
75
- isAlnum('alpha1234')
76
- // true
77
-
78
- isAlnum('oops!')
79
- // false
80
- ```
81
- */
82
- export function isAlnum(s) {
83
- return /^[\p{L}\p{N}]+$/gu.test(s);
84
- }
85
- /**
86
- * Checks if a string contains only alphabetic characters.
87
- * @param {string} s The string
88
- * @returns {boolean} Returns `true` if the string contains only alphabetic.
89
- * @example
90
- ```js
91
- isAlpha('alpha1234')
92
- // false
93
-
94
- isAlpha('letters')
95
- // true
96
- ```
97
- */
98
- export function isAlpha(s) {
99
- return /^[\p{L}]+$/gu.test(s);
100
- }
101
- /**
102
- * Checks if a string contains only numeric characters.
103
- * @param {string} s The string
104
- * @returns {boolean} Returns `true` if the string contains only numeric.
105
- * @example
106
- ```js
107
- isNumeric('alpha1234')
108
- // false
109
-
110
- isNumeric('1234')
111
- // true
112
- ```
113
- */
114
- export function isNumeric(s) {
115
- return /^[\p{N}]+$/gu.test(s);
116
- }
117
- /**
118
- * Returns a new string with only valid ASCII characters
119
- * @param {string} s The string
120
- * @returns {string} The ASCII characters in the given string
121
- * @example
122
- ```js
123
- asciify('äÄçÇéÉêPHP-MySQLöÖÐþúÚ')
124
- // 'PHP-MySQL'
125
- ```
126
- */
127
- export function asciify(s) {
128
- // eslint-disable-next-line no-control-regex
129
- return s.replace(/[^\x00-\x7F]/g, '');
130
- }
131
- /**
132
- * Replaces any accent characters in the string with non accents.
133
- * @param {string} s The string
134
- * @returns {string} The new string without accents
135
- * @example
136
- ```js
137
- removeAccents('Antoine de Saint-Exupéry')
138
- // 'Antoine de Saint-Exupery'
139
- ```
140
- */
141
- export function removeAccents(s) {
142
- return s.normalize('NFD').replace(/[\u0300-\u036f]/g, '');
143
- }
144
- /**
145
- * Capitalizes the first letter of every word in the string.
146
- * @param {string} s The string to capitalize
147
- * @returns {string} Returns the title cased string.
148
- * @example
149
- ```js
150
- titleCase('lord of the rings')
151
- // 'Lord Of The Rings'
152
- ```
153
- @see {@link capitalize}
154
- */
155
- export function titleCase(s) {
156
- return words(s)
157
- .map((word) => capitalize(word))
158
- .join(' ');
159
- }
160
- /**
161
- * Converts the string to camel case.
162
- * @param {string} s The string to convert
163
- * @returns {string} Returns the camel cased string.
164
- * @example
165
- ```js
166
- camelCase('Equipment class name')
167
- // 'equipmentClassName'
168
- ```
169
- */
170
- export function camelCase(s) {
171
- return words(s).reduce((acc, next) => `${acc}${!acc ? next.toLowerCase() : next[0].toUpperCase() + next.slice(1).toLowerCase()}`, '');
172
- }
173
- /**
174
- * Converts the string to Pascal case.
175
- * @param {string} s The string to convert
176
- * @returns {string} Returns the Pascal cased string.
177
- * @example
178
- ```js
179
- pascalCase('equipment class name')
180
- // 'EquipmentClassName'
181
- ```
182
- */
183
- export function pascalCase(s) {
184
- return sentenceCase(camelCase(s));
185
- }
186
- /**
187
- * Converts the string to snake case.
188
- * @param {string} s The string to convert
189
- * @returns {string} Returns the snake cased string.
190
- * @example
191
- ```js
192
- snakeCase('Equipment Class Name')
193
- // 'equipment_class_name'
194
- ```
195
- */
196
- export function snakeCase(s) {
197
- return join(s, '_').toLowerCase();
198
- }
199
- /**
200
- * Converts a string to kebab case.
201
- * @param {string} s The string
202
- * @returns {string} Returns the kebab cased string.
203
- * @example
204
- ```js
205
- kebabCase('Equipment Class Name')
206
- // 'equipment-class-name'
207
-
208
- kebabCase('equipment_class_name')
209
- // 'equipment-class-name'
210
- ```
211
- */
212
- export function kebabCase(s) {
213
- return join(s, '-').toLowerCase();
214
- }
215
- /**
216
- * Returns a url-safe slug for the given string. Note that by default it will preserve any capitalization in the given string.
217
- * @param {string} s The string
218
- * @returns {string} A url-safe slug version of the string
219
- * @example
220
- ```js
221
- slugify('Antoine de Saint-Exupéry')
222
- // 'Antoine-de-Saint-Exupery'
223
- ```
224
- */
225
- export function slugify(s) {
226
- const slug = join(removeAccents(s), '-');
227
- const urlSafe = slug.replace(/[^a-zA-Z0-9-_]/g, '');
228
- return urlSafe;
229
- }
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
+ }