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();