#!/usr/bin/node --trace-uncaught /* The MIT License (MIT) Copyright © 2024 pacman64 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ 'use strict'; const info = ` tln [options...] [node-js expression] [filepaths/URIs...] Transform Lines (with Node) runs a NodeJS expression on each line of plain-text data: each expression given emits its result as its own line. Each input line is available to the expression as either 'line', or 'l'. Lines are always stripped of any trailing end-of-line bytes/sequences. When the expression results in non-string iterable values, a sort of input 'amplification' happens for the current input-line, where each item from the result is emitted on its own output line. Dictionaries emit their data as a single JSON line. When a formula's result is the None value, it emits no output line, which filters-out the current line, the same way empty-iterable results do. When in 'all' mode, all input lines are read first into a list of strings, whose items are all stripped of any end-of-line sequences, and kept in the 'lines' global variable: the expression given is then run only once. Similarly, if the argument before the expression is a single equals sign (a '=', but without the quotes), no data are read/loaded: the expression is then run only once, effectively acting as a 'pure' plain-text generator. Options, where leading double-dashes are also allowed, except for alias '=': -a read all lines at once into a string-list called 'lines' -all same as -a -lines same as -a -once same as -a -b read uninterrupted blocks/groups of lines, as paragraphs -blocks same as -b -g same as -b -groups same as -b -p same as -b -par same as -b -para same as -b -paragraphs same as -b -h show this help message -help same as -h -jsonl transform JSON Lines into proper JSON -nil don't read any input, and run the expression only once -no-input same as -nil -noinput same as -nil -none same as -nil -null same as -nil = same as -nil -t show a full traceback of this script for exceptions -trace same as -t -traceback same as -t Extra Functions plain(s) ignore ANSI-style sequences in strings blue(s) color strings blue, using surrounding ANSI-style sequences gray(s) color strings gray, using surrounding ANSI-style sequences green(s) color strings green, using surrounding ANSI-style sequences highlight(s) highlight strings, using surrounding ANSI-style sequences hilite(s) same as func highlight orange(s) color strings orange, using surrounding ANSI-style sequences purple(s) color strings purple, using surrounding ANSI-style sequences red(s) color strings red, using surrounding ANSI-style sequences chunk(x, size) split/resequence items into chunks of the length given cond(...) expression-friendly fully-evaluated if-else chain dedup(x) ignore later (re)occurrences of values in a sequence defunc(x) call value if it's a func, else return as given defunct(x) same as func defunc denan(x, y) turn a floating-point NaN value into the fallback given denil(*args) return the first non-null/none value among those given denone(*args) same as func denil denull(*args) same as func denil dive(x, f) transform value in depth-first-recursive fashion divebin(x, y, f) binary (2-input) version of recursive-transform func dive drop(x, *what) ignore keys or substrings; for strings, dicts, dict-lists flat(*args) flatten everything into a sequence flatten(*args) same as func flat flattened(*args) same as func flat harden(f, v) make funcs return a fallback value instead of exceptions ints(x, y) make sequences of increasing integers, which include the end compose(*args) make a func which chain-calls all funcs given composed(*args) same as func compose parse(s) try to parse JSON strings length(x) same as the built-in func len recover(...args) recover from exceptions with a fallback value idiota(x) object-counterpart of func iota iota(x) make an integer sequence from 1 up to the number given join(x, y) join values into a string; make a dict from keys and values json0(x) turn a value into its smallest JSON-string representation json2(x) turn a value into a 2-space-indented multi-line JSON string lines(s) split strings into their lines, ignoring end-of-line markers links(x) auto-detect all hyperlink-like (HTTP/HTTPS) substrings lower(s) same as the built-in str.lower lowered(s) same as the built-in str.lower numstats(x) calculate various "single-pass" numeric stats once(x, y=null) avoid returning the same value more than once; stateful func pick(x, *what) keep only the keys given; works on dicts, or dict-sequences quote(s, q='"') surround a string with the (optional) quoting-symbol given split(x, y) split string by separator; split string into n substrings squeeze(s) strip/trim a string, squishing inner runs of spaces stround(x, d=6) format numbers into decimal-number strings unique(x) same as func dedup uniqued(x) same as func dedup unquote(s) ignore surrounding quotes, if present count(x, f) same as func countif countif(x, f) count how many values make the func given true-like each(x, f) same as func map filter(x, pred) generalization of built-in func Array.filter group(x, by) group values into dicts of lists; optional transform func keep(x, pred) same as func filter map(x, f) generalization of built-in func Array.map tally(x, by) count/tally values, using an optional transformation func after(x, y) ignore items until the one given; for strings and sequences before(x, y) ignore items since the one given; for strings and sequences since(x, y) ignore items before the one given; for strings and sequences until(x, y) ignore items after the one given; for strings and sequences Examples # numbers from 0 to 5, each on its own output line; no input is read/used tln = 'range(6)' # all powers up to the 4th, using each input line auto-parsed into a 'float' tln = 'range(1, 6)' | tln 'range(1, 4+1).map(p => (+l)**p)' # separate input lines with an empty line between each; global var 'empty' # can be used to avoid bothering with nested shell-quoting tln = 'range(6)' | tln '(i > 0) ? ["", l] : l' # keep only the last 2 lines from the input tln = 'range(1, 6)' | tln -all 'lines.slice(-2)' # join input lines into tab-separated lines of up to 3 items each; global # var named 'tab' can be used to avoid bothering with nested shell-quoting tln = 'range(1, 8)' | tln -all 'chunk(lines, 3).map(c => c.join("\\t"))' # ignore all lines before the first one with just a '5' in it tln = 'range(8)' | tln -all 'since(lines, "5")' # ignore errors/exceptions, in favor of the original lines/values # tln = '["abc", "123"]' | tln 'recover(() => 2 * JSON.parse(line), line)' (echo "abc"; echo "123") | tln 'recover(() => 2 * JSON.parse(line), line)' `; if (process.argv.length < 3) { process.stdout.write(info.trim()); process.stdout.write('\n'); process.exit(0); } if (process.argv.length === 3) { switch (process.argv[2]) { case '-h': case '--h': case '-help': case '--help': process.stdout.write(info.trim()); process.stdout.write('\n'); process.exit(0); break; } } const opts2modes = { '=': 'no-input', '-noinput': 'no-input', '--noinput': 'no-input', '-no-input': 'no-input', '--no-input': 'no-input', '-nil': 'no-input', '--nil': 'no-input', '-none': 'no-input', '--none': 'no-input', '-null': 'no-input', '--null': 'no-input', '-a': 'all-lines', '--a': 'all-lines', '-all': 'all-lines', '--all': 'all-lines', '-lines': 'all-lines', '--lines': 'all-lines', '-once': 'all-lines', '--once': 'all-lines', '-b': 'each-block', '--b': 'each-block', '-bl': 'each-block', '--bl': 'each-block', '-block': 'each-block', '--block': 'each-block', '-p': 'each-block', '--p': 'each-block', '-par': 'each-block', '--par': 'each-block', '-para': 'each-block', '--para': 'each-block', '-paragraph': 'each-block', '--paragraph': 'each-block', '-jl': 'json-lines', '--jl': 'json-lines', '-jsonl': 'json-lines', '--jsonl': 'json-lines', }; const opts2run = { 'no-input': runNoInput, 'each-line': runEachLine, 'all-lines': runAllLines, 'each-block': runEachBlock, 'json-lines': runJSONL, }; const opts2defaultExpr = { 'no-input': 'null', 'each-line': 'line', 'all-lines': 'lines', 'each-block': 'block', 'json-lines': 'data', }; const createReadStream = require('fs').createReadStream; const createInterface = require('readline').createInterface; const getHTTP = process.argv.some(s => s.startsWith('http://')) ? require('http').get : null; const getHTTPS = process.argv.some(s => s.startsWith('https://')) ? require('https').get : null; require = function () { throw `function 'require' is disabled`; }; function loopLinesInputs(inputs, handle, end = null) { if (end == null) { end = () => { }; } let i = 0; function handleLines(stream, nextIndex) { const inter = createInterface({ input: stream, crlfDelay: Infinity, }); let j = 0; inter.on('line', line => { if (j === 0 && line.startsWith('\xef\xbb\xbf')) { line = line.slice('\xef\xbb\xbf'.length); } handle(line, i); i++; j++; }); inter.on('close', () => { rec(nextIndex) }); } function handleURI(uri, how, nextIndex) { function go(uri, redirCount) { how(uri, resp => { if (redirCount > 5) { throw `giving up web-request after ${redirCount} redirects`; } if (resp.statusCode === 301 || resp.statusCode === 302) { go(resp.headers.location, redirCount + 1); return; } handleLines(resp, nextIndex); }); } go(uri, 0); } if (inputs.length === 0) { handleLines(process.stdin, 1); return; } let dashes = 0; function rec(j) { if (j >= inputs.length) { end(); return; } const name = inputs[j]; if (name === '-') { if (dashes > 0) { rec(j + 1); return; } dashes++; handleLines(process.stdin, j + 1); return; } if (name.startsWith('https://')) { handleURI(name, getHTTPS, j + 1); return; } if (name.startsWith('http://')) { handleURI(name, getHTTP, j + 1); return; } let path = name; if (name.startsWith('file://')) { path = name.slice('file://'.length); } handleLines(createReadStream(path), j + 1); } rec(0); } function runNoInput(w, expr, inputs) { let res = eval(expr); if (res == null) { res = 'null'; } else if (typeof res === 'boolean') { res = res.toString(); } showResult(w, fixResult(res, null)); } function runEachLine(w, expr, inputs) { let _ = null; let prev = ''; let previous = ''; loopLinesInputs(inputs, (line, i) => { const c = i + 1; const l = line; const f = +line; _ = fixResult(eval(expr), line); showResult(w, _); prev = previous = line; }); } function runAllLines(w, expr, inputs) { const lines = []; loopLinesInputs(inputs, (line, i) => { lines.push(line); }, () => showResult(w, fixResult(eval(expr), lines))); } function runEachBlock(w, expr, inputs) { let block = []; let blockIndex = 0; let _ = null; let prev = []; let previous = []; function handleBlock(block, i) { if (block.length === 0) { return; } const c = i + 1; const b = block; const bl = block; const p = block; const par = block; const para = block; const paragraph = block; const d = block; const dat = block; const data = block; const v = block; const val = block; const values = block; _ = fixResult(eval(expr), block) showResult(w, _); prev = previous = block; } loopLinesInputs(inputs, (line, _) => { if (line.trim() === '') { handleBlock(block, blockIndex); blockIndex++; block = []; } else { block.push(line); } }, () => handleBlock(block, blockIndex)); } function runJSONL(w, expr, inputs) { let _ = null; let prev = null; let previous = null; loopLinesInputs(inputs, (line, i) => { const c = i + 1; const data = JSON.parse(line); const value = data; const d = data; const v = data; const dat = data; const val = data; _ = fixResult(eval(expr), data); w.write(JSON.stringify(_, null, 0)); w.write('\n'); prev = previous = line; }); } function fixResult(x, arg) { if (x instanceof Skip) { return null; } if (typeof x === 'boolean') { return x ? arg : null; } if (typeof x === 'function') { return x(arg); } return x; } function showResult(w, x) { function showSingleResult(w, x) { if (x == null || (x instanceof Skip)) { return; } if (typeof x === 'object') { x = JSON.stringify(x, null, 0); } w.write(x.toString()); w.write('\n'); } if (!Array.isArray(x)) { showSingleResult(w, x); return; } for (const e of x) { showSingleResult(w, e); } } function main() { let srcIndex = 2; let mode = 'each-line'; let traceMode = false; out: while (srcIndex < process.argv.length) { if (opts2modes[process.argv[srcIndex]]) { mode = opts2modes[process.argv[srcIndex]]; srcIndex++; continue; } switch (process.argv[srcIndex]) { case '-t': case '--t': case '-trace': case '--trace': case '-traceback': case '--traceback': traceMode = true; srcIndex++; break; default: break out; } } try { const args = process.argv.slice(srcIndex + 1); let expr = process.argv[srcIndex]; if (expr === '' || expr === '.') { expr = opts2defaultExpr[mode]; } globalThis.now = new Date(); globalThis.expr = expr; globalThis.args = args; // ignore broken-pipe-style errors, making piping output to the // likes of `head` just work as intended process.stdout.on('error', _ => { process.exit(0); }); opts2run[mode](process.stdout, expr, args); } catch (error) { if (traceMode) { throw error; } process.stderr.write(`\x1b[31m${error}\x1b[0m\n`); process.exit(1); } } function plain(s) { return s.replaceAll(ansi_style_re, ''); } function blue(s) { return `\x1b[38;5;26m${s}\x1b[0m`; } function blueback(s) { return `\x1b[48;5;26m\x1b[38;5;255m${s}\x1b[0m`; } const bluebg = blueback; function bold(s) { return `\x1b[1m${s}\x1b[0m`; } function gbm(s, good = false, bad = false, meh = false) { if (good) { return green(s); } if (bad) { return red(s); } if (meh) { return gray(s); } return s; } function gray(s) { return `\x1b[38;5;248m${s}\x1b[0m`; } function grayback(s) { return `\x1b[48;5;253m${s}\x1b[0m`; } const graybg = grayback; function green(s) { return `\x1b[38;5;29m${s}\x1b[0m`; } function greenback(s) { return `\x1b[48;5;29m\x1b[38;5;255m${s}\x1b[0m`; } const greenbg = greenback; function highlight(s) { return `\x1b[7m${s}\x1b[0m`; } const hilite = highlight; function magenta(s) { return `\x1b[38;5;165m${s}\x1b[0m`; } function magentaback(s) { return `\x1b[48;5;165m\x1b[38;5;255m${s}\x1b[0m`; } const magback = magentaback; const magbg = magentaback; const magentabg = magentaback; function orange(s) { return `\x1b[38;5;166m${s}\x1b[0m`; } function orangeback(s) { return `\x1b[48;5;166m\x1b[38;5;255m${s}\x1b[0m`; } const orangebg = orangeback; const orback = orangeback; const orbg = orangeback; function purple(s) { return `\x1b[38;5;99m${s}\x1b[0m`; } function purpleback(s) { return `\x1b[48;5;99m\x1b[38;5;255m${s}\x1b[0m`; } const purback = purpleback; const purbg = purpleback; const purplebg = purpleback; function red(s) { return `\x1b[38;5;1m${s}\x1b[0m`; } function redback(s) { return `\x1b[48;5;1m\x1b[38;5;255m${s}\x1b[0m`; } const redbg = redback; function underline(s) { return `\x1b[4m${s}\x1b[0m`; } function message(x) { process.stderr.write(`${x}\n`); } const msg = message; class Skip { } const skip = new Skip(); const assert = process.assert; const env = process.env; const exit = process.exit; const hrtime = process.hrtime; /* some convenience aliases to commonly-used values: using literal strings on the cmd-line is often tricky / annoying, and some of these can help get around multiple levels of string-quoting */ const math = Math; const e = Math.E; const pi = Math.PI; const tau = 2 * Math.PI; const E = Math.E; const LN10 = Math.LN10; const LN2 = Math.LN2; const LOG10E = Math.LOG10E; const LOG2E = Math.LOG2E; const PI = Math.PI; const SQRT1_2 = Math.SQRT1_2; const SQRT2 = Math.SQRT2; const abs = Math.abs; const acos = Math.acos; const acosh = Math.acosh; const asin = Math.asin; const asinh = Math.asinh; const atan = Math.atan; const atan2 = Math.atan2; const atanh = Math.atanh; const cbrt = Math.cbrt; const ceil = Math.ceil; const clz32 = Math.clz32; const cos = Math.cos; const cosh = Math.cosh; const exp = Math.exp; const expm1 = Math.expm1; const floor = Math.floor; const fround = Math.fround; const hypot = Math.hypot; const imul = Math.imul; const log = Math.log; const log10 = Math.log10; const log1p = Math.log1p; const log2 = Math.log2; const max = Math.max; const min = Math.min; const pow = Math.pow; const random = Math.random; // const round = Math.round; const sign = Math.sign; const sin = Math.sin; const sinh = Math.sinh; const sqrt = Math.sqrt; const tan = Math.tan; const tanh = Math.tanh; const trunc = Math.trunc; const inf = Infinity; const infinity = Infinity; const infty = Infinity; const nan = NaN; const nil = null; const none = null; const s = ''; const True = true; const False = false; const None = null; const digits = '0123456789'; const hexDigits = '0123456789ABCDEF'; const lowercase = 'abcdefghijklmnopqrstuvwxyz'; const uppercase = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; const amp = '&'; const ampersand = '&'; const ansireset = '\x1b[0m'; const ansiReset = '\x1b[0m'; const apo = '\''; const apos = '\''; const ast = '*'; const asterisk = '*'; const at = '@'; const backquote = '`'; const backslash = '\\'; const backtick = '`'; const ball = '●'; const bang = '!'; const block = '█'; const bquo = '`'; const bquote = '`'; const bslash = '\\'; const bullet = '•'; const caret = '^'; const cdot = '·'; const circle = '●'; const colon = ':'; const comma = ','; const cr = '\r'; const crlf = '\r\n'; const cs = ', '; const dash = '—'; const dollar = '$'; const dot = '.'; const dquo = '"'; const dquote = '"'; const emark = '!'; const emdash = '—'; const empty = ''; const endash = '–'; const eq = '='; const et = '&'; const ge = '≥'; const geq = '≥'; const gt = '>'; const hellip = '…'; const hole = '○'; const hyphen = '-'; const ldquo = '“'; const ldquote = '“'; const le = '≤'; const leq = '≤'; const lf = '\n'; const lt = '<'; const mdash = '—'; const mdot = '·'; const minus = '-'; const ndash = '–'; const neq = '≠'; const perc = '%'; const percent = '%'; const period = '.'; const plus = '+'; const qmark = '?'; const que = '?'; const rdquo = '”'; const rdquote = '”'; const sball = '•'; const semi = ';'; const semicolon = ';'; const sharp = '#'; const slash = '/'; const smallball = '•'; const space = ' '; const square = '■'; const squo = '\''; const squote = '\''; const tab = '\t'; const tilde = '~'; const underscore = '_'; const uscore = '_'; const utf8bom = '\xef\xbb\xbf'; const kb = 1024; const mb = 1024 * kb; const gb = 1024 * mb; const tb = 1024 * gb; const pb = 1024 * tb; const kib = 1024; const mib = 1024 * kib; const gib = 1024 * mib; const tib = 1024 * gib; const pib = 1024 * tib; const mol = 602214076000000000000000n; const mole = 602214076000000000000000n; const hour = 3_600; const day = 86_400; const week = 604_800; const hr = 3_600; const wk = 604_800; const cup2l = 0.23658824; const floz2l = 0.0295735295625; const floz2ml = 29.5735295625; const ft2m = 0.3048; const gal2l = 3.785411784; const in2cm = 2.54; const lb2kg = 0.45359237; const mi2km = 1.609344; const mpg2kpl = 0.425143707; const nmi2km = 1.852; const oz2g = 28.34952312; const psi2pa = 6894.757293168; const ton2kg = 907.18474; const yd2m = 0.9144; const months = [ 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December', ]; const monweek = [ 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday', ]; const sunweek = [ 'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', ]; const physics = { kilo: 1_000, mega: 1_000_000, giga: 1_000_000_000, tera: 1_000_000_000_000, peta: 1_000_000_000_000_000, // exa: 1_000_000_000_000_000_000, // zetta: 1_000_000_000_000_000_000_000, c: 299_792_458, kcd: 683, na: 602214076000000000000000n, femto: 1e-15, pico: 1e-12, nano: 1e-9, micro: 1e-6, milli: 1e-3, e: 1.602176634e-19, f: 96_485.33212, h: 6.62607015e-34, k: 1.380649e-23, mu: 1.66053906892e-27, ge: 9.7803267715, gn: 9.80665, }; // seen remembers values already given to func `once` const seen = new Set(); const ansi_style_re = new RegExp('\x1b\\[([0-9;]+m|[0-9]*[A-HJKST])', 'g'); const link_src = 'https?://[A-Za-z0-9+_.:%-]+(/[A-Za-z0-9+_.%/,#?&=-]*)*'; const link_re = new RegExp(link_src, 'g'); const loads = JSON.parse; const parse = JSON.parse; const stringify = JSON.stringify; const assign = Object.assign; const entries = Object.entries; const fromEntries = Object.fromEntries; const is = Object.is; const chr = String.fromCharCode; const fromCharCode = String.fromCharCode; const fromCodePoint = String.fromCodePoint; function append(...what) { let res = []; for (const e of what) { flappend(e, res); } return res; } function bin(x) { return x.toString(2); } function cases(x, ...args) { for (let i = 0; i < args.length - args.length % 2; i += 2) { const test = args[i]; if (Array.isArray(test)) { for (const v of test) { if (x === v) { return args[i + 1]; } } continue; } if (x === test) { return args[i + 1]; } } return len(args) % 2 === 0 ? null : args[args.length - 1]; } function chunk(what, chunkSize) { const chunks = []; for (let i = 0; i < what.length; i += chunkSize) { const end = Math.min(i + chunkSize, what.length); chunks.push(what.slice(i, end)); } return chunks; } const chunked = chunk; function compose(...funcs) { if (funcs.length === 0) { return identity; } return function (...args) { let res = funcs[0](...args); for (let i = 1; i < funcs.length; i++) { res = funcs[i](res); } return res; } } const composed = compose; function cond(...args) { for (let i = 0; i < args.length - args.length % 2; i += 2) { if (args[i]) { return args[i + 1]; } } return len(args) % 2 === 0 ? null : args[args.length - 1]; } function dedup(src, f = identity) { const unique = []; const got = new Set(); for (let i = 0; i < src.length; i++) { const v = f(src[i], i, src); if (!got.has(v)) { unique.push(v); got.add(v); } } return unique; } const dedupe = dedup; const deduped = dedup; const undup = dedup; const undupe = dedup; const unduped = dedup; const unique = dedup; const uniqued = dedup; function defunc(x) { return (typeof x === 'function') ? x() : x; } const defunct = defunc; const unfunc = defunc; const unfunct = defunc; function dejson(x, fallback = null) { try { return (typeof x === 'string') ? JSON.parse(x) : x; } catch { return fallback; } } const tryparse = dejson; const tryParse = dejson; const unjson = dejson; function denan(x, instead = null) { return isNaN(x) ? instead : x; } const deNaN = denan; function denull(...args) { for (const x of args) { if (x != null) { return x; } } return null; } const denil = denull; const denone = denull; function dive(into, using) { if (using == null) { throw 'dive: no diving func given'; } function go(into, using, key, src) { if (Array.isArray(into)) { return into.map((x, i) => go(x, using, i, into)); } if (typeof into === 'object') { const kv = {}; for (const k in into) { if (Object.hasOwnProperty.call(into, k)) { kv[k] = go(into[k], using, k, into); } } return kv; } return using(into, key, src); } if (typeof into === 'function') { return go(using, into, null, into); } return go(into, using, null, into); } const dive1 = dive; function divebin(x, y, using) { if (using == null) { throw 'divebin: no diving func given'; } if (typeof x === 'function') { return divebin(y, using, x) } switch (using.length) { case 2: return dive(x, a => dive(y, b => using(a, b))); case 4: return dive(x, (a, i) => dive(y, (b, j) => using(a, i, b, j))); default: throw `divebin(...) only supports funcs with 2 or 4 args`; } } const dive2 = divebin; // const dualDive = divebin; // const duoDive = divebin; function dumps(x) { return JSON.stringify(x); } function emptyish(s) { return /^ *#/.test(s); } const isemptyish = emptyish; function every(src, f) { if (typeof src === 'function') { const t = src; src = f; f = t; } if (Array.isArray(src)) { return src.every(f); } switch (typeof src) { case 'string': let i = 0; for (const c of src) { if (!f(c, i, src)) { return false; } i++; } return true; case 'object': for (const k in src) { if (Object.prototype.hasOwnProperty.call(src, k)) { if (!f(src[k], i, src)) { return false; } } } return true; default: throw `can't loop on type ${typeof src}`; } } const all = every; function fillTable(src, fallback = null) { const allKeys = []; const got = new Set(); let fillingNeeded = false; if (typeof fallback !== 'function') { const filler = fallback; fallback = (v, k) => filler; } let first = true; for (const row of src) { if (typeof row !== 'object') { throw 'function fillTable only works with an array of objects'; } for (const k in row) { if (Object.hasOwnProperty.call(row, k)) { if (got.has(k)) { continue; } if (!first) { fillingNeeded = true; } allKeys.push(k); got.add(k); } } first = false; } if (!fillingNeeded) { return src; } return src.map(row => { const kv = {}; for (const k of allKeys) { const v = row[k]; kv[k] = (v !== undefined) ? v : fallback(v, k); } return kv; }) } function flappend(src, into = null) { if (into == null) { into = []; } if (Array.isArray(src)) { for (const e of src) { flappend(e, into); } return into; } if (typeof src === 'object') { for (const k in src) { if (Object.hasOwnProperty.call(src, k)) { flappend(src[k], into); } } return into; } into.push(src); return into; } function flat(src) { return flappend(src, []); } const flatten = flat; const flattened = flat; function harden(f, fallback = null) { return function (...args) { try { return f(...args); } catch { return fallback; } } } const hardened = harden; function hex(x) { return x.toString(16); } function horner(coeffs, x) { if (Array.isArray(x)) { let t = coeffs; coeffs = x; x = t; } if (coeffs.length === 0) { return 0; } let y = coeffs[0]; for (let i = 1; i < coeffs.length; i++) { y *= x; y += coeffs[i]; } return y; } const polyval = horner; function identity(x) { return x; } const idem = identity; const iden = identity; function idiota(n, f = identity) { if (n < 1 || isNaN(n) || !isFinite(n)) { return {}; } n = Math.floor(n); const range = {}; for (let i = 0; i < n; i++) { range[i + 1] = f(i + 1); } return range; } const kviota = idiota; function insist(...attempts) { if (attempts.length === 0) { throw 'insist: no funcs/attempts given'; } for (let i = 0; i < attempts.length; i++) { try { const a = attempts[i]; return (typeof a === 'function') ? a() : a; } catch (error) { if (i === attempts.length - 1) { throw error; } } } throw 'insist(...): nothing worked'; } function ints(start, stop, f = identity) { return iota(stop - start + 1, n => f(n + start)); } function iota(n, f = identity) { if (n < 1 || isNaN(n) || !isFinite(n)) { return []; } n = Math.floor(n); const range = new Array(n); for (let i = 0; i < n; i++) { range[i] = f(i + 1); } return range; } function join(what, sep = '') { if (Array.isArray(what) && Array.isArray(sep)) { const kv = {}; for (let i = 0; i < what.length; i++) { kv[what[i]] = i < sep.length ? sep[i] : null; } return kv; } if (typeof what === 'string') { return sep.join(what); } return what.join(sep); } function json0(x) { return JSON.stringify(x, null, 0); } function json2(x) { return JSON.stringify(x, null, 2); } function length(x) { if (typeof x === 'object') { let n = 0; for (const k in x) { n += Object.hasOwnProperty.call(x, k); } return n; } return x.length; } const len = length; function lines(s) { return s.split(/\r?\n/g); } function links(src) { const links = []; function rec(src) { if (Array.isArray(src)) { for (const e of src) { rec(e); } return; } if (typeof src === 'string') { for (const s of src.matchAll(link_re)) { links.push(s[0]); } } } rec(src); return links; } function mappend(...what) { const kv = {}; for (const src of what) { if (typeof src == 'object') { Object.assign(kv, src); } else { throw 'mappend only works with objects'; } } return kv; } function maybe(f, x = null) { try { return f(x); } catch (e) { return x; } } function maybeNumber(x) { if (typeof x === 'string') { const f = parseFloat(x); return isNaN(f) ? x : f; } return x; } const mayben = maybeNumber; const maybenum = maybeNumber; const maybeNum = maybeNumber; function mustNumber(x) { if (typeof x === 'boolean') { return +x; } if (typeof x === 'number') { return x; } if (typeof x === 'string') { const n = +x; if (!isNaN(n)) { return n; } } throw `can't turn value of type ${typeof x} into a valid number`; } const mustn = mustNumber; const mustnum = mustNumber; const mustNum = mustNumber; function narrow(x) { if (typeof x !== 'string') { return x; } try { return JSON.parse(x); } catch { const f = +x; return (isNaN(f) || !isFinite(f)) ? x : f; } } const autocast = narrow; const autoCast = narrow; const recast = narrow; function oct(x) { return x.toString(8); } function ord(s) { return s.charCodeAt(0); } function predicate(x) { if (typeof x === 'number' && isNaN(x)) { return y => ((typeof y === 'number') && isNaN(y)); } if (typeof x === 'function') { return x; } return y => x === y; } function quote(what, quote = '"') { return `${quote}${what}${quote}`; } function range(start, stop = null, f = identity) { if (stop === null) { stop = start; start = 0; } if (isNaN(start) || !isFinite(start)) { return []; } if (isNaN(stop) || !isFinite(stop)) { return []; } if (start > stop) { return []; } start = Math.floor(start); stop = Math.floor(stop); const n = stop - start; const range = new Array(n); for (let i = 0; i < n; i++) { range[i] = f(start + i); } return range; } function recover(...args) { switch (args.length) { case 1: try { return args[0](); } catch (e) { return null; } case 2: if (args[0].length === 0) { try { return args[0](); } catch { return args[1]; } } try { return args[0](args[1]); } catch { return null; } case 3: try { return args[0](args[1]); } catch { return args[2]; } default: throw `recover(...) only supports 1..3 args`; } } const rescue = recover; const trycatch = recover; const tryexcept = recover; const tryCatch = recover; const tryExcept = recover; function reverse(src) { if (Array.isArray(src)) { return src.slice().reverse(); } switch (typeof src) { case 'number': return riota(src); case 'string': return src.split('').reverse().join(''); case 'object': const kv = {}; const keys = Object.keys(src); for (let i = keys.length - 1; i >= 0; i--) { const k = keys[i]; kv[k] = src[k]; } return kv; default: throw `can't reverse values of type ${typeof src}`; } } const rev = reverse; function reviota(src, f = identity) { if (src < 1 || isNaN(src) || !isFinite(src)) { return []; } src = Math.floor(src); const rev = new Array(src); for (let i = 0; i < src; i++) { rev[i] = f(src - i); } return src; } const riota = reviota; function scale(x, x0, x1, y0, y1) { return (y1 - y0) * (x - x0) / (x1 - x0) + y0; } function some(src, f) { if (typeof src === 'function') { const t = src; src = f; f = t; } if (Array.isArray(src)) { return src.some(f); } switch (typeof src) { case 'string': let i = 0; for (const c of src) { if (f(c, i, src)) { return true; } i++; } return false; case 'object': for (const k in src) { if (Object.prototype.hasOwnProperty.call(src, k)) { if (f(src[k], i, src)) { return true; } } } return false; default: throw `can't loop on type ${typeof src}`; } } const any = some; function split(what, n) { if (typeof what === 'string' && typeof n === 'string') { return what.split(n); } if (n < 1) { return []; } const len = what.length; if (len < n) { return what; } const chunks = []; const chunkSize = Math.max(1, Math.floor(len / n)); for (let i = 0; i < n; i++) { const start = i * chunkSize; const end = Math.min(start + chunkSize, len); chunks.push(i < n - 1 ? what.slice(start, end) : what.slice(start)); } return chunks; } const broken = split; const splitted = split; const splitten = split; function squeeze(s) { return s.trim().replace(/ +/g, ''); } function string(x) { if (x == null) { return 'null'; } switch (typeof x) { case 'string': return x; case 'object': // if (typeof x.toString === 'function') { // return x.toString(); // } return JSON.stringify(x, null, 0); case 'function': return x.toString(); default: return JSON.stringify(x, null, 0); } } const str = string; function stround(s, decimals = 6) { return s.toFixed(decimals); } function trim(x) { return x.trim(); } const strip = trim; const stripped = trim; const trimmed = trim; const trimSpace = trim; const trimSpaces = trim; function trimEnd(x) { return x.trimEnd(); } const rstrip = trimEnd; function type(x) { if (x == null) { return 'null'; } return Array.isArray(x) ? 'array' : typeof x; } function unquote(s) { if (s.startsWith(`'`) && s.endsWith(`'`)) { return s.slice(1, s.length - 1); } if (s.startsWith(`"`) && s.endsWith(`"`)) { return s.slice(1, s.length - 1); } if (s.startsWith('`') && s.endsWith('`')) { return s.slice(1, s.length - 1); } return s; } function wrap(x, y0, y1) { return (y1 - y0) * (x - y0); } function after(data, what) { if (typeof data === 'string') { if (typeof what !== 'string') { return ''; } const i = data.indexOf(what); return i >= 0 ? data.slice(i + what.length) : ''; } const check = predicate(what); for (let i = 0; i < data.length; i++) { if (check(data[i], i, data)) { return data.slice(i + 1); } } return []; } function afterLast(data, what) { if (typeof data === 'string') { if (typeof what !== 'string') { return ''; } const i = data.lastIndexOf(what); return i >= 0 ? data.slice(i + what.length) : ''; } const check = predicate(what); for (let i = data.length - 1; i >= 0; i--) { if (check(data[i], i, data)) { return data.slice(i + 1); } } return []; } const afterfinal = afterLast; const afterFinal = afterLast; const afterlast = afterLast; function before(data, what) { if (typeof data === 'string') { if (typeof what !== 'string') { return data; } const i = data.indexOf(what); return i >= 0 ? data.slice(0, i) : data; } const check = predicate(what); for (let i = 0; i < data.length; i++) { if (check(data[i], i, data)) { return data.slice(0, i); } } return data; } function beforeLast(data, what) { if (typeof data === 'string') { if (typeof what !== 'string') { return data; } const i = data.lastIndexOf(what); return i >= 0 ? data.slice(0, i) : data; } const check = predicate(what); for (let i = data.length - 1; i >= 0; i--) { if (check(data[i], i, data)) { return data.slice(0, i); } } return []; } const beforefinal = beforeLast; const beforeFinal = beforeLast; const beforelast = beforeLast; function since(data, what) { if (typeof data === 'string') { if (typeof what !== 'string') { return ''; } const i = data.indexOf(what); return i >= 0 ? data.slice(i) : ''; } const check = predicate(what); for (let i = 0; i < data.length; i++) { if (check(data[i], i, data)) { return data.slice(i); } } return []; } function sinceLast(data, what) { if (typeof data === 'string') { if (typeof what !== 'string') { return ''; } const i = data.lastIndexOf(what); return i >= 0 ? data.slice(i) : ''; } const check = predicate(what); for (let i = data.length - 1; i >= 0; i--) { if (check(data[i], i, data)) { return data.slice(i); } } return []; } const sincefinal = sinceLast; const sinceFinal = sinceLast; const sincelast = sinceLast; function until(data, what) { if (typeof data === 'string') { if (typeof what !== 'string') { return ''; } const i = data.indexOf(what); return i >= 0 ? data.slice(0, i + what.length) : data; } const check = predicate(what); for (let i = 0; i < data.length; i++) { if (check(data[i], i, data)) { return data.slice(0, i + 1); } } } function untilLast(data, what) { if (typeof data === 'string') { if (typeof what !== 'string') { return ''; } const i = data.lastIndexOf(what); return i >= 0 ? data.slice(0, i + what.length) : data; } const check = predicate(what); for (let i = data.length - 1; i >= 0; i--) { if (check(data[i], i, data)) { return data.slice(0, i + 1); } } return data; } const untilfinal = untilLast; const untilFinal = untilLast; const untillast = untilLast; function loop(src, f) { if (typeof src === 'function') { const t = src; src = f; f = t; } if (Array.isArray(src)) { for (let i = 0; i < src.length; i++) { f(src[i], i, src); } return; } switch (typeof src) { case 'number': if (isNaN(src) || !isFinite(src)) { return; } src = Math.floor(src); for (let i = 0; i < src; i++) { f(i + 1, i, src); } return; case 'string': let i = 0; for (const c of src) { f(c, i, src); i++; } return; case 'object': for (const k in src) { if (Object.hasOwnProperty.call(src, k)) { f(src[k], k, src); } } return; default: throw `can't loop on values of type ${typeof src}`; } } function avoid(src, f) { f = predicate(f); return filter(src, (v, k, src) => !f(v, k, src)); } function count(src, f) { if (typeof src === 'function') { const t = src; src = f; f = t; } let count = 0; check = predicate(f); loop(src, (v, k, src) => { if (check(v, k, src)) { count++; } }); return count; } const countif = count; function extent(src, f = identity) { let min = +Infinity; let max = -Infinity; loop(src, (v, k, src) => { const e = f(v, k, src); if (typeof e === 'number' && !isNaN(e)) { min = Math.min(min, e); max = Math.max(max, e); } }); return [min, max]; } const span = extent; function filter(src, f) { if (typeof src === 'function') { const t = src; src = f; f = t; } f = predicate(f); if (Array.isArray(src)) { return src.filter(f); } switch (typeof src) { case 'string': return filterString(src, f); case 'object': return filterObject(src, f); default: throw `can't filter values of type ${typeof src}`; } } const keep = filter; function filterString(src, f) { let i = 0; let out = ''; for (const c of src) { if (f(c, i, src)) { out += c; } i++; } return out; } function filterObject(src, f) { const kv = {}; for (const k in src) { if (Object.hasOwnProperty.call(src, k)) { const v = src[k]; if (f(src[k], k, src)) { kv[k] = v; } } } return kv; } function first(src, n = null) { return n == null ? firstItem(src) : limit(src, n); } function firstItem(src) { if (Array.isArray(src)) { return src.length > 0 ? src[0] : null; } switch (typeof src) { case 'string': for (const c of src) { return c; } return ''; case 'object': for (const k in src) { if (Object.hasOwnProperty.call(src, k)) { return src[k]; } } return null; default: return null; } } function group(src, f = identity) { if (typeof src === 'function') { const t = src; src = f; f = t; } const groups = {}; loop(src, (v, k, src) => { const dk = f(v, k, src); if (dk in groups) { groups[dk].push(v); return; } groups[dk] = [v]; }); return groups; } const grouped = group; function last(src, n = null) { if (n == null) { return lastItem(src); } if (Array.isArray(src)) { return src.length <= n ? src : src.slice(src.length - n); } switch (typeof src) { case 'string': return lastItemsString(src, n); case 'object': return lastItemsObject(src, n); default: return null; } } function lastItemsString(src, n) { let c = 0; for (const _ of src) { c++; } if (c <= n) { return src; } let out = ''; for (const c of src) { if (c === n) { return out; } out += c; c--; } return out; } function lastItemsObject(src, n) { let c = 0; for (const k in src) { c += Object.hasOwnProperty.call(src, k); } if (c <= n) { return src; } const kv = {}; for (const k in src) { if (Object.hasOwnProperty.call(src, k)) { c--; if (c >= n) { continue; } kv[k] = src[k]; } } return kv; } function lastItem(src) { if (Array.isArray(src)) { return src.length > 0 ? src[src.length - 1] : null; } let last = null; switch (typeof src) { case 'object': for (const k in src) { if (Object.hasOwnProperty.call(src, k)) { last = src[k]; } } return last; default: return null; } } function limit(src, n) { if (Array.isArray(src)) { return src.slice(0, n); } switch (typeof src) { case 'string': return limitString(src, n); case 'object': return limitObject(src, n); default: return null; } } function limitString(src, n) { let i = 0; let out = ''; for (const c of src) { if (i === n) { return out; } out += c; i++; } return out; } function limitObject(src, n) { let i = 0; const kv = {}; for (const k in src) { if (i === n) { return kv; } if (Object.hasOwnProperty.call(src, k)) { return kv[k] = src[k]; } i++; } return kv; } function map(src, f) { if (typeof src === 'function') { const t = src; src = f; f = t; } if (Array.isArray(src)) { return src.map(f); } switch (typeof src) { case 'string': return mapString(src, f); case 'object': return mapObject(src, f); default: throw `can't map values of type ${typeof src}`; } } const each = map; function mapString(src, f) { let i = 0; let out = ''; for (const c of src) { out += f(c, i, src); i++; } return out; } function mapObject(src, f) { const kv = {}; for (const k in src) { if (Object.hasOwnProperty.call(src, k)) { kv[k] = f(src[k], k, src); } } return kv; } function mapkv(src, key, value = identity) { const kv = {}; loop(src, (v, k, src) => { const dk = key(v, k, src); kv[dk] = value(v, k, src); }); return kv; } function tally(src, f = identity) { if (typeof src === 'function') { const t = src; src = f; f = t; } const tally = {}; loop(src, (v, k, src) => { const dk = f(v, k, src); tally[dk] = (tally[dk] | 0) + 1; }); return tally; } const tallied = tally; function keys(src) { const keys = []; if (Array.isArray(src)) { for (let i = 0; i < src.length; i++) { keys.push(i); } return keys; } switch (typeof src) { case 'string': let i = 0; for (const _ of src) { keys.push(i); i++; } return keys; case 'object': for (const k in src) { if (Object.hasOwnProperty.call(src, k)) { keys.push(k); } } return keys; default: return keys; } } function values(src) { if (Array.isArray(src)) { return src; } const values = []; switch (typeof src) { case 'string': for (const c of src) { values.push(c); } return values; case 'object': for (const k in src) { if (Object.hasOwnProperty.call(src, k)) { values.push(src[k]); } } return values; default: return [src]; } } function padEnd(s, n, how = ' ') { return s.length >= n ? s : s + how.repeat((n - s.length) / how.length); } const padend = padEnd; const rightpad = padEnd; const rightPad = padEnd; const rpad = padEnd; function padStart(s, n, how = ' ') { return s.length >= n ? s : how.repeat((n - s.length) / how.length) + s; } const padstart = padStart; const leftpad = padStart; const leftPad = padStart; const lpad = padStart; function pick(src, ...what) { return _pick(src, what); } function _pick(src, what) { if (Array.isArray(src)) { return src.map(e => _pick(e, what)); } if (typeof src !== 'object') { return null; } const kv = {}; for (const k of what) { kv[k] = src[k]; } return kv; } function drop(src, ...what) { if (typeof src === 'string') { for (const s of what) { src = src.replaceAll(s, ''); } return src; } return _drop(src, new Set(what)); } function _drop(src, what) { if (Array.isArray(src)) { return src.map(e => _drop(e, what)); } if (typeof src !== 'object') { return null; } const kv = {}; for (const k in src) { if (Object.hasOwnProperty.call(src, k)) { if (!(what.has(k))) { kv[k] = src[k]; } } } return kv; } function lower(s) { return s.toLowerCase(); } const lowered = lower; const tolower = lower; function numstats(src, f = identity) { let n = 0, meanSq = 0, lnSum = 0; let min = +Infinity, max = -Infinity, sum = 0, mean = 0, prod = 1; let nans = 0, ints = 0, pos = 0, zero = 0, neg = 0; src.map(x => { x = f(x); if (typeof x !== 'number') { return; } if (isNaN(x)) { nans++; return; } n++; ints += (x === Math.floor(x)); if (x > 0) { pos++; } else if (x < 0) { neg++; } else { zero++; } min = Math.min(min, x); max = Math.max(max, x); // sum += x; prod *= x; lnSum += Math.log(x); const d1 = x - mean; mean += d1 / n; const d2 = x - mean; meanSq += d1 * d2; }); let sd = NaN, geomean = NaN; if (n > 0) { sd = Math.sqrt(meanSq / n); geomean = (lnSum !== -Infinity) ? Math.exp(lnSum / n) : NaN; } sum = n * mean; // prod = (n > 0) ? prod : NaN; return { 'n': n, 'nan': nans, 'min': min, 'max': max, 'sum': sum, 'mean': mean, 'geomean': geomean, 'sd': sd, 'product': prod, 'integer': ints, 'positive': pos, 'zero': zero, 'negative': neg, }; } function once(what, fallback = null) { if (!seen.has(what)) { seen.add(what); return what; } return fallback; } function replace(s, what, repl = '') { return s.replaceAll(what, repl); } function round(x, decimals = 0) { const k = 10 ** decimals; return Math.round(k * x) / k; } function tabstats(src, maxDecimals = 6) { if (!Array.isArray(src)) { throw 'function tabstats only works with an array of objects'; } if (src.length === 0) { return numstats([]); } for (const row of src) { if (typeof row !== 'object') { throw 'function tabstats only works with an array of objects'; } } const stats = []; const keys = Object.keys(src[0]); for (const k of keys) { const colstats = numstats(src, row => row[k]); for (const k in colstats) { const v = colstats[k]; if (isNaN(v) || !isFinite(v) || v === Math.round(v)) { continue; } colstats[k] = round(v, maxDecimals); } stats.push({ name: k, numeric: colstats.n, integers: colstats.integer, pos: colstats.positive, zero: colstats.zero, neg: colstats.negative, min: isFinite(colstats.min) ? colstats.min : NaN, max: isFinite(colstats.max) ? colstats.max : NaN, sum: colstats.sum, mean: colstats.mean, geomean: colstats.geomean, sd: colstats.sd, }); } return stats; } function transpose(what) { if (!Array.isArray(what) && typeof what !== 'object') { throw 'transpose only supports objects or arrays of objects/arrays'; } if (!Array.isArray(what)) { const kv = {}; for (const k in what) { if (Object.hasOwnProperty.call(what, k)) { kv[what[k]] = k; } } return kv; } const kv = {}; const seq = []; for (const e of what) { if (Array.isArray(e)) { for (let i = 0; i < e.length; i++) { if (i < seq.length) { seq[i].push(e[i]); } else { seq.push([e[i]]); } } continue; } if (typeof e === 'object') { for (const k in e) { if (Object.hasOwnProperty.call(e, k)) { if (k in kv) { kv[k].push(e[k]); } else { kv[k].push([e[k]]); } } } continue; } throw 'transpose(...): not all items are arrays/objects'; } let kvEmpty = true; for (const k in kv) { if (Object.hasOwnProperty.call(kv, k)) { kvEmpty = false; break; } } if (seq.length > 0 && !kvEmpty) { throw 'transpose(...): mix of arrays and objects not supported'; } return kvEmpty ? seq : kv; } const tr = transpose; const transp = transpose; const transposed = transpose; function upper(s) { return s.toUpperCase(); } const uppered = upper; const toupper = upper; // function wait(seconds, result) { // if ((typeof seconds !== 'number') && (typeof result === 'number')) { // const t = seconds; // seconds = result; // result = t; // } // const p = new Promise(f => setTimeout(f, seconds)); // eval('(async function () { await p; })();'); // return result; // } function wat(...args) { for (const x of args) { if (typeof x !== 'function') { continue; } const title = padEnd(x.name, 80, ' '); process.stderr.write(`\x1b[48;5;253m\x1b[38;5;26m${title}\x1b[0m\n`); process.stderr.write(x.toString()); process.stderr.write('\n'); } return new Skip(); } main();