-
Notifications
You must be signed in to change notification settings - Fork 337
/
Copy pathunderscore.ts
164 lines (145 loc) · 4.15 KB
/
underscore.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
/**
* Convert words to a sentence.
*
* @param items - An array of words to be joined.
* @returns A string with the items joined by a comma and the last item joined by ", or".
*/
export const toSentence = (items: string[]): string => {
// TODO: Once Safari supports it, use Intl.ListFormat
if (items.length == 0) {
return '';
}
if (items.length == 1) {
return items[0];
}
let sentence = items.slice(0, -1).join(', ');
sentence += `, or ${items.slice(-1)}`;
return sentence;
};
const IP_V4_ADDRESS_REGEX =
/^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
/**
* Checks if a string is a valid IPv4 address.
*
* @returns True if the string is a valid IPv4 address, false otherwise.
*/
export function isIPV4Address(str: string | undefined | null): boolean {
return IP_V4_ADDRESS_REGEX.test(str || '');
}
/**
* Converts the first character of a string to uppercase.
*
* @param str - The string to be converted.
* @returns The modified string with the rest of the string unchanged.
*
* @example
* ```ts
* titleize('hello world') // 'Hello world'
* ```
*/
export function titleize(str: string | undefined | null): string {
const s = str || '';
return s.charAt(0).toUpperCase() + s.slice(1);
}
/**
* Converts a string from snake_case to camelCase.
*/
export function snakeToCamel(str: string | undefined): string {
return str ? str.replace(/([-_][a-z])/g, match => match.toUpperCase().replace(/-|_/, '')) : '';
}
/**
* Converts a string from camelCase to snake_case.
*/
export function camelToSnake(str: string | undefined): string {
return str ? str.replace(/[A-Z]/g, letter => `_${letter.toLowerCase()}`) : '';
}
const createDeepObjectTransformer = (transform: any) => {
const deepTransform = (obj: any): any => {
if (!obj) {
return obj;
}
if (Array.isArray(obj)) {
return obj.map(el => {
if (typeof el === 'object' || Array.isArray(el)) {
return deepTransform(el);
}
return el;
});
}
const copy = { ...obj };
const keys = Object.keys(copy);
for (const oldName of keys) {
const newName = transform(oldName.toString());
if (newName !== oldName) {
copy[newName] = copy[oldName];
delete copy[oldName];
}
if (typeof copy[newName] === 'object') {
copy[newName] = deepTransform(copy[newName]);
}
}
return copy;
};
return deepTransform;
};
/**
* Transforms camelCased objects/ arrays to snake_cased.
* This function recursively traverses all objects and arrays of the passed value
* camelCased keys are removed.
*
* @function
*/
export const deepCamelToSnake = createDeepObjectTransformer(camelToSnake);
/**
* Transforms snake_cased objects/ arrays to camelCased.
* This function recursively traverses all objects and arrays of the passed value
* camelCased keys are removed.
*
* @function
*/
export const deepSnakeToCamel = createDeepObjectTransformer(snakeToCamel);
/**
* A function to determine if a value is truthy.
*
* @returns True for `true`, true, positive numbers. False for `false`, false, 0, negative integers and anything else.
*/
export function isTruthy(value: unknown): boolean {
// Return if Boolean
if (typeof value === `boolean`) {
return value;
}
// Return false if null or undefined
if (value === undefined || value === null) {
return false;
}
// If the String is true or false
if (typeof value === `string`) {
if (value.toLowerCase() === `true`) {
return true;
}
if (value.toLowerCase() === `false`) {
return false;
}
}
// Now check if it's a number
const number = parseInt(value as string, 10);
if (isNaN(number)) {
return false;
}
if (number > 0) {
return true;
}
// Default to false
return false;
}
/**
* Get all non-undefined values from an object.
*/
export function getNonUndefinedValues<T extends object>(obj: T): Partial<T> {
return Object.entries(obj).reduce((acc, [key, value]) => {
if (value !== undefined) {
acc[key as keyof T] = value;
}
return acc;
}, {} as Partial<T>);
}