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