mirror of
https://github.com/wader/fq.git
synced 2024-12-23 21:31:33 +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
|
github.com/pmezard/go-difflib v1.0.0
|
||||||
|
|
||||||
// fork of github.com/itchyny/gojq
|
// 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
|
// fork of github.com/chzyer/readline
|
||||||
github.com/wader/readline v0.0.0-20210817095433-c868eb04b8b2
|
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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
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/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.20210903162226-412e6dd62f26 h1:kRCT5/y+A38ZyZxJRraU9xndTAQvPIk7ojBs0MZcifc=
|
||||||
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/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 h1:MGg7fsdEsoi7rattHGyU21wpOPeL3FonbUbJibpPBxc=
|
||||||
github.com/wader/readline v0.0.0-20210817095433-c868eb04b8b2/go.mod h1:jYXyt9wQg3DifxQ8FM5M/ZoskO23GIwmo05QLHtO9CQ=
|
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=
|
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
@ -8,6 +8,7 @@ import (
|
|||||||
"crypto/md5"
|
"crypto/md5"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"hash"
|
"hash"
|
||||||
@ -41,6 +42,9 @@ func (i *Interp) makeFunctions(registry *registry.Registry) []Function {
|
|||||||
{[]string{"stdout"}, 0, 0, nil, i.makeStdioFn(i.os.Stdout())},
|
{[]string{"stdout"}, 0, 0, nil, i.makeStdioFn(i.os.Stdout())},
|
||||||
{[]string{"stderr"}, 0, 0, nil, i.makeStdioFn(i.os.Stderr())},
|
{[]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{"_complete_query"}, 0, 0, i._completeQuery, nil},
|
||||||
{[]string{"_display_name"}, 0, 0, i._displayName, nil},
|
{[]string{"_display_name"}, 0, 0, i._displayName, nil},
|
||||||
{[]string{"_extkeys"}, 0, 0, i._extKeys, 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{} {
|
func (i *Interp) _completeQuery(c interface{}, a []interface{}) interface{} {
|
||||||
s, ok := c.(string)
|
s, ok := c.(string)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
@ -7,13 +7,17 @@ def debug:
|
|||||||
def debug(f): . as $c | f | debug | $c;
|
def debug(f): . as $c | f | debug | $c;
|
||||||
|
|
||||||
# eval f and finally eval fin even on empty or error
|
# eval f and finally eval fin even on empty or error
|
||||||
def finally(f; fin):
|
def _finally(f; fin):
|
||||||
( try f // (fin | empty)
|
( try f // (fin | empty)
|
||||||
catch (fin as $_ | error)
|
catch (fin as $_ | error)
|
||||||
| fin as $_
|
| fin as $_
|
||||||
| .
|
| .
|
||||||
);
|
);
|
||||||
|
|
||||||
|
def _repeat_break(f):
|
||||||
|
try repeat(f)
|
||||||
|
catch if . == "break" then empty else error end;
|
||||||
|
|
||||||
def _error_str: "error: \(.)";
|
def _error_str: "error: \(.)";
|
||||||
def _errorln: ., "\n" | stderr;
|
def _errorln: ., "\n" | stderr;
|
||||||
|
|
||||||
|
@ -34,6 +34,7 @@ import (
|
|||||||
//go:embed internal.jq
|
//go:embed internal.jq
|
||||||
//go:embed funcs.jq
|
//go:embed funcs.jq
|
||||||
//go:embed args.jq
|
//go:embed args.jq
|
||||||
|
//go:embed query.jq
|
||||||
var builtinFS embed.FS
|
var builtinFS embed.FS
|
||||||
|
|
||||||
var initSource = `include "@builtin/interp";`
|
var initSource = `include "@builtin/interp";`
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
include "internal";
|
include "internal";
|
||||||
include "funcs";
|
include "funcs";
|
||||||
include "args";
|
include "args";
|
||||||
|
include "query";
|
||||||
|
|
||||||
# will include all per format specific function etc
|
# will include all per format specific function etc
|
||||||
include "@format/all";
|
include "@format/all";
|
||||||
@ -8,6 +9,7 @@ include "@format/all";
|
|||||||
# optional user init
|
# optional user init
|
||||||
include "@config/init?";
|
include "@config/init?";
|
||||||
|
|
||||||
|
|
||||||
# def readline: #:: [a]| => string
|
# def readline: #:: [a]| => string
|
||||||
# Read a line.
|
# 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);
|
def _repl_eval($e): _eval($e; "repl"; _repl_display; _repl_on_error; _repl_on_compile_error);
|
||||||
|
|
||||||
# run read-eval-print-loop
|
# run read-eval-print-loop
|
||||||
def repl($opts; iter): #:: a|(Opts) => @
|
def _repl($opts): #:: a|(Opts) => @
|
||||||
def _read_expr:
|
def _read_expr:
|
||||||
# both _prompt and _complete want arrays
|
# both _prompt and _complete want arrays
|
||||||
( [iter]
|
( . as $c
|
||||||
| readline(_prompt; "_complete")
|
| readline(_prompt; "_complete")
|
||||||
| trim
|
| if trim == "" then
|
||||||
|
$c | _read_expr
|
||||||
|
end
|
||||||
);
|
);
|
||||||
def _repl:
|
|
||||||
|
def _repl_loop:
|
||||||
( . as $c
|
( . as $c
|
||||||
| try
|
| try
|
||||||
( _read_expr as $e
|
( _read_expr as $expr
|
||||||
| if $e != "" then
|
# TODO: catch error here?
|
||||||
(iter | _repl_eval($e))
|
| $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
|
else
|
||||||
empty
|
( $c
|
||||||
|
| .[]
|
||||||
|
| _repl_eval($expr)
|
||||||
|
)
|
||||||
end
|
end
|
||||||
, _repl
|
|
||||||
)
|
)
|
||||||
catch
|
catch
|
||||||
if . == "interrupt" then $c | _repl
|
if . == "interrupt" then empty
|
||||||
elif . == "eof" then empty
|
elif . == "eof" then error("break")
|
||||||
|
elif _eval_is_compile_error then _repl_on_error
|
||||||
else error(.)
|
else error(.)
|
||||||
end
|
end
|
||||||
);
|
);
|
||||||
( _options_stack(. + [$opts]) as $_
|
( _options_stack(. + [$opts]) as $_
|
||||||
| finally(
|
| _finally(
|
||||||
_repl;
|
_repeat_break(_repl_loop);
|
||||||
_options_stack(.[:-1])
|
_options_stack(.[:-1])
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
# same as repl({})
|
|
||||||
def repl($opts): repl($opts; .);
|
def _repl_iter($opts): _repl($opts);
|
||||||
def repl: repl({}; .); #:: a| => @
|
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:
|
def _cli_expr_on_error:
|
||||||
( . as $err
|
( . 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; 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 _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
|
# next valid input
|
||||||
def input:
|
def input:
|
||||||
def _input($opts; f):
|
def _input($opts; f):
|
||||||
@ -644,8 +662,8 @@ def _main:
|
|||||||
, null | halt_error(_exit_code_args_error)
|
, null | halt_error(_exit_code_args_error)
|
||||||
)
|
)
|
||||||
else
|
else
|
||||||
# use finally as display etc prints and results in empty
|
# use _finally as display etc prints and results in empty
|
||||||
finally(
|
_finally(
|
||||||
( _include_paths([
|
( _include_paths([
|
||||||
$opts.include_path // empty
|
$opts.include_path // empty
|
||||||
]) as $_
|
]) as $_
|
||||||
@ -661,16 +679,14 @@ def _main:
|
|||||||
);
|
);
|
||||||
if $opts.repl then
|
if $opts.repl then
|
||||||
( [_inputs]
|
( [_inputs]
|
||||||
| ( [.[] | _cli_expr_eval($opts.expr; $opts.expr_eval_path)]
|
| map(_cli_expr_eval($opts.expr; $opts.expr_eval_path))
|
||||||
| repl({}; .[])
|
| _repl({})
|
||||||
)
|
|
||||||
)
|
)
|
||||||
else
|
else
|
||||||
( _inputs
|
( _inputs
|
||||||
# iterate all inputs
|
# iterate all inputs
|
||||||
| ( _cli_last_expr_error(null) as $_
|
| _cli_last_expr_error(null) as $_
|
||||||
| _cli_expr_eval($opts.expr; $opts.expr_eval_path; _repl_display)
|
| _cli_expr_eval($opts.expr; $opts.expr_eval_path; _repl_display)
|
||||||
)
|
|
||||||
)
|
)
|
||||||
end
|
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
|
> number> .+1
|
||||||
2
|
2
|
||||||
> number> ^D
|
> number> ^D
|
||||||
null> [1,2,3] | repl({}; .[])
|
null> 1 | 2 | repl
|
||||||
|
> number> .+1
|
||||||
|
3
|
||||||
|
> number> ^D
|
||||||
|
null> 1,2,3 | repl
|
||||||
> number, [3]> .
|
> number, [3]> .
|
||||||
1
|
1
|
||||||
2
|
2
|
||||||
3
|
3
|
||||||
> number, [3]> ^D
|
> number, [3]> ^D
|
||||||
null> [[1,2,3]] | repl({}; .[])
|
null> [1,2,3] | repl
|
||||||
> [number, ...][3]> .
|
> [number, ...][3]> .
|
||||||
[
|
[
|
||||||
1,
|
1,
|
||||||
@ -25,16 +29,27 @@ null> [[1,2,3]] | repl({}; .[])
|
|||||||
3
|
3
|
||||||
]
|
]
|
||||||
> [number, ...][3]> ^D
|
> [number, ...][3]> ^D
|
||||||
null> [[1]] | repl({}; .[])
|
null> [1] | repl
|
||||||
> [number]> .
|
> [number]> .
|
||||||
[
|
[
|
||||||
1
|
1
|
||||||
]
|
]
|
||||||
> [number]> ^D
|
> [number]> ^D
|
||||||
null> [] | repl({}; .[])
|
null> [] | repl
|
||||||
> empty> 1
|
> []> ^D
|
||||||
> empty> ^D
|
|
||||||
null> ^D
|
null> ^D
|
||||||
$ fq -i 'empty'
|
$ fq -i 'empty'
|
||||||
empty> 1
|
empty> 1
|
||||||
empty> ^D
|
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