1
1
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:
Mattias Wadman 2021-08-19 18:11:37 +02:00
parent 161dcaffd4
commit 49f541c317
8 changed files with 210 additions and 38 deletions

2
go.mod
View File

@ -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
View File

@ -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=

View File

@ -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 {

View File

@ -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;

View File

@ -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";`

View File

@ -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
View 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)
);

View File

@ -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