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