Skip to content

Commit

Permalink
cli: Add proper repl iterator support
Browse files Browse the repository at this point in the history
  • Loading branch information
wader committed Sep 12, 2021
1 parent 161dcaf commit 49f541c
Show file tree
Hide file tree
Showing 8 changed files with 210 additions and 38 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down
46 changes: 46 additions & 0 deletions pkg/interp/funcs.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"crypto/md5"
"encoding/base64"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"hash"
Expand Down Expand Up @@ -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},
Expand Down Expand Up @@ -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 {
Expand Down
6 changes: 5 additions & 1 deletion pkg/interp/internal.jq
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
1 change: 1 addition & 0 deletions pkg/interp/interp.go
Original file line number Diff line number Diff line change
Expand Up @@ -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";`
Expand Down
72 changes: 44 additions & 28 deletions pkg/interp/interp.jq
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
include "internal";
include "funcs";
include "args";
include "query";

# will include all per format specific function etc
include "@format/all";

# optional user init
include "@config/init?";


# def readline: #:: [a]| => string
# Read a line.

Expand Down Expand Up @@ -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
Expand All @@ -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):
Expand Down Expand Up @@ -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 $_
Expand All @@ -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
)
Expand Down
90 changes: 90 additions & 0 deletions pkg/interp/query.jq
Original file line number Diff line number Diff line change
@@ -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 changes: 21 additions & 6 deletions pkg/interp/testdata/repl.fqtest
Original file line number Diff line number Diff line change
Expand Up @@ -11,30 +11,45 @@ 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,
2,
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

0 comments on commit 49f541c

Please sign in to comment.