upd8-util.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299
  1. // This is used by upd8.js! It's part of the 8ackend. Read the notes there if
  2. // you're curious.
  3. //
  4. // Friendly(!) disclaimer: these utility functions haven't 8een tested all that
  5. // much. Do not assume it will do exactly what you want it to do in all cases.
  6. // It will likely only do exactly what I want it to, and only in the cases I
  7. // decided were relevant enough to 8other handling.
  8. 'use strict';
  9. // Apparently JavaScript doesn't come with a function to split an array into
  10. // chunks! Weird. Anyway, this is an awesome place to use a generator, even
  11. // though we don't really make use of the 8enefits of generators any time we
  12. // actually use this. 8ut it's still awesome, 8ecause I say so.
  13. module.exports.splitArray = function*(array, fn) {
  14. let lastIndex = 0;
  15. while (lastIndex < array.length) {
  16. let nextIndex = array.findIndex((item, index) => index >= lastIndex && fn(item));
  17. if (nextIndex === -1) {
  18. nextIndex = array.length;
  19. }
  20. yield array.slice(lastIndex, nextIndex);
  21. // Plus one because we don't want to include the dividing line in the
  22. // next array we yield.
  23. lastIndex = nextIndex + 1;
  24. }
  25. };
  26. // This function's name is a joke. Jokes! Hahahahahahahaha. Funny.
  27. module.exports.joinNoOxford = function(array, plural = 'and') {
  28. if (array.length === 0) {
  29. // ????????
  30. return '';
  31. }
  32. if (array.length === 1) {
  33. return array[0];
  34. }
  35. if (array.length === 2) {
  36. return `${array[0]} ${plural} ${array[1]}`;
  37. }
  38. return `${array.slice(0, -1).join(', ')} ${plural} ${array[array.length - 1]}`;
  39. };
  40. module.exports.progressPromiseAll = function (msg, array) {
  41. let done = 0, total = array.length;
  42. process.stdout.write(`\r${msg} [0/${total}]`);
  43. const start = Date.now();
  44. return Promise.all(array.map(promise => promise.then(val => {
  45. done++;
  46. // const pc = `${done}/${total}`;
  47. const pc = (Math.round(done / total * 1000) / 10 + '%').padEnd('99.9%'.length, ' ');
  48. if (done === total) {
  49. const time = Date.now() - start;
  50. process.stdout.write(`\r\x1b[2m${msg} [${pc}] \x1b[0;32mDone! \x1b[0;2m(${time} ms) \x1b[0m\n`)
  51. } else {
  52. process.stdout.write(`\r${msg} [${pc}] `);
  53. }
  54. return val;
  55. })));
  56. };
  57. module.exports.queue = function (array, max = 50) {
  58. if (max === 0) {
  59. return array.map(fn => fn());
  60. }
  61. const begin = [];
  62. let current = 0;
  63. const ret = array.map(fn => new Promise((resolve, reject) => {
  64. begin.push(() => {
  65. current++;
  66. Promise.resolve(fn()).then(value => {
  67. current--;
  68. if (current < max && begin.length) {
  69. begin.shift()();
  70. }
  71. resolve(value);
  72. }, reject);
  73. });
  74. }));
  75. for (let i = 0; i < max && begin.length; i++) {
  76. begin.shift()();
  77. }
  78. return ret;
  79. };
  80. module.exports.th = function (n) {
  81. if (n % 10 === 1 && n !== 11) {
  82. return n + 'st';
  83. } else if (n % 10 === 2 && n !== 12) {
  84. return n + 'nd';
  85. } else if (n % 10 === 3 && n !== 13) {
  86. return n + 'rd';
  87. } else {
  88. return n + 'th';
  89. }
  90. };
  91. // My function names just keep getting 8etter.
  92. module.exports.s = function (n, word) {
  93. return `${n} ${word}` + (n === 1 ? '' : 's');
  94. };
  95. // Hey, did you know I apparently put a space 8efore the parameters in function
  96. // names? 8ut only in function expressions, not declar8tions? I mean, I guess
  97. // you did. You're pro8a8ly more familiar with my code than I am 8y this
  98. // point. I haven't messed with any of this code in ages. Yay!!!!!!!!
  99. //
  100. // This function only does anything on o8jects you're going to 8e reusing.
  101. // Argua8ly I could use a WeakMap here, 8ut since the o8ject needs to 8e
  102. // reused to 8e useful anyway, I just store the result with a symbol.
  103. // Sorry if it's 8een frozen I guess??
  104. module.exports.cacheOneArg = function (fn) {
  105. const symbol = Symbol('Cache');
  106. return arg => {
  107. if (!arg[symbol]) {
  108. arg[symbol] = fn(arg);
  109. }
  110. return arg[symbol];
  111. };
  112. };
  113. const decorateTime = function (functionToBeWrapped) {
  114. const fn = function(...args) {
  115. const start = Date.now();
  116. const ret = functionToBeWrapped(...args);
  117. const end = Date.now();
  118. fn.timeSpent += end - start;
  119. fn.timesCalled++;
  120. return ret;
  121. };
  122. fn.wrappedName = functionToBeWrapped.name;
  123. fn.timeSpent = 0;
  124. fn.timesCalled = 0;
  125. fn.displayTime = function() {
  126. const averageTime = fn.timeSpent / fn.timesCalled;
  127. console.log(`\x1b[1m${fn.wrappedName}(...):\x1b[0m ${fn.timeSpent} ms / ${fn.timesCalled} calls \x1b[2m(avg: ${averageTime} ms)\x1b[0m`);
  128. };
  129. decorateTime.decoratedFunctions.push(fn);
  130. return fn;
  131. };
  132. decorateTime.decoratedFunctions = [];
  133. decorateTime.displayTime = function() {
  134. if (decorateTime.decoratedFunctions.length) {
  135. console.log(`\x1b[1mdecorateTime results: ` + '-'.repeat(40) + '\x1b[0m');
  136. for (const fn of decorateTime.decoratedFunctions) {
  137. fn.displayTime();
  138. }
  139. }
  140. };
  141. module.exports.decorateTime = decorateTime;
  142. // Stolen as #@CK from mtui!
  143. const parseOptions = async function(options, optionDescriptorMap) {
  144. // This function is sorely lacking in comments, but the basic usage is
  145. // as such:
  146. //
  147. // options is the array of options you want to process;
  148. // optionDescriptorMap is a mapping of option names to objects that describe
  149. // the expected value for their corresponding options.
  150. // Returned is a mapping of any specified option names to their values, or
  151. // a process.exit(1) and error message if there were any issues.
  152. //
  153. // Here are examples of optionDescriptorMap to cover all the things you can
  154. // do with it:
  155. //
  156. // optionDescriptorMap: {
  157. // 'telnet-server': {type: 'flag'},
  158. // 't': {alias: 'telnet-server'}
  159. // }
  160. //
  161. // options: ['t'] -> result: {'telnet-server': true}
  162. //
  163. // optionDescriptorMap: {
  164. // 'directory': {
  165. // type: 'value',
  166. // validate(name) {
  167. // // const whitelistedDirectories = ['apple', 'banana']
  168. // if (whitelistedDirectories.includes(name)) {
  169. // return true
  170. // } else {
  171. // return 'a whitelisted directory'
  172. // }
  173. // }
  174. // },
  175. // 'files': {type: 'series'}
  176. // }
  177. //
  178. // ['--directory', 'apple'] -> {'directory': 'apple'}
  179. // ['--directory', 'artichoke'] -> (error)
  180. // ['--files', 'a', 'b', 'c', ';'] -> {'files': ['a', 'b', 'c']}
  181. //
  182. // TODO: Be able to validate the values in a series option.
  183. const handleDashless = optionDescriptorMap[parseOptions.handleDashless];
  184. const handleUnknown = optionDescriptorMap[parseOptions.handleUnknown];
  185. const result = Object.create(null);
  186. for (let i = 0; i < options.length; i++) {
  187. const option = options[i];
  188. if (option.startsWith('--')) {
  189. // --x can be a flag or expect a value or series of values
  190. let name = option.slice(2).split('=')[0]; // '--x'.split('=') = ['--x']
  191. let descriptor = optionDescriptorMap[name];
  192. if (!descriptor) {
  193. if (handleUnknown) {
  194. handleUnknown(option);
  195. } else {
  196. console.error(`Unknown option name: ${name}`);
  197. process.exit(1);
  198. }
  199. continue;
  200. }
  201. if (descriptor.alias) {
  202. name = descriptor.alias;
  203. descriptor = optionDescriptorMap[name];
  204. }
  205. if (descriptor.type === 'flag') {
  206. result[name] = true;
  207. } else if (descriptor.type === 'value') {
  208. let value = option.slice(2).split('=')[1];
  209. if (!value) {
  210. value = options[++i];
  211. if (!value || value.startsWith('-')) {
  212. value = null;
  213. }
  214. }
  215. if (!value) {
  216. console.error(`Expected a value for --${name}`);
  217. process.exit(1);
  218. }
  219. result[name] = value;
  220. } else if (descriptor.type === 'series') {
  221. if (!options.slice(i).includes(';')) {
  222. console.error(`Expected a series of values concluding with ; (\\;) for --${name}`);
  223. process.exit(1);
  224. }
  225. const endIndex = i + options.slice(i).indexOf(';');
  226. result[name] = options.slice(i + 1, endIndex);
  227. i = endIndex;
  228. }
  229. if (descriptor.validate) {
  230. const validation = await descriptor.validate(result[name]);
  231. if (validation !== true) {
  232. console.error(`Expected ${validation} for --${name}`);
  233. process.exit(1);
  234. }
  235. }
  236. } else if (option.startsWith('-')) {
  237. // mtui doesn't use any -x=y or -x y format optionuments
  238. // -x will always just be a flag
  239. let name = option.slice(1);
  240. let descriptor = optionDescriptorMap[name];
  241. if (!descriptor) {
  242. if (handleUnknown) {
  243. handleUnknown(option);
  244. } else {
  245. console.error(`Unknown option name: ${name}`);
  246. process.exit(1);
  247. }
  248. continue;
  249. }
  250. if (descriptor.alias) {
  251. name = descriptor.alias;
  252. descriptor = optionDescriptorMap[name];
  253. }
  254. if (descriptor.type === 'flag') {
  255. result[name] = true;
  256. } else {
  257. console.error(`Use --${name} (value) to specify ${name}`);
  258. process.exit(1);
  259. }
  260. } else if (handleDashless) {
  261. handleDashless(option);
  262. }
  263. }
  264. return result;
  265. }
  266. parseOptions.handleDashless = Symbol();
  267. parseOptions.handleUnknown = Symbol();
  268. module.exports.parseOptions = parseOptions;
  269. // Cheap FP for a cheap dyke!
  270. // I have no idea if this is what curry actually means.
  271. module.exports.curry = f => x => (...args) => f(x, ...args);
  272. module.exports.mapInPlace = (array, fn) => array.splice(0, array.length, ...array.map(fn));