mirror of
https://github.com/wader/fq.git
synced 2024-12-28 08:02:28 +03:00
c4219d69db
Rewrite cli query to use display as output query, same as for repl. Without this "tovalue" values that don't print in the current eval will end up in the paren eval and be written using an output without a cancel context.
342 lines
9.2 KiB
Plaintext
342 lines
9.2 KiB
Plaintext
include "internal";
|
|
include "options";
|
|
include "eval";
|
|
include "query";
|
|
include "decode";
|
|
include "interp";
|
|
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 | map(split("/")[0]) | unique)
|
|
, _complete_keywords
|
|
] | add;
|
|
def _complete_keys:
|
|
# uses try as []? will not catch errors
|
|
[try keys[] catch empty, try _extkeys[] catch empty];
|
|
|
|
# TODO: "def zzz: null; abc" scope won't see zzz after completion rewrite as def will be inside map(def zzz: null; abc | ...)
|
|
# 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 _is_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 separately like cli file args, 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;
|