def print: stdout; def println: ., "\n" | stdout; def debug: ( ((["DEBUG", .] | tojson), "\n" | stderr) , . ); def debug(f): . as $c | f | debug | $c; # TODO: introspect and show doc, reflection somehow? def help: ( "Type jq expression to evaluate" , "\\t Auto completion" , "Up/Down History" , "^C Interrupt execution" , "... | repl Start a new REPL" , "^D Exit REPL" ) | println; # null input means done, otherwise {approx_read_bytes: 123, total_size: 123} # TODO: decode provide even more detailed progress, post-process sort etc? def _decode_progress: # _input_filenames is remaning files to read ( (_input_filenames | length) as $inputs_len | ( options.filenames | length) as $filenames_len | _ansi.clear_line , "\r" , if . != null then ( if $filenames_len > 1 then "\($filenames_len - $inputs_len)/\($filenames_len) \(_input_filename) " else empty end , "\((.approx_read_bytes / .total_size * 100 | _numbertostring(1)))%" ) else empty end | stderr ); def decode($name; $opts): ( options as $opts | (null | stdout) as $stdout | _decode( $name; $opts + { _progress: ( if $opts.decode_progress and $opts.repl and $stdout.is_terminal then "_decode_progress" else null end ) } ) ); def decode($name): decode($name; {}); def decode: decode(options.decode_format; {}); def tovalue($opts): _tovalue(options($opts)); def tovalue: _tovalue({}); def display($opts): _display(options($opts)); def display: display({}); def d($opts): display($opts); def d: display({}); def full($opts): display({array_truncate: 0} + $opts); # TODO: rename, gets mixed up with f args often def full: full({}); def f($opts): full($opts); def f: full; def verbose($opts): display({verbose: true, array_truncate: 0} + $opts); def verbose: verbose({}); def v($opts): verbose($opts); def v: verbose; def hexdump($opts): _hexdump(options({display_bytes: 0} + $opts)); def hexdump: hexdump({display_bytes: 0}); def hd($opts): hexdump($opts); def hd: hexdump; def format: _decode_value(._format; null); def root: _decode_value(._root); def buffer_root: _decode_value(._buffer_root); def format_root: _decode_value(._format_root); def parent: _decode_value(._parent); def parents: # TODO: refactor, _while_break? ( _decode_value(._parent) | if . == null then empty else _recurse_break( ( ._parent | if . == null then error("break") end ) ) end ); def tobitsrange: _tobitsrange; def tobytesrange: _tobitsrange(8); def tobits: _tobitsrange(1; false); def tobytes: _tobitsrange(8; false); # overload match to support buffers def _orig_match($val): match($val); def _orig_match($regex; $flags): match($regex; $flags); def _is_buffer: type == "buffer"; def match($val): if _is_buffer then _bits_match($val) else _orig_match($val) end; def match($regex; $flags): if _is_buffer then _bits_match($regex; $flags) else _orig_match($regex; $flags) end; def formats: _registry.formats; def intdiv(a; b): _intdiv(a; b); # TODO: escape for safe key names # path ["a", 1, "b"] -> "a[1].b" def path_to_expr: ( if length == 0 or (.[0] | type) != "string" then [""] + . end | map( if type == "number" then "[", ., "]" else ( "." , # empty (special case for leading index or empty path) or key if . == "" or _is_ident then . else ( "\"" , _escape_ident , "\"" ) end ) end ) | join("") ); # TODO: don't use eval? should support '.a.b[1]."c.c"' and escapes? def expr_to_path: ( if type != "string" then error("require string argument") end | eval("null | path(\(.))") ); def trim: capture("^\\s*(?.*?)\\s*$"; "").str; # does +1 and [:1] as " "*0 is null def rpad($s; $w): . + ($s * ($w+1-length))[1:]; # like group but groups streaks based on condition def streaks_by(f): ( . as $a | length as $l | if $l == 0 then [] else ( [ foreach $a[] as $v ( {cf: ($a[0] | f), index: 0, start: 0, extract: null}; ( ($v | f) as $vf | (.index == 0 or (.cf == $vf)) as $equal | if $equal then ( .extract = null ) else ( .cf = $vf | .extract = [.start, .index] | .start = .index ) end | .index += 1 ); ( if .extract then .extract else empty end , if .index == $l then [.start, .index] else empty end ) ) ] | map($a[.[0]:.[1]]) ) end ); # [1, 2, 2, 3] => [[1], [2, 2], [3]] def streaks: streaks_by(.); # same as group_by but counts, array or pairs with [value, count] def count_by(exp): group_by(exp) | map([(.[0] | exp), length]); def count: count_by(.); # array of result of applying f on all consecutive pairs def delta_by(f): ( . as $a | if length < 1 then [] else [ range(length-1) as $i | {a: $a[$i], b: $a[$i+1]} | f ] end ); # array of minus between all consecutive pairs def delta: delta_by(.b - .a); # split array or string into even chunks, except maybe the last def chunk($size): if length == 0 then [] else [ ( range( ( (length / $size) | ceil | if . == 0 then 1 end ) ) as $i | .[$i * $size:($i + 1) * $size] ) ] end; # helper to build path query/generate functions for tree structures with # non-unique children, ex: mp4_path def tree_path(children; name; $v): def _lookup: # add implicit zeros to get first value # ["a", "b", 1] => ["a", 0, "b", 1] def _normalize_path: ( . as $np | if $np | last | type == "string" then $np+[0] end # state is [path acc, possible pending zero index] | ( reduce .[] as $np ([[], []]; if $np | type == "string" then [(.[0]+.[1]+[$np]), [0]] else [.[0]+[$np], []] end )) )[0]; ( . as $c | $v | expr_to_path | _normalize_path | reduce .[] as $n ($c; if $n | type == "string" then children | map(select(name==$n)) else .[$n] end ) ); def _path: [ . as $r | $v._path as $p | foreach range(($p | length)/2) as $i (null; null; ( ($r | getpath($p[0:($i+1)*2]) | name) as $name | [($r | getpath($p[0:($i+1)*2-1]))[] | name][0:$p[($i*2)+1]+1] as $before | [ $name , ($before | map(select(. == $name)) | length)-1 ] ) ) | [ ".", .[0], (.[1] | if . == 0 then empty else "[", ., "]" end) ] ] | flatten | join(""); if $v | type == "string" then _lookup else _path end; # [{a: 123, ...}, ...] # colmap maps something into [col, ...] # render maps [{column: 0, string: "coltext", maxwidth: 12}, ..] into a row def table(colmap; render): def _column_widths: [ . as $rs | range($rs[0] | length) as $i | [$rs[] | colmap | (.[$i] | length)] | max ]; if length == 0 then "" else ( _column_widths as $cw | . as $rs | ( $rs[] | . as $r | [ range($r | length) as $i | ($r | colmap | {column: $i, string: .[$i], maxwidth: $cw[$i]}) ] | render ) ) end; # convert number to array of bytes def number_to_bytes($bits): def _number_to_bytes($d): if . > 0 then . % $d, (intdiv(.; $d) | _number_to_bytes($d)) else empty end; if . == 0 then [0] else [_number_to_bytes(pow(2; $bits) | _to_int)] | reverse end; def number_to_bytes: number_to_bytes(8); def from_radix($base; $table): ( split("") | reverse | map($table[.]) | if . == null then error("invalid char \(.)") end # state: [power, ans] | reduce .[] as $c ([1,0]; ( (.[0] * $base) as $b | [$b, .[1] + (.[0] * $c)] ) ) | .[1] ); def to_radix($base; $table): if . == 0 then "0" else ( [ recurse(if . > 0 then intdiv(.; $base) else empty end) | . % $base] | reverse | .[1:] | if $base <= ($table | length) then map($table[.]) | join("") else error("base too large") end ) end; def radix($base; $to_table; $from_table): if . | type == "number" then to_radix($base; $to_table) elif . | type == "string" then from_radix($base; $from_table) else error("needs to be number or string") end; def radix2: radix(2; "01"; {"0": 0, "1": 1}); def radix8: radix(8; "01234567"; {"0": 0, "1": 1, "2": 2, "3": 3,"4": 4, "5": 5, "6": 6, "7": 7}); def radix16: radix(16; "0123456789abcdef"; { "0": 0, "1": 1, "2": 2, "3": 3,"4": 4, "5": 5, "6": 6, "7": 7, "8": 8, "9": 9, "a": 10, "b": 11, "c": 12, "d": 13, "e": 14, "f": 15 }); def radix62: radix(62; "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; { "0": 0, "1": 1, "2": 2, "3": 3,"4": 4, "5": 5, "6": 6, "7": 7, "8": 8, "9": 9, "A": 10, "B": 11, "C": 12, "D": 13, "E": 14, "F": 15, "G": 16, "H": 17, "I": 18, "J": 19, "K": 20, "L": 21, "M": 22, "N": 23, "O": 24, "P": 25, "Q": 26, "R": 27, "S": 28, "T": 29, "U": 30, "V": 31, "W": 32, "X": 33, "Y": 34, "Z": 35, "a": 36, "b": 37, "c": 38, "d": 39, "e": 40, "f": 41, "g": 42, "h": 43, "i": 44, "j": 45, "k": 46, "l": 47, "m": 48, "n": 49, "o": 50, "p": 51, "q": 52, "r": 53, "s": 54, "t": 55, "u": 56, "v": 57, "w": 58, "x": 59, "y": 60, "z": 61 }); def radix62: radix(62; "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; { "A": 0, "B": 1, "C": 2, "D": 3, "E": 4, "F": 5, "G": 6, "H": 7, "I": 8, "J": 9, "K": 10, "L": 11, "M": 12, "N": 13, "O": 14, "P": 15, "Q": 16, "R": 17, "S": 18, "T": 19, "U": 20, "V": 21, "W": 22, "X": 23, "Y": 24, "Z": 25, "a": 26, "b": 27, "c": 28, "d": 29, "e": 30, "f": 31, "g": 32, "h": 33, "i": 34, "j": 35, "k": 36, "l": 37, "m": 38, "n": 39, "o": 40, "p": 41, "q": 42, "r": 43, "s": 44, "t": 45, "u": 46, "v": 47, "w": 48, "x": 49, "y": 50, "z": 51, "0": 52, "1": 53, "2": 54, "3": 55, "4": 56, "5": 57, "6": 58, "7": 59, "8": 60, "9": 61 }); def radix64: radix(64; "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; { "A": 0, "B": 1, "C": 2, "D": 3, "E": 4, "F": 5, "G": 6, "H": 7, "I": 8, "J": 9, "K": 10, "L": 11, "M": 12, "N": 13, "O": 14, "P": 15, "Q": 16, "R": 17, "S": 18, "T": 19, "U": 20, "V": 21, "W": 22, "X": 23, "Y": 24, "Z": 25, "a": 26, "b": 27, "c": 28, "d": 29, "e": 30, "f": 31, "g": 32, "h": 33, "i": 34, "j": 35, "k": 36, "l": 37, "m": 38, "n": 39, "o": 40, "p": 41, "q": 42, "r": 43, "s": 44, "t": 45, "u": 46, "v": 47, "w": 48, "x": 49, "y": 50, "z": 51, "0": 52, "1": 53, "2": 54, "3": 55, "4": 56, "5": 57, "6": 58, "7": 59, "8": 60, "9": 61, "+": 62, "/": 63 }); # TODO: rename keys and add more, ascii/utf8/utf16/codepoint name?, le/be, signed/unsigned? def iprint: { bin: "0b\(radix2)", oct: "0o\(radix8)", dec: "\(.)", hex: "0x\(radix16)", str: (try ([.] | implode) catch null), }; # produce a/b pairs for diffing values def diff($a; $b): ( ( $a | type) as $at | ( $b | type) as $bt | if $at != $bt then {a: $a, b: $b} elif ($at == "array" or $at == "object") then ( [ ((($a | keys) + ($b | keys)) | unique)[] as $k | { ($k | tostring): ( [($a | has($k)), ($b | has($k))] | if . == [true, true] then diff($a[$k]; $b[$k]) elif . == [true, false] then {a: $a[$k]} elif . == [false, true] then {b: $b[$k]} else empty # TODO: can't happen? error? end ) } ] | add | if . == null then empty end ) else if $a == $b then empty else {a: $a, b: $b} end end ); def in_bits_range($p): select(._start <= $p and $p < ._stop); def in_bytes_range($p): select(._start/8 <= $p and $p < ._stop/8);