mirror of
https://github.com/wader/fq.git
synced 2024-11-27 14:14:58 +03:00
f4480c6fe5
interp: Refactor format help and also include options interp: Add -o name=@path to load file content as value (not documented yet, might change) interp,decode: Expose decode out value as _out (might change) interp: Refactor foramts.jq into format_{decode,func,include}.jq interp: Refactor torepr into _format_func for generic format function overloading interp: Refactor -o options parsing to be more generic and collect unknowns options to be used as format options decode of decode alises func for format overloaded functions include for format specific jq functions (also _help, torepr etc) flac_frame: Add bits_per_sample option mp3: Add max_unique_header_config and max_sync_seek options mp4: Add decode_samples and allow_truncate options avc_au: Has length_size option hevc_au: Has length_size option aac_frame: Has object_typee option doc: Rewrite format doc generation, less hack more jq
338 lines
9.0 KiB
Plaintext
338 lines
9.0 KiB
Plaintext
include "internal";
|
|
include "options";
|
|
include "eval";
|
|
include "query";
|
|
include "decode";
|
|
include "funcs";
|
|
include "ansi";
|
|
|
|
# TODO: currently only make sense to allow keywords starting a term or directive
|
|
def _complete_keywords:
|
|
[
|
|
"and",
|
|
#"as",
|
|
#"break",
|
|
#"catch",
|
|
"def",
|
|
#"elif",
|
|
#"else",
|
|
#"end",
|
|
"false",
|
|
"foreach",
|
|
"if",
|
|
"import",
|
|
"include",
|
|
"label",
|
|
"module",
|
|
"null",
|
|
"or",
|
|
"reduce",
|
|
#"then",
|
|
"true",
|
|
"try"
|
|
];
|
|
|
|
def _complete_scope:
|
|
[scope[], _complete_keywords[]];
|
|
def _complete_keys:
|
|
# uses try as []? will not catch errors
|
|
[try keys[] catch empty, try _extkeys[] catch empty];
|
|
|
|
# TODO: handle variables via ast walk?
|
|
# TODO: refactor this
|
|
# TODO: completionMode
|
|
# TODO: return escaped identifier, not sure current readline implementation supports
|
|
# modifying "previous" characters if quoting is needed
|
|
# completions that needs to change previous input, ex: .a\t -> ."a \" b" etc
|
|
def _complete($line; $cursor_pos):
|
|
# TODO: reverse this? word or non-ident char?
|
|
def _is_separator: . as $c | " .;[]()|=" | contains($c);
|
|
def _is_internal: startswith("_") or startswith("$_");
|
|
def _query_index_or_key($q):
|
|
( ([.[] | _eval($q) | type]) as $n
|
|
| if ($n | all(. == "object")) then "."
|
|
elif ($n | all(. == "array")) then "[]"
|
|
else null
|
|
end
|
|
);
|
|
# only complete if at end or there is a whitespace for now
|
|
if ($line[$cursor_pos] | . == null or _is_separator) then
|
|
( . as $c
|
|
| $line[0:$cursor_pos]
|
|
| . as $line_query
|
|
# expr -> map(partial-expr | . | f?) | add
|
|
# TODO: move map/add logic to here?
|
|
| _query_completion(
|
|
if .type | . == "func" or . == "var" then "_complete_scope"
|
|
elif .type == "index" then "_complete_keys"
|
|
else error("unreachable")
|
|
end
|
|
) as {$type, $query, $prefix}
|
|
| {
|
|
prefix: $prefix,
|
|
names: (
|
|
if $type == "none" then
|
|
( $c
|
|
| _query_index_or_key($line_query)
|
|
| if . then [.] else [] end
|
|
)
|
|
else
|
|
( $c
|
|
| _eval($query)
|
|
| ($prefix | _is_internal) as $prefix_is_internal
|
|
| map(
|
|
select(
|
|
strings and
|
|
# TODO: var type really needed? just func?
|
|
(_is_ident or $type == "var") and
|
|
((_is_internal | not) or $prefix_is_internal) and
|
|
startswith($prefix)
|
|
)
|
|
)
|
|
| unique
|
|
| sort
|
|
| if length == 1 and .[0] == $prefix then
|
|
( $c
|
|
| _query_index_or_key($line_query)
|
|
| if . then [$prefix+.] else [$prefix] end
|
|
)
|
|
end
|
|
)
|
|
end
|
|
)
|
|
}
|
|
)
|
|
else
|
|
{prefix: "", names: []}
|
|
end;
|
|
def _complete($line): _complete($line; $line | length);
|
|
|
|
# empty input []
|
|
# >* empty>
|
|
# single input [v]
|
|
# >* VALUE_PATH VALUE_PREVIEW>
|
|
# multiple inputs [v,...]
|
|
# >* VALUE_PATH VALUE_PREVIEW, ...[#]>
|
|
# single/multi inputs where first input is array [[v,...], ...]
|
|
# >* [VALUE_PATH VALUE_PREVIEW, ...][#], ...[#]>
|
|
def _prompt($opts):
|
|
def _repl_level:
|
|
(_options_stack | length | if . > 2 then ((.-2) * ">") else empty end);
|
|
def _value_path:
|
|
(._path? // []) | if . == [] then empty else _path_to_expr($opts) end;
|
|
def _value_preview($depth):
|
|
if $depth == 0 and format == null and type == "array" then
|
|
[ "["
|
|
, if length == 0 then empty
|
|
else
|
|
( (.[0] | _value_preview(1))
|
|
, if length > 1 then ", ..." else empty end
|
|
)
|
|
end
|
|
, "]"
|
|
, if length > 1 then
|
|
( ("[" | _ansi_if($opts; "array"))
|
|
, ("0" | _ansi_if($opts; "number"))
|
|
, ":"
|
|
, (length | tostring | _ansi_if($opts; "number"))
|
|
, ("]" | _ansi_if($opts; "array"))
|
|
)
|
|
else empty
|
|
end
|
|
] | join("")
|
|
else
|
|
( . as $c
|
|
| format
|
|
| if . != null then
|
|
( .
|
|
+ if $c._error then "!" else "" end
|
|
)
|
|
else
|
|
($c | type)
|
|
end
|
|
) | _ansi_if($opts; "prompt_value")
|
|
end;
|
|
def _value:
|
|
[ _value_path
|
|
, _value_preview(0)
|
|
] | join(" ");
|
|
def _values:
|
|
if length == 0 then "empty"
|
|
else
|
|
[ (.[0] | _value)
|
|
, if length > 1 then
|
|
( ", ..."
|
|
, ("[" | _ansi_if($opts; "array"))
|
|
, ("0" | _ansi_if($opts; "number"))
|
|
, ":"
|
|
, (length | tostring | _ansi_if($opts; "number"))
|
|
, ("]" | _ansi_if($opts; "array"))
|
|
, "[]"
|
|
)
|
|
else empty
|
|
end
|
|
] | join("")
|
|
end;
|
|
[ (_repl_level | _ansi_if($opts; "prompt_repl_level")) , _values
|
|
] | join(" ") + "> ";
|
|
def _prompt: _prompt(null);
|
|
|
|
# user expr error
|
|
def _repl_on_expr_error:
|
|
( if _eval_is_compile_error then _eval_compile_error_tostring
|
|
else tostring
|
|
end
|
|
| _error_str
|
|
| println
|
|
);
|
|
# other expr error, interrupted or something unexpected happened
|
|
def _repl_on_error:
|
|
# was interrupted by user, just ignore
|
|
if .error | _is_context_canceled_error then empty
|
|
else halt_error(_exit_code_expr_error)
|
|
end;
|
|
# compile error
|
|
def _repl_on_compile_error:
|
|
( if .error | _eval_is_compile_error then
|
|
( # TODO: move, redo as: def _symbols: if unicode then {...} else {...} end?
|
|
def _arrow_up: if options.unicode then "⬆" else "^" end;
|
|
if .error.column != 0 then
|
|
( ((.input | _prompt | length) + .error.column-1) as $pos
|
|
| " " * $pos + "\(_arrow_up) \(.error.error)"
|
|
)
|
|
else
|
|
( .error
|
|
| _eval_compile_error_tostring
|
|
| _error_str
|
|
)
|
|
end
|
|
)
|
|
else .error | _error_str
|
|
end
|
|
| println
|
|
);
|
|
def _repl_display:
|
|
display(_display_default_opts);
|
|
def _repl_eval($expr; on_error; on_compile_error):
|
|
eval(
|
|
$expr;
|
|
{ slurps:
|
|
{ repl: "_repl_slurp",
|
|
help: "_help_slurp",
|
|
slurp: "_slurp"
|
|
},
|
|
# input to repl is always array of values to iterate
|
|
input_query: (_query_ident | _query_iter), # .[]
|
|
# each input should be evaluted separatel like with cli, so catch and just print errors
|
|
catch_query: _query_func("_repl_on_expr_error"),
|
|
# run display in sub eval so it can be interrupted
|
|
output_query: _query_func("_repl_display")
|
|
};
|
|
on_error;
|
|
on_compile_error
|
|
);
|
|
|
|
# run read-eval-print-loop
|
|
# input is array of inputs to iterate
|
|
def _repl($opts):
|
|
def _read_expr:
|
|
_repeat_break(
|
|
# both _prompt and _complete want input arrays
|
|
( _readline({
|
|
prompt: _prompt(options($opts)),
|
|
complete: "_complete",
|
|
timeout: options.completion_timeout
|
|
})
|
|
| if trim == "" then empty
|
|
else (., error("break"))
|
|
end
|
|
)
|
|
);
|
|
def _repl_loop:
|
|
try
|
|
_repl_eval(
|
|
_read_expr;
|
|
_repl_on_error;
|
|
_repl_on_compile_error
|
|
)
|
|
catch
|
|
if . == "interrupt" then empty
|
|
elif . == "eof" then error("break")
|
|
elif _eval_is_compile_error then _repl_on_error
|
|
else error
|
|
end;
|
|
if $opts | type != "object" then
|
|
error("options must be an object")
|
|
elif _is_completing | not then
|
|
( _options_stack(. + [$opts]) as $_
|
|
| _finally(
|
|
_repeat_break(_repl_loop);
|
|
_options_stack(.[:-1])
|
|
)
|
|
)
|
|
else empty
|
|
end;
|
|
|
|
def _repl_slurp_eval($query):
|
|
try
|
|
[ eval(
|
|
$query | _query_tostring;
|
|
{};
|
|
_repl_on_expr_error;
|
|
error
|
|
)
|
|
]
|
|
catch
|
|
error(.error);
|
|
|
|
def _repl_slurp($query):
|
|
if ($query.slurp_args | length) > 1 then
|
|
_eval_error("compile"; "repl requires none or one options argument. ex: ... | repl or ... | repl({compact: true})")
|
|
else
|
|
# only allow one output for args, multiple would be confusing i think (would start multiples repl:s)
|
|
( ( if ($query.slurp_args | length) > 0 then
|
|
first(_repl_slurp_eval($query.slurp_args[0])[])
|
|
else {}
|
|
end
|
|
) as $opts
|
|
| if $opts | type != "object" then
|
|
_eval_error("compile"; "options must be an object")
|
|
end
|
|
| _repl_slurp_eval($query.rewrite)
|
|
| _repl($opts)
|
|
)
|
|
end;
|
|
|
|
# just gives error, call appearing last will be renamed to _repl_slurp
|
|
def repl($_): error("repl must be last in pipeline. ex: ... | repl");
|
|
def repl: repl(null);
|
|
|
|
def _slurp($query):
|
|
if ($query.slurp_args | length != 1) then
|
|
_eval_error("compile"; "slurp requires one string argument. ex: ... | slurp(\"name\")")
|
|
else
|
|
# TODO: allow only one output?
|
|
( _repl_slurp_eval($query.slurp_args[0])[] as $name
|
|
| if ($name | _is_ident | not) then
|
|
_eval_error("compile"; "invalid slurp name \"\($name)\", must be a valid identifier. ex: ... | slurp(\"name\")")
|
|
else
|
|
( _repl_slurp_eval($query.rewrite) as $v
|
|
| _slurps(.[$name] |= $v)
|
|
| empty
|
|
)
|
|
end
|
|
)
|
|
end;
|
|
|
|
def slurp($_): error("slurp must be last in pipeline. ex: ... | slurp(\"name\")");
|
|
def slurp: slurp(null);
|
|
|
|
def spew($name):
|
|
( _slurps[$name]
|
|
| if . then .[]
|
|
else error("no such slurp: \($name)")
|
|
end
|
|
);
|
|
def spew:
|
|
_slurps;
|