1
1
mirror of https://github.com/wader/fq.git synced 2024-11-30 09:58:13 +03:00
fq/pkg/interp/internal.jq

251 lines
6.7 KiB
Plaintext

include "ansi";
# is here to be defined as early as possible to allow debugging
# TODO: move some _* to builtin.jq etc?
def _stdio($name):
if . == null then _stdio_info($name)
else _stdio_write($name)
end;
def _stdio($name; $l):
_stdio_read($name; $l);
def _stdin: _stdio("stdin");
def _stdin($l): _stdio("stdin"; $l);
def _stdout: _stdio("stdout");
def _stdout($l): _stdio("stdout"; $l);
def _stderr: _stdio("stderr");
def _stderr($l): _stdio("stderr"; $l);
def stdin_tty: null | _stdin;
def stdout_tty: null | _stdout;
def print: tostring | _stdout;
def println: ., "\n" | print;
def printerr: tostring | _stderr;
def printerrln: ., "\n" | printerr;
def _debug($name):
( (([$name, .] | tojson) | printerrln)
, .
);
# jq compat
def debug: _debug("DEBUG");
def debug(f): . as $c | f | debug | $c;
# jq compat, output to compact json to stderr and let input thru
def stderr:
( (tojson | printerr)
, .
);
# try to be same exit codes as jq
# TODO: jq seems to halt processing inputs on JSON decode error but not IO errors,
# seems strange.
# jq '(' <(echo 1) <(echo 2) ; echo $? => 3 and no inputs processed
# jq '.' missing <(echo 2) ; echo $? => 2 and continues process inputs
# jq '.' <(echo 'a') <(echo 123) ; echo $? => 4 and stops process inputs
# jq '.' missing <(echo 'a') <(echo 123) ; echo $? => 2 ???
# jq '"a"+.' <(echo '"a"') <(echo 1) ; echo $? => 5
# jq '"a"+.' <(echo 1) <(echo '"a"') ; echo $? => 0
def _exit_code_args_error: 2;
def _exit_code_input_io_error: 2;
def _exit_code_compile_error: 3;
def _exit_code_input_decode_error: 4;
def _exit_code_expr_error: 5;
def _global_var($k): _global_state[$k];
def _global_var($k; f): _global_state(_global_state | .[$k] |= f) | .[$k];
def _include_paths: _global_var("include_paths");
def _include_paths(f): _global_var("include_paths"; f);
def _options_stack: _global_var("options_stack");
def _options_stack(f): _global_var("options_stack"; f);
def _cli_last_expr_error: _global_var("cli_last_expr_error");
def _cli_last_expr_error(f): _global_var("cli_last_expr_error"; f);
def _input_filename: _global_var("input_filename");
def _input_filename(f): _global_var("input_filename"; f);
def _input_filenames: _global_var("input_filenames");
def _input_filenames(f): _global_var("input_filenames"; f);
def _input_strings: _global_var("input_strings");
def _input_strings(f): _global_var("input_strings"; f);
def _input_strings_lines: _global_var("input_strings_lines");
def _input_strings_lines(f): _global_var("input_strings_lines"; f);
def _input_io_errors: _global_var("input_io_errors");
def _input_io_errors(f): _global_var("input_io_errors"; f);
def _input_decode_errors: _global_var("input_decode_errors");
def _input_decode_errors(f): _global_var("input_decode_errors"; f);
def _slurps: _global_var("slurps");
def _slurps(f): _global_var("slurps"; f);
# call f and finally eval fin even if empty or error.
# _finally(1; debug)
# _finally(null; debug)
# _finally(empty; debug)
# _finally(1,2,3; debug)
# _finally({a:1}; debug)
# _finally(error("a"); debug)
# _finally(error("a"); empty)
def _finally(f; fin):
try
( f
, (fin | empty)
)
catch
( (fin | empty)
, error
);
# TODO: figure out a saner way to force int
def _to_int: (. % (. + 1));
# integer division
# inspried by https://github.com/itchyny/gojq/issues/63#issuecomment-765066351
def _intdiv($a; $b):
( ($a | _to_int) as $a
| ($b | _to_int) as $b
| ($a - ($a % $b)) / $b
);
# escape " and \
def _escape_ident: gsub("(?<g>[\\\\\"])"; "\\\(.g)");
# format number with fixed number of decimals
def _numbertostring($decimals):
( . as $n
| [ (. % (. + 1)) # truncate to integer
, "."
, foreach range($decimals) as $_ (1; . * 10; ($n * .) % 10)
]
| join("")
);
def _repeat_break(f):
try repeat(f)
catch
if . == "break" then empty
else error
end;
def _recurse_break(f):
try recurse(f)
catch
if . == "break" then empty
else error
end;
def _is_null: type == "null";
def _is_string: type == "string";
def _is_number: type == "number";
def _is_boolean: type == "boolean";
def _is_array: type == "array";
def _is_object: type == "object";
def _is_scalar: (_is_array or _is_object) | not;
# valid jq identifier, start with alpha or underscore then zero or more alpha, num or underscore
def _is_ident: _is_string and test("^[a-zA-Z_][a-zA-Z_0-9]*$");
def _is_context_canceled_error: . == "context canceled";
def _error_str($contexts): (["error"] + $contexts + [.]) | join(": ");
def _error_str: _error_str([]);
# TODO: escape for safe key names
# path ["a", 1, "b"] -> "a[1].b"
def _path_to_expr($opts):
( if length == 0 or (.[0] | type) != "string" then
[""] + .
end
| map(
if _is_number then
( ("[" | _ansi_if($opts; "array"))
, _ansi_if($opts; "number")
, ("]" | _ansi_if($opts; "array"))
) else
( "."
, # empty (special case for leading index or empty path) or key
if . == "" or _is_ident then _ansi_if($opts; "objectkey")
else
"\"\(_escape_ident)\"" | _ansi_if($opts; "string")
end
)
end
)
| join("")
);
def _path_to_expr: _path_to_expr(null);
# 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(\(.))"; {})
);
# 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 | _is_string then $np+[0] end
# state is [path acc, possible pending zero index]
| ( reduce .[] as $np ([[], []];
if $np | _is_string then
[(.[0]+.[1]+[$np]), [0]]
else
[.[0]+[$np], []]
end
))
)[0];
( . as $c
| $v
| _expr_to_path
| _normalize_path
| reduce .[] as $n (
$c;
if . then
if $n | _is_string then
children | map(select(name == $n))
else
.[$n]
end
else empty
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 | _is_string then _lookup
else _path
end;