-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathlib.ts
289 lines (264 loc) · 10.1 KB
/
lib.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
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
// Version 2.1
namespace Lib {
/**
* Use to create a run once version of a function
* Example usage:
*
* let myRunOnceFunc = once(function (value) {
* console.log('Fired!');
* return "Input value was " + value;
* });
*
* console.log(myRunOnceFunc(123)); //"Fired" and "Input value was 123"
* console.log(myRunOnceFunc(2)); //Only "Input value was 123"
*
* @param fn function to be converted
* @param context not sure, more testing needed
* @returns The run only once version of fn
*/
export function once(fn: Function, context: any): Function {
let result: Function;
return function () {
if (fn) {
// @ts-ignore: Using ignore because expect fails to find the error (I think because the error is with `this` and not the actual next line) - Not sure how to fix error and not worth spending time to fix and then test at this time
result = fn.apply(context || this, arguments);
fn = function () { };
}
return result;
};
}
/**
* Asserts the condition and if it fails throws an exception.
*
* Patterned on <https://gist.github.com/jeromeetienne/2651899>
*
* @param cond The condition that should be true
* @param text The text to show on assert failure
*/
export function assertOrDie(cond: boolean, text?: string) {
if (!cond)
throw new Error(text || "Assertion failed!");
}
/**
* Only considers the time and checks if `x` is greater than `y`
* @param a the first time to check
* @param b the second time to check
* @returns true if a > b, considering only time and false otherwise
*/
export function isTimeGreater(a: Date, b: Date): boolean {
return a.getHours() > b.getHours() ||
(a.getHours() === b.getHours() && a.getMinutes() > b.getMinutes());
}
/**
* Checks if two date/time values are on the same day (ignoring the time)
* @param a the first date to check
* @param b the second date to check
* @returns true if they are on the same day and false otherwise
*/
export function isSameDay(a: Date, b: Date): boolean {
return a.getDate() === b.getDate()
&& a.getMonth() === b.getMonth()
&& a.getFullYear() === b.getFullYear();
}
/**
* Check if the month of two dates is the same. NB: Must also be same year.
* @param {date} a the first date to check
* @param {date} b the second date to check
* @returns {Boolean} True if the month of the dates is the same otherwise false
*/
export function isSameMonth(a: Date, b: Date) {
return a.getMonth() === b.getMonth() && a.getFullYear() === b.getFullYear();
}
/**
* Returns only the date portion of the date passed
* @param aDate The datetime to extract the date from
* @returns Date portion of date passed in
*/
export function getDateOnly(aDate: Date): Date {
return new Date(aDate.getFullYear(), aDate.getMonth(), aDate.getDate());
}
/**
* Merges the date of one date with the time of another
* @param date_part Use only date part of this
* @param time_part Use only time part of this
* @returns A new date with the date of the first argument and the time of the second
*/
export function mergeDateAndTime(date_part: Date, time_part: Date): Date {
return new Date(
date_part.getFullYear(),
date_part.getMonth(),
date_part.getDate(),
time_part.getHours(),
time_part.getMinutes(),
time_part.getSeconds(),
time_part.getMilliseconds())
}
/**
* Prepends 0's if number has less than `minDigits` before the decimal point
* @param num The number to prepend the 0's to
* @param minDigits The minimum number of digits before the decimal point
* @returns The number prefixed with 0's as needed
*/
export function zeroPad(num: number, minDigits: number): string {
const intPart = Math.floor(num);
const digitsNeeded = minDigits - (intPart + '').length;
let prefix = '';
while (prefix.length < digitsNeeded) {
prefix += '0';
}
return prefix + num;
}
/**
* Check if the date/time value passed is on the Google Sheets Epoch (independent of the time)
* @param d the date to check
* @returns true if it is on the Epoch and false otherwise
*/
export function isOnEpochGSheets(d: Date): boolean {
return isSameDay(d, getEpochGSheets());
}
/**
* Returns the Epoch used by Google Sheets
* @returns Google Sheets Epoch
*/
export function getEpochGSheets(): Date {
return new Date(1899, 11, 30);
}
/**
* Converts a 2d array into a csv string
*
* @param arr The array to be converted
*/
export function generateCsvOutputFrom2dArray<T>(arr: T[][]): string {
let result = "";
if (arr.length === 0 || arr[0].length === 0) {
return result;
}
for (let row = 0; row < arr.length; row++) {
for (let col = 0; col < arr[0].length; col++) {
result += `${arr[row][col]},`;
}
result += '\n'
}
return result;
}
/**
* Each unique value in `arr` is assigned a number and those numbers are used to generate a new array that is
* returned along with a dictionary of what the values were converted to. This is helpful when the values in `arr`
* are too longer to be exported to say a string using `generateCsvOutputFrom2dArray`.
*
* NB: Values are converted to strings before comparison as this is used as the key to the dictionary.
*
* @param arr The values to be converted
*/
export function arrValuesToNums<T>(arr: T[][]): [number[][], Record<string, number>] {
let nextID = 1;
const records: Record<string, number> = {};
const converted: number[][] = [];
for (let row = 0; row < arr.length; row++) {
const newRowValues: number[] = [];
converted.push(newRowValues);
for (let col = 0; col < arr[0].length; col++) {
const arrValueAsStr = `${arr[row][col]}`;
let value = records[arrValueAsStr];
if (value === undefined) {
records[arrValueAsStr] = nextID++;
value = records[arrValueAsStr]
assertOrDie(records[arrValueAsStr] !== undefined, "Logic error this value was just supposed to have been set");
}
newRowValues.push(value);
}
}
return [converted, records];
}
/**
* Returns true if any of the elements in the array are exactly equal to searchValue
*/
export function arrayIncludes(searchValue: any, arr: any[]): boolean {
for (let i = 0; i < arr.length; i++) {
if (arr[i] === searchValue) {
return true;
}
}
return false;
}
/**
* Asserts that the input is a number
*/
export function assertIsNumber(val: any, msg_prefix?: string): number {
// TODO 4: Try to write generic version
if (typeof val !== 'number') {
throw Error(`${(msg_prefix === undefined) ? '' : msg_prefix + ' '}Expected a number but got "${val}" of type "${typeof val}"`);
} else {
return val;
}
}
/**
* Asserts that the input is a string
*/
export function assertIsString(val: any, msg_prefix?: string): string {
// TODO 4: Try to write generic version
if (typeof val !== 'string') {
throw Error(`${(msg_prefix === undefined) ? '' : msg_prefix + ' '}Expected a string but got "${val}" of type "${typeof val}"`);
} else {
return val;
}
}
/**
* Asserts that the input is a date (Fails if objects are passed across frame boundaries)
*/
export function assertIsDate(val: any, msg_prefix?: string): Date {
if (val instanceof Date) {
return val;
} else {
throw Error(`${(msg_prefix === undefined) ? '' : msg_prefix + ' '}Expected a date but got "${val}" of type "${typeof val}"`);
}
}
/**
* Ensures all inner arrays are the same length by increasing their length to the max length
*/
export function makeAllInnerArraysSameLength(arr: any[][]) {
// Get max inner length
let max = 0;
for (let i = 0; i < arr.length; i++) {
if (arr[i] === undefined) {
arr[i] = [];
}
if (arr[i].length > max) {
max = arr[i].length;
}
}
// Set all to max
for (let i = 0; i < arr.length; i++) {
if (arr[i].length < max) {
arr[i][max - 1] = '';
}
}
}
/**
* Throws and Error if all the inner arrays are not the same length
* @param arr Array to be checked
*/
export function assertArrayIsRectangular(arr: any[][]) {
if (arr.length == 0) {
return;
}
const expected_length = arr[0].length;
for (let i = 1; i < arr.length; i++) {
if (arr[i].length !== expected_length) {
throw new Error(`All inner arrays are not the same length. The first was ${expected_length} but the one in position ${i} is ${arr[i].length}`);
}
}
}
export function assertAllElementsAreString(arr: any[][], name: string = "all elements"): string[][] {
// TODO 4: Try to make a generic version
for (let row = 0; row < arr.length; row++) {
for (let col = 0; col < arr[0].length; col++) {
const element = arr[row][col];
if (typeof element != 'string') {
throw new Error(`Expected ${name} to be of type string but got '${element}' of type ${typeof element} in row: ${row}, col: ${col}`);
}
}
}
return arr;
}
}