topkat-utils 1.0.60 → 1.1.2
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/CHANGELOG.md +4 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.js +56 -0
- package/dist/index.js.map +1 -0
- package/dist/src/array-utils.d.ts +56 -0
- package/dist/src/array-utils.js +138 -0
- package/dist/src/array-utils.js.map +1 -0
- package/dist/src/date-utils.d.ts +100 -0
- package/dist/src/date-utils.js +357 -0
- package/dist/src/date-utils.js.map +1 -0
- package/dist/src/env-utils.d.ts +8 -0
- package/dist/src/env-utils.js +38 -0
- package/dist/src/env-utils.js.map +1 -0
- package/dist/src/error-utils.d.ts +8 -0
- package/dist/src/error-utils.js +99 -0
- package/dist/src/error-utils.js.map +1 -0
- package/dist/src/is-empty.d.ts +1 -0
- package/dist/src/is-empty.js +13 -0
- package/dist/src/is-empty.js.map +1 -0
- package/dist/src/is-object.d.ts +2 -0
- package/dist/src/is-object.js +7 -0
- package/dist/src/is-object.js.map +1 -0
- package/dist/src/isset.d.ts +1 -0
- package/dist/src/isset.js +8 -0
- package/dist/src/isset.js.map +1 -0
- package/dist/src/logger-utils.d.ts +76 -0
- package/dist/src/logger-utils.js +355 -0
- package/dist/src/logger-utils.js.map +1 -0
- package/dist/src/loop-utils.d.ts +37 -0
- package/dist/src/loop-utils.js +105 -0
- package/dist/src/loop-utils.js.map +1 -0
- package/dist/src/math-utils.d.ts +23 -0
- package/dist/src/math-utils.js +43 -0
- package/dist/src/math-utils.js.map +1 -0
- package/dist/src/mongo-utils.d.ts +11 -0
- package/dist/src/mongo-utils.js +49 -0
- package/dist/src/mongo-utils.js.map +1 -0
- package/dist/src/object-utils.d.ts +96 -0
- package/dist/src/object-utils.js +369 -0
- package/dist/src/object-utils.js.map +1 -0
- package/dist/src/private/config.d.ts +44 -0
- package/dist/src/private/config.js +55 -0
- package/dist/src/private/config.js.map +1 -0
- package/dist/src/private/error-handler.d.ts +10 -0
- package/dist/src/private/error-handler.js +18 -0
- package/dist/src/private/error-handler.js.map +1 -0
- package/dist/src/private/types.d.ts +4 -0
- package/dist/src/private/types.js +3 -0
- package/dist/src/private/types.js.map +1 -0
- package/dist/src/regexp-utils.d.ts +12 -0
- package/dist/src/regexp-utils.js +44 -0
- package/dist/src/regexp-utils.js.map +1 -0
- package/dist/src/remove-circular-json-stringify.d.ts +1 -0
- package/dist/src/remove-circular-json-stringify.js +20 -0
- package/dist/src/remove-circular-json-stringify.js.map +1 -0
- package/dist/src/string-utils.d.ts +77 -0
- package/dist/src/string-utils.js +209 -0
- package/dist/src/string-utils.js.map +1 -0
- package/dist/src/tests-utils.js +77 -0
- package/dist/src/tests-utils.js.map +1 -0
- package/dist/src/timer-utils.d.ts +16 -0
- package/dist/src/timer-utils.js +79 -0
- package/dist/src/timer-utils.js.map +1 -0
- package/dist/src/transaction-utils.d.ts +14 -0
- package/dist/src/transaction-utils.js +87 -0
- package/dist/src/transaction-utils.js.map +1 -0
- package/dist/src/validation-utils.d.ts +89 -0
- package/dist/src/validation-utils.js +192 -0
- package/dist/src/validation-utils.js.map +1 -0
- package/dist/src/wtf-utils.d.ts +7 -0
- package/dist/src/wtf-utils.js +83 -0
- package/dist/src/wtf-utils.js.map +1 -0
- package/index.ts +38 -0
- package/package.json +2 -2
- package/src/array-utils.ts +128 -0
- package/src/date-utils.ts +377 -0
- package/src/env-utils.ts +29 -0
- package/src/error-utils.ts +77 -0
- package/src/is-empty.ts +5 -0
- package/src/is-object.ts +3 -0
- package/src/isset.ts +3 -0
- package/src/logger-utils.ts +349 -0
- package/src/loop-utils.ts +101 -0
- package/src/math-utils.ts +38 -0
- package/src/mongo-utils.ts +38 -0
- package/src/object-utils.ts +356 -0
- package/src/private/config.ts +85 -0
- package/src/private/error-handler.ts +21 -0
- package/src/private/types.ts +6 -0
- package/src/regexp-utils.ts +37 -0
- package/src/remove-circular-json-stringify.ts +17 -0
- package/src/string-utils.ts +212 -0
- package/src/tests-utils.ts +70 -0
- package/src/timer-utils.ts +58 -0
- package/src/transaction-utils.ts +63 -0
- package/src/validation-utils.ts +253 -0
- package/src/wtf-utils.ts +88 -0
- package/tsconfig.json +11 -4
- package/utils.d.ts +0 -694
- package/utils.js +0 -2227
- package/utils.js.map +0 -1
- package/utils.ts +0 -2304
package/src/isset.ts
ADDED
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
//----------------------------------------
|
|
2
|
+
// LOGGER
|
|
3
|
+
//----------------------------------------
|
|
4
|
+
import { configFn } from "./private/config"
|
|
5
|
+
import { isset } from "./isset"
|
|
6
|
+
import { Color } from "./private/types"
|
|
7
|
+
import { removeCircularJSONstringify } from "./remove-circular-json-stringify"
|
|
8
|
+
import { cleanStackTrace } from "./error-utils"
|
|
9
|
+
|
|
10
|
+
export const logger = {
|
|
11
|
+
log(str, type = 'log') {
|
|
12
|
+
const { preprocessLog } = configFn()
|
|
13
|
+
if (typeof preprocessLog === 'function') str = preprocessLog(str) || str
|
|
14
|
+
if (isset(console[type])) console[type](str)
|
|
15
|
+
else if (type === 'appError') console.warn(str)
|
|
16
|
+
else console.log(str)
|
|
17
|
+
|
|
18
|
+
logger.raw.push(str + `\n`)
|
|
19
|
+
logger.raw = logger.raw.slice(logger.raw.length - configFn().nbOfLogsToKeep, logger.raw.length)
|
|
20
|
+
},
|
|
21
|
+
/**
|
|
22
|
+
* @param {String[]|String} inputLogs
|
|
23
|
+
*/
|
|
24
|
+
toHtml(inputLogs = [...logger.raw]) {
|
|
25
|
+
if (!Array.isArray(inputLogs)) inputLogs = [inputLogs]
|
|
26
|
+
const code2css = {
|
|
27
|
+
2: `opacity:.5`, // dim
|
|
28
|
+
32: `color:#679933`, // green
|
|
29
|
+
31: `color:#A8383B`, // red
|
|
30
|
+
33: `color:#D7941C`, // yellow
|
|
31
|
+
35: `color:#C21949`, // magenta
|
|
32
|
+
36: `color:#128C6D`, // cyan
|
|
33
|
+
34: `color:#1B568B`, // blue
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
let htmlLogs = ''
|
|
37
|
+
inputLogs.join('<br/>').split('\x1b[').forEach((bit, i) => {
|
|
38
|
+
// the first doesn't have a preceding (may be undef)
|
|
39
|
+
if (bit) {
|
|
40
|
+
if (i !== 0) {
|
|
41
|
+
const [, code, R, G, B, content] = bit.match(/(^\d\d?)(?:;|m)(?:\d;(\d+);(\d+);(\d+)m)?(.*$)/) || []
|
|
42
|
+
if (content) {
|
|
43
|
+
const style = !isset(R) ? isset(code) ? code2css[code] : undefined : `color:rgb(${R},${G},${B})`
|
|
44
|
+
htmlLogs += isset(style) ? `<span style='${style}'>${content.replace(/\n/g, '<br>')}</span>` : content.replace(/\n/g, '<br>')
|
|
45
|
+
}
|
|
46
|
+
} else htmlLogs += bit.replace(/\n/g, '<br>')
|
|
47
|
+
}
|
|
48
|
+
})
|
|
49
|
+
return `<div style='color:#ccc'>${htmlLogs}<br></div>`
|
|
50
|
+
},
|
|
51
|
+
toText(inputLogs = [...logger.raw]) {
|
|
52
|
+
const str = Array.isArray(inputLogs) ? inputLogs.join('\n') : inputLogs
|
|
53
|
+
return str.replace(/\x1b\[.*?m/g, '')
|
|
54
|
+
},
|
|
55
|
+
raw: [],
|
|
56
|
+
json: [],
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
// console colored output
|
|
61
|
+
// * console.log(C.green(C.dim('Hey bro !')))
|
|
62
|
+
// * or C.log() // will use padding and color defined by themes
|
|
63
|
+
// * or C.line('MY TITLE', 53)
|
|
64
|
+
// * or C.gradientize(myLongString)
|
|
65
|
+
*/
|
|
66
|
+
export const C = {
|
|
67
|
+
dim: str => C.output(2, str), // opacity 0.5
|
|
68
|
+
green: str => C.output(32, str),
|
|
69
|
+
red: str => C.output(31, str),
|
|
70
|
+
yellow: str => C.output(33, str),
|
|
71
|
+
grey: str => C.output(2, str),
|
|
72
|
+
magenta: str => C.output(35, str),
|
|
73
|
+
cyan: str => C.output(36, str),
|
|
74
|
+
blue: str => C.output(34, str),
|
|
75
|
+
primary: str => {
|
|
76
|
+
const primary: Color = configFn().terminal.theme.primary
|
|
77
|
+
return C.rgb(...primary) + str + C.reset
|
|
78
|
+
},
|
|
79
|
+
reset: '\x1b[0m',
|
|
80
|
+
output: (code, str = '') => configFn().terminal.noColor ? str : `\x1b[${code}m${str.toString().split('\n').join('\x1b[0m\n\x1b[2m')}\x1b[0m`,
|
|
81
|
+
// true RGB colors B-*
|
|
82
|
+
rgb: (r, g = 0, b = 0) => configFn().terminal.noColor || !isset(r, g, b) ? '' : `\x1b[38;2;${r};${g};${b}m`,
|
|
83
|
+
bg: (r?, g?, b?) => configFn().terminal.noColor || !isset(r, g, b) ? '' : `${'\x1b['}48;2;${r};${g};${b}m`,
|
|
84
|
+
/** Output a line of title */
|
|
85
|
+
line(title = '', length = configFn().terminal.theme.pageWidth, clr = configFn().terminal.theme.primary, char = '=', paddingX = configFn().terminal.theme.paddingX) {
|
|
86
|
+
const padX = ' '.repeat(paddingX)
|
|
87
|
+
this.log('\u00A0\n' + padX + this.rgb(...clr) + (title + ' ').padEnd(length, char) + this.reset + padX + '\u00A0\n')
|
|
88
|
+
},
|
|
89
|
+
/** Eg: ['cell1', 'cell2', 'cell3'], [25, 15] will start cell2 at 25 and cell 3 at 25 + 15
|
|
90
|
+
* @param {Array} limits default divide the viewport
|
|
91
|
+
*/
|
|
92
|
+
cols(strings, limits = [], clr = configFn().terminal.theme.fontColor, paddingX = configFn().terminal.theme.paddingX) {
|
|
93
|
+
|
|
94
|
+
if (!limits.length) {
|
|
95
|
+
const colWidth = Math.round(configFn().terminal.theme.pageWidth / strings.length)
|
|
96
|
+
limits = Array(strings.length - 1).fill(2).map((itm, i) => colWidth * i + 1)
|
|
97
|
+
}
|
|
98
|
+
const str = strings.reduce((glob, str = '', i) => {
|
|
99
|
+
const realCharLength = str.toString().replace(/\x1b\[.*?m/, '').length
|
|
100
|
+
const charLength = str.toString().length
|
|
101
|
+
const realLimit = limits[i] + charLength - realCharLength
|
|
102
|
+
return glob + str.toString().substring(0, realLimit || 999).padEnd(realLimit || 0, ' ')
|
|
103
|
+
}, '')
|
|
104
|
+
this.logClr(str, clr, paddingX)
|
|
105
|
+
},
|
|
106
|
+
/** Console log alias */
|
|
107
|
+
log(...stringsCtxMayBeFirstParam) {
|
|
108
|
+
stringsCtxMayBeFirstParam.forEach(str => this.logClr(str, undefined, undefined))
|
|
109
|
+
},
|
|
110
|
+
logClr(str, clr = configFn().terminal.theme.fontColor, paddingX = configFn().terminal.theme.paddingX) {
|
|
111
|
+
if (!isset(str)) return
|
|
112
|
+
const padX = ' '.repeat(paddingX)
|
|
113
|
+
str = padX + (isset(clr) ? this.rgb(...clr) : '') + str.toString().replace(/\n/g, '\n' + padX + (isset(clr) ? this.rgb(...clr) : ''))
|
|
114
|
+
logger.log(str + this.reset, 'log')
|
|
115
|
+
},
|
|
116
|
+
info(...str) {
|
|
117
|
+
str.forEach((s, i) => {
|
|
118
|
+
if (i === 0) this.logClr('ⓘ ' + s, configFn().terminal.theme.primary)
|
|
119
|
+
else this.log(this.dimStrSplit(s))
|
|
120
|
+
})
|
|
121
|
+
this.log(' ')
|
|
122
|
+
},
|
|
123
|
+
success(...str) {
|
|
124
|
+
str.forEach((s, i) => {
|
|
125
|
+
if (i === 0) this.log(this.green('✓ ' + s))
|
|
126
|
+
else this.log(this.dimStrSplit(s))
|
|
127
|
+
})
|
|
128
|
+
},
|
|
129
|
+
/** First param **false** to avoid logging stack trace */
|
|
130
|
+
error: (...errors) => logErrPrivate('error', [255, 0, 0], ...errors),
|
|
131
|
+
/** First param **false** to avoid logging stack trace */
|
|
132
|
+
warning: (...str) => logErrPrivate('warn', [255, 122, 0], ...str),
|
|
133
|
+
customError: (color, ...str) => logErrPrivate('error', color, ...str),
|
|
134
|
+
customWarning: (color, ...str) => logErrPrivate('warn', color, ...str),
|
|
135
|
+
applicationError: (color, ...str) => logErrPrivate('appError', color, ...str),
|
|
136
|
+
warningLight: (color, ...str) => logErrPrivate('warn', [196, 120, 52], ...str),
|
|
137
|
+
dimStrSplit(...logs) {
|
|
138
|
+
let logsStr = []
|
|
139
|
+
logs.filter(isset).forEach(log => log.toString().split('\n').forEach(line => line && logsStr.push(this.dim(` ${line}`))))
|
|
140
|
+
return logsStr.join('\n')
|
|
141
|
+
},
|
|
142
|
+
notifShow() {
|
|
143
|
+
console.log('\n\u00A0')
|
|
144
|
+
this.notifications.forEach(fn => fn())
|
|
145
|
+
this.notifications = []
|
|
146
|
+
console.log('\n\u00A0')
|
|
147
|
+
},
|
|
148
|
+
/** Keep in memory the logs to show when needed with C.notifShow()
|
|
149
|
+
* Ex: C.notification('info', str); */
|
|
150
|
+
notification(type, ...messages) {
|
|
151
|
+
this.notifications.push(() => {
|
|
152
|
+
if (isset(this[type])) {
|
|
153
|
+
this[type](...messages)
|
|
154
|
+
} else {
|
|
155
|
+
this.warning('Wrong param for C.notification')
|
|
156
|
+
}
|
|
157
|
+
})
|
|
158
|
+
},
|
|
159
|
+
notifications: [],
|
|
160
|
+
/** Gratientize lines of text (separated by \n) */
|
|
161
|
+
gradientize(str = '', rgb1 = configFn().terminal.theme.shade1, rgb2 = configFn().terminal.theme.shade2, bgRgb = configFn().terminal.theme.bgColor, paddingY = configFn().terminal.theme.paddingY, paddingX = configFn().terminal.theme.paddingX) {
|
|
162
|
+
|
|
163
|
+
const lines = str.split('\n')
|
|
164
|
+
const largestLine = lines.reduce((sum, line) => sum < line.length ? line.length : sum, 0)
|
|
165
|
+
const rgbParts = rgb1.map((val, i) => (val - rgb2[i]) / (lines.length))
|
|
166
|
+
|
|
167
|
+
const bg = bgRgb ? this.bg(bgRgb[0], bgRgb[1], bgRgb[2]) : ''
|
|
168
|
+
const padX = ' '.repeat(paddingX)
|
|
169
|
+
const padLine = bg + padX + ' '.padEnd(largestLine, ' ') + padX + '\x1b[0m\n'
|
|
170
|
+
|
|
171
|
+
console.log(padLine.repeat(paddingY) +
|
|
172
|
+
lines.reduce((s, line, i) => {
|
|
173
|
+
return s + bg + padX + this.rgb(...rgb1.map((val, i2) => Math.round(val - i * rgbParts[i2]))) + line.padEnd(largestLine, ' ') + padX + '\x1b[0m\n'
|
|
174
|
+
}, '') +
|
|
175
|
+
padLine.repeat(paddingY))
|
|
176
|
+
},
|
|
177
|
+
debugModeLog(title, ...string) {
|
|
178
|
+
this.logClr('🐞 ' + title, configFn().terminal.theme.debugModeColor, 0)
|
|
179
|
+
this.log(this.dimStrSplit(...string))
|
|
180
|
+
},
|
|
181
|
+
// DEPRECATED
|
|
182
|
+
useTheme() { },
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export function logErrPrivate(type, color: Color, ...errors) {
|
|
186
|
+
const { isProd } = configFn()
|
|
187
|
+
|
|
188
|
+
if (errors.length === 1 && typeof errors[0].log === 'function') return errors[0].log()
|
|
189
|
+
|
|
190
|
+
let stackTrace = (new Error('')).stack || ''
|
|
191
|
+
const displayStack = errors[0] === false ? errors.shift() : true
|
|
192
|
+
const symbol = type === 'error' ? '✘ ' : '⚠ '
|
|
193
|
+
if (errors.length > 1 && !isset(errors[0])) errors.shift()
|
|
194
|
+
|
|
195
|
+
const getStringFromErr = (err, i) => {
|
|
196
|
+
if (!isset(err)) return ''
|
|
197
|
+
else if (typeof err === 'string') {
|
|
198
|
+
if (i === 0) return C.rgb(...color) + symbol + err + C.reset
|
|
199
|
+
else return err.split('\n').map(val => C.dim(val)).join('\n')
|
|
200
|
+
} else if (err instanceof Error) {
|
|
201
|
+
const { str, stackTrace: stkTrc } = stringifyInstanceOfError(err, type, color)
|
|
202
|
+
if (stkTrc) stackTrace = stkTrc
|
|
203
|
+
return str
|
|
204
|
+
} else if (typeof err === 'object') {
|
|
205
|
+
let msg = ''
|
|
206
|
+
msg += removeCircularJSONstringify(err, 2).split('\n').map(val => C.dim(val)).join('\n') + '\n'
|
|
207
|
+
const { str, stackTrace: stkTrc } = stringifyExtraInfos(err.extraInfo || err, type, color)
|
|
208
|
+
if (stkTrc) stackTrace = stkTrc
|
|
209
|
+
msg += str
|
|
210
|
+
return msg
|
|
211
|
+
} else return ''
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (errors.length && errors[0]) {
|
|
215
|
+
const messages = errors.map((e, i) => {
|
|
216
|
+
if (typeof e.log === 'function') {
|
|
217
|
+
e.log()
|
|
218
|
+
return ''
|
|
219
|
+
} else return getStringFromErr(e, i)
|
|
220
|
+
})
|
|
221
|
+
// Stack
|
|
222
|
+
if (displayStack) {
|
|
223
|
+
messages.push(isProd ? stackTrace : cleanStackTrace(stackTrace) + '\n')
|
|
224
|
+
}
|
|
225
|
+
logger.log(messages.join(''), type)
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export function stringifyInstanceOfError(err, type = 'error', color: Color = [255, 0, 0], level = 0) { // level = keep track of recursions
|
|
230
|
+
if (level > 5) return { str: '' }
|
|
231
|
+
let str = ''
|
|
232
|
+
let stackTrace
|
|
233
|
+
const symbol = type === 'error' ? '✘ ' : '⚠ '
|
|
234
|
+
const title = err.msg || err.message || err.id || (err.stack ? err.stack.split('\n')[0] : 'Error')
|
|
235
|
+
// Err mess
|
|
236
|
+
str += C.rgb(...color) + symbol + title + C.reset + '\n'
|
|
237
|
+
if (err.stack) stackTrace = err.stack // more relevant
|
|
238
|
+
// ExtraInfos
|
|
239
|
+
if (isset(err.extraInfo)) {
|
|
240
|
+
const { str: str2, stackTrace: stkTrc } = stringifyExtraInfos(err.extraInfo, type, color, level++)
|
|
241
|
+
if (stkTrc) stackTrace = stkTrc
|
|
242
|
+
str += str2
|
|
243
|
+
}
|
|
244
|
+
return { str, stackTrace }
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export function stringifyExtraInfos(extraInfoOriginal, type, color, level = 0) {
|
|
248
|
+
let stackTrace
|
|
249
|
+
const originalError = [C.dim(`ORIGINAL ERROR ${'-'.repeat(39)}\n`)]
|
|
250
|
+
if (extraInfoOriginal instanceof Error) { // case where error is passed directly to extraInfos
|
|
251
|
+
return stringifyInstanceOfError(extraInfoOriginal, type, color)
|
|
252
|
+
} else {
|
|
253
|
+
const extraInfo = { ...extraInfoOriginal }
|
|
254
|
+
const extraInfos = [C.dim(`EXTRA INFOS ${'-'.repeat(41)}\n`)]
|
|
255
|
+
if (typeof extraInfo === 'object' && Object.keys(extraInfo).length) {
|
|
256
|
+
for (const itemName in extraInfo) {
|
|
257
|
+
if (extraInfo[itemName] instanceof Error) {
|
|
258
|
+
const { str, stackTrace: stkTrc } = stringifyInstanceOfError(extraInfo[itemName], type, color, level++)
|
|
259
|
+
originalError.push(str)
|
|
260
|
+
stackTrace = stkTrc
|
|
261
|
+
delete extraInfo[itemName]
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
if (Object.keys(extraInfo).length) {
|
|
266
|
+
extraInfos.push(
|
|
267
|
+
removeCircularJSONstringify(extraInfo, 2)
|
|
268
|
+
.replace(/(?:^\s*{(?:\n {2})?|}\s*$)/g, '')
|
|
269
|
+
.replace(/\n {2}/g, '\n')
|
|
270
|
+
.split('\n')
|
|
271
|
+
.map(val => C.dim(val)).join('\n') + '\n')
|
|
272
|
+
}
|
|
273
|
+
return {
|
|
274
|
+
str: (extraInfos.length > 1 ? extraInfos.join('') : '') + (originalError.length > 1 ? originalError.join('') + '\n' : ''),
|
|
275
|
+
stackTrace
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Call this at each steps of your progress and change the step value
|
|
282
|
+
* @param {Number} step Number of "char" to output
|
|
283
|
+
* @param {String} char Default: '.'
|
|
284
|
+
* @param {String} msg String before char. Final output will be `${str}${char.repeat(step)}`
|
|
285
|
+
*/
|
|
286
|
+
export function cliProgressBar(step, char = '.', msg = `\x1b[2mⓘ Waiting response`) {
|
|
287
|
+
if (isset(process) && isset(process.stdout) && isset(process.stdout.clearLine)) {
|
|
288
|
+
process.stdout.clearLine(0)
|
|
289
|
+
process.stdout.cursorTo(0)
|
|
290
|
+
process.stdout.write(`${msg}${char.repeat(step)}\x1b[0m`) // \x1b[0m == reset color
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/** This allow an intuitive inline loading spinner with a check mark when loading as finished or a red cross for errors */
|
|
295
|
+
class cliLoadingSpinner {
|
|
296
|
+
/** Please use it like spinner.start('myStuff') then spinner.end()
|
|
297
|
+
* @param {String} type in: ['arrow', 'dots']
|
|
298
|
+
*/
|
|
299
|
+
frameRate: number
|
|
300
|
+
animFrames: string[]
|
|
301
|
+
activeProcess: any
|
|
302
|
+
frameNb: number
|
|
303
|
+
progressMessage: string
|
|
304
|
+
interval: any
|
|
305
|
+
|
|
306
|
+
constructor(type = 'dots', activeProcess = process) {
|
|
307
|
+
const anims = {
|
|
308
|
+
arrow: {
|
|
309
|
+
interval: 120,
|
|
310
|
+
frames: ['▹▹▹▹▹', '▸▹▹▹▹', '▹▸▹▹▹', '▹▹▸▹▹', '▹▹▹▸▹', '▹▹▹▹▸']
|
|
311
|
+
},
|
|
312
|
+
dots: {
|
|
313
|
+
interval: 80,
|
|
314
|
+
frames: ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
this.frameRate = anims[type].interval
|
|
318
|
+
this.animFrames = anims[type].frames
|
|
319
|
+
this.activeProcess = activeProcess
|
|
320
|
+
}
|
|
321
|
+
start(msg) {
|
|
322
|
+
this.frameNb = 0
|
|
323
|
+
this.progressMessage = msg
|
|
324
|
+
this.interval = setInterval(() => {
|
|
325
|
+
this.activeProcess.stdout.clearLine()
|
|
326
|
+
this.activeProcess.stdout.cursorTo(0)
|
|
327
|
+
const symbol = this.animFrames[this.frameNb++ % this.animFrames.length]
|
|
328
|
+
this.activeProcess.stdout.write(C.primary(symbol) + ' ' + this.progressMessage)
|
|
329
|
+
}, this.frameRate)
|
|
330
|
+
}
|
|
331
|
+
end(error = false) {
|
|
332
|
+
clearInterval(this.interval)
|
|
333
|
+
this.activeProcess.stdout.clearLine()
|
|
334
|
+
this.activeProcess.stdout.cursorTo(0)
|
|
335
|
+
this.activeProcess.stdout.write(
|
|
336
|
+
error ? C.red('✘ ' + this.progressMessage + '\n\n')
|
|
337
|
+
: '\x1b[32m✓ ' + this.progressMessage + '\n\n')
|
|
338
|
+
this.progressMessage = ''
|
|
339
|
+
}
|
|
340
|
+
error() {
|
|
341
|
+
return this.end(true)
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
export function dim(str = '') {
|
|
347
|
+
return configFn().terminal.noColor ? str : `\x1b[2m${str.toString().split('\n').join('\x1b[0m\n\x1b[2m')}\x1b[0m`
|
|
348
|
+
}
|
|
349
|
+
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
//----------------------------------------
|
|
2
|
+
// LOOP UTILS
|
|
3
|
+
//----------------------------------------
|
|
4
|
+
import { ObjectGeneric } from "./private/types"
|
|
5
|
+
import { err500IfNotSet } from "./error-utils"
|
|
6
|
+
import { isObject } from "./is-object"
|
|
7
|
+
|
|
8
|
+
export function forI<T extends any[] | any>(nbIterations: number, callback: (number: number, previousValue, arrayOfPreviousValues: any[]) => T): T[] {
|
|
9
|
+
const results = []
|
|
10
|
+
for (let i = 0; i < nbIterations; i++) {
|
|
11
|
+
const prevValue = results[results.length - 1]
|
|
12
|
+
results.push(callback(i, prevValue, results))
|
|
13
|
+
}
|
|
14
|
+
return results
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export async function forIasync<T extends any[] | any>(nbIterations: number, callback: (number) => T): Promise<T[]> {
|
|
18
|
+
const results = []
|
|
19
|
+
for (let i = 0; i < nbIterations; i++) {
|
|
20
|
+
results.push(await callback(i))
|
|
21
|
+
}
|
|
22
|
+
return results
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
export type RecursiveCallback = (item: any, addr: string, lastElementKey: string | number, parent: ObjectGeneric | any[]) => false | any
|
|
28
|
+
/**
|
|
29
|
+
* @param {any} item the first array or object or whatever you want to recursively browse
|
|
30
|
+
* @param {function} callback the callback you want to apply on items including the main one
|
|
31
|
+
* * this callback has 2 arguments: (item, address) =>
|
|
32
|
+
* * `item` => the actual item
|
|
33
|
+
* * `addr` => the address of the item, not including root (Eg: subItem1.sub2.[3].[2]) array indexes are juste written as numbers
|
|
34
|
+
* * `lastElementKey` => the key of last item. May be a number if last item is an array
|
|
35
|
+
* * `parent` => reference the parent object as this is the only way of reassigning a value for the item. Eg: parent[lastElementKey] = myNewItem
|
|
36
|
+
* * **NOTE** => if a key of an item contains dots, they will be replaced by '%' in `addr`
|
|
37
|
+
* * **NOTE2** => if false is returned by the callback it will stop all other iterations (but not in an array)
|
|
38
|
+
* @param {string} addr$ optional, the base address for the callback function
|
|
39
|
+
* @param lastElementKey technical field
|
|
40
|
+
* NOTE: will remove circular references
|
|
41
|
+
* /!\ check return values
|
|
42
|
+
*/
|
|
43
|
+
export async function recursiveGenericFunction(item: ObjectGeneric | any[], callback: RecursiveCallback, addr$ = '', lastElementKey: string | number = '', parent?, techFieldToAvoidCircularDependency = []) {
|
|
44
|
+
err500IfNotSet({ callback })
|
|
45
|
+
|
|
46
|
+
if (!techFieldToAvoidCircularDependency.includes(item)) {
|
|
47
|
+
const result = addr$ === '' ? true : await callback(item, addr$, lastElementKey, parent)
|
|
48
|
+
|
|
49
|
+
if (result !== false) {
|
|
50
|
+
const addr = addr$ ? addr$ + '.' : ''
|
|
51
|
+
if (Array.isArray(item)) {
|
|
52
|
+
techFieldToAvoidCircularDependency.push(item)
|
|
53
|
+
await Promise.all(item.map(
|
|
54
|
+
(e, i) => recursiveGenericFunction(e, callback, addr + '[' + i + ']', i, item, techFieldToAvoidCircularDependency)
|
|
55
|
+
))
|
|
56
|
+
} else if (isObject(item)) {
|
|
57
|
+
techFieldToAvoidCircularDependency.push(item)
|
|
58
|
+
await Promise.all(Object.entries(item).map(
|
|
59
|
+
([key, val]) => recursiveGenericFunction(val, callback, addr + key.replace(/\./g, '%'), key, item, techFieldToAvoidCircularDependency)
|
|
60
|
+
))
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return item
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* @param {any} item the first array or object or whatever you want to recursively browse
|
|
69
|
+
* @param {function} callback the callback you want to apply on items including the main one
|
|
70
|
+
* * this callback has 2 arguments: (item, address) =>
|
|
71
|
+
* * `item` => the actual item
|
|
72
|
+
* * `addr` => the address of the item, not including root (Eg: subItem1.sub2.[3].[2]) array indexes are juste written as numbers
|
|
73
|
+
* * `lastElementKey` => the key of last item. May be a number if last item is an array
|
|
74
|
+
* * `parent` => reference the parent object as this is the only way of reassigning a value for the item. Eg: parent[lastElementKey] = myNewItem
|
|
75
|
+
* * **NOTE** => if a key of an item contains dots, they will be replaced by '%' in `addr`
|
|
76
|
+
* * **NOTE2** => if false is returned by the callback it will stop all other iterations (but not in an array)
|
|
77
|
+
* * **NOTE3** => to reassign a key use => parent[lastElementKey] = myNewItem
|
|
78
|
+
* @param {string} addr$ optional, the base address for the callback function
|
|
79
|
+
* @param lastElementKey technical field
|
|
80
|
+
* NOTE: will remove circular references
|
|
81
|
+
* /!\ check return values
|
|
82
|
+
*/
|
|
83
|
+
export function recursiveGenericFunctionSync(item: ObjectGeneric | any[], callback: RecursiveCallback, addr$ = '', lastElementKey: string | number = '', parent?, techFieldToAvoidCircularDependency = []) {
|
|
84
|
+
err500IfNotSet({ callback })
|
|
85
|
+
|
|
86
|
+
if (!techFieldToAvoidCircularDependency.includes(item)) {
|
|
87
|
+
const result = addr$ === '' ? true : callback(item, addr$, lastElementKey, parent)
|
|
88
|
+
|
|
89
|
+
if (result !== false) {
|
|
90
|
+
const addr = addr$ ? addr$ + '.' : ''
|
|
91
|
+
if (Array.isArray(item)) {
|
|
92
|
+
techFieldToAvoidCircularDependency.push(item) // do not up one level
|
|
93
|
+
item.forEach((e, i) => recursiveGenericFunctionSync(e, callback, addr + '[' + i + ']', i, item, techFieldToAvoidCircularDependency))
|
|
94
|
+
} else if (isObject(item)) {
|
|
95
|
+
techFieldToAvoidCircularDependency.push(item)
|
|
96
|
+
Object.entries(item).forEach(([key, val]) => recursiveGenericFunctionSync(val, callback, addr + key.replace(/\./g, '%'), key, item, techFieldToAvoidCircularDependency))
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return item
|
|
101
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
//----------------------------------------
|
|
2
|
+
// MATH UTILS
|
|
3
|
+
//----------------------------------------
|
|
4
|
+
import { isset } from "./isset"
|
|
5
|
+
|
|
6
|
+
/** Round with custom number of decimals (default:0) */
|
|
7
|
+
export function round(number: number | string, decimals = 0) { return Math.round((typeof number === 'number' ? number : parseFloat(number)) * Math.pow(10, decimals)) / Math.pow(10, decimals) }
|
|
8
|
+
/** Round with custom number of decimals (default:2) */
|
|
9
|
+
export function round2(number: number | string, decimals = 2) { return round(number, decimals) }
|
|
10
|
+
|
|
11
|
+
/** Is number between two numbers (including those numbers) */
|
|
12
|
+
export function isBetween(number: number, min: number, max: number, inclusive = true) { return inclusive ? number <= max && number >= min : number < max && number > min }
|
|
13
|
+
|
|
14
|
+
/** Random number between two values with 0 decimals by default */
|
|
15
|
+
export function random(nb1: number, nb2: number, nbOfDecimals = 0) { return round(Math.random() * (nb2 - nb1) + nb1, nbOfDecimals) }
|
|
16
|
+
|
|
17
|
+
/** Sum all values of an array, all values MUST be numbers */
|
|
18
|
+
export function sumArray(array: number[]) {
|
|
19
|
+
return array.filter(item => typeof item === 'number').reduce((sum, val) => isset(val) ? val + sum : sum, 0)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Moyenne / average between array of values
|
|
23
|
+
* @param {Number} round number of decimals to keep. Default:2
|
|
24
|
+
*/
|
|
25
|
+
export function moyenne(array: number[], nbOfDecimals = 2) {
|
|
26
|
+
return round(sumArray(array) / array.length, nbOfDecimals)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** length default 2, shortcut for 1 to 01 */
|
|
30
|
+
export function pad(numberOrStr: number | string, length = 2) { return ('' + numberOrStr).padStart(length, '0') }
|
|
31
|
+
|
|
32
|
+
/** return the number or the closest number of the range
|
|
33
|
+
* * nb min max => returns
|
|
34
|
+
* * 7 5 10 => 7 // in the range
|
|
35
|
+
* * 2 5 10 => 5 // below the min value
|
|
36
|
+
* * 99 5 10 => 10// above the max value
|
|
37
|
+
*/
|
|
38
|
+
export function minMax(nb: number, min: number, max: number) { return Math.max(min, Math.min(nb, max)) }
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
//----------------------------------------
|
|
2
|
+
// MONGO UTILS
|
|
3
|
+
//----------------------------------------
|
|
4
|
+
import { isType } from "./validation-utils"
|
|
5
|
+
import { isset } from "./isset"
|
|
6
|
+
|
|
7
|
+
/** @return undefined if cannot find _id */
|
|
8
|
+
export function getId(obj: any = {}): string {
|
|
9
|
+
if (!obj) return // null case
|
|
10
|
+
if (obj._id) return obj._id.toString()
|
|
11
|
+
else if (isType(obj, 'objectId')) return obj.toString()
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** Merge filter with correct handling of OR and AND
|
|
15
|
+
* @param {Object} filterA
|
|
16
|
+
* @param {Object} filterB
|
|
17
|
+
* @param {Boolean} assignToFilterA defualt false: if true, it will modify filterA, else it will return merged filters as a new object
|
|
18
|
+
*/
|
|
19
|
+
export function mongoFilterMerger(filterA, filterB, assignToFilterA = false) {
|
|
20
|
+
if (isset(filterA.$and) && isset(filterB.$and)) {
|
|
21
|
+
filterA.$and.push(...filterB.$and)
|
|
22
|
+
delete filterB.$and
|
|
23
|
+
}
|
|
24
|
+
if (isset(filterA.$or) && isset(filterB.$or)) {
|
|
25
|
+
if (!isset(filterA.$and)) filterA.$and = []
|
|
26
|
+
filterA.$and.push({ $or: filterA.$or }, { $or: filterA.$or })
|
|
27
|
+
delete filterB.$or
|
|
28
|
+
}
|
|
29
|
+
if (assignToFilterA) {
|
|
30
|
+
Object.assign(filterA, filterB)
|
|
31
|
+
return filterA
|
|
32
|
+
} else return { ...filterA, ...filterB }
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function mongoPush(field: string, value: any, fields: { [k: string]: any }) {
|
|
36
|
+
if (!isset(fields.$push)) fields.$push = {}
|
|
37
|
+
fields.$push[field] = value
|
|
38
|
+
}
|