File: tjn.js 1 #!/usr/bin/node --trace-uncaught 2 3 /* 4 The MIT License (MIT) 5 6 Copyright © 2024 pacman64 7 8 Permission is hereby granted, free of charge, to any person obtaining a copy of 9 this software and associated documentation files (the “Software”), to deal 10 in the Software without restriction, including without limitation the rights to 11 use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 12 of the Software, and to permit persons to whom the Software is furnished to do 13 so, subject to the following conditions: 14 15 The above copyright notice and this permission notice shall be included in all 16 copies or substantial portions of the Software. 17 18 THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 SOFTWARE. 25 */ 26 27 'use strict'; 28 29 const info = ` 30 tjn [options...] [node-js expression] [filepath/URI...] 31 32 Transform Json (with Node) loads JSON data, runs a NodeJS expression on it, 33 and emits the result as JSON. Parsed input-data are available to the given 34 expression as any of the variables named 'v', 'value', 'd', and 'data'. 35 36 If no file/URI is given, it loads JSON data from its standard input. If the 37 argument before the expression is a single equals sign (a '=', without the 38 quotes), no data are read/parsed, and the expression is evaluated as given. 39 40 Options, where leading double-dashes are also allowed, except for alias '=': 41 42 -c compact single-line JSON output (JSON-0) 43 -compact same as -c 44 -j0 same as -c 45 -json0 same as -c 46 -json-0 same as -c 47 48 -h show this help message 49 -help same as -h 50 51 -nil don't read any input 52 -no-input same as -nil 53 -noinput same as -nil 54 -none same as -nil 55 -null same as -nil 56 = same as -nil 57 58 -t show a full traceback of this script for exceptions 59 -trace same as -t 60 -traceback same as -t 61 62 -z zoom JSON value read from stdin, using all the keys given 63 -zj same as -z 64 -zoom same as -z 65 66 67 Extra Functions 68 69 plain(s) ignore ANSI-style sequences in strings 70 71 chunk(x, size) split/resequence items into chunks of the length given 72 cond(...) expression-friendly fully-evaluated if-else chain 73 dedup(x) ignore later (re)occurrences of values in a sequence 74 defunc(x) call value if it's a func, else return as given 75 defunct(x) same as func defunc 76 denan(x, y) turn a floating-point NaN value into the fallback given 77 denil(*args) return the first non-null/none value among those given 78 denone(*args) same as func denil 79 denull(*args) same as func denil 80 dive(x, f) transform value in depth-first-recursive fashion 81 divebin(x, y, f) binary (2-input) version of recursive-transform func dive 82 drop(x, *what) ignore keys or substrings; for strings, dicts, dict-lists 83 flat(*args) flatten everything into a sequence 84 flatten(*args) same as func flat 85 flattened(*args) same as func flat 86 harden(f, v) make funcs return a fallback value instead of exceptions 87 ints(x, y) make sequences of increasing integers, which include the end 88 compose(*args) make a func which chain-calls all funcs given 89 composed(*args) same as func compose 90 parse(s) try to parse JSON strings 91 length(x) same as the built-in func len 92 recover(...args) recover from exceptions with a fallback value 93 idiota(x) object-counterpart of func iota 94 iota(x) make an integer sequence from 1 up to the number given 95 join(x, y) join values into a string; make a dict from keys and values 96 json0(x) turn a value into its smallest JSON-string representation 97 json2(x) turn a value into a 2-space-indented multi-line JSON string 98 lines(s) split strings into their lines, ignoring end-of-line markers 99 links(x) auto-detect all hyperlink-like (HTTP/HTTPS) substrings 100 lower(s) same as the built-in str.lower 101 lowered(s) same as the built-in str.lower 102 numstats(x) calculate various "single-pass" numeric stats 103 once(x, y=null) avoid returning the same value more than once; stateful func 104 pick(x, *what) keep only the keys given; works on dicts, or dict-sequences 105 quote(s, q='"') surround a string with the (optional) quoting-symbol given 106 split(x, y) split string by separator; split string into n substrings 107 squeeze(s) strip/trim a string, squishing inner runs of spaces 108 stround(x, d=6) format numbers into decimal-number strings 109 unique(x) same as func dedup 110 uniqued(x) same as func dedup 111 unquote(s) ignore surrounding quotes, if present 112 113 count(x, f) same as func countif 114 countif(x, f) count how many values make the func given true-like 115 each(x, f) same as func map 116 filter(x, pred) generalization of built-in func Array.filter 117 group(x, by) group values into dicts of lists; optional transform func 118 keep(x, pred) same as func filter 119 map(x, f) generalization of built-in func Array.map 120 tally(x, by) count/tally values, using an optional transformation func 121 122 after(x, y) ignore items until the one given; for strings and sequences 123 before(x, y) ignore items since the one given; for strings and sequences 124 since(x, y) ignore items before the one given; for strings and sequences 125 until(x, y) ignore items after the one given; for strings and sequences 126 127 128 Examples 129 130 # numbers from 0 to 5; no input is read/used 131 tjn = 'range(6)' 132 133 # using bases 1 to 5, find all their powers up to the 4th 134 tjn = 'range(1, 6).map(n => range(1, 4+1).map(p => n**p))' 135 136 # keep only the last 2 items from the input 137 tjn = 'range(1, 6)' | tjn 'data.slice(-2)' 138 139 # chunk/regroup input items into arrays of up to 3 items each 140 tjn = 'range(1, 8)' | tjn 'chunk(data, 3)' 141 142 # ignore all items before the first one with just a 5 in it 143 tjn = 'range(8)' | tjn 'since(data, 5)' 144 `; 145 146 if (process.argv.length < 3) { 147 process.stdout.write(info.trim()); 148 process.stdout.write('\n'); 149 process.exit(0); 150 } 151 152 if (process.argv.length === 3) { 153 switch (process.argv[2]) { 154 case '-h': 155 case '--h': 156 case '-help': 157 case '--help': 158 process.stdout.write(info.trim()); 159 process.stdout.write('\n'); 160 process.exit(0); 161 break; 162 } 163 } 164 165 function zoomArray(data, key) { 166 function str2index(s) { 167 const i = +s; 168 return (i >= 0) ? i : i + data.length; 169 } 170 171 // handle fake-key operators 172 switch (key) { 173 case '.': 174 case ':': 175 case '..': 176 return data; 177 178 case '.len': 179 case '.length': 180 case ':len': 181 case ':length': 182 case ':len:': 183 case ':length:': 184 return data.length; 185 186 case '.val': 187 case ':val': 188 case ':val:': 189 case '.values': 190 case ':values': 191 case ':values:': 192 return data; 193 } 194 195 // handle regular/exclusive slicing 196 let k = key.indexOf(':'); 197 if (k >= 0) { 198 if (k === 0) { 199 return data.slice(0, str2index(key.slice(1))); 200 } 201 if (k === key.length - 1) { 202 return data.slice(str2index(key.slice(0, k))); 203 } 204 205 const i = str2index(key.slice(0, k)); 206 const j = str2index(key.slice(k + 1)); 207 return data.slice(i, j); 208 } 209 210 // handle inclusive slicing 211 k = key.indexOf('..') 212 if (k >= 0) { 213 if (k === 0) { 214 return data.slice(0, str2index(key.slice(2))); 215 } 216 if (k === key.length - 2) { 217 return data.slice(str2index(key.slice(0, k)) + 1); 218 } 219 220 const i = str2index(key.slice(0, k)); 221 const j = str2index(key.slice(k + 2)); 222 return data.slice(i, j + 1); 223 } 224 225 const i = +key; 226 if (isNaN(i) || !isFinite(i) || i % 1 !== 0) { 227 // throw `can't index an array with ${k}`; 228 return null; 229 } 230 231 const j = (i >= 0) ? i : i + data.length; 232 return (0 <= j && j < data.length) ? data[j] : null; 233 } 234 235 function zoomObject(data, key) { 236 if (key in data) { 237 return data[key]; 238 } 239 240 // try matching key case-insensitively 241 for (const k in data) { 242 if (Object.prototype.hasOwnProperty.call(data, k)) { 243 if (key.localeCompare(k) === 0) { 244 return data[k]; 245 } 246 } 247 } 248 249 // handle fake-key operators 250 switch (key) { 251 case '.': 252 case ':': 253 return data; 254 255 case '.keys': 256 case ':keys': 257 case ':keys:': 258 return Object.keys(data); 259 260 case '.len': 261 case '.length': 262 case ':len': 263 case ':length': 264 case ':len:': 265 case ':length:': 266 return objectLength(data); 267 268 case '.val': 269 case ':val': 270 case ':val:': 271 case '.values': 272 case ':values': 273 case ':values:': 274 return Object.values(data); 275 } 276 277 let i = +key; 278 if (isNaN(i) || !isFinite(i) || i % 1 !== 0) { 279 // throw `can't numerically index an object's keys with ${k}`; 280 return null; 281 } 282 283 // handle regular/0-based indexing 284 if (i >= 0) { 285 for (const k in data) { 286 if (Object.prototype.hasOwnProperty.call(data, k)) { 287 if (i === 0) { 288 return data[k]; 289 } 290 i--; 291 } 292 } 293 294 return null; 295 } 296 297 // handle negative/backwards indexing 298 // const keys = Object.keys(data); 299 // const j = (i >= 0) ? i : i + keys.length; 300 // return (0 <= j && j < keys.length) ? data[keys[j]] : null; 301 302 // count keys, to avoid allocations via Object.keys, then try to make 303 // index into a 0-based one 304 const n = objectLength(data); 305 i += n; 306 if (i < 0) { 307 return null; 308 } 309 310 for (const sk in data) { 311 if (i === 0) { 312 return data[sk]; 313 } 314 i--; 315 } 316 return null; 317 } 318 319 function objectLength(kv) { 320 let n = 0; 321 for (const k in kv) { 322 if (Object.prototype.hasOwnProperty.call(data, k)) { 323 n++; 324 } 325 } 326 return n; 327 } 328 329 function zoomValue(data, keys) { 330 for (const k of keys) { 331 if (Array.isArray(data)) { 332 data = zoomArray(data, k); 333 continue; 334 } 335 336 if ((typeof data === 'object') && (data != null)) { 337 data = zoomObject(data, k); 338 continue; 339 } 340 341 // handle identity fake-key operator 342 if (k === '.') { 343 continue; 344 } 345 346 throw `can't index a value of type ${type(data)}`; 347 } 348 349 return data; 350 } 351 352 const readFileSync = require('fs').readFileSync; 353 354 require = function () { 355 throw `function 'require' is disabled`; 356 }; 357 358 359 function main() { 360 let srcIndex = 2; 361 let useInput = true; 362 let traceMode = false; 363 let json0 = false; 364 365 out: 366 while (srcIndex < process.argv.length) { 367 switch (process.argv[srcIndex]) { 368 case '=': 369 case '-nil': 370 case '--nil': 371 case '-noinput': 372 case '--noinput': 373 case '-no-input': 374 case '--no-input': 375 case '-none': 376 case '--none': 377 case '-null': 378 case '--null': 379 useInput = false; 380 srcIndex++; 381 break; 382 383 case '-c': 384 case '--c': 385 case '-compact': 386 case '--compact': 387 case '-j0': 388 case '--j0': 389 case '-json0': 390 case '--json0': 391 case '-json-0': 392 case '--json-0': 393 json0 = true; 394 srcIndex++; 395 break; 396 397 case '-t': 398 case '--t': 399 case '-trace': 400 case '--trace': 401 case '-traceback': 402 case '--traceback': 403 traceMode = true; 404 srcIndex++; 405 break; 406 407 case '-z': 408 case '--z': 409 case '-zj': 410 case '--zj': 411 case '-zoom': 412 case '--zoom': 413 try { 414 const data = JSON.parse(readFileSync(0)); 415 const keys = process.argv.slice(srcIndex + 1); 416 const show = json0 ? showJSON0 : showJSON; 417 show(zoomValue(data, keys)); 418 } catch (error) { 419 if (traceMode) { 420 throw error; 421 } 422 process.stderr.write(`\x1b[31m${error}\x1b[0m\n`); 423 process.exit(1); 424 } 425 return; 426 427 default: 428 break out; 429 } 430 } 431 432 try { 433 if (useInput && process.argv.length > srcIndex + 2) { 434 throw `multiple named inputs not supported`; 435 } 436 437 const args = process.argv.slice(srcIndex + 1); 438 const expr = process.argv[srcIndex]; 439 const show = json0 ? showJSON0 : showJSON; 440 globalThis.now = new Date(); 441 globalThis.expr = expr; 442 globalThis.args = args; 443 444 // ignore broken-pipe-style errors, making piping output to the 445 // likes of `head` just work as intended 446 process.stdout.on('error', _ => { process.exit(0); }); 447 448 if (!useInput) { 449 show(run(null, expr)); 450 } else if (process.argv.length === srcIndex + 2) { 451 const name = process.argv[process.argv.length - 1]; 452 453 if (name.startsWith('https://') || name.startsWith('http://')) { 454 fetch(name).then(resp => resp.json()).then(data => { 455 show(run(data, expr)); 456 }); 457 } else if (name === '-') { 458 show(run(JSON.parse(readFileSync(0)), expr)); 459 } else { 460 let path = name; 461 if (name.startsWith('file://')) { 462 path = name.slice('file://'.length); 463 } 464 show(run(JSON.parse(readFileSync(path)), expr)); 465 } 466 } else { 467 show(run(JSON.parse(readFileSync(0)), expr)); 468 } 469 } catch (error) { 470 if (traceMode) { 471 throw error; 472 } 473 process.stderr.write(`\x1b[31m${error}\x1b[0m\n`); 474 process.exit(1); 475 } 476 } 477 478 function conform(x) { 479 if (x == null) { 480 return x; 481 } 482 483 if (Array.isArray(x)) { 484 return x.filter(e => !(e instanceof Skip)).map(e => conform(e)); 485 } 486 487 switch (typeof x) { 488 case 'bigint': 489 return x.toString(); 490 491 case 'boolean': 492 case 'number': 493 case 'string': 494 return x; 495 496 case 'object': 497 const kv = {}; 498 for (const k in x) { 499 if (Object.prototype.hasOwnProperty.call(x, k)) { 500 const v = kv[k]; 501 if (!(v instanceof Skip)) { 502 kv[k] = conform(v); 503 } 504 } 505 } 506 return kv; 507 508 default: 509 return x.toString(); 510 } 511 } 512 513 function conforms(x) { 514 if (x == null) { 515 return true; 516 } 517 518 if (x instanceof Skip) { 519 return false; 520 } 521 522 if (Array.isArray(x)) { 523 for (const e of x) { 524 if (!conforms(e)) { 525 return false; 526 } 527 } 528 return true; 529 } 530 531 switch (typeof x) { 532 case 'boolean': 533 case 'number': 534 case 'string': 535 return true; 536 537 case 'object': 538 for (const k in x) { 539 if (Object.prototype.hasOwnProperty.call(x, k)) { 540 if (!conforms(x[k])) { 541 return false; 542 } 543 } 544 } 545 return true; 546 547 default: 548 return false; 549 } 550 } 551 552 function showJSON(data) { 553 process.stdout.write(JSON.stringify(data, null, 2)); 554 process.stdout.write('\n'); 555 } 556 557 function showJSON0(data) { 558 process.stdout.write(JSON.stringify(data, null, 0)); 559 process.stdout.write('\n'); 560 } 561 562 function run(data, expr) { 563 switch (expr.trim()) { 564 case '', '.': 565 return data; 566 } 567 568 const d = data; 569 const v = data; 570 const value = data; 571 const f = Number.parseFloat(data); 572 const n = Number.parseInt(data); 573 574 let res = eval(expr); 575 if (typeof res === 'function') { 576 res = res(data); 577 } 578 return conforms(res) ? res : conform(res); 579 } 580 581 function plain(s) { 582 return s.replaceAll(ansi_style_re, ''); 583 } 584 585 function message(x) { 586 process.stderr.write(`${x}\n`); 587 } 588 589 const msg = message; 590 591 592 class Skip { } 593 594 const skip = new Skip(); 595 596 597 const assert = process.assert; 598 const env = process.env; 599 const exit = process.exit; 600 const hrtime = process.hrtime; 601 602 603 /* 604 some convenience aliases to commonly-used values: using literal strings 605 on the cmd-line is often tricky / annoying, and some of these can help 606 get around multiple levels of string-quoting 607 */ 608 609 const math = Math; 610 const e = Math.E; 611 const pi = Math.PI; 612 const tau = 2 * Math.PI; 613 614 const E = Math.E; 615 const LN10 = Math.LN10; 616 const LN2 = Math.LN2; 617 const LOG10E = Math.LOG10E; 618 const LOG2E = Math.LOG2E; 619 const PI = Math.PI; 620 const SQRT1_2 = Math.SQRT1_2; 621 const SQRT2 = Math.SQRT2; 622 623 const abs = Math.abs; 624 const acos = Math.acos; 625 const acosh = Math.acosh; 626 const asin = Math.asin; 627 const asinh = Math.asinh; 628 const atan = Math.atan; 629 const atan2 = Math.atan2; 630 const atanh = Math.atanh; 631 const cbrt = Math.cbrt; 632 const ceil = Math.ceil; 633 const clz32 = Math.clz32; 634 const cos = Math.cos; 635 const cosh = Math.cosh; 636 const exp = Math.exp; 637 const expm1 = Math.expm1; 638 const floor = Math.floor; 639 const fround = Math.fround; 640 const hypot = Math.hypot; 641 const imul = Math.imul; 642 const log = Math.log; 643 const log10 = Math.log10; 644 const log1p = Math.log1p; 645 const log2 = Math.log2; 646 const max = Math.max; 647 const min = Math.min; 648 const pow = Math.pow; 649 const random = Math.random; 650 // const round = Math.round; 651 const sign = Math.sign; 652 const sin = Math.sin; 653 const sinh = Math.sinh; 654 const sqrt = Math.sqrt; 655 const tan = Math.tan; 656 const tanh = Math.tanh; 657 const trunc = Math.trunc; 658 659 const inf = Infinity; 660 const infinity = Infinity; 661 const infty = Infinity; 662 const nan = NaN; 663 const nil = null; 664 const none = null; 665 666 const s = ''; 667 668 const True = true; 669 const False = false; 670 const None = null; 671 672 const digits = '0123456789'; 673 const hexDigits = '0123456789ABCDEF'; 674 const lowercase = 'abcdefghijklmnopqrstuvwxyz'; 675 const uppercase = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; 676 677 const amp = '&'; 678 const ampersand = '&'; 679 const ansireset = '\x1b[0m'; 680 const ansiReset = '\x1b[0m'; 681 const apo = '\''; 682 const apos = '\''; 683 const ast = '*'; 684 const asterisk = '*'; 685 const at = '@'; 686 const backquote = '`'; 687 const backslash = '\\'; 688 const backtick = '`'; 689 const ball = '●'; 690 const bang = '!'; 691 const block = '█'; 692 const bquo = '`'; 693 const bquote = '`'; 694 const bslash = '\\'; 695 const bullet = '•'; 696 const caret = '^'; 697 const cdot = '·'; 698 const circle = '●'; 699 const colon = ':'; 700 const comma = ','; 701 const cr = '\r'; 702 const crlf = '\r\n'; 703 const cs = ', '; 704 const dash = '—'; 705 const dollar = '$'; 706 const dot = '.'; 707 const dquo = '"'; 708 const dquote = '"'; 709 const emark = '!'; 710 const emdash = '—'; 711 const empty = ''; 712 const endash = '–'; 713 const eq = '='; 714 const et = '&'; 715 const ge = '≥'; 716 const geq = '≥'; 717 const gt = '>'; 718 const hellip = '…'; 719 const hole = '○'; 720 const hyphen = '-'; 721 const ldquo = '“'; 722 const ldquote = '“'; 723 const le = '≤'; 724 const leq = '≤'; 725 const lf = '\n'; 726 const lt = '<'; 727 const mdash = '—'; 728 const mdot = '·'; 729 const minus = '-'; 730 const ndash = '–'; 731 const neq = '≠'; 732 const perc = '%'; 733 const percent = '%'; 734 const period = '.'; 735 const plus = '+'; 736 const qmark = '?'; 737 const que = '?'; 738 const rdquo = '”'; 739 const rdquote = '”'; 740 const sball = '•'; 741 const semi = ';'; 742 const semicolon = ';'; 743 const sharp = '#'; 744 const slash = '/'; 745 const smallball = '•'; 746 const space = ' '; 747 const square = '■'; 748 const squo = '\''; 749 const squote = '\''; 750 const tab = '\t'; 751 const tilde = '~'; 752 const underscore = '_'; 753 const uscore = '_'; 754 const utf8bom = '\xef\xbb\xbf'; 755 756 const kb = 1024; 757 const mb = 1024 * kb; 758 const gb = 1024 * mb; 759 const tb = 1024 * gb; 760 const pb = 1024 * tb; 761 762 const kib = 1024; 763 const mib = 1024 * kib; 764 const gib = 1024 * mib; 765 const tib = 1024 * gib; 766 const pib = 1024 * tib; 767 768 const mol = 602214076000000000000000n; 769 const mole = 602214076000000000000000n; 770 771 const hour = 3_600; 772 const day = 86_400; 773 const week = 604_800; 774 775 const hr = 3_600; 776 const wk = 604_800; 777 778 const cup2l = 0.23658824; 779 const floz2l = 0.0295735295625; 780 const floz2ml = 29.5735295625; 781 const ft2m = 0.3048; 782 const gal2l = 3.785411784; 783 const in2cm = 2.54; 784 const lb2kg = 0.45359237; 785 const mi2km = 1.609344; 786 const mpg2kpl = 0.425143707; 787 const nmi2km = 1.852; 788 const oz2g = 28.34952312; 789 const psi2pa = 6894.757293168; 790 const ton2kg = 907.18474; 791 const yd2m = 0.9144; 792 793 const months = [ 794 'January', 'February', 'March', 'April', 'May', 'June', 795 'July', 'August', 'September', 'October', 'November', 'December', 796 ]; 797 798 const monweek = [ 799 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 800 'Saturday', 'Sunday', 801 ]; 802 803 const sunweek = [ 804 'Sunday', 805 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 806 ]; 807 808 const physics = { 809 kilo: 1_000, 810 mega: 1_000_000, 811 giga: 1_000_000_000, 812 tera: 1_000_000_000_000, 813 peta: 1_000_000_000_000_000, 814 // exa: 1_000_000_000_000_000_000, 815 // zetta: 1_000_000_000_000_000_000_000, 816 817 c: 299_792_458, 818 kcd: 683, 819 na: 602214076000000000000000n, 820 821 femto: 1e-15, pico: 1e-12, nano: 1e-9, micro: 1e-6, milli: 1e-3, 822 823 e: 1.602176634e-19, 824 f: 96_485.33212, 825 h: 6.62607015e-34, 826 k: 1.380649e-23, 827 mu: 1.66053906892e-27, 828 829 ge: 9.7803267715, 830 gn: 9.80665, 831 }; 832 833 // seen remembers values already given to func `once` 834 const seen = new Set(); 835 836 const ansi_style_re = new RegExp('\x1b\\[([0-9;]+m|[0-9]*[A-HJKST])', 'g'); 837 const link_src = 'https?://[A-Za-z0-9+_.:%-]+(/[A-Za-z0-9+_.%/,#?&=-]*)*'; 838 const link_re = new RegExp(link_src, 'g'); 839 840 841 const loads = JSON.parse; 842 const parse = JSON.parse; 843 const stringify = JSON.stringify; 844 const assign = Object.assign; 845 const entries = Object.entries; 846 const fromEntries = Object.fromEntries; 847 const is = Object.is; 848 const chr = String.fromCharCode; 849 const fromCharCode = String.fromCharCode; 850 const fromCodePoint = String.fromCodePoint; 851 852 853 function append(...what) { 854 let res = []; 855 for (const e of what) { 856 flappend(e, res); 857 } 858 return res; 859 } 860 861 function bin(x) { 862 return x.toString(2); 863 } 864 865 function cases(x, ...args) { 866 for (let i = 0; i < args.length - args.length % 2; i += 2) { 867 const test = args[i]; 868 869 if (Array.isArray(test)) { 870 for (const v of test) { 871 if (x === v) { 872 return args[i + 1]; 873 } 874 } 875 continue; 876 } 877 878 if (x === test) { 879 return args[i + 1]; 880 } 881 } 882 883 return len(args) % 2 === 0 ? null : args[args.length - 1]; 884 } 885 886 function chunk(what, chunkSize) { 887 const chunks = []; 888 for (let i = 0; i < what.length; i += chunkSize) { 889 const end = Math.min(i + chunkSize, what.length); 890 chunks.push(what.slice(i, end)); 891 } 892 return chunks; 893 } 894 895 const chunked = chunk; 896 897 function compose(...funcs) { 898 if (funcs.length === 0) { 899 return identity; 900 } 901 902 return function (...args) { 903 let res = funcs[0](...args); 904 for (let i = 1; i < funcs.length; i++) { 905 res = funcs[i](res); 906 } 907 return res; 908 } 909 } 910 911 const composed = compose; 912 913 function cond(...args) { 914 for (let i = 0; i < args.length - args.length % 2; i += 2) { 915 if (args[i]) { 916 return args[i + 1]; 917 } 918 } 919 return len(args) % 2 === 0 ? null : args[args.length - 1]; 920 } 921 922 function dedup(src, f = identity) { 923 const unique = []; 924 const got = new Set(); 925 for (let i = 0; i < src.length; i++) { 926 const v = f(src[i], i, src); 927 if (!got.has(v)) { 928 unique.push(v); 929 got.add(v); 930 } 931 } 932 return unique; 933 } 934 935 const dedupe = dedup; 936 const deduped = dedup; 937 const undup = dedup; 938 const undupe = dedup; 939 const unduped = dedup; 940 const unique = dedup; 941 const uniqued = dedup; 942 943 function defunc(x) { 944 return (typeof x === 'function') ? x() : x; 945 } 946 947 const defunct = defunc; 948 const unfunc = defunc; 949 const unfunct = defunc; 950 951 function dejson(x, fallback = null) { 952 try { 953 return (typeof x === 'string') ? JSON.parse(x) : x; 954 } catch { 955 return fallback; 956 } 957 } 958 959 const tryparse = dejson; 960 const tryParse = dejson; 961 const unjson = dejson; 962 963 function denan(x, instead = null) { 964 return isNaN(x) ? instead : x; 965 } 966 967 const deNaN = denan; 968 969 function denull(...args) { 970 for (const x of args) { 971 if (x != null) { 972 return x; 973 } 974 } 975 return null; 976 } 977 978 const denil = denull; 979 const denone = denull; 980 981 function dive(into, using) { 982 if (using == null) { 983 throw 'dive: no diving func given'; 984 } 985 986 function go(into, using, key, src) { 987 if (Array.isArray(into)) { 988 return into.map((x, i) => go(x, using, i, into)); 989 } 990 991 if (typeof into === 'object') { 992 const kv = {}; 993 for (const k in into) { 994 if (Object.hasOwnProperty.call(into, k)) { 995 kv[k] = go(into[k], using, k, into); 996 } 997 } 998 return kv; 999 } 1000 1001 return using(into, key, src); 1002 } 1003 1004 if (typeof into === 'function') { 1005 return go(using, into, null, into); 1006 } 1007 return go(into, using, null, into); 1008 } 1009 1010 const dive1 = dive; 1011 1012 function divebin(x, y, using) { 1013 if (using == null) { 1014 throw 'divebin: no diving func given'; 1015 } 1016 1017 if (typeof x === 'function') { 1018 return divebin(y, using, x) 1019 } 1020 1021 switch (using.length) { 1022 case 2: 1023 return dive(x, a => dive(y, b => using(a, b))); 1024 case 4: 1025 return dive(x, (a, i) => dive(y, (b, j) => using(a, i, b, j))); 1026 default: 1027 throw `divebin(...) only supports funcs with 2 or 4 args`; 1028 } 1029 } 1030 1031 const dive2 = divebin; 1032 // const dualDive = divebin; 1033 // const duoDive = divebin; 1034 1035 function dumps(x) { 1036 return JSON.stringify(x); 1037 } 1038 1039 function emptyish(s) { 1040 return /^ *#/.test(s); 1041 } 1042 1043 const isemptyish = emptyish; 1044 1045 function every(src, f) { 1046 if (typeof src === 'function') { 1047 const t = src; 1048 src = f; 1049 f = t; 1050 } 1051 1052 if (Array.isArray(src)) { 1053 return src.every(f); 1054 } 1055 1056 switch (typeof src) { 1057 case 'string': 1058 let i = 0; 1059 for (const c of src) { 1060 if (!f(c, i, src)) { 1061 return false; 1062 } 1063 i++; 1064 } 1065 return true; 1066 1067 case 'object': 1068 for (const k in src) { 1069 if (Object.prototype.hasOwnProperty.call(src, k)) { 1070 if (!f(src[k], i, src)) { 1071 return false; 1072 } 1073 } 1074 } 1075 return true; 1076 1077 default: 1078 throw `can't loop on type ${typeof src}`; 1079 } 1080 } 1081 1082 const all = every; 1083 1084 function fillTable(src, fallback = null) { 1085 const allKeys = []; 1086 const got = new Set(); 1087 let fillingNeeded = false; 1088 1089 if (typeof fallback !== 'function') { 1090 const filler = fallback; 1091 fallback = (v, k) => filler; 1092 } 1093 1094 let first = true; 1095 for (const row of src) { 1096 if (typeof row !== 'object') { 1097 throw 'function fillTable only works with an array of objects'; 1098 } 1099 1100 for (const k in row) { 1101 if (Object.hasOwnProperty.call(row, k)) { 1102 if (got.has(k)) { 1103 continue; 1104 } 1105 if (!first) { 1106 fillingNeeded = true; 1107 } 1108 1109 allKeys.push(k); 1110 got.add(k); 1111 } 1112 } 1113 1114 first = false; 1115 } 1116 1117 if (!fillingNeeded) { 1118 return src; 1119 } 1120 1121 return src.map(row => { 1122 const kv = {}; 1123 for (const k of allKeys) { 1124 const v = row[k]; 1125 kv[k] = (v !== undefined) ? v : fallback(v, k); 1126 } 1127 return kv; 1128 }) 1129 } 1130 1131 function flappend(src, into = null) { 1132 if (into == null) { 1133 into = []; 1134 } 1135 1136 if (Array.isArray(src)) { 1137 for (const e of src) { 1138 flappend(e, into); 1139 } 1140 return into; 1141 } 1142 1143 if (typeof src === 'object') { 1144 for (const k in src) { 1145 if (Object.hasOwnProperty.call(src, k)) { 1146 flappend(src[k], into); 1147 } 1148 } 1149 return into; 1150 } 1151 1152 into.push(src); 1153 return into; 1154 } 1155 1156 function flat(src) { 1157 return flappend(src, []); 1158 } 1159 1160 const flatten = flat; 1161 const flattened = flat; 1162 1163 function harden(f, fallback = null) { 1164 return function (...args) { 1165 try { 1166 return f(...args); 1167 } catch { 1168 return fallback; 1169 } 1170 } 1171 } 1172 1173 const hardened = harden; 1174 1175 function hex(x) { 1176 return x.toString(16); 1177 } 1178 1179 function horner(coeffs, x) { 1180 if (Array.isArray(x)) { 1181 let t = coeffs; 1182 coeffs = x; 1183 x = t; 1184 } 1185 1186 if (coeffs.length === 0) { 1187 return 0; 1188 } 1189 1190 let y = coeffs[0]; 1191 for (let i = 1; i < coeffs.length; i++) { 1192 y *= x; 1193 y += coeffs[i]; 1194 } 1195 return y; 1196 } 1197 1198 const polyval = horner; 1199 1200 function identity(x) { 1201 return x; 1202 } 1203 1204 const idem = identity; 1205 const iden = identity; 1206 1207 function idiota(n, f = identity) { 1208 if (n < 1 || isNaN(n) || !isFinite(n)) { 1209 return {}; 1210 } 1211 1212 n = Math.floor(n); 1213 const range = {}; 1214 for (let i = 0; i < n; i++) { 1215 range[i + 1] = f(i + 1); 1216 } 1217 return range; 1218 } 1219 1220 const kviota = idiota; 1221 1222 function insist(...attempts) { 1223 if (attempts.length === 0) { 1224 throw 'insist: no funcs/attempts given'; 1225 } 1226 1227 for (let i = 0; i < attempts.length; i++) { 1228 try { 1229 const a = attempts[i]; 1230 return (typeof a === 'function') ? a() : a; 1231 } catch (error) { 1232 if (i === attempts.length - 1) { 1233 throw error; 1234 } 1235 } 1236 } 1237 1238 throw 'insist(...): nothing worked'; 1239 } 1240 1241 function ints(start, stop, f = identity) { 1242 return iota(stop - start + 1, n => f(n + start)); 1243 } 1244 1245 function iota(n, f = identity) { 1246 if (n < 1 || isNaN(n) || !isFinite(n)) { 1247 return []; 1248 } 1249 1250 n = Math.floor(n); 1251 const range = new Array(n); 1252 for (let i = 0; i < n; i++) { 1253 range[i] = f(i + 1); 1254 } 1255 return range; 1256 } 1257 1258 function join(what, sep = '') { 1259 if (Array.isArray(what) && Array.isArray(sep)) { 1260 const kv = {}; 1261 for (let i = 0; i < what.length; i++) { 1262 kv[what[i]] = i < sep.length ? sep[i] : null; 1263 } 1264 return kv; 1265 } 1266 1267 if (typeof what === 'string') { 1268 return sep.join(what); 1269 } 1270 return what.join(sep); 1271 } 1272 1273 function json0(x) { 1274 return JSON.stringify(x, null, 0); 1275 } 1276 1277 function json2(x) { 1278 return JSON.stringify(x, null, 2); 1279 } 1280 1281 1282 function length(x) { 1283 if (typeof x === 'object') { 1284 let n = 0; 1285 for (const k in x) { 1286 n += Object.hasOwnProperty.call(x, k); 1287 } 1288 return n; 1289 } 1290 return x.length; 1291 } 1292 1293 const len = length; 1294 1295 function lines(s) { 1296 return s.split(/\r?\n/g); 1297 } 1298 1299 function links(src) { 1300 const links = []; 1301 1302 function rec(src) { 1303 if (Array.isArray(src)) { 1304 for (const e of src) { 1305 rec(e); 1306 } 1307 return; 1308 } 1309 1310 if (typeof src === 'string') { 1311 for (const s of src.matchAll(link_re)) { 1312 links.push(s[0]); 1313 } 1314 } 1315 } 1316 1317 rec(src); 1318 return links; 1319 } 1320 1321 function mappend(...what) { 1322 const kv = {}; 1323 for (const src of what) { 1324 if (typeof src == 'object') { 1325 Object.assign(kv, src); 1326 } else { 1327 throw 'mappend only works with objects'; 1328 } 1329 } 1330 return kv; 1331 } 1332 1333 function maybe(f, x = null) { 1334 try { 1335 return f(x); 1336 } catch (e) { 1337 return x; 1338 } 1339 } 1340 1341 function maybeNumber(x) { 1342 if (typeof x === 'string') { 1343 const f = parseFloat(x); 1344 return isNaN(f) ? x : f; 1345 } 1346 return x; 1347 } 1348 1349 const mayben = maybeNumber; 1350 const maybenum = maybeNumber; 1351 const maybeNum = maybeNumber; 1352 1353 function mustNumber(x) { 1354 if (typeof x === 'boolean') { 1355 return +x; 1356 } 1357 1358 if (typeof x === 'number') { 1359 return x; 1360 } 1361 1362 if (typeof x === 'string') { 1363 const n = +x; 1364 if (!isNaN(n)) { 1365 return n; 1366 } 1367 } 1368 1369 throw `can't turn value of type ${typeof x} into a valid number`; 1370 } 1371 1372 const mustn = mustNumber; 1373 const mustnum = mustNumber; 1374 const mustNum = mustNumber; 1375 1376 function narrow(x) { 1377 if (typeof x !== 'string') { 1378 return x; 1379 } 1380 1381 try { 1382 return JSON.parse(x); 1383 } catch { 1384 const f = +x; 1385 return (isNaN(f) || !isFinite(f)) ? x : f; 1386 } 1387 } 1388 1389 const autocast = narrow; 1390 const autoCast = narrow; 1391 const recast = narrow; 1392 1393 function oct(x) { 1394 return x.toString(8); 1395 } 1396 1397 function ord(s) { 1398 return s.charCodeAt(0); 1399 } 1400 1401 function predicate(x) { 1402 if (typeof x === 'number' && isNaN(x)) { 1403 return y => ((typeof y === 'number') && isNaN(y)); 1404 } 1405 if (typeof x === 'function') { 1406 return x; 1407 } 1408 return y => x === y; 1409 } 1410 1411 function quote(what, quote = '"') { 1412 return `${quote}${what}${quote}`; 1413 } 1414 1415 function range(start, stop = null, f = identity) { 1416 if (stop === null) { 1417 stop = start; 1418 start = 0; 1419 } 1420 1421 if (isNaN(start) || !isFinite(start)) { 1422 return []; 1423 } 1424 if (isNaN(stop) || !isFinite(stop)) { 1425 return []; 1426 } 1427 if (start > stop) { 1428 return []; 1429 } 1430 1431 start = Math.floor(start); 1432 stop = Math.floor(stop); 1433 const n = stop - start; 1434 const range = new Array(n); 1435 for (let i = 0; i < n; i++) { 1436 range[i] = f(start + i); 1437 } 1438 return range; 1439 } 1440 1441 function recover(...args) { 1442 switch (args.length) { 1443 case 1: 1444 try { 1445 return args[0](); 1446 } catch (e) { 1447 return null; 1448 } 1449 1450 case 2: 1451 if (args[0].length === 0) { 1452 try { 1453 return args[0](); 1454 } catch { 1455 return args[1]; 1456 } 1457 } 1458 1459 try { 1460 return args[0](args[1]); 1461 } catch { 1462 return null; 1463 } 1464 1465 case 3: 1466 try { 1467 return args[0](args[1]); 1468 } catch { 1469 return args[2]; 1470 } 1471 1472 default: 1473 throw `recover(...) only supports 1..3 args`; 1474 } 1475 } 1476 1477 const rescue = recover; 1478 const trycatch = recover; 1479 const tryexcept = recover; 1480 const tryCatch = recover; 1481 const tryExcept = recover; 1482 1483 function reverse(src) { 1484 if (Array.isArray(src)) { 1485 return src.slice().reverse(); 1486 } 1487 1488 switch (typeof src) { 1489 case 'number': 1490 return riota(src); 1491 case 'string': 1492 return src.split('').reverse().join(''); 1493 case 'object': 1494 const kv = {}; 1495 const keys = Object.keys(src); 1496 for (let i = keys.length - 1; i >= 0; i--) { 1497 const k = keys[i]; 1498 kv[k] = src[k]; 1499 } 1500 return kv; 1501 default: 1502 throw `can't reverse values of type ${typeof src}`; 1503 } 1504 } 1505 1506 const rev = reverse; 1507 1508 function reviota(src, f = identity) { 1509 if (src < 1 || isNaN(src) || !isFinite(src)) { 1510 return []; 1511 } 1512 src = Math.floor(src); 1513 const rev = new Array(src); 1514 for (let i = 0; i < src; i++) { 1515 rev[i] = f(src - i); 1516 } 1517 return src; 1518 } 1519 1520 const riota = reviota; 1521 1522 function scale(x, x0, x1, y0, y1) { 1523 return (y1 - y0) * (x - x0) / (x1 - x0) + y0; 1524 } 1525 1526 function some(src, f) { 1527 if (typeof src === 'function') { 1528 const t = src; 1529 src = f; 1530 f = t; 1531 } 1532 1533 if (Array.isArray(src)) { 1534 return src.some(f); 1535 } 1536 1537 switch (typeof src) { 1538 case 'string': 1539 let i = 0; 1540 for (const c of src) { 1541 if (f(c, i, src)) { 1542 return true; 1543 } 1544 i++; 1545 } 1546 return false; 1547 1548 case 'object': 1549 for (const k in src) { 1550 if (Object.prototype.hasOwnProperty.call(src, k)) { 1551 if (f(src[k], i, src)) { 1552 return true; 1553 } 1554 } 1555 } 1556 return false; 1557 1558 default: 1559 throw `can't loop on type ${typeof src}`; 1560 } 1561 } 1562 1563 const any = some; 1564 1565 function split(what, n) { 1566 if (typeof what === 'string' && typeof n === 'string') { 1567 return what.split(n); 1568 } 1569 1570 if (n < 1) { 1571 return []; 1572 } 1573 1574 const len = what.length; 1575 if (len < n) { 1576 return what; 1577 } 1578 1579 const chunks = []; 1580 const chunkSize = Math.max(1, Math.floor(len / n)); 1581 for (let i = 0; i < n; i++) { 1582 const start = i * chunkSize; 1583 const end = Math.min(start + chunkSize, len); 1584 chunks.push(i < n - 1 ? what.slice(start, end) : what.slice(start)); 1585 } 1586 return chunks; 1587 } 1588 1589 const broken = split; 1590 const splitted = split; 1591 const splitten = split; 1592 1593 function squeeze(s) { 1594 return s.trim().replace(/ +/g, ''); 1595 } 1596 1597 function string(x) { 1598 if (x == null) { 1599 return 'null'; 1600 } 1601 1602 switch (typeof x) { 1603 case 'string': 1604 return x; 1605 1606 case 'object': 1607 // if (typeof x.toString === 'function') { 1608 // return x.toString(); 1609 // } 1610 return JSON.stringify(x, null, 0); 1611 1612 case 'function': 1613 return x.toString(); 1614 1615 default: 1616 return JSON.stringify(x, null, 0); 1617 } 1618 } 1619 1620 const str = string; 1621 1622 function stround(s, decimals = 6) { 1623 return s.toFixed(decimals); 1624 } 1625 1626 function trim(x) { 1627 return x.trim(); 1628 } 1629 1630 const strip = trim; 1631 const stripped = trim; 1632 const trimmed = trim; 1633 const trimSpace = trim; 1634 const trimSpaces = trim; 1635 1636 function trimEnd(x) { 1637 return x.trimEnd(); 1638 } 1639 1640 const rstrip = trimEnd; 1641 1642 function type(x) { 1643 if (x == null) { 1644 return 'null'; 1645 } 1646 return Array.isArray(x) ? 'array' : typeof x; 1647 } 1648 1649 function unquote(s) { 1650 if (s.startsWith(`'`) && s.endsWith(`'`)) { 1651 return s.slice(1, s.length - 1); 1652 } 1653 if (s.startsWith(`"`) && s.endsWith(`"`)) { 1654 return s.slice(1, s.length - 1); 1655 } 1656 if (s.startsWith('`') && s.endsWith('`')) { 1657 return s.slice(1, s.length - 1); 1658 } 1659 return s; 1660 } 1661 1662 function wrap(x, y0, y1) { 1663 return (y1 - y0) * (x - y0); 1664 } 1665 1666 function after(data, what) { 1667 if (typeof data === 'string') { 1668 if (typeof what !== 'string') { 1669 return ''; 1670 } 1671 const i = data.indexOf(what); 1672 return i >= 0 ? data.slice(i + what.length) : ''; 1673 } 1674 1675 const check = predicate(what); 1676 for (let i = 0; i < data.length; i++) { 1677 if (check(data[i], i, data)) { 1678 return data.slice(i + 1); 1679 } 1680 } 1681 return []; 1682 } 1683 1684 function afterLast(data, what) { 1685 if (typeof data === 'string') { 1686 if (typeof what !== 'string') { 1687 return ''; 1688 } 1689 const i = data.lastIndexOf(what); 1690 return i >= 0 ? data.slice(i + what.length) : ''; 1691 } 1692 1693 const check = predicate(what); 1694 for (let i = data.length - 1; i >= 0; i--) { 1695 if (check(data[i], i, data)) { 1696 return data.slice(i + 1); 1697 } 1698 } 1699 return []; 1700 } 1701 1702 const afterfinal = afterLast; 1703 const afterFinal = afterLast; 1704 const afterlast = afterLast; 1705 1706 function before(data, what) { 1707 if (typeof data === 'string') { 1708 if (typeof what !== 'string') { 1709 return data; 1710 } 1711 const i = data.indexOf(what); 1712 return i >= 0 ? data.slice(0, i) : data; 1713 } 1714 1715 const check = predicate(what); 1716 for (let i = 0; i < data.length; i++) { 1717 if (check(data[i], i, data)) { 1718 return data.slice(0, i); 1719 } 1720 } 1721 return data; 1722 } 1723 1724 function beforeLast(data, what) { 1725 if (typeof data === 'string') { 1726 if (typeof what !== 'string') { 1727 return data; 1728 } 1729 const i = data.lastIndexOf(what); 1730 return i >= 0 ? data.slice(0, i) : data; 1731 } 1732 1733 const check = predicate(what); 1734 for (let i = data.length - 1; i >= 0; i--) { 1735 if (check(data[i], i, data)) { 1736 return data.slice(0, i); 1737 } 1738 } 1739 return []; 1740 } 1741 1742 const beforefinal = beforeLast; 1743 const beforeFinal = beforeLast; 1744 const beforelast = beforeLast; 1745 1746 function since(data, what) { 1747 if (typeof data === 'string') { 1748 if (typeof what !== 'string') { 1749 return ''; 1750 } 1751 const i = data.indexOf(what); 1752 return i >= 0 ? data.slice(i) : ''; 1753 } 1754 1755 const check = predicate(what); 1756 for (let i = 0; i < data.length; i++) { 1757 if (check(data[i], i, data)) { 1758 return data.slice(i); 1759 } 1760 } 1761 return []; 1762 } 1763 1764 function sinceLast(data, what) { 1765 if (typeof data === 'string') { 1766 if (typeof what !== 'string') { 1767 return ''; 1768 } 1769 const i = data.lastIndexOf(what); 1770 return i >= 0 ? data.slice(i) : ''; 1771 } 1772 1773 const check = predicate(what); 1774 for (let i = data.length - 1; i >= 0; i--) { 1775 if (check(data[i], i, data)) { 1776 return data.slice(i); 1777 } 1778 } 1779 return []; 1780 } 1781 1782 const sincefinal = sinceLast; 1783 const sinceFinal = sinceLast; 1784 const sincelast = sinceLast; 1785 1786 function until(data, what) { 1787 if (typeof data === 'string') { 1788 if (typeof what !== 'string') { 1789 return ''; 1790 } 1791 const i = data.indexOf(what); 1792 return i >= 0 ? data.slice(0, i + what.length) : data; 1793 } 1794 1795 const check = predicate(what); 1796 for (let i = 0; i < data.length; i++) { 1797 if (check(data[i], i, data)) { 1798 return data.slice(0, i + 1); 1799 } 1800 } 1801 } 1802 1803 function untilLast(data, what) { 1804 if (typeof data === 'string') { 1805 if (typeof what !== 'string') { 1806 return ''; 1807 } 1808 const i = data.lastIndexOf(what); 1809 return i >= 0 ? data.slice(0, i + what.length) : data; 1810 } 1811 1812 const check = predicate(what); 1813 for (let i = data.length - 1; i >= 0; i--) { 1814 if (check(data[i], i, data)) { 1815 return data.slice(0, i + 1); 1816 } 1817 } 1818 return data; 1819 } 1820 1821 const untilfinal = untilLast; 1822 const untilFinal = untilLast; 1823 const untillast = untilLast; 1824 1825 function loop(src, f) { 1826 if (typeof src === 'function') { 1827 const t = src; 1828 src = f; 1829 f = t; 1830 } 1831 1832 if (Array.isArray(src)) { 1833 for (let i = 0; i < src.length; i++) { 1834 f(src[i], i, src); 1835 } 1836 return; 1837 } 1838 1839 1840 switch (typeof src) { 1841 case 'number': 1842 if (isNaN(src) || !isFinite(src)) { 1843 return; 1844 } 1845 1846 src = Math.floor(src); 1847 for (let i = 0; i < src; i++) { 1848 f(i + 1, i, src); 1849 } 1850 return; 1851 1852 case 'string': 1853 let i = 0; 1854 for (const c of src) { 1855 f(c, i, src); 1856 i++; 1857 } 1858 return; 1859 1860 case 'object': 1861 for (const k in src) { 1862 if (Object.hasOwnProperty.call(src, k)) { 1863 f(src[k], k, src); 1864 } 1865 } 1866 return; 1867 1868 default: 1869 throw `can't loop on values of type ${typeof src}`; 1870 } 1871 } 1872 1873 function avoid(src, f) { 1874 f = predicate(f); 1875 return filter(src, (v, k, src) => !f(v, k, src)); 1876 } 1877 1878 function count(src, f) { 1879 if (typeof src === 'function') { 1880 const t = src; 1881 src = f; 1882 f = t; 1883 } 1884 1885 let count = 0; 1886 check = predicate(f); 1887 loop(src, (v, k, src) => { 1888 if (check(v, k, src)) { 1889 count++; 1890 } 1891 }); 1892 return count; 1893 } 1894 1895 const countif = count; 1896 1897 function extent(src, f = identity) { 1898 let min = +Infinity; 1899 let max = -Infinity; 1900 loop(src, (v, k, src) => { 1901 const e = f(v, k, src); 1902 if (typeof e === 'number' && !isNaN(e)) { 1903 min = Math.min(min, e); 1904 max = Math.max(max, e); 1905 } 1906 }); 1907 return [min, max]; 1908 } 1909 1910 const span = extent; 1911 1912 function filter(src, f) { 1913 if (typeof src === 'function') { 1914 const t = src; 1915 src = f; 1916 f = t; 1917 } 1918 f = predicate(f); 1919 1920 if (Array.isArray(src)) { 1921 return src.filter(f); 1922 } 1923 1924 switch (typeof src) { 1925 case 'string': 1926 return filterString(src, f); 1927 case 'object': 1928 return filterObject(src, f); 1929 default: 1930 throw `can't filter values of type ${typeof src}`; 1931 } 1932 } 1933 1934 const keep = filter; 1935 1936 function filterString(src, f) { 1937 let i = 0; 1938 let out = ''; 1939 for (const c of src) { 1940 if (f(c, i, src)) { 1941 out += c; 1942 } 1943 i++; 1944 } 1945 return out; 1946 } 1947 1948 function filterObject(src, f) { 1949 const kv = {}; 1950 for (const k in src) { 1951 if (Object.hasOwnProperty.call(src, k)) { 1952 const v = src[k]; 1953 if (f(src[k], k, src)) { 1954 kv[k] = v; 1955 } 1956 } 1957 } 1958 return kv; 1959 } 1960 1961 function first(src, n = null) { 1962 return n == null ? firstItem(src) : limit(src, n); 1963 } 1964 1965 function firstItem(src) { 1966 if (Array.isArray(src)) { 1967 return src.length > 0 ? src[0] : null; 1968 } 1969 1970 switch (typeof src) { 1971 case 'string': 1972 for (const c of src) { 1973 return c; 1974 } 1975 return ''; 1976 1977 case 'object': 1978 for (const k in src) { 1979 if (Object.hasOwnProperty.call(src, k)) { 1980 return src[k]; 1981 } 1982 } 1983 return null; 1984 1985 default: 1986 return null; 1987 } 1988 } 1989 1990 function group(src, f = identity) { 1991 if (typeof src === 'function') { 1992 const t = src; 1993 src = f; 1994 f = t; 1995 } 1996 1997 const groups = {}; 1998 loop(src, (v, k, src) => { 1999 const dk = f(v, k, src); 2000 if (dk in groups) { 2001 groups[dk].push(v); 2002 return; 2003 } 2004 groups[dk] = [v]; 2005 }); 2006 return groups; 2007 } 2008 2009 const grouped = group; 2010 2011 function last(src, n = null) { 2012 if (n == null) { 2013 return lastItem(src); 2014 } 2015 2016 if (Array.isArray(src)) { 2017 return src.length <= n ? src : src.slice(src.length - n); 2018 } 2019 2020 switch (typeof src) { 2021 case 'string': 2022 return lastItemsString(src, n); 2023 case 'object': 2024 return lastItemsObject(src, n); 2025 default: 2026 return null; 2027 } 2028 } 2029 2030 function lastItemsString(src, n) { 2031 let c = 0; 2032 for (const _ of src) { 2033 c++; 2034 } 2035 2036 if (c <= n) { 2037 return src; 2038 } 2039 2040 let out = ''; 2041 for (const c of src) { 2042 if (c === n) { 2043 return out; 2044 } 2045 out += c; 2046 c--; 2047 } 2048 return out; 2049 } 2050 2051 function lastItemsObject(src, n) { 2052 let c = 0; 2053 for (const k in src) { 2054 c += Object.hasOwnProperty.call(src, k); 2055 } 2056 2057 if (c <= n) { 2058 return src; 2059 } 2060 2061 const kv = {}; 2062 for (const k in src) { 2063 if (Object.hasOwnProperty.call(src, k)) { 2064 c--; 2065 if (c >= n) { 2066 continue; 2067 } 2068 kv[k] = src[k]; 2069 } 2070 } 2071 return kv; 2072 } 2073 2074 function lastItem(src) { 2075 if (Array.isArray(src)) { 2076 return src.length > 0 ? src[src.length - 1] : null; 2077 } 2078 2079 let last = null; 2080 2081 switch (typeof src) { 2082 case 'object': 2083 for (const k in src) { 2084 if (Object.hasOwnProperty.call(src, k)) { 2085 last = src[k]; 2086 } 2087 } 2088 return last; 2089 2090 default: 2091 return null; 2092 } 2093 } 2094 2095 function limit(src, n) { 2096 if (Array.isArray(src)) { 2097 return src.slice(0, n); 2098 } 2099 2100 switch (typeof src) { 2101 case 'string': 2102 return limitString(src, n); 2103 case 'object': 2104 return limitObject(src, n); 2105 default: 2106 return null; 2107 } 2108 } 2109 2110 function limitString(src, n) { 2111 let i = 0; 2112 let out = ''; 2113 for (const c of src) { 2114 if (i === n) { 2115 return out; 2116 } 2117 out += c; 2118 i++; 2119 } 2120 return out; 2121 } 2122 2123 function limitObject(src, n) { 2124 let i = 0; 2125 const kv = {}; 2126 for (const k in src) { 2127 if (i === n) { 2128 return kv; 2129 } 2130 if (Object.hasOwnProperty.call(src, k)) { 2131 return kv[k] = src[k]; 2132 } 2133 i++; 2134 } 2135 return kv; 2136 } 2137 2138 function map(src, f) { 2139 if (typeof src === 'function') { 2140 const t = src; 2141 src = f; 2142 f = t; 2143 } 2144 2145 if (Array.isArray(src)) { 2146 return src.map(f); 2147 } 2148 2149 switch (typeof src) { 2150 case 'string': 2151 return mapString(src, f); 2152 case 'object': 2153 return mapObject(src, f); 2154 default: 2155 throw `can't map values of type ${typeof src}`; 2156 } 2157 } 2158 2159 const each = map; 2160 2161 function mapString(src, f) { 2162 let i = 0; 2163 let out = ''; 2164 for (const c of src) { 2165 out += f(c, i, src); 2166 i++; 2167 } 2168 return out; 2169 } 2170 2171 function mapObject(src, f) { 2172 const kv = {}; 2173 for (const k in src) { 2174 if (Object.hasOwnProperty.call(src, k)) { 2175 kv[k] = f(src[k], k, src); 2176 } 2177 } 2178 return kv; 2179 } 2180 2181 function mapkv(src, key, value = identity) { 2182 const kv = {}; 2183 loop(src, (v, k, src) => { 2184 const dk = key(v, k, src); 2185 kv[dk] = value(v, k, src); 2186 }); 2187 return kv; 2188 } 2189 2190 function tally(src, f = identity) { 2191 if (typeof src === 'function') { 2192 const t = src; 2193 src = f; 2194 f = t; 2195 } 2196 2197 const tally = {}; 2198 loop(src, (v, k, src) => { 2199 const dk = f(v, k, src); 2200 tally[dk] = (tally[dk] | 0) + 1; 2201 }); 2202 return tally; 2203 } 2204 2205 const tallied = tally; 2206 2207 function keys(src) { 2208 const keys = []; 2209 2210 if (Array.isArray(src)) { 2211 for (let i = 0; i < src.length; i++) { 2212 keys.push(i); 2213 } 2214 return keys; 2215 } 2216 2217 switch (typeof src) { 2218 case 'string': 2219 let i = 0; 2220 for (const _ of src) { 2221 keys.push(i); 2222 i++; 2223 } 2224 return keys; 2225 2226 case 'object': 2227 for (const k in src) { 2228 if (Object.hasOwnProperty.call(src, k)) { 2229 keys.push(k); 2230 } 2231 } 2232 return keys; 2233 2234 default: 2235 return keys; 2236 } 2237 } 2238 2239 function values(src) { 2240 if (Array.isArray(src)) { 2241 return src; 2242 } 2243 2244 const values = []; 2245 2246 switch (typeof src) { 2247 case 'string': 2248 for (const c of src) { 2249 values.push(c); 2250 } 2251 return values; 2252 2253 case 'object': 2254 for (const k in src) { 2255 if (Object.hasOwnProperty.call(src, k)) { 2256 values.push(src[k]); 2257 } 2258 } 2259 return values; 2260 2261 default: 2262 return [src]; 2263 } 2264 } 2265 2266 function padEnd(s, n, how = ' ') { 2267 return s.length >= n ? s : s + how.repeat((n - s.length) / how.length); 2268 } 2269 2270 const padend = padEnd; 2271 const rightpad = padEnd; 2272 const rightPad = padEnd; 2273 const rpad = padEnd; 2274 2275 function padStart(s, n, how = ' ') { 2276 return s.length >= n ? s : how.repeat((n - s.length) / how.length) + s; 2277 } 2278 2279 const padstart = padStart; 2280 const leftpad = padStart; 2281 const leftPad = padStart; 2282 const lpad = padStart; 2283 2284 function pick(src, ...what) { 2285 return _pick(src, what); 2286 } 2287 2288 function _pick(src, what) { 2289 if (Array.isArray(src)) { 2290 return src.map(e => _pick(e, what)); 2291 } 2292 2293 if (typeof src !== 'object') { 2294 return null; 2295 } 2296 2297 const kv = {}; 2298 for (const k of what) { 2299 kv[k] = src[k]; 2300 } 2301 return kv; 2302 } 2303 2304 function drop(src, ...what) { 2305 if (typeof src === 'string') { 2306 for (const s of what) { 2307 src = src.replaceAll(s, ''); 2308 } 2309 return src; 2310 } 2311 return _drop(src, new Set(what)); 2312 } 2313 2314 function _drop(src, what) { 2315 if (Array.isArray(src)) { 2316 return src.map(e => _drop(e, what)); 2317 } 2318 2319 if (typeof src !== 'object') { 2320 return null; 2321 } 2322 2323 const kv = {}; 2324 for (const k in src) { 2325 if (Object.hasOwnProperty.call(src, k)) { 2326 if (!(what.has(k))) { 2327 kv[k] = src[k]; 2328 } 2329 } 2330 } 2331 return kv; 2332 } 2333 2334 function lower(s) { 2335 return s.toLowerCase(); 2336 } 2337 2338 const lowered = lower; 2339 const tolower = lower; 2340 2341 function numstats(src, f = identity) { 2342 let n = 0, meanSq = 0, lnSum = 0; 2343 let min = +Infinity, max = -Infinity, sum = 0, mean = 0, prod = 1; 2344 let nans = 0, ints = 0, pos = 0, zero = 0, neg = 0; 2345 2346 src.map(x => { 2347 x = f(x); 2348 2349 if (typeof x !== 'number') { 2350 return; 2351 } 2352 2353 if (isNaN(x)) { 2354 nans++; 2355 return; 2356 } 2357 2358 n++; 2359 ints += (x === Math.floor(x)); 2360 2361 if (x > 0) { 2362 pos++; 2363 } else if (x < 0) { 2364 neg++; 2365 } else { 2366 zero++; 2367 } 2368 2369 min = Math.min(min, x); 2370 max = Math.max(max, x); 2371 2372 // sum += x; 2373 prod *= x; 2374 lnSum += Math.log(x); 2375 2376 const d1 = x - mean; 2377 mean += d1 / n; 2378 const d2 = x - mean; 2379 meanSq += d1 * d2; 2380 }); 2381 2382 let sd = NaN, geomean = NaN; 2383 if (n > 0) { 2384 sd = Math.sqrt(meanSq / n); 2385 geomean = (lnSum !== -Infinity) ? Math.exp(lnSum / n) : NaN; 2386 } 2387 sum = n * mean; 2388 // prod = (n > 0) ? prod : NaN; 2389 2390 return { 2391 'n': n, 2392 'nan': nans, 2393 'min': min, 2394 'max': max, 2395 'sum': sum, 2396 'mean': mean, 2397 'geomean': geomean, 2398 'sd': sd, 2399 'product': prod, 2400 'integer': ints, 2401 'positive': pos, 2402 'zero': zero, 2403 'negative': neg, 2404 }; 2405 } 2406 2407 function once(what, fallback = null) { 2408 if (!seen.has(what)) { 2409 seen.add(what); 2410 return what; 2411 } 2412 return fallback; 2413 } 2414 2415 function replace(s, what, repl = '') { 2416 return s.replaceAll(what, repl); 2417 } 2418 2419 function round(x, decimals = 0) { 2420 const k = 10 ** decimals; 2421 return Math.round(k * x) / k; 2422 } 2423 2424 function tabstats(src, maxDecimals = 6) { 2425 if (!Array.isArray(src)) { 2426 throw 'function tabstats only works with an array of objects'; 2427 } 2428 2429 if (src.length === 0) { 2430 return numstats([]); 2431 } 2432 2433 for (const row of src) { 2434 if (typeof row !== 'object') { 2435 throw 'function tabstats only works with an array of objects'; 2436 } 2437 } 2438 2439 const stats = []; 2440 const keys = Object.keys(src[0]); 2441 for (const k of keys) { 2442 const colstats = numstats(src, row => row[k]); 2443 for (const k in colstats) { 2444 const v = colstats[k]; 2445 if (isNaN(v) || !isFinite(v) || v === Math.round(v)) { 2446 continue; 2447 } 2448 colstats[k] = round(v, maxDecimals); 2449 } 2450 2451 stats.push({ 2452 name: k, 2453 numeric: colstats.n, 2454 integers: colstats.integer, 2455 pos: colstats.positive, 2456 zero: colstats.zero, 2457 neg: colstats.negative, 2458 min: isFinite(colstats.min) ? colstats.min : NaN, 2459 max: isFinite(colstats.max) ? colstats.max : NaN, 2460 sum: colstats.sum, 2461 mean: colstats.mean, 2462 geomean: colstats.geomean, 2463 sd: colstats.sd, 2464 }); 2465 } 2466 return stats; 2467 } 2468 2469 function transpose(what) { 2470 if (!Array.isArray(what) && typeof what !== 'object') { 2471 throw 'transpose only supports objects or arrays of objects/arrays'; 2472 } 2473 2474 if (!Array.isArray(what)) { 2475 const kv = {}; 2476 for (const k in what) { 2477 if (Object.hasOwnProperty.call(what, k)) { 2478 kv[what[k]] = k; 2479 } 2480 } 2481 return kv; 2482 } 2483 2484 const kv = {}; 2485 const seq = []; 2486 2487 for (const e of what) { 2488 if (Array.isArray(e)) { 2489 for (let i = 0; i < e.length; i++) { 2490 if (i < seq.length) { 2491 seq[i].push(e[i]); 2492 } else { 2493 seq.push([e[i]]); 2494 } 2495 } 2496 continue; 2497 } 2498 2499 if (typeof e === 'object') { 2500 for (const k in e) { 2501 if (Object.hasOwnProperty.call(e, k)) { 2502 if (k in kv) { 2503 kv[k].push(e[k]); 2504 } else { 2505 kv[k].push([e[k]]); 2506 } 2507 } 2508 } 2509 continue; 2510 } 2511 2512 throw 'transpose(...): not all items are arrays/objects'; 2513 } 2514 2515 let kvEmpty = true; 2516 for (const k in kv) { 2517 if (Object.hasOwnProperty.call(kv, k)) { 2518 kvEmpty = false; 2519 break; 2520 } 2521 } 2522 2523 if (seq.length > 0 && !kvEmpty) { 2524 throw 'transpose(...): mix of arrays and objects not supported'; 2525 } 2526 return kvEmpty ? seq : kv; 2527 } 2528 2529 const tr = transpose; 2530 const transp = transpose; 2531 const transposed = transpose; 2532 2533 function upper(s) { 2534 return s.toUpperCase(); 2535 } 2536 2537 const uppered = upper; 2538 const toupper = upper; 2539 2540 // function wait(seconds, result) { 2541 // if ((typeof seconds !== 'number') && (typeof result === 'number')) { 2542 // const t = seconds; 2543 // seconds = result; 2544 // result = t; 2545 // } 2546 2547 // const p = new Promise(f => setTimeout(f, seconds)); 2548 // eval('(async function () { await p; })();'); 2549 // return result; 2550 // } 2551 2552 function wat(...args) { 2553 for (const x of args) { 2554 if (typeof x !== 'function') { 2555 continue; 2556 } 2557 2558 const title = padEnd(x.name, 80, ' '); 2559 process.stderr.write(`\x1b[48;5;253m\x1b[38;5;26m${title}\x1b[0m\n`); 2560 process.stderr.write(x.toString()); 2561 process.stderr.write('\n'); 2562 } 2563 2564 return new Skip(); 2565 } 2566 2567 2568 main();