mirror of
https://github.com/wader/fq.git
synced 2024-12-23 13:22:58 +03:00
cli: Add proper repl iterator support
This commit is contained in:
parent
161dcaffd4
commit
49f541c317
2
go.mod
2
go.mod
@ -8,7 +8,7 @@ require (
|
||||
github.com/pmezard/go-difflib v1.0.0
|
||||
|
||||
// fork of github.com/itchyny/gojq
|
||||
github.com/wader/gojq v0.12.1-0.20210901170335-cebead64fa4a
|
||||
github.com/wader/gojq v0.12.1-0.20210903162226-412e6dd62f26
|
||||
// fork of github.com/chzyer/readline
|
||||
github.com/wader/readline v0.0.0-20210817095433-c868eb04b8b2
|
||||
)
|
||||
|
4
go.sum
4
go.sum
@ -13,8 +13,8 @@ github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354/go.mod h1:KSVJ
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/testify v1.1.4/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/wader/gojq v0.12.1-0.20210901170335-cebead64fa4a h1:aUZqwKb9RmPVRg29LiHtKWN7uVd68VToj3qFlya07YU=
|
||||
github.com/wader/gojq v0.12.1-0.20210901170335-cebead64fa4a/go.mod h1:RYeLRsFp5ABZ5Wr7Tuerp01wOdzZr0kmpnCU/6uPyTw=
|
||||
github.com/wader/gojq v0.12.1-0.20210903162226-412e6dd62f26 h1:kRCT5/y+A38ZyZxJRraU9xndTAQvPIk7ojBs0MZcifc=
|
||||
github.com/wader/gojq v0.12.1-0.20210903162226-412e6dd62f26/go.mod h1:RYeLRsFp5ABZ5Wr7Tuerp01wOdzZr0kmpnCU/6uPyTw=
|
||||
github.com/wader/readline v0.0.0-20210817095433-c868eb04b8b2 h1:MGg7fsdEsoi7rattHGyU21wpOPeL3FonbUbJibpPBxc=
|
||||
github.com/wader/readline v0.0.0-20210817095433-c868eb04b8b2/go.mod h1:jYXyt9wQg3DifxQ8FM5M/ZoskO23GIwmo05QLHtO9CQ=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
|
@ -8,6 +8,7 @@ import (
|
||||
"crypto/md5"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"hash"
|
||||
@ -41,6 +42,9 @@ func (i *Interp) makeFunctions(registry *registry.Registry) []Function {
|
||||
{[]string{"stdout"}, 0, 0, nil, i.makeStdioFn(i.os.Stdout())},
|
||||
{[]string{"stderr"}, 0, 0, nil, i.makeStdioFn(i.os.Stderr())},
|
||||
|
||||
{[]string{"_query_fromstring"}, 0, 0, i.queryFromString, nil},
|
||||
{[]string{"_query_tostring"}, 0, 0, i.queryToString, nil},
|
||||
|
||||
{[]string{"_complete_query"}, 0, 0, i._completeQuery, nil},
|
||||
{[]string{"_display_name"}, 0, 0, i._displayName, nil},
|
||||
{[]string{"_extkeys"}, 0, 0, i._extKeys, nil},
|
||||
@ -280,6 +284,48 @@ func (i *Interp) makeStdioFn(t Terminal) func(c interface{}, a []interface{}) go
|
||||
}
|
||||
}
|
||||
|
||||
func (i *Interp) queryFromString(c interface{}, a []interface{}) interface{} {
|
||||
s, err := toString(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
q, err := gojq.Parse(s)
|
||||
if err != nil {
|
||||
p := queryErrorPosition(s, err)
|
||||
return compileError{
|
||||
err: err,
|
||||
what: "parse",
|
||||
pos: p,
|
||||
}
|
||||
}
|
||||
b, err := json.Marshal(q)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var v interface{}
|
||||
if err := json.Unmarshal(b, &v); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return v
|
||||
|
||||
}
|
||||
|
||||
func (i *Interp) queryToString(c interface{}, a []interface{}) interface{} {
|
||||
b, err := json.Marshal(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var q gojq.Query
|
||||
if err := json.Unmarshal(b, &q); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return q.String()
|
||||
}
|
||||
|
||||
func (i *Interp) _completeQuery(c interface{}, a []interface{}) interface{} {
|
||||
s, ok := c.(string)
|
||||
if !ok {
|
||||
|
@ -7,13 +7,17 @@ def debug:
|
||||
def debug(f): . as $c | f | debug | $c;
|
||||
|
||||
# eval f and finally eval fin even on empty or error
|
||||
def finally(f; fin):
|
||||
def _finally(f; fin):
|
||||
( try f // (fin | empty)
|
||||
catch (fin as $_ | error)
|
||||
| fin as $_
|
||||
| .
|
||||
);
|
||||
|
||||
def _repeat_break(f):
|
||||
try repeat(f)
|
||||
catch if . == "break" then empty else error end;
|
||||
|
||||
def _error_str: "error: \(.)";
|
||||
def _errorln: ., "\n" | stderr;
|
||||
|
||||
|
@ -34,6 +34,7 @@ import (
|
||||
//go:embed internal.jq
|
||||
//go:embed funcs.jq
|
||||
//go:embed args.jq
|
||||
//go:embed query.jq
|
||||
var builtinFS embed.FS
|
||||
|
||||
var initSource = `include "@builtin/interp";`
|
||||
|
@ -1,6 +1,7 @@
|
||||
include "internal";
|
||||
include "funcs";
|
||||
include "args";
|
||||
include "query";
|
||||
|
||||
# will include all per format specific function etc
|
||||
include "@format/all";
|
||||
@ -8,6 +9,7 @@ include "@format/all";
|
||||
# optional user init
|
||||
include "@config/init?";
|
||||
|
||||
|
||||
# def readline: #:: [a]| => string
|
||||
# Read a line.
|
||||
|
||||
@ -272,39 +274,59 @@ def _repl_on_compile_error: _repl_on_error;
|
||||
def _repl_eval($e): _eval($e; "repl"; _repl_display; _repl_on_error; _repl_on_compile_error);
|
||||
|
||||
# run read-eval-print-loop
|
||||
def repl($opts; iter): #:: a|(Opts) => @
|
||||
def _repl($opts): #:: a|(Opts) => @
|
||||
def _read_expr:
|
||||
# both _prompt and _complete want arrays
|
||||
( [iter]
|
||||
( . as $c
|
||||
| readline(_prompt; "_complete")
|
||||
| trim
|
||||
| if trim == "" then
|
||||
$c | _read_expr
|
||||
end
|
||||
);
|
||||
def _repl:
|
||||
|
||||
def _repl_loop:
|
||||
( . as $c
|
||||
| try
|
||||
( _read_expr as $e
|
||||
| if $e != "" then
|
||||
(iter | _repl_eval($e))
|
||||
( _read_expr as $expr
|
||||
# TODO: catch error here?
|
||||
| $expr
|
||||
| try _query_fromstring
|
||||
# TODO: nicer way to set filename
|
||||
catch (. | .filename = "repl")
|
||||
| if _query_pipe_last | _query_is_func("repl") then
|
||||
( _query_slurp_wrap(_query_func_rename("_repl_iter"))
|
||||
| _query_tostring as $wrap_expr
|
||||
| $c
|
||||
| _repl_eval($wrap_expr)
|
||||
)
|
||||
else
|
||||
empty
|
||||
( $c
|
||||
| .[]
|
||||
| _repl_eval($expr)
|
||||
)
|
||||
end
|
||||
, _repl
|
||||
)
|
||||
catch
|
||||
if . == "interrupt" then $c | _repl
|
||||
elif . == "eof" then empty
|
||||
if . == "interrupt" then empty
|
||||
elif . == "eof" then error("break")
|
||||
elif _eval_is_compile_error then _repl_on_error
|
||||
else error(.)
|
||||
end
|
||||
);
|
||||
( _options_stack(. + [$opts]) as $_
|
||||
| finally(
|
||||
_repl;
|
||||
| _finally(
|
||||
_repeat_break(_repl_loop);
|
||||
_options_stack(.[:-1])
|
||||
)
|
||||
);
|
||||
# same as repl({})
|
||||
def repl($opts): repl($opts; .);
|
||||
def repl: repl({}; .); #:: a| => @
|
||||
|
||||
def _repl_iter($opts): _repl($opts);
|
||||
def _repl_iter: _repl({});
|
||||
|
||||
# just gives error, call appearing last will be renamed to _repl_iter
|
||||
def repl($_): error("repl must be last");
|
||||
def repl: error("repl must be last");
|
||||
|
||||
|
||||
def _cli_expr_on_error:
|
||||
( . as $err
|
||||
@ -319,10 +341,6 @@ def _cli_expr_on_compile_error:
|
||||
def _cli_expr_eval($e; $filename; f): _eval($e; $filename; f; _cli_expr_on_error; _cli_expr_on_compile_error);
|
||||
def _cli_expr_eval($e; $filename): _eval($e; $filename; .; _cli_expr_on_error; _cli_expr_on_compile_error);
|
||||
|
||||
def _repeat_break(f):
|
||||
try repeat(f)
|
||||
catch if . == "break" then empty else error end;
|
||||
|
||||
# next valid input
|
||||
def input:
|
||||
def _input($opts; f):
|
||||
@ -644,8 +662,8 @@ def _main:
|
||||
, null | halt_error(_exit_code_args_error)
|
||||
)
|
||||
else
|
||||
# use finally as display etc prints and results in empty
|
||||
finally(
|
||||
# use _finally as display etc prints and results in empty
|
||||
_finally(
|
||||
( _include_paths([
|
||||
$opts.include_path // empty
|
||||
]) as $_
|
||||
@ -661,16 +679,14 @@ def _main:
|
||||
);
|
||||
if $opts.repl then
|
||||
( [_inputs]
|
||||
| ( [.[] | _cli_expr_eval($opts.expr; $opts.expr_eval_path)]
|
||||
| repl({}; .[])
|
||||
)
|
||||
| map(_cli_expr_eval($opts.expr; $opts.expr_eval_path))
|
||||
| _repl({})
|
||||
)
|
||||
else
|
||||
( _inputs
|
||||
# iterate all inputs
|
||||
| ( _cli_last_expr_error(null) as $_
|
||||
| _cli_expr_eval($opts.expr; $opts.expr_eval_path; _repl_display)
|
||||
)
|
||||
| _cli_last_expr_error(null) as $_
|
||||
| _cli_expr_eval($opts.expr; $opts.expr_eval_path; _repl_display)
|
||||
)
|
||||
end
|
||||
)
|
||||
|
90
pkg/interp/query.jq
Normal file
90
pkg/interp/query.jq
Normal file
@ -0,0 +1,90 @@
|
||||
# []
|
||||
def _query_array:
|
||||
{
|
||||
term: {
|
||||
type: "TermTypeArray",
|
||||
array: {
|
||||
query: .
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
# a() -> b()
|
||||
def _query_func_rename(name): .term.func.name = name;
|
||||
|
||||
# . | r
|
||||
def _query_pipe(r):
|
||||
{ op: "|",
|
||||
left: .,
|
||||
right: r
|
||||
};
|
||||
|
||||
def _query_ident: {term: {type: "TermTypeIdentity"}};
|
||||
|
||||
# .[]
|
||||
def _query_iter:
|
||||
{ "term": {
|
||||
"suffix_list": [{
|
||||
"iter": true
|
||||
}],
|
||||
"type": "TermTypeIdentity"
|
||||
}
|
||||
};
|
||||
|
||||
# last query in pipeline
|
||||
def _query_pipe_last:
|
||||
if .term then
|
||||
( . as $t
|
||||
| .term
|
||||
| if .suffix_list then
|
||||
( .suffix_list[-1]
|
||||
| if .bind.body then (.bind.body | _query_pipe_last)
|
||||
else .
|
||||
end
|
||||
)
|
||||
else $t
|
||||
end
|
||||
)
|
||||
elif .op == "|" then (.right | _query_pipe_last)
|
||||
else .
|
||||
end;
|
||||
|
||||
def _query_is_func(name): .term.func.name == name;
|
||||
|
||||
def _query_replace_last(f):
|
||||
# TODO: hack TCO bug
|
||||
def _f:
|
||||
if .term.suffix_list then
|
||||
.term.suffix_list[-1] |=
|
||||
if .bind.body then (.bind.body |= _f)
|
||||
else f
|
||||
end
|
||||
elif .term then f
|
||||
elif .op == "|" then (.right |= _f)
|
||||
else f
|
||||
end;
|
||||
_f;
|
||||
|
||||
def _query_find(f):
|
||||
( if f then . else empty end
|
||||
, if .op == "|" or .op == "," then
|
||||
( (.left | _query_find(f))
|
||||
, (.right | _query_find(f))
|
||||
)
|
||||
elif .term.suffix_list then
|
||||
( .term.suffix_list
|
||||
| map(.bind.body | _query_find(f))
|
||||
)
|
||||
else empty
|
||||
end
|
||||
);
|
||||
|
||||
# <filter...> | <slurp_func> -> [.[] | <filter...> | .] | (<slurp_func> | f)
|
||||
def _query_slurp_wrap(f):
|
||||
( _query_pipe_last as $lq
|
||||
| _query_replace_last(_query_ident) as $pipe
|
||||
| _query_iter
|
||||
| _query_pipe($pipe)
|
||||
| _query_array
|
||||
| _query_pipe($lq | f)
|
||||
);
|
27
pkg/interp/testdata/repl.fqtest
vendored
27
pkg/interp/testdata/repl.fqtest
vendored
@ -11,13 +11,17 @@ null> 1 | repl
|
||||
> number> .+1
|
||||
2
|
||||
> number> ^D
|
||||
null> [1,2,3] | repl({}; .[])
|
||||
null> 1 | 2 | repl
|
||||
> number> .+1
|
||||
3
|
||||
> number> ^D
|
||||
null> 1,2,3 | repl
|
||||
> number, [3]> .
|
||||
1
|
||||
2
|
||||
3
|
||||
> number, [3]> ^D
|
||||
null> [[1,2,3]] | repl({}; .[])
|
||||
null> [1,2,3] | repl
|
||||
> [number, ...][3]> .
|
||||
[
|
||||
1,
|
||||
@ -25,16 +29,27 @@ null> [[1,2,3]] | repl({}; .[])
|
||||
3
|
||||
]
|
||||
> [number, ...][3]> ^D
|
||||
null> [[1]] | repl({}; .[])
|
||||
null> [1] | repl
|
||||
> [number]> .
|
||||
[
|
||||
1
|
||||
]
|
||||
> [number]> ^D
|
||||
null> [] | repl({}; .[])
|
||||
> empty> 1
|
||||
> empty> ^D
|
||||
null> [] | repl
|
||||
> []> ^D
|
||||
null> ^D
|
||||
$ fq -i 'empty'
|
||||
empty> 1
|
||||
empty> ^D
|
||||
$ fq -i 1,2,3
|
||||
number, [3]> .*2
|
||||
2
|
||||
4
|
||||
6
|
||||
number, [3]> ^D
|
||||
$ fq -i '[1,2,3]'
|
||||
[number, ...][3]> repl({compact: true})
|
||||
> [number, ...][3]> tovalue
|
||||
[1,2,3]
|
||||
> [number, ...][3]> ^D
|
||||
[number, ...][3]> ^D
|
||||
|
Loading…
Reference in New Issue
Block a user