complete-common 1.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/LICENSE +9 -0
- package/README.md +7 -0
- package/dist/index.cjs +535 -0
- package/dist/index.d.cts +705 -0
- package/dist/index.d.mts +705 -0
- package/dist/index.d.ts +705 -0
- package/dist/index.mjs +463 -0
- package/package.json +36 -0
- package/src/constants.ts +3 -0
- package/src/functions/array.ts +209 -0
- package/src/functions/enums.ts +105 -0
- package/src/functions/map.ts +90 -0
- package/src/functions/math.ts +9 -0
- package/src/functions/object.ts +27 -0
- package/src/functions/random.ts +43 -0
- package/src/functions/set.ts +131 -0
- package/src/functions/sort.ts +18 -0
- package/src/functions/string.test.ts +42 -0
- package/src/functions/string.ts +304 -0
- package/src/functions/tuple.ts +31 -0
- package/src/functions/types.ts +15 -0
- package/src/functions/utils.test.ts +910 -0
- package/src/functions/utils.ts +238 -0
- package/src/index.ts +27 -0
- package/src/types/AddSubtract.ts +19 -0
- package/src/types/CompositionTypeSatisfiesEnum.ts +67 -0
- package/src/types/ERange.ts +15 -0
- package/src/types/IRange.ts +16 -0
- package/src/types/Immutable.ts +28 -0
- package/src/types/NaturalNumbersLessThan.ts +12 -0
- package/src/types/NaturalNumbersLessThanOrEqualTo.ts +14 -0
- package/src/types/ObjectValues.ts +1 -0
- package/src/types/ReadonlyMap.ts +12 -0
- package/src/types/ReadonlyRecord.ts +3 -0
- package/src/types/ReadonlySet.ts +9 -0
- package/src/types/Tuple.ts +14 -0
- package/src/types/WidenLiteral.ts +11 -0
- package/src/types/Writeable.ts +6 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,463 @@
|
|
|
1
|
+
const SECOND_IN_MILLISECONDS = 1e3;
|
|
2
|
+
const MINUTE_IN_MILLISECONDS = 60 * SECOND_IN_MILLISECONDS;
|
|
3
|
+
const HOUR_IN_MILLISECONDS = 60 * MINUTE_IN_MILLISECONDS;
|
|
4
|
+
|
|
5
|
+
const ReadonlySet = Set;
|
|
6
|
+
|
|
7
|
+
function getRandomInt(min, max, exceptions = []) {
|
|
8
|
+
min = Math.ceil(min);
|
|
9
|
+
max = Math.floor(max);
|
|
10
|
+
if (min > max) {
|
|
11
|
+
const oldMin = min;
|
|
12
|
+
const oldMax = max;
|
|
13
|
+
min = oldMax;
|
|
14
|
+
max = oldMin;
|
|
15
|
+
}
|
|
16
|
+
const exceptionsSet = new ReadonlySet(exceptions);
|
|
17
|
+
let randomInt;
|
|
18
|
+
do {
|
|
19
|
+
randomInt = Math.floor(Math.random() * (max - min + 1)) + min;
|
|
20
|
+
} while (exceptionsSet.has(randomInt));
|
|
21
|
+
return randomInt;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const FLOAT_REGEX = /^-?\d*\.?\d+$/;
|
|
25
|
+
const INTEGER_REGEX = /^-?\d+$/;
|
|
26
|
+
function assertDefined(value, ...[msg]) {
|
|
27
|
+
if (value === void 0) {
|
|
28
|
+
throw new TypeError(msg);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function assertNotNull(value, ...[msg]) {
|
|
32
|
+
if (value === null) {
|
|
33
|
+
throw new TypeError(msg);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
function* eRange(start, end, increment = 1) {
|
|
37
|
+
if (end === void 0) {
|
|
38
|
+
yield* eRange(0, start, increment);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
for (let i = start; i < end; i += increment) {
|
|
42
|
+
yield i;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function* iRange(start, end, increment = 1) {
|
|
46
|
+
if (end === void 0) {
|
|
47
|
+
yield* iRange(0, start, increment);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
const exclusiveEnd = end + 1;
|
|
51
|
+
yield* eRange(start, exclusiveEnd, increment);
|
|
52
|
+
}
|
|
53
|
+
function isKeyOf(key, target) {
|
|
54
|
+
return key in target;
|
|
55
|
+
}
|
|
56
|
+
function noop() {
|
|
57
|
+
}
|
|
58
|
+
function parseFloatSafe(string) {
|
|
59
|
+
if (typeof string !== "string") {
|
|
60
|
+
return void 0;
|
|
61
|
+
}
|
|
62
|
+
const trimmedString = string.trim();
|
|
63
|
+
if (FLOAT_REGEX.exec(trimmedString) === null) {
|
|
64
|
+
return void 0;
|
|
65
|
+
}
|
|
66
|
+
const number = Number.parseFloat(trimmedString);
|
|
67
|
+
return Number.isNaN(number) ? void 0 : number;
|
|
68
|
+
}
|
|
69
|
+
function parseIntSafe(string) {
|
|
70
|
+
if (typeof string !== "string") {
|
|
71
|
+
return void 0;
|
|
72
|
+
}
|
|
73
|
+
const trimmedString = string.trim();
|
|
74
|
+
if (INTEGER_REGEX.exec(trimmedString) === null) {
|
|
75
|
+
return void 0;
|
|
76
|
+
}
|
|
77
|
+
const number = Number.parseInt(trimmedString, 10);
|
|
78
|
+
return Number.isNaN(number) ? void 0 : number;
|
|
79
|
+
}
|
|
80
|
+
function repeat(num, func) {
|
|
81
|
+
for (let i = 0; i < num; i++) {
|
|
82
|
+
func(i);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
function todo(...args) {
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function arrayCopyTwoDimensional(array) {
|
|
89
|
+
const copiedArray = [];
|
|
90
|
+
for (const subArray of array) {
|
|
91
|
+
copiedArray.push([...subArray]);
|
|
92
|
+
}
|
|
93
|
+
return copiedArray;
|
|
94
|
+
}
|
|
95
|
+
function arrayEquals(array1, array2) {
|
|
96
|
+
if (array1.length !== array2.length) {
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
return array1.every((array1Element, i) => {
|
|
100
|
+
const array2Element = array2[i];
|
|
101
|
+
return array1Element === array2Element;
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
function arrayRemove(originalArray, ...elementsToRemove) {
|
|
105
|
+
const elementsToRemoveSet = new ReadonlySet(elementsToRemove);
|
|
106
|
+
const array = [];
|
|
107
|
+
for (const element of originalArray) {
|
|
108
|
+
if (!elementsToRemoveSet.has(element)) {
|
|
109
|
+
array.push(element);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return array;
|
|
113
|
+
}
|
|
114
|
+
function arrayRemoveInPlace(array, ...elementsToRemove) {
|
|
115
|
+
const removedElements = [];
|
|
116
|
+
for (const element of elementsToRemove) {
|
|
117
|
+
const index = array.indexOf(element);
|
|
118
|
+
if (index > -1) {
|
|
119
|
+
const removedElement = array.splice(index, 1);
|
|
120
|
+
removedElements.push(...removedElement);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return removedElements;
|
|
124
|
+
}
|
|
125
|
+
function emptyArray(array) {
|
|
126
|
+
array.splice(0, array.length);
|
|
127
|
+
}
|
|
128
|
+
function filterMap(array, func) {
|
|
129
|
+
const filteredArray = [];
|
|
130
|
+
for (const element of array) {
|
|
131
|
+
const newElement = func(element);
|
|
132
|
+
if (newElement !== void 0) {
|
|
133
|
+
filteredArray.push(newElement);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return filteredArray;
|
|
137
|
+
}
|
|
138
|
+
function getRandomArrayElement(array, exceptions = []) {
|
|
139
|
+
if (array.length === 0) {
|
|
140
|
+
throw new Error(
|
|
141
|
+
"Failed to get a random array element since the provided array is empty."
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
const arrayToUse = exceptions.length > 0 ? arrayRemove(array, ...exceptions) : array;
|
|
145
|
+
const randomIndex = getRandomArrayIndex(arrayToUse);
|
|
146
|
+
const randomElement = arrayToUse[randomIndex];
|
|
147
|
+
assertDefined(
|
|
148
|
+
randomElement,
|
|
149
|
+
`Failed to get a random array element since the random index of ${randomIndex} was not valid.`
|
|
150
|
+
);
|
|
151
|
+
return randomElement;
|
|
152
|
+
}
|
|
153
|
+
function getRandomArrayIndex(array, exceptions = []) {
|
|
154
|
+
if (array.length === 0) {
|
|
155
|
+
throw new Error(
|
|
156
|
+
"Failed to get a random array index since the provided array is empty."
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
return getRandomInt(0, array.length - 1, exceptions);
|
|
160
|
+
}
|
|
161
|
+
function includes(array, searchElement) {
|
|
162
|
+
const widenedArray = array;
|
|
163
|
+
return widenedArray.includes(searchElement);
|
|
164
|
+
}
|
|
165
|
+
function isArray(arg) {
|
|
166
|
+
return Array.isArray(arg);
|
|
167
|
+
}
|
|
168
|
+
function newArray(length, value) {
|
|
169
|
+
return Array.from({ length }, () => value);
|
|
170
|
+
}
|
|
171
|
+
function sumArray(array) {
|
|
172
|
+
return array.reduce((accumulator, element) => accumulator + element, 0);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function getEnumEntries(transpiledEnum) {
|
|
176
|
+
const entries = Object.entries(transpiledEnum);
|
|
177
|
+
const numberEntries = entries.filter(
|
|
178
|
+
([_key, value]) => typeof value === "number"
|
|
179
|
+
);
|
|
180
|
+
const entriesToReturn = numberEntries.length > 0 ? numberEntries : entries;
|
|
181
|
+
return entriesToReturn;
|
|
182
|
+
}
|
|
183
|
+
function getEnumKeys(transpiledEnum) {
|
|
184
|
+
const enumEntries = getEnumEntries(transpiledEnum);
|
|
185
|
+
return enumEntries.map(([key, _value]) => key);
|
|
186
|
+
}
|
|
187
|
+
function getEnumValues(transpiledEnum) {
|
|
188
|
+
const enumEntries = getEnumEntries(transpiledEnum);
|
|
189
|
+
return enumEntries.map(([_key, value]) => value);
|
|
190
|
+
}
|
|
191
|
+
function interfaceSatisfiesEnum() {
|
|
192
|
+
}
|
|
193
|
+
function isEnumValue(value, transpiledEnum, set) {
|
|
194
|
+
if (set !== void 0) {
|
|
195
|
+
return set.has(value);
|
|
196
|
+
}
|
|
197
|
+
const enumValues = getEnumValues(transpiledEnum);
|
|
198
|
+
return enumValues.includes(value);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function mapFilter(map, predicate) {
|
|
202
|
+
const array = [];
|
|
203
|
+
for (const value of map.values()) {
|
|
204
|
+
const match = predicate(value);
|
|
205
|
+
if (match) {
|
|
206
|
+
array.push(value);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return array;
|
|
210
|
+
}
|
|
211
|
+
function mapFind(map, predicate) {
|
|
212
|
+
for (const value of map.values()) {
|
|
213
|
+
const match = predicate(value);
|
|
214
|
+
if (match) {
|
|
215
|
+
return value;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
return void 0;
|
|
219
|
+
}
|
|
220
|
+
function objectToMap(object) {
|
|
221
|
+
const map = /* @__PURE__ */ new Map();
|
|
222
|
+
for (const [key, value] of Object.entries(object)) {
|
|
223
|
+
map.set(key, value);
|
|
224
|
+
}
|
|
225
|
+
return map;
|
|
226
|
+
}
|
|
227
|
+
function objectToReadonlyMap(object) {
|
|
228
|
+
return objectToMap(object);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function clamp(num, min, max) {
|
|
232
|
+
return Math.max(min, Math.min(num, max));
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function objectFilter(object, predicate) {
|
|
236
|
+
const array = [];
|
|
237
|
+
for (const key in object) {
|
|
238
|
+
const value = object[key];
|
|
239
|
+
const match = predicate(value);
|
|
240
|
+
if (match) {
|
|
241
|
+
array.push(value);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
return array;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function addSetsToSet(mainSet, ...setsToAdd) {
|
|
248
|
+
for (const set of setsToAdd) {
|
|
249
|
+
for (const value of set) {
|
|
250
|
+
mainSet.add(value);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
function combineSets(...sets) {
|
|
255
|
+
const newSet = /* @__PURE__ */ new Set();
|
|
256
|
+
for (const set of sets) {
|
|
257
|
+
for (const value of set) {
|
|
258
|
+
newSet.add(value);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
return newSet;
|
|
262
|
+
}
|
|
263
|
+
function copySet(oldSet) {
|
|
264
|
+
const newSet = /* @__PURE__ */ new Set();
|
|
265
|
+
for (const value of oldSet) {
|
|
266
|
+
newSet.add(value);
|
|
267
|
+
}
|
|
268
|
+
return newSet;
|
|
269
|
+
}
|
|
270
|
+
function objectKeysToReadonlySet(object) {
|
|
271
|
+
return objectKeysToSet(object);
|
|
272
|
+
}
|
|
273
|
+
function objectKeysToSet(object) {
|
|
274
|
+
const set = /* @__PURE__ */ new Set();
|
|
275
|
+
for (const key of Object.keys(object)) {
|
|
276
|
+
set.add(key);
|
|
277
|
+
}
|
|
278
|
+
return set;
|
|
279
|
+
}
|
|
280
|
+
function objectValuesToReadonlySet(object) {
|
|
281
|
+
return objectValuesToSet(object);
|
|
282
|
+
}
|
|
283
|
+
function objectValuesToSet(object) {
|
|
284
|
+
const set = /* @__PURE__ */ new Set();
|
|
285
|
+
for (const key of Object.values(object)) {
|
|
286
|
+
set.add(key);
|
|
287
|
+
}
|
|
288
|
+
return set;
|
|
289
|
+
}
|
|
290
|
+
function setAdd(set, ...elements) {
|
|
291
|
+
for (const element of elements) {
|
|
292
|
+
set.add(element);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
function setHas(set, ...elements) {
|
|
296
|
+
return elements.some((element) => set.has(element));
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function sortCaseInsensitive(array) {
|
|
300
|
+
const newArray = [...array];
|
|
301
|
+
newArray.sort(
|
|
302
|
+
(a, b) => a.localeCompare(b, void 0, {
|
|
303
|
+
sensitivity: "base"
|
|
304
|
+
})
|
|
305
|
+
);
|
|
306
|
+
return newArray;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const DIACRITIC_REGEX = /\p{Diacritic}/u;
|
|
310
|
+
const EMOJI_REGEX = /(\p{Extended_Pictographic}|\p{Emoji_Component})/u;
|
|
311
|
+
const FIRST_LETTER_CAPITALIZED_REGEX = /^\p{Lu}/u;
|
|
312
|
+
const KEBAB_CASE_REGEX = /^([a-z](?!\d)|\d(?![a-z]))+(-?([a-z](?!\d)|\d(?![a-z])))*$|^$/;
|
|
313
|
+
const SEMANTIC_VERSION_REGEX = /^v*(?<major>\d+)\.(?<minor>\d+)\.(?<patch>\d+)/;
|
|
314
|
+
const WHITESPACE_REGEX = /\s/g;
|
|
315
|
+
function capitalizeFirstLetter(string) {
|
|
316
|
+
if (string === "") {
|
|
317
|
+
return string;
|
|
318
|
+
}
|
|
319
|
+
const firstCharacter = string.charAt(0);
|
|
320
|
+
const capitalizedFirstLetter = firstCharacter.toUpperCase();
|
|
321
|
+
const restOfString = string.slice(1);
|
|
322
|
+
return `${capitalizedFirstLetter}${restOfString}`;
|
|
323
|
+
}
|
|
324
|
+
function escapeHTMLCharacters(string) {
|
|
325
|
+
return string.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
326
|
+
}
|
|
327
|
+
function getNumConsecutiveDiacritics(string) {
|
|
328
|
+
const normalizedString = string.normalize("NFD");
|
|
329
|
+
let numConsecutiveDiacritic = 0;
|
|
330
|
+
let maxConsecutiveDiacritic = 0;
|
|
331
|
+
for (const character of normalizedString) {
|
|
332
|
+
if (hasDiacritic(character)) {
|
|
333
|
+
numConsecutiveDiacritic++;
|
|
334
|
+
if (numConsecutiveDiacritic > maxConsecutiveDiacritic) {
|
|
335
|
+
maxConsecutiveDiacritic = numConsecutiveDiacritic;
|
|
336
|
+
}
|
|
337
|
+
} else {
|
|
338
|
+
numConsecutiveDiacritic = 0;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
return maxConsecutiveDiacritic;
|
|
342
|
+
}
|
|
343
|
+
function hasDiacritic(string) {
|
|
344
|
+
const normalizedString = string.normalize("NFD");
|
|
345
|
+
return DIACRITIC_REGEX.test(normalizedString);
|
|
346
|
+
}
|
|
347
|
+
function hasEmoji(string) {
|
|
348
|
+
return EMOJI_REGEX.test(string);
|
|
349
|
+
}
|
|
350
|
+
function hasWhitespace(string) {
|
|
351
|
+
return WHITESPACE_REGEX.test(string);
|
|
352
|
+
}
|
|
353
|
+
function isFirstLetterCapitalized(string) {
|
|
354
|
+
return FIRST_LETTER_CAPITALIZED_REGEX.test(string);
|
|
355
|
+
}
|
|
356
|
+
function isKebabCase(string) {
|
|
357
|
+
return KEBAB_CASE_REGEX.test(string);
|
|
358
|
+
}
|
|
359
|
+
function isSemanticVersion(versionString) {
|
|
360
|
+
const match = versionString.match(SEMANTIC_VERSION_REGEX);
|
|
361
|
+
return match !== null;
|
|
362
|
+
}
|
|
363
|
+
function kebabCaseToCamelCase(string) {
|
|
364
|
+
return string.replaceAll(/-./g, (match) => {
|
|
365
|
+
const firstLetterOfWord = match[1];
|
|
366
|
+
return firstLetterOfWord === void 0 ? "" : firstLetterOfWord.toUpperCase();
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
function normalizeString(string) {
|
|
370
|
+
let sanitizedString = string;
|
|
371
|
+
sanitizedString = removeNonPrintableCharacters(sanitizedString);
|
|
372
|
+
sanitizedString = sanitizedString.replaceAll("\n\r", "\n");
|
|
373
|
+
sanitizedString = sanitizedString.replaceAll(/\p{Zl}/gu, "\n");
|
|
374
|
+
sanitizedString = sanitizedString.replaceAll(/\p{Zp}/gu, "\n");
|
|
375
|
+
sanitizedString = sanitizedString.replaceAll(/\p{Zs}/gu, " ");
|
|
376
|
+
sanitizedString = sanitizedString.trim();
|
|
377
|
+
return sanitizedString;
|
|
378
|
+
}
|
|
379
|
+
function parseSemanticVersion(versionString) {
|
|
380
|
+
const match = versionString.match(SEMANTIC_VERSION_REGEX);
|
|
381
|
+
if (match === null || match.groups === void 0) {
|
|
382
|
+
return void 0;
|
|
383
|
+
}
|
|
384
|
+
const { major, minor, patch } = match.groups;
|
|
385
|
+
if (major === void 0 || minor === void 0 || patch === void 0) {
|
|
386
|
+
return void 0;
|
|
387
|
+
}
|
|
388
|
+
const majorVersion = parseIntSafe(major);
|
|
389
|
+
const minorVersion = parseIntSafe(minor);
|
|
390
|
+
const patchVersion = parseIntSafe(patch);
|
|
391
|
+
if (majorVersion === void 0 || minorVersion === void 0 || patchVersion === void 0) {
|
|
392
|
+
return void 0;
|
|
393
|
+
}
|
|
394
|
+
return { majorVersion, minorVersion, patchVersion };
|
|
395
|
+
}
|
|
396
|
+
function removeLinesBetweenMarkers(string, marker) {
|
|
397
|
+
const lines = string.split("\n");
|
|
398
|
+
const newLines = [];
|
|
399
|
+
let skippingLines = false;
|
|
400
|
+
for (const line of lines) {
|
|
401
|
+
if (line.includes(`${marker}-start`)) {
|
|
402
|
+
skippingLines = true;
|
|
403
|
+
continue;
|
|
404
|
+
}
|
|
405
|
+
if (line.includes(`${marker}-end`)) {
|
|
406
|
+
skippingLines = false;
|
|
407
|
+
continue;
|
|
408
|
+
}
|
|
409
|
+
if (!skippingLines) {
|
|
410
|
+
newLines.push(line);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
return newLines.join("\n");
|
|
414
|
+
}
|
|
415
|
+
function removeLinesMatching(string, match) {
|
|
416
|
+
const lines = string.split("\n");
|
|
417
|
+
const newLines = lines.filter((line) => !line.includes(match));
|
|
418
|
+
return newLines.join("\n");
|
|
419
|
+
}
|
|
420
|
+
function removeNonPrintableCharacters(string) {
|
|
421
|
+
return string.replaceAll(/\p{C}/gu, "");
|
|
422
|
+
}
|
|
423
|
+
function removeWhitespace(string) {
|
|
424
|
+
return string.replaceAll(WHITESPACE_REGEX, "");
|
|
425
|
+
}
|
|
426
|
+
function trimPrefix(string, prefix, trimAll = false) {
|
|
427
|
+
if (trimAll) {
|
|
428
|
+
const regExp = new RegExp(`^${prefix}+`, "g");
|
|
429
|
+
return string.replaceAll(regExp, "");
|
|
430
|
+
}
|
|
431
|
+
if (!string.startsWith(prefix)) {
|
|
432
|
+
return string;
|
|
433
|
+
}
|
|
434
|
+
return string.slice(prefix.length);
|
|
435
|
+
}
|
|
436
|
+
function trimSuffix(string, prefix) {
|
|
437
|
+
if (!string.endsWith(prefix)) {
|
|
438
|
+
return string;
|
|
439
|
+
}
|
|
440
|
+
const endCharacter = string.length - prefix.length;
|
|
441
|
+
return string.slice(0, endCharacter);
|
|
442
|
+
}
|
|
443
|
+
function truncateString(string, maxLength) {
|
|
444
|
+
if (string.length <= maxLength) {
|
|
445
|
+
return string;
|
|
446
|
+
}
|
|
447
|
+
return string.slice(0, maxLength);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
function* tupleEntries(tuple) {
|
|
451
|
+
yield* tuple.entries();
|
|
452
|
+
}
|
|
453
|
+
function* tupleKeys(tuple) {
|
|
454
|
+
yield* tuple.keys();
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
function isObject(variable) {
|
|
458
|
+
return typeof variable === "object" && variable !== null && !Array.isArray(variable);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
const ReadonlyMap = Map;
|
|
462
|
+
|
|
463
|
+
export { HOUR_IN_MILLISECONDS, MINUTE_IN_MILLISECONDS, ReadonlyMap, ReadonlySet, SECOND_IN_MILLISECONDS, addSetsToSet, arrayCopyTwoDimensional, arrayEquals, arrayRemove, arrayRemoveInPlace, assertDefined, assertNotNull, capitalizeFirstLetter, clamp, combineSets, copySet, eRange, emptyArray, escapeHTMLCharacters, filterMap, getEnumEntries, getEnumKeys, getEnumValues, getNumConsecutiveDiacritics, getRandomArrayElement, getRandomArrayIndex, getRandomInt, hasDiacritic, hasEmoji, hasWhitespace, iRange, includes, interfaceSatisfiesEnum, isArray, isEnumValue, isFirstLetterCapitalized, isKebabCase, isKeyOf, isObject, isSemanticVersion, kebabCaseToCamelCase, mapFilter, mapFind, newArray, noop, normalizeString, objectFilter, objectKeysToReadonlySet, objectKeysToSet, objectToMap, objectToReadonlyMap, objectValuesToReadonlySet, objectValuesToSet, parseFloatSafe, parseIntSafe, parseSemanticVersion, removeLinesBetweenMarkers, removeLinesMatching, removeNonPrintableCharacters, removeWhitespace, repeat, setAdd, setHas, sortCaseInsensitive, sumArray, todo, trimPrefix, trimSuffix, truncateString, tupleEntries, tupleKeys };
|
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "complete-common",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Helper functions for TypeScript projects.",
|
|
5
|
+
"keywords": [],
|
|
6
|
+
"homepage": "https://complete-js.github.io/",
|
|
7
|
+
"bugs": {
|
|
8
|
+
"url": "https://github.com/complete-ts/complete/issues"
|
|
9
|
+
},
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "git+https://github.com/complete-ts/complete.git"
|
|
13
|
+
},
|
|
14
|
+
"license": "MIT",
|
|
15
|
+
"author": "Zamiell",
|
|
16
|
+
"type": "module",
|
|
17
|
+
"exports": {
|
|
18
|
+
".": {
|
|
19
|
+
"import": "./dist/index.mjs",
|
|
20
|
+
"require": "./dist/index.cjs"
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"types": "./dist/index.d.ts",
|
|
24
|
+
"files": [
|
|
25
|
+
"dist",
|
|
26
|
+
"src",
|
|
27
|
+
"LICENSE",
|
|
28
|
+
"package.json",
|
|
29
|
+
"README.md"
|
|
30
|
+
],
|
|
31
|
+
"scripts": {
|
|
32
|
+
"build": "tsx ./scripts/build.ts",
|
|
33
|
+
"lint": "tsx ./scripts/lint.ts",
|
|
34
|
+
"test": "glob \"./src/**/*.test.ts\" --cmd=\"node --import tsx --test --test-reporter spec\""
|
|
35
|
+
}
|
|
36
|
+
}
|
package/src/constants.ts
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import type { WidenLiteral } from "../index.js";
|
|
2
|
+
import { ReadonlySet } from "../types/ReadonlySet.js";
|
|
3
|
+
import { getRandomInt } from "./random.js";
|
|
4
|
+
import { assertDefined } from "./utils.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Helper function to copy a two-dimensional array. Note that the sub-arrays will only be shallow
|
|
8
|
+
* copied (using the spread operator).
|
|
9
|
+
*/
|
|
10
|
+
// eslint-disable-next-line complete/no-mutable-return
|
|
11
|
+
export function arrayCopyTwoDimensional<T>(
|
|
12
|
+
array: ReadonlyArray<readonly T[]>,
|
|
13
|
+
): T[][] {
|
|
14
|
+
const copiedArray: T[][] = [];
|
|
15
|
+
|
|
16
|
+
for (const subArray of array) {
|
|
17
|
+
copiedArray.push([...subArray]);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return copiedArray;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Helper function for determining if two arrays contain the exact same elements. Note that this
|
|
25
|
+
* only performs a shallow comparison.
|
|
26
|
+
*/
|
|
27
|
+
export function arrayEquals<T>(
|
|
28
|
+
array1: readonly T[],
|
|
29
|
+
array2: readonly T[],
|
|
30
|
+
): boolean {
|
|
31
|
+
if (array1.length !== array2.length) {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return array1.every((array1Element, i) => {
|
|
36
|
+
const array2Element = array2[i];
|
|
37
|
+
return array1Element === array2Element;
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Builds a new array based on the original array without the specified element(s). Returns the new
|
|
43
|
+
* array. If the specified element(s) are not found in the array, it will simply return a shallow
|
|
44
|
+
* copy of the array.
|
|
45
|
+
*
|
|
46
|
+
* This function is variadic, meaning that you can specify N arguments to remove N elements.
|
|
47
|
+
*/
|
|
48
|
+
// eslint-disable-next-line complete/no-mutable-return
|
|
49
|
+
export function arrayRemove<T>(
|
|
50
|
+
originalArray: readonly T[],
|
|
51
|
+
...elementsToRemove: readonly T[]
|
|
52
|
+
): T[] {
|
|
53
|
+
const elementsToRemoveSet = new ReadonlySet(elementsToRemove);
|
|
54
|
+
|
|
55
|
+
const array: T[] = [];
|
|
56
|
+
for (const element of originalArray) {
|
|
57
|
+
if (!elementsToRemoveSet.has(element)) {
|
|
58
|
+
array.push(element);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return array;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Removes the specified element(s) from the array. If the specified element(s) are not found in the
|
|
67
|
+
* array, this function will do nothing.
|
|
68
|
+
*
|
|
69
|
+
* This function is variadic, meaning that you can specify N arguments to remove N elements.
|
|
70
|
+
*
|
|
71
|
+
* If there is more than one matching element in the array, this function will only remove the first
|
|
72
|
+
* matching element. If you want to remove all of the elements, use the `arrayRemoveAllInPlace`
|
|
73
|
+
* function instead.
|
|
74
|
+
*
|
|
75
|
+
* @returns The removed elements. This will be an empty array if no elements were removed.
|
|
76
|
+
*/
|
|
77
|
+
// eslint-disable-next-line complete/no-mutable-return
|
|
78
|
+
export function arrayRemoveInPlace<T>(
|
|
79
|
+
// eslint-disable-next-line complete/prefer-readonly-parameter-types
|
|
80
|
+
array: T[],
|
|
81
|
+
...elementsToRemove: readonly T[]
|
|
82
|
+
): T[] {
|
|
83
|
+
const removedElements: T[] = [];
|
|
84
|
+
|
|
85
|
+
for (const element of elementsToRemove) {
|
|
86
|
+
const index = array.indexOf(element);
|
|
87
|
+
if (index > -1) {
|
|
88
|
+
const removedElement = array.splice(index, 1);
|
|
89
|
+
removedElements.push(...removedElement);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return removedElements;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** Helper function to remove all of the elements in an array in-place. */
|
|
97
|
+
// eslint-disable-next-line complete/prefer-readonly-parameter-types
|
|
98
|
+
export function emptyArray<T>(array: T[]): void {
|
|
99
|
+
array.splice(0, array.length);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Helper function to perform a filter and a map at the same time. Similar to `Array.map`, provide a
|
|
104
|
+
* function that transforms a value, but return `undefined` if the value should be skipped. (Thus,
|
|
105
|
+
* this function cannot be used in situations where `undefined` can be a valid array element.)
|
|
106
|
+
*
|
|
107
|
+
* This function is useful because the `Array.map` method will always produce an array with the same
|
|
108
|
+
* amount of elements as the original array.
|
|
109
|
+
*
|
|
110
|
+
* This is named `filterMap` after the Rust function:
|
|
111
|
+
* https://doc.rust-lang.org/std/iter/struct.FilterMap.html
|
|
112
|
+
*/
|
|
113
|
+
// eslint-disable-next-line complete/no-mutable-return
|
|
114
|
+
export function filterMap<OldT, NewT>(
|
|
115
|
+
array: readonly OldT[],
|
|
116
|
+
func: (element: OldT) => NewT | undefined,
|
|
117
|
+
): NewT[] {
|
|
118
|
+
const filteredArray: NewT[] = [];
|
|
119
|
+
|
|
120
|
+
for (const element of array) {
|
|
121
|
+
const newElement = func(element);
|
|
122
|
+
if (newElement !== undefined) {
|
|
123
|
+
filteredArray.push(newElement);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return filteredArray;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Helper function to get a random element from the provided array.
|
|
132
|
+
*
|
|
133
|
+
* Note that this will only work with arrays that do not contain values of `undefined`, since the
|
|
134
|
+
* function uses `undefined` as an indication that the corresponding element does not exist.
|
|
135
|
+
*
|
|
136
|
+
* @param array The array to get an element from.
|
|
137
|
+
* @param exceptions Optional. An array of elements to skip over if selected.
|
|
138
|
+
*/
|
|
139
|
+
export function getRandomArrayElement<T>(
|
|
140
|
+
array: readonly T[],
|
|
141
|
+
exceptions: readonly T[] = [],
|
|
142
|
+
): T {
|
|
143
|
+
if (array.length === 0) {
|
|
144
|
+
throw new Error(
|
|
145
|
+
"Failed to get a random array element since the provided array is empty.",
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const arrayToUse =
|
|
150
|
+
exceptions.length > 0 ? arrayRemove(array, ...exceptions) : array;
|
|
151
|
+
const randomIndex = getRandomArrayIndex(arrayToUse);
|
|
152
|
+
const randomElement = arrayToUse[randomIndex];
|
|
153
|
+
assertDefined(
|
|
154
|
+
randomElement,
|
|
155
|
+
`Failed to get a random array element since the random index of ${randomIndex} was not valid.`,
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
return randomElement;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Helper function to get a random index from the provided array.
|
|
163
|
+
*
|
|
164
|
+
* @param array The array to get the index from.
|
|
165
|
+
* @param exceptions Optional. An array of indexes that will be skipped over when getting the random
|
|
166
|
+
* index. Default is an empty array.
|
|
167
|
+
*/
|
|
168
|
+
export function getRandomArrayIndex<T>(
|
|
169
|
+
array: readonly T[],
|
|
170
|
+
exceptions: readonly number[] = [],
|
|
171
|
+
): number {
|
|
172
|
+
if (array.length === 0) {
|
|
173
|
+
throw new Error(
|
|
174
|
+
"Failed to get a random array index since the provided array is empty.",
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return getRandomInt(0, array.length - 1, exceptions);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Similar to the `Array.includes` method, but works on a widened version of the array.
|
|
183
|
+
*
|
|
184
|
+
* This is useful when the normal `Array.includes` produces a type error from an array that uses an
|
|
185
|
+
* `as const` assertion.
|
|
186
|
+
*/
|
|
187
|
+
export function includes<T, TupleElement extends WidenLiteral<T>>(
|
|
188
|
+
array: readonly TupleElement[],
|
|
189
|
+
searchElement: WidenLiteral<T>,
|
|
190
|
+
): searchElement is TupleElement {
|
|
191
|
+
const widenedArray: ReadonlyArray<WidenLiteral<T>> = array;
|
|
192
|
+
return widenedArray.includes(searchElement);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/** A wrapper around `Array.isArray` that narrows to `unknown[]` instead of `any[]`. */
|
|
196
|
+
export function isArray(arg: unknown): arg is unknown[] {
|
|
197
|
+
return Array.isArray(arg);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/** Initializes an array with all elements containing the specified default value. */
|
|
201
|
+
// eslint-disable-next-line complete/no-mutable-return
|
|
202
|
+
export function newArray<T>(length: number, value: T): T[] {
|
|
203
|
+
return Array.from({ length }, () => value);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/** Helper function to sum every value in an array together. */
|
|
207
|
+
export function sumArray(array: readonly number[]): number {
|
|
208
|
+
return array.reduce((accumulator, element) => accumulator + element, 0);
|
|
209
|
+
}
|