-
Notifications
You must be signed in to change notification settings - Fork 7
/
Copy pathhelpers.js
201 lines (183 loc) · 5.18 KB
/
helpers.js
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
const fs = require('fs');
const path = require('path');
const readline = require('readline');
const crypto = require('crypto');
const prompt = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
/**
* Recursive helper function to sequentially create and resolve a series of Promises
* over an array, and return all results
*
* @param {array} array array of input values for promise
* @param {array} resultStore pointer to array that will store resolved results
* @param {function} promiseFn promise function
*/
const sequencePromises = (array, resultStore, promiseFn) => {
return promiseFn(array.shift()).then((result) => {
resultStore.push(result);
return array.length === 0
? resultStore
: sequencePromises(array, resultStore, promiseFn);
});
};
/**
* Convert total byte count to human readable size
*
* @param {number} bytes total number of bytes
*
* @return {string} human readable file size to 2 decimal points
*/
function bytesToSize(bytes) {
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
if (bytes === 0) return '0 Byte';
const i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)), 10);
return `${Math.round(bytes / (1024 ** i), 2)} ${sizes[i]}`;
}
/**
* Count number of lines in given file
*
* @param {string} filePath full path of file
*
* @return {number} number of lines in file
*/
function countFileLines(filePath) {
return new Promise((resolve, reject) => {
let lineCount = 0;
fs.createReadStream(filePath)
.on('data', (buffer) => {
let idx = -1;
lineCount--; // Because the loop will run once for idx=-1
do {
idx = buffer.indexOf(10, idx + 1);
lineCount++;
} while (idx !== -1);
})
.on('end', () => {
resolve(lineCount);
})
.on('error', reject);
});
}
/**
* Create target directory, recurring over subdirs if necessary
*
* @param {string} targetDir full path of target directory to create
*
* @return {string} absolute value of final targetDir
*/
function mkDirByPathSync(targetDir) {
const { sep } = path;
const initDir = path.isAbsolute(targetDir) ? sep : '';
return targetDir.split(sep).reduce((parentDir, childDir) => {
const curDir = path.resolve('.', parentDir, childDir);
try {
fs.mkdirSync(curDir);
} catch (err) {
if (err.code === 'EEXIST') {
// curDir already exists!
return curDir;
}
// To avoid `EISDIR` error on Mac and `EACCES`-->`ENOENT` and `EPERM` on Windows.
if (err.code === 'ENOENT') {
// Throw the original parentDir error on curDir `ENOENT` failure.
throw new Error(`EACCES: permission denied, mkdir '${parentDir}'`);
}
const caughtErr = ['EACCES', 'EPERM', 'EISDIR'].indexOf(err.code) > -1;
if (!caughtErr || (caughtErr && curDir === path.resolve(targetDir))) {
throw err; // Throw if it's just the last created dir.
}
}
return curDir;
}, initDir);
}
/**
* Turn prompt into a promise
*
* @param {string} question question to show user
*
* @return {Promise} promisify'd prompt object
*/
function promptAsync(question) {
return new Promise((resolve) => {
prompt.question(question, resolve);
});
}
/**
* Continue prompting for user input until valid option is provided
*
* @param {string} promptInstance question to show user
* @param {Object} options key: correct answer; value: callback function
*
* @return {function} call the callback function
*/
async function promptLoop(promptInstance, options) {
const answer = await promptAsync(promptInstance);
const callback = options[answer.toLowerCase()];
if (callback) await callback();
else await promptLoop(promptInstance, options);
}
/**
* Given an object, apply a consistent function to each value
*
* @param {Object} object key/value pair object
* @param {function} mapFn the function to apply to each value
*
* @return {Object} transformed object
*/
function objectMap(object, mapFn) {
return Object.keys(object).reduce((result, key) => {
const newResult = result;
newResult[key] = mapFn(object[key]);
return newResult;
}, {});
}
/**
* Convert a given duration to # of hours
*
* @param {number} duration integer of time duration
* @param {string} unit unit of duration - ms, s, min
* @param {number} sigDig # of decimals to include
*
* @return {number} number of hours to given significant digits
*/
function unitToHours(duration, unit, sigDig) {
let perHr = 1;
const sigDigMultiplier = 10 ** sigDig;
switch (unit) {
case 'ms':
perHr = 60 * 60 * 1000;
break;
case 's':
perHr = 60 * 60;
break;
case 'min':
perHr = 60;
break;
default:
perHr = 1;
break;
}
return Math.floor((duration / perHr) * sigDigMultiplier) / sigDigMultiplier;
}
/**
* Hash client ID
*
* @param {string} id uuid of client
*
* @return {string} sha512 hash of client id
*/
function hashId(id) {
return crypto.createHash('sha512').update(id).digest('hex');
}
module.exports = {
countFileLines,
mkDirByPathSync,
promptLoop,
unitToHours,
objectMap,
hashId,
bytesToSize,
sequencePromises,
};