@rebasepro/utils 0.17.3-canary.gdd23447 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -0
- package/dist/index.es.js +46 -1
- package/dist/index.es.js.map +1 -1
- package/dist/strings.d.ts +23 -0
- package/package.json +25 -21
- package/src/arrays.ts +0 -12
- package/src/dates.ts +0 -69
- package/src/fields.ts +0 -27
- package/src/flatten_object.ts +0 -49
- package/src/hash.ts +0 -12
- package/src/index.ts +0 -13
- package/src/names.ts +0 -186
- package/src/objects.ts +0 -459
- package/src/os.ts +0 -13
- package/src/plurals.ts +0 -188
- package/src/policy-names.ts +0 -65
- package/src/regexp.ts +0 -41
- package/src/sha1.ts +0 -98
- package/src/storage.ts +0 -148
- package/src/strings.ts +0 -117
package/src/objects.ts
DELETED
|
@@ -1,459 +0,0 @@
|
|
|
1
|
-
import hash from "object-hash";
|
|
2
|
-
import { GeoPoint } from "@rebasepro/types";
|
|
3
|
-
|
|
4
|
-
/** @private is the value an empty array? */
|
|
5
|
-
export const isEmptyArray = (value?: unknown) =>
|
|
6
|
-
Array.isArray(value) && value.length === 0;
|
|
7
|
-
|
|
8
|
-
/** @private is the given object a Function? */
|
|
9
|
-
export const isFunction = (obj: unknown): obj is (...args: unknown[]) => unknown =>
|
|
10
|
-
typeof obj === "function";
|
|
11
|
-
|
|
12
|
-
/** @private is the given object an integer? */
|
|
13
|
-
export const isInteger = (obj: unknown): boolean =>
|
|
14
|
-
String(Math.floor(Number(obj))) === String(obj);
|
|
15
|
-
|
|
16
|
-
/** @private is the given object a NaN? */
|
|
17
|
-
|
|
18
|
-
export const isNaN = (obj: unknown): boolean => obj !== obj;
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Segments that reach the prototype chain rather than a property of the object.
|
|
22
|
-
*
|
|
23
|
-
* The twin of this function in `@rebasepro/forms` could be made to write onto
|
|
24
|
-
* `Object.prototype` through a path of `__proto__.x`. This copy survives the
|
|
25
|
-
* write by accident — its `clone` always spreads into a fresh object, while the
|
|
26
|
-
* form engine's has a "preserve class instances" branch that hands back
|
|
27
|
-
* `Object.prototype` itself — but `getIn` still *reads* through the chain, and
|
|
28
|
-
* handing back `Object.prototype` is how a polluted value is read out again.
|
|
29
|
-
*
|
|
30
|
-
* Closed on both sides here, so the two implementations agree.
|
|
31
|
-
*/
|
|
32
|
-
const UNSAFE_PATH_SEGMENTS = new Set(["__proto__", "constructor", "prototype"]);
|
|
33
|
-
|
|
34
|
-
/** Whether any segment of this path would traverse the prototype chain. */
|
|
35
|
-
export function pathTraversesPrototype(path: string | string[]): boolean {
|
|
36
|
-
return toPath(path).some(segment => UNSAFE_PATH_SEGMENTS.has(segment));
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Whether writing this single key with `obj[key] = …` would reach the prototype
|
|
41
|
-
* chain instead of creating a property.
|
|
42
|
-
*
|
|
43
|
-
* The single-key counterpart of {@link pathTraversesPrototype}, for the many
|
|
44
|
-
* places that copy an object one key at a time. `JSON.parse` creates
|
|
45
|
-
* `__proto__` as an *own* property, so it survives `hasOwnProperty` — and then
|
|
46
|
-
* `target[key] = value` invokes the setter and replaces the target's prototype.
|
|
47
|
-
*/
|
|
48
|
-
export function isPrototypePollutingKey(key: string): boolean {
|
|
49
|
-
return UNSAFE_PATH_SEGMENTS.has(key);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Deeply get a value from an object via its path.
|
|
54
|
-
*/
|
|
55
|
-
export function getIn(
|
|
56
|
-
obj: Record<string, unknown> | unknown[] | unknown,
|
|
57
|
-
key: string | string[],
|
|
58
|
-
def?: unknown,
|
|
59
|
-
p = 0
|
|
60
|
-
) {
|
|
61
|
-
if (pathTraversesPrototype(key)) return def;
|
|
62
|
-
|
|
63
|
-
const path = toPath(key);
|
|
64
|
-
while (obj && p < path.length) {
|
|
65
|
-
obj = (obj as Record<string, unknown>)[path[p++]];
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// check if path is not in the end
|
|
69
|
-
if (p !== path.length && !obj) {
|
|
70
|
-
return def;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
return obj === undefined ? def : obj;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
export function setIn<T>(obj: T, path: string, value: unknown): T {
|
|
77
|
-
// See `pathTraversesPrototype`. This copy's `clone` happens to contain the
|
|
78
|
-
// write, but relying on that is relying on an implementation detail of a
|
|
79
|
-
// different function.
|
|
80
|
-
if (pathTraversesPrototype(path)) return obj;
|
|
81
|
-
|
|
82
|
-
const res = clone(obj) as Record<string, unknown>;
|
|
83
|
-
let resVal: Record<string, unknown> = res;
|
|
84
|
-
let i = 0;
|
|
85
|
-
const pathArray = toPath(path);
|
|
86
|
-
|
|
87
|
-
for (; i < pathArray.length - 1; i++) {
|
|
88
|
-
const currentPath: string = pathArray[i];
|
|
89
|
-
const currentObj = getIn(obj as Record<string, unknown>, pathArray.slice(0, i + 1));
|
|
90
|
-
|
|
91
|
-
if (currentObj && (isObject(currentObj) || Array.isArray(currentObj))) {
|
|
92
|
-
resVal = resVal[currentPath] = clone(currentObj) as Record<string, unknown>;
|
|
93
|
-
} else {
|
|
94
|
-
const nextPath: string = pathArray[i + 1];
|
|
95
|
-
resVal = resVal[currentPath] =
|
|
96
|
-
(isInteger(nextPath) && Number(nextPath) >= 0 ? [] : {}) as Record<string, unknown>;
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// Return original object if new value is the same as current
|
|
101
|
-
if ((i === 0 ? obj as Record<string, unknown> : resVal)[pathArray[i]] === value) {
|
|
102
|
-
return obj;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
if (value === undefined) {
|
|
106
|
-
delete resVal[pathArray[i]];
|
|
107
|
-
} else {
|
|
108
|
-
resVal[pathArray[i]] = value;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// If the path array has a single element, the loop did not run.
|
|
112
|
-
// Deleting on `resVal` had no effect in this scenario, so we delete on the result instead.
|
|
113
|
-
if (i === 0 && value === undefined) {
|
|
114
|
-
delete res[pathArray[i]];
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
return res as T;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
export function clone<T>(value: T): T {
|
|
121
|
-
if (Array.isArray(value)) {
|
|
122
|
-
return [...value] as T;
|
|
123
|
-
} else if (typeof value === "object" && value !== null) {
|
|
124
|
-
return { ...value } as T;
|
|
125
|
-
} else {
|
|
126
|
-
return value; // This is for primitive types which do not need cloning.
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
/**
|
|
131
|
-
* Deep clone a value, preserving function references and class instances.
|
|
132
|
-
* Unlike structuredClone, this handles objects that contain functions
|
|
133
|
-
* (e.g. CollectionConfig with target(), childCollections(), callbacks).
|
|
134
|
-
*/
|
|
135
|
-
export function deepClone<T>(value: T): T {
|
|
136
|
-
if (value === null || value === undefined) return value;
|
|
137
|
-
if (typeof value === "function") return value;
|
|
138
|
-
if (typeof value !== "object") return value;
|
|
139
|
-
|
|
140
|
-
if (Array.isArray(value)) {
|
|
141
|
-
return value.map(item => deepClone(item)) as T;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
// Preserve class instances (Date, GeoPoint, etc.) — don't recurse
|
|
145
|
-
if (Object.getPrototypeOf(value) !== Object.prototype) {
|
|
146
|
-
return value;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
const result: Record<string, unknown> = {};
|
|
150
|
-
for (const key of Object.keys(value)) {
|
|
151
|
-
result[key] = deepClone((value as Record<string, unknown>)[key]);
|
|
152
|
-
}
|
|
153
|
-
return result as T;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
function toPath(value: string | string[]) {
|
|
157
|
-
if (Array.isArray(value)) return value; // Already in path array form.
|
|
158
|
-
// Replace brackets with dots, remove leading/trailing dots, then split by dot.
|
|
159
|
-
return value.replace(/\[(\d+)]/g, ".$1").replace(/^\./, "").replace(/\.$/, "").split(".");
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
export const pick: <T extends Record<string, unknown>>(obj: T, ...args: (keyof T)[]) => Partial<T> = <T extends Record<string, unknown>>(obj: T, ...args: (keyof T)[]) => ({
|
|
164
|
-
...args.reduce<Record<string, unknown>>((res, key) => ({
|
|
165
|
-
...res,
|
|
166
|
-
[key as string]: obj[key as string]
|
|
167
|
-
}), {})
|
|
168
|
-
}) as Partial<T>;
|
|
169
|
-
|
|
170
|
-
export function isObject(item: unknown): item is Record<string, unknown> {
|
|
171
|
-
return !!item && typeof item === "object" && !Array.isArray(item);
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
export function isPlainObject(obj: unknown): obj is Record<string, unknown> {
|
|
175
|
-
// 1. Rule out non-objects, null, and arrays
|
|
176
|
-
if (typeof obj !== "object" || obj === null || Array.isArray(obj)) {
|
|
177
|
-
return false;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
// 2. Get the object's direct prototype
|
|
181
|
-
const proto = Object.getPrototypeOf(obj);
|
|
182
|
-
|
|
183
|
-
// 3. A plain object's direct prototype is Object.prototype
|
|
184
|
-
return proto === Object.prototype;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
export function mergeDeep<T extends object, U extends object>(
|
|
188
|
-
target: T,
|
|
189
|
-
source: U,
|
|
190
|
-
ignoreUndefined = false
|
|
191
|
-
): T & U {
|
|
192
|
-
// If target is not a true object (e.g., null, array, primitive), return target itself.
|
|
193
|
-
if (!isObject(target)) {
|
|
194
|
-
return target as T & U;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
// Create a shallow copy of the target to avoid modifying the original object.
|
|
198
|
-
const output = { ...target };
|
|
199
|
-
|
|
200
|
-
// If source is not a true object, there's nothing to merge from it.
|
|
201
|
-
// Return the shallow copy of target.
|
|
202
|
-
if (!isObject(source)) {
|
|
203
|
-
return output as T & U;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
// Iterate over keys in the source object.
|
|
207
|
-
for (const key in source) {
|
|
208
|
-
if (key === "__proto__" || key === "constructor" || key === "prototype") {
|
|
209
|
-
continue;
|
|
210
|
-
}
|
|
211
|
-
if (Object.prototype.hasOwnProperty.call(source, key)) {
|
|
212
|
-
const sourceValue = source[key];
|
|
213
|
-
const outputValue = (output as Record<string, unknown>)[key]; // Current value in our merged object (originating from target)
|
|
214
|
-
|
|
215
|
-
// Skip if source value is undefined and ignoreUndefined is true.
|
|
216
|
-
// This handles both not adding new undefined properties and not overwriting existing properties with undefined.
|
|
217
|
-
if (ignoreUndefined && sourceValue === undefined) {
|
|
218
|
-
continue;
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
if (sourceValue instanceof Date) {
|
|
222
|
-
// If source value is a Date, create a new Date instance.
|
|
223
|
-
(output as Record<string, unknown>)[key] = new Date(sourceValue.getTime());
|
|
224
|
-
} else if (Array.isArray(sourceValue)) {
|
|
225
|
-
if (Array.isArray(outputValue)) {
|
|
226
|
-
// If the array contains primitives or class instances (non-plain objects),
|
|
227
|
-
// overwrite the array entirely instead of doing element-wise merging.
|
|
228
|
-
const hasPlainObjects = sourceValue.some(isPlainObject) || outputValue.some(isPlainObject);
|
|
229
|
-
if (!hasPlainObjects) {
|
|
230
|
-
(output as Record<string, unknown>)[key] = [...sourceValue];
|
|
231
|
-
} else {
|
|
232
|
-
const newArray = [];
|
|
233
|
-
const maxLength = Math.max(outputValue.length, sourceValue.length);
|
|
234
|
-
for (let i = 0; i < maxLength; i++) {
|
|
235
|
-
const sourceItem = sourceValue[i];
|
|
236
|
-
const targetItem = outputValue[i];
|
|
237
|
-
|
|
238
|
-
if (i >= sourceValue.length) { // source is shorter
|
|
239
|
-
newArray[i] = targetItem;
|
|
240
|
-
} else if (i >= outputValue.length) { // target is shorter
|
|
241
|
-
newArray[i] = sourceItem;
|
|
242
|
-
} else if (sourceItem === null) {
|
|
243
|
-
newArray[i] = targetItem;
|
|
244
|
-
} else if (isPlainObject(sourceItem) && isPlainObject(targetItem)) {
|
|
245
|
-
// Only recursively merge plain objects, preserve class instances
|
|
246
|
-
newArray[i] = mergeDeep(targetItem, sourceItem, ignoreUndefined);
|
|
247
|
-
} else {
|
|
248
|
-
// For class instances and primitives, use source directly
|
|
249
|
-
newArray[i] = sourceItem;
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
(output as Record<string, unknown>)[key] = newArray;
|
|
253
|
-
}
|
|
254
|
-
} else {
|
|
255
|
-
// If output's value (from target) is not an array,
|
|
256
|
-
// overwrite with a shallow copy of the source array.
|
|
257
|
-
(output as Record<string, unknown>)[key] = [...sourceValue];
|
|
258
|
-
}
|
|
259
|
-
} else if (isPlainObject(sourceValue)) {
|
|
260
|
-
// If source value is a plain object (not a class instance like EntityReference, GeoPoint, etc.):
|
|
261
|
-
if (isPlainObject(outputValue)) {
|
|
262
|
-
// If the corresponding value in output (from target) is also a plain object, recurse.
|
|
263
|
-
// Ensure the ignoreUndefined flag is passed down.
|
|
264
|
-
(output as Record<string, unknown>)[key] = mergeDeep(outputValue as Record<string, unknown>, sourceValue, ignoreUndefined);
|
|
265
|
-
} else {
|
|
266
|
-
// If output's value (from target) is not a plain object (e.g., null, primitive, class instance, or key didn't exist in original target),
|
|
267
|
-
// overwrite with the source object.
|
|
268
|
-
(output as Record<string, unknown>)[key] = sourceValue;
|
|
269
|
-
}
|
|
270
|
-
} else if (isObject(sourceValue)) {
|
|
271
|
-
// If source value is a class instance (not a plain object), use it directly to preserve prototype
|
|
272
|
-
(output as Record<string, unknown>)[key] = sourceValue;
|
|
273
|
-
} else {
|
|
274
|
-
// If source value is a primitive, null, or undefined (and not ignored).
|
|
275
|
-
(output as Record<string, unknown>)[key] = sourceValue;
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
return output as T & U;
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
export function getValueInPath(o: object | undefined, path: string): unknown {
|
|
284
|
-
if (!o) return undefined;
|
|
285
|
-
if (typeof o === "object") {
|
|
286
|
-
if (path in o) {
|
|
287
|
-
return (o as Record<string, unknown>)[path];
|
|
288
|
-
}
|
|
289
|
-
if (path.includes(".") || path.includes("[")) {
|
|
290
|
-
let pathSegments = path.split(/[.[]/);
|
|
291
|
-
if (path.includes("[")) {
|
|
292
|
-
pathSegments = pathSegments.map(segment => segment.replace("]", ""));
|
|
293
|
-
}
|
|
294
|
-
const firstSegment = pathSegments[0];
|
|
295
|
-
const isArrayAndIndexExists = Array.isArray((o as Record<string, unknown>)[firstSegment]) && !isNaN(parseInt(pathSegments[1]));
|
|
296
|
-
const nextObject = isArrayAndIndexExists
|
|
297
|
-
? ((o as Record<string, unknown>)[firstSegment] as unknown[])[parseInt(pathSegments[1])]
|
|
298
|
-
: (o as Record<string, unknown>)[firstSegment];
|
|
299
|
-
|
|
300
|
-
const nextPath = pathSegments.slice(isArrayAndIndexExists ? 2 : 1).join(".");
|
|
301
|
-
if (nextPath === "")
|
|
302
|
-
return nextObject;
|
|
303
|
-
return getValueInPath(nextObject as object | undefined, nextPath);
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
return undefined;
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
export function removeInPath(o: object, path: string): object | undefined {
|
|
310
|
-
const res = clone(o) as Record<string, unknown>;
|
|
311
|
-
let current = res;
|
|
312
|
-
const parts = path.split(".");
|
|
313
|
-
const last = parts.pop();
|
|
314
|
-
for (const part of parts) {
|
|
315
|
-
if (part in current && current[part] !== null && typeof current[part] === "object") {
|
|
316
|
-
current[part] = clone(current[part]) as Record<string, unknown>;
|
|
317
|
-
current = current[part] as Record<string, unknown>;
|
|
318
|
-
} else {
|
|
319
|
-
return res;
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
if (last && current && typeof current === "object") {
|
|
323
|
-
delete current[last];
|
|
324
|
-
}
|
|
325
|
-
return res;
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
export function removeFunctions(o: unknown): unknown {
|
|
329
|
-
if (o === undefined) return undefined;
|
|
330
|
-
if (o === null) return null;
|
|
331
|
-
if (typeof o === "object") {
|
|
332
|
-
// Handle arrays first - drop function elements, then recurse.
|
|
333
|
-
// Only object *properties* used to be filtered, so a function sitting
|
|
334
|
-
// directly in an array survived — and the callers strip functions
|
|
335
|
-
// precisely because a function survives no deep comparison.
|
|
336
|
-
if (Array.isArray(o)) {
|
|
337
|
-
return o
|
|
338
|
-
.filter(v => typeof v !== "function")
|
|
339
|
-
.map(v => removeFunctions(v));
|
|
340
|
-
}
|
|
341
|
-
// Preserve class instances (EntityReference, GeoPoint, etc.) - don't recurse into them
|
|
342
|
-
if (!isPlainObject(o)) {
|
|
343
|
-
return o;
|
|
344
|
-
}
|
|
345
|
-
return Object.entries(o)
|
|
346
|
-
.filter(([_, value]) => typeof value !== "function")
|
|
347
|
-
.reduce<Record<string, unknown>>((acc, [key, value]) => {
|
|
348
|
-
acc[key] = removeFunctions(value);
|
|
349
|
-
return acc;
|
|
350
|
-
}, {});
|
|
351
|
-
}
|
|
352
|
-
return o;
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
export function getHashValue<T>(v: T): string | null {
|
|
356
|
-
if (!v) return null;
|
|
357
|
-
if (typeof v === "object" && v !== null) {
|
|
358
|
-
if ("id" in v)
|
|
359
|
-
return String((v as Record<string, unknown>).id);
|
|
360
|
-
else if (v instanceof Date)
|
|
361
|
-
return v.toLocaleString();
|
|
362
|
-
else if (v instanceof GeoPoint)
|
|
363
|
-
return hash(v as Record<string, unknown>);
|
|
364
|
-
}
|
|
365
|
-
return hash(v as object, { ignoreUnknown: true });
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
export function removeUndefined(value: unknown, removeEmptyStrings?: boolean): unknown {
|
|
369
|
-
if (typeof value === "function") {
|
|
370
|
-
return value;
|
|
371
|
-
}
|
|
372
|
-
if (Array.isArray(value)) {
|
|
373
|
-
return value.map((v: unknown) => removeUndefined(v, removeEmptyStrings));
|
|
374
|
-
}
|
|
375
|
-
if (typeof value === "object") {
|
|
376
|
-
if (value === null)
|
|
377
|
-
return value;
|
|
378
|
-
// Preserve class instances (EntityReference, GeoPoint, etc.) - don't recurse into them
|
|
379
|
-
if (!isPlainObject(value)) {
|
|
380
|
-
return value;
|
|
381
|
-
}
|
|
382
|
-
const res: Record<string, unknown> = {};
|
|
383
|
-
Object.keys(value).forEach((key) => {
|
|
384
|
-
if (!isEmptyObject(value as object)) {
|
|
385
|
-
const childRes = removeUndefined((value as Record<string, unknown>)[key], removeEmptyStrings);
|
|
386
|
-
const isString = typeof childRes === "string";
|
|
387
|
-
const shouldKeepIfString = !removeEmptyStrings || (removeEmptyStrings && !isString) || (removeEmptyStrings && isString && childRes !== "");
|
|
388
|
-
if (childRes !== undefined && !isEmptyObject(childRes as object) && shouldKeepIfString)
|
|
389
|
-
res[key] = childRes;
|
|
390
|
-
}
|
|
391
|
-
});
|
|
392
|
-
return res;
|
|
393
|
-
}
|
|
394
|
-
return value;
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
export function removeNulls(value: unknown): unknown {
|
|
398
|
-
if (typeof value === "function") {
|
|
399
|
-
return value;
|
|
400
|
-
}
|
|
401
|
-
if (Array.isArray(value)) {
|
|
402
|
-
return value.map((v: unknown) => removeNulls(v));
|
|
403
|
-
}
|
|
404
|
-
if (typeof value === "object") {
|
|
405
|
-
if (value === null)
|
|
406
|
-
return value;
|
|
407
|
-
// Preserve class instances (EntityReference, GeoPoint, etc.) - don't recurse into them
|
|
408
|
-
if (!isPlainObject(value)) {
|
|
409
|
-
return value;
|
|
410
|
-
}
|
|
411
|
-
const res: Record<string, unknown> = {};
|
|
412
|
-
const obj = value as Record<string, unknown>;
|
|
413
|
-
Object.keys(obj).forEach((key) => {
|
|
414
|
-
if (obj[key] !== null)
|
|
415
|
-
res[key] = removeNulls(obj[key]);
|
|
416
|
-
});
|
|
417
|
-
return res;
|
|
418
|
-
}
|
|
419
|
-
return value;
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
export function isEmptyObject(obj: object) {
|
|
423
|
-
return obj &&
|
|
424
|
-
Object.getPrototypeOf(obj) === Object.prototype &&
|
|
425
|
-
Object.keys(obj).length === 0
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
export function removePropsIfExisting(source: Record<string, unknown> | unknown[], comparison: Record<string, unknown> | unknown[]) {
|
|
429
|
-
const isObject = (val: unknown): val is Record<string, unknown> => typeof val === "object" && val !== null;
|
|
430
|
-
const isArray = (val: unknown): val is unknown[] => Array.isArray(val);
|
|
431
|
-
|
|
432
|
-
if (!isObject(source) || !isObject(comparison)) {
|
|
433
|
-
return source;
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
const res = isArray(source) ? [...source] : { ...source };
|
|
437
|
-
|
|
438
|
-
if (isArray(res)) {
|
|
439
|
-
for (let i = res.length - 1; i >= 0; i--) {
|
|
440
|
-
if (res[i] === comparison[i]) {
|
|
441
|
-
res.splice(i, 1);
|
|
442
|
-
} else if (isObject(res[i]) && isObject(comparison[i])) {
|
|
443
|
-
res[i] = removePropsIfExisting(res[i] as unknown as Record<string, unknown>, (comparison as unknown as unknown[])[i] as Record<string, unknown>);
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
|
-
} else {
|
|
447
|
-
Object.keys(comparison).forEach(key => {
|
|
448
|
-
if (key in res) {
|
|
449
|
-
if (isObject(res[key]) && isObject(comparison[key])) {
|
|
450
|
-
res[key] = removePropsIfExisting(res[key], comparison[key]);
|
|
451
|
-
} else if (res[key] === comparison[key]) {
|
|
452
|
-
delete res[key];
|
|
453
|
-
}
|
|
454
|
-
}
|
|
455
|
-
});
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
return res;
|
|
459
|
-
}
|
package/src/os.ts
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
export function getOS() {
|
|
2
|
-
let OS = "Unknown";
|
|
3
|
-
if (navigator.userAgent.indexOf("Win") !== -1) OS = "Windows";
|
|
4
|
-
if (navigator.userAgent.indexOf("Mac") !== -1) OS = "MacOS";
|
|
5
|
-
if (navigator.userAgent.indexOf("X11") !== -1) OS = "UNIX";
|
|
6
|
-
if (navigator.userAgent.indexOf("Linux") !== -1) OS = "Linux";
|
|
7
|
-
return OS;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export function getAltSymbol() {
|
|
11
|
-
if (getOS() === "MacOS") return "⌥";
|
|
12
|
-
return "Alt";
|
|
13
|
-
}
|
package/src/plurals.ts
DELETED
|
@@ -1,188 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Returns the plural of an English word.
|
|
3
|
-
*
|
|
4
|
-
* @param {string} word
|
|
5
|
-
* @param {number} [amount]
|
|
6
|
-
* @returns {string}
|
|
7
|
-
*/
|
|
8
|
-
export function plural(word: string, amount?: number): string {
|
|
9
|
-
if (amount !== undefined && amount === 1) {
|
|
10
|
-
return word
|
|
11
|
-
}
|
|
12
|
-
const plurals: { [key: string]: string } = {
|
|
13
|
-
"(quiz)$": "$1zes",
|
|
14
|
-
"^(ox)$": "$1en",
|
|
15
|
-
"([m|l])ouse$": "$1ice",
|
|
16
|
-
"(matr|vert|ind)ix|ex$": "$1ices",
|
|
17
|
-
"(x|ch|ss|sh)$": "$1es",
|
|
18
|
-
"([^aeiouy]|qu)y$": "$1ies",
|
|
19
|
-
"(hive)$": "$1s",
|
|
20
|
-
"(?:([^f])fe|([lr])f)$": "$1$2ves",
|
|
21
|
-
"(shea|lea|loa|thie)f$": "$1ves",
|
|
22
|
-
sis$: "ses",
|
|
23
|
-
"([ti])um$": "$1a",
|
|
24
|
-
"(tomat|potat|ech|her|vet)o$": "$1oes",
|
|
25
|
-
"(bu)s$": "$1ses",
|
|
26
|
-
"(alias)$": "$1es",
|
|
27
|
-
"(octop)us$": "$1i",
|
|
28
|
-
"(ax|test)is$": "$1es",
|
|
29
|
-
"(us)$": "$1es",
|
|
30
|
-
"([^s]+)$": "$1s"
|
|
31
|
-
}
|
|
32
|
-
const irregular: { [key: string]: string } = {
|
|
33
|
-
move: "moves",
|
|
34
|
-
foot: "feet",
|
|
35
|
-
goose: "geese",
|
|
36
|
-
sex: "sexes",
|
|
37
|
-
child: "children",
|
|
38
|
-
man: "men",
|
|
39
|
-
tooth: "teeth",
|
|
40
|
-
person: "people"
|
|
41
|
-
}
|
|
42
|
-
const uncountable: string[] = [
|
|
43
|
-
"sheep",
|
|
44
|
-
"fish",
|
|
45
|
-
"deer",
|
|
46
|
-
"moose",
|
|
47
|
-
"series",
|
|
48
|
-
"species",
|
|
49
|
-
"money",
|
|
50
|
-
"rice",
|
|
51
|
-
"information",
|
|
52
|
-
"equipment",
|
|
53
|
-
"bison",
|
|
54
|
-
"cod",
|
|
55
|
-
"offspring",
|
|
56
|
-
"pike",
|
|
57
|
-
"salmon",
|
|
58
|
-
"shrimp",
|
|
59
|
-
"swine",
|
|
60
|
-
"trout",
|
|
61
|
-
"aircraft",
|
|
62
|
-
"hovercraft",
|
|
63
|
-
"spacecraft",
|
|
64
|
-
"sugar",
|
|
65
|
-
"tuna",
|
|
66
|
-
"you",
|
|
67
|
-
"wood"
|
|
68
|
-
]
|
|
69
|
-
// save some time in the case that singular and plural are the same
|
|
70
|
-
if (uncountable.indexOf(word.toLowerCase()) >= 0) {
|
|
71
|
-
return word;
|
|
72
|
-
}
|
|
73
|
-
// check for irregular forms
|
|
74
|
-
for (const w in irregular) {
|
|
75
|
-
const pattern = new RegExp(`${w}$`, "i")
|
|
76
|
-
const replace = irregular[w]
|
|
77
|
-
if (pattern.test(word)) {
|
|
78
|
-
return word.replace(pattern, replace);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
// check for matches using regular expressions
|
|
82
|
-
for (const reg in plurals) {
|
|
83
|
-
const pattern = new RegExp(reg, "i")
|
|
84
|
-
if (pattern.test(word)) {
|
|
85
|
-
return word.replace(pattern, plurals[reg])
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
return word;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* Returns the singular of an English word.
|
|
93
|
-
*
|
|
94
|
-
* @param {string} word
|
|
95
|
-
* @param {number} [amount]
|
|
96
|
-
* @returns {string}
|
|
97
|
-
*/
|
|
98
|
-
export function singular(word: string, amount?: number): string {
|
|
99
|
-
if (amount !== undefined && amount !== 1) {
|
|
100
|
-
return word;
|
|
101
|
-
}
|
|
102
|
-
const singulars: { [key: string]: string } = {
|
|
103
|
-
"(quiz)zes$": "$1",
|
|
104
|
-
"(matr)ices$": "$1ix",
|
|
105
|
-
"(vert|ind)ices$": "$1ex",
|
|
106
|
-
"^(ox)en$": "$1",
|
|
107
|
-
"(alias)es$": "$1",
|
|
108
|
-
"(octop|vir)i$": "$1us",
|
|
109
|
-
"(cris|ax|test)es$": "$1is",
|
|
110
|
-
"(shoe)s$": "$1",
|
|
111
|
-
"(o)es$": "$1",
|
|
112
|
-
"(bus)es$": "$1",
|
|
113
|
-
"([m|l])ice$": "$1ouse",
|
|
114
|
-
"(x|ch|ss|sh)es$": "$1",
|
|
115
|
-
"(m)ovies$": "$1ovie",
|
|
116
|
-
"(s)eries$": "$1eries",
|
|
117
|
-
"([^aeiouy]|qu)ies$": "$1y",
|
|
118
|
-
"([lr])ves$": "$1f",
|
|
119
|
-
"(tive)s$": "$1",
|
|
120
|
-
"(hive)s$": "$1",
|
|
121
|
-
"(li|wi|kni)ves$": "$1fe",
|
|
122
|
-
"(shea|loa|lea|thie)ves$": "$1f",
|
|
123
|
-
"(^analy)ses$": "$1sis",
|
|
124
|
-
"((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$": "$1$2sis",
|
|
125
|
-
"([ti])a$": "$1um",
|
|
126
|
-
"(n)ews$": "$1ews",
|
|
127
|
-
"(h|bl)ouses$": "$1ouse",
|
|
128
|
-
"(corpse)s$": "$1",
|
|
129
|
-
"(us)es$": "$1",
|
|
130
|
-
s$: ""
|
|
131
|
-
}
|
|
132
|
-
const irregular: { [key: string]: string } = {
|
|
133
|
-
move: "moves",
|
|
134
|
-
foot: "feet",
|
|
135
|
-
goose: "geese",
|
|
136
|
-
sex: "sexes",
|
|
137
|
-
child: "children",
|
|
138
|
-
man: "men",
|
|
139
|
-
tooth: "teeth",
|
|
140
|
-
person: "people"
|
|
141
|
-
}
|
|
142
|
-
const uncountable: string[] = [
|
|
143
|
-
"sheep",
|
|
144
|
-
"fish",
|
|
145
|
-
"deer",
|
|
146
|
-
"moose",
|
|
147
|
-
"series",
|
|
148
|
-
"species",
|
|
149
|
-
"money",
|
|
150
|
-
"rice",
|
|
151
|
-
"information",
|
|
152
|
-
"equipment",
|
|
153
|
-
"bison",
|
|
154
|
-
"cod",
|
|
155
|
-
"offspring",
|
|
156
|
-
"pike",
|
|
157
|
-
"salmon",
|
|
158
|
-
"shrimp",
|
|
159
|
-
"swine",
|
|
160
|
-
"trout",
|
|
161
|
-
"aircraft",
|
|
162
|
-
"hovercraft",
|
|
163
|
-
"spacecraft",
|
|
164
|
-
"sugar",
|
|
165
|
-
"tuna",
|
|
166
|
-
"you",
|
|
167
|
-
"wood"
|
|
168
|
-
]
|
|
169
|
-
// save some time in the case that singular and plural are the same
|
|
170
|
-
if (uncountable.indexOf(word.toLowerCase()) >= 0) {
|
|
171
|
-
return word;
|
|
172
|
-
}
|
|
173
|
-
// check for irregular forms
|
|
174
|
-
for (const w in irregular) {
|
|
175
|
-
const pattern = new RegExp(`${irregular[w]}$`, "i");
|
|
176
|
-
if (pattern.test(word)) {
|
|
177
|
-
return word.replace(pattern, w);
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
// check for matches using regular expressions
|
|
181
|
-
for (const reg in singulars) {
|
|
182
|
-
const pattern = new RegExp(reg, "i");
|
|
183
|
-
if (pattern.test(word)) {
|
|
184
|
-
return word.replace(pattern, singulars[reg]);
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
return word;
|
|
188
|
-
}
|