1
1
mirror of https://github.com/wader/fq.git synced 2024-11-23 00:57:15 +03:00

interp: Refactor and use mapstructure

This commit is contained in:
Mattias Wadman 2021-09-22 01:35:27 +02:00
parent 6ce4ba919b
commit 528e6b91ab
5 changed files with 72 additions and 182 deletions

3
go.mod
View File

@ -3,6 +3,9 @@ module github.com/wader/fq
go 1.17 go 1.17
require ( require (
// bump: mapstructure /github.com\/mitchellh\/mapstructure v(.*)/ git://github.com/mitchellh/mapstructure|^1
// bump: mapstructure command go get -d github.com/mitchellh/mapstructure@v$LATEST && go mod tidy
github.com/mitchellh/mapstructure v1.4.2
// bump: go-difflib /github.com\/pmezard\/go-difflib v(.*)/ git://github.com/pmezard/go-difflib|^1 // bump: go-difflib /github.com\/pmezard\/go-difflib v(.*)/ git://github.com/pmezard/go-difflib|^1
// bump: go-difflib command go get -d github.com/pmezard/go-difflib@v$LATEST && go mod tidy // bump: go-difflib command go get -d github.com/pmezard/go-difflib@v$LATEST && go mod tidy
github.com/pmezard/go-difflib v1.0.0 github.com/pmezard/go-difflib v1.0.0

2
go.sum
View File

@ -9,6 +9,8 @@ github.com/itchyny/timefmt-go v0.1.3 h1:7M3LGVDsqcd0VZH2U+x393obrzZisp7C0uEe921i
github.com/itchyny/timefmt-go v0.1.3/go.mod h1:0osSSCQSASBJMsIZnhAaF1C2fCBTJZXrnj37mG8/c+A= github.com/itchyny/timefmt-go v0.1.3/go.mod h1:0osSSCQSASBJMsIZnhAaF1C2fCBTJZXrnj37mG8/c+A=
github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mitchellh/mapstructure v1.4.2 h1:6h7AQ0yhTcIsmFmnAwQls75jp2Gzs4iB8W7pjMO+rqo=
github.com/mitchellh/mapstructure v1.4.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354/go.mod h1:KSVJerMDfblTH7p5MZaTt+8zaT2iEk3AkVb9PQdZuE8= github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354/go.mod h1:KSVJerMDfblTH7p5MZaTt+8zaT2iEk3AkVb9PQdZuE8=
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=

View File

@ -20,6 +20,7 @@ import (
"net/url" "net/url"
"time" "time"
"github.com/mitchellh/mapstructure"
"github.com/wader/fq/internal/aheadreadseeker" "github.com/wader/fq/internal/aheadreadseeker"
"github.com/wader/fq/internal/ctxreadseeker" "github.com/wader/fq/internal/ctxreadseeker"
"github.com/wader/fq/internal/gojqextra" "github.com/wader/fq/internal/gojqextra"
@ -196,8 +197,12 @@ func makeHashFn(fn func() (hash.Hash, error)) func(c interface{}, a []interface{
} }
func (i *Interp) readline(c interface{}, a []interface{}) interface{} { func (i *Interp) readline(c interface{}, a []interface{}) interface{} {
var opts struct {
Complete string `mapstructure:"complete"`
Timeout float64 `mapstructure:"timeout"`
}
var err error var err error
completeFn := ""
prompt := "" prompt := ""
if len(a) > 0 { if len(a) > 0 {
@ -207,21 +212,22 @@ func (i *Interp) readline(c interface{}, a []interface{}) interface{} {
} }
} }
if len(a) > 1 { if len(a) > 1 {
completeFn, err = toString(a[1]) _ = mapstructure.Decode(a[1], &opts)
if err != nil {
return fmt.Errorf("complete function: %w", err)
}
} }
src, err := i.os.Readline( src, err := i.os.Readline(
prompt, prompt,
func(line string, pos int) (newLine []string, shared int) { func(line string, pos int) (newLine []string, shared int) {
completeCtx, completeCtxCancelFn := context.WithTimeout(i.evalContext.ctx, 1*time.Second) completeCtx := i.evalContext.ctx
defer completeCtxCancelFn() if opts.Timeout > 0 {
var completeCtxCancelFn context.CancelFunc
completeCtx, completeCtxCancelFn = context.WithTimeout(i.evalContext.ctx, time.Duration(opts.Timeout*float64(time.Second)))
defer completeCtxCancelFn()
}
names, shared, err := func() (newLine []string, shared int, err error) { names, shared, err := func() (newLine []string, shared int, err error) {
vs, err := i.EvalFuncValues( vs, err := i.EvalFuncValues(
completeCtx, CompletionMode, c, completeFn, []interface{}{line, pos}, DiscardCtxWriter{Ctx: completeCtx}, completeCtx, c, opts.Complete, []interface{}{line, pos}, DiscardCtxWriter{Ctx: completeCtx},
) )
if err != nil { if err != nil {
return nil, pos, err return nil, pos, err
@ -236,32 +242,19 @@ func (i *Interp) readline(c interface{}, a []interface{}) interface{} {
// {abc: 123, abd: 123} | complete(".ab"; 3) will return {prefix: "ab", names: ["abc", "abd"]} // {abc: 123, abd: 123} | complete(".ab"; 3) will return {prefix: "ab", names: ["abc", "abd"]}
var names []string var result struct {
var prefix string Names []string `mapstructure:"names"`
cm, ok := v.(map[string]interface{}) Prefix string `mapstructure:"prefix"`
if !ok {
return nil, pos, fmt.Errorf("%v: complete function return value not an object", cm)
}
if namesV, ok := cm["names"].([]interface{}); ok {
for _, name := range namesV {
names = append(names, name.(string))
}
} else {
return nil, pos, fmt.Errorf("%v: names missing in complete return object", cm)
}
if prefixV, ok := cm["prefix"]; ok {
prefix, _ = prefixV.(string)
} else {
return nil, pos, fmt.Errorf("%v: prefix missing in complete return object", cm)
} }
if len(names) == 0 { _ = mapstructure.Decode(v, &result)
if len(result.Names) == 0 {
return nil, pos, nil return nil, pos, nil
} }
sharedLen := len(prefix) sharedLen := len(result.Prefix)
return names, sharedLen, nil return result.Names, sharedLen, nil
}() }()
// TODO: how to report err? // TODO: how to report err?
@ -296,7 +289,7 @@ func (i *Interp) eval(c interface{}, a []interface{}) gojq.Iter {
} }
} }
iter, err := i.Eval(i.evalContext.ctx, ScriptMode, c, src, filenameHint, i.evalContext.output) iter, err := i.Eval(i.evalContext.ctx, c, src, filenameHint, i.evalContext.output)
if err != nil { if err != nil {
return gojq.NewIter(err) return gojq.NewIter(err)
} }
@ -585,33 +578,26 @@ func (i *Interp) _open(c interface{}, a []interface{}) interface{} {
} }
func (i *Interp) _decode(c interface{}, a []interface{}) interface{} { func (i *Interp) _decode(c interface{}, a []interface{}) interface{} {
filename := "" var opts struct {
Filename string `mapstructure:"filename"`
opts := map[string]interface{}{} Progress string `mapstructure:"_progress"`
if m, ok := a[1].(map[string]interface{}); ok { Remain map[string]interface{} `mapstructure:",remain"`
opts = m
}
// TODO: structmap
var progress string
if progressV, ok := opts["_progress"]; ok {
progress, _ = progressV.(string)
} }
_ = mapstructure.Decode(a[1], &opts)
// TODO: progress hack // TODO: progress hack
// would be nice to move all progress code into decode but it might be // would be nice to move all progress code into decode but it might be
// tricky to keep track of absolute positions in the underlaying readers // tricky to keep track of absolute positions in the underlaying readers
// when it uses BitBuf slices, maybe only in Pos()? // when it uses BitBuf slices, maybe only in Pos()?
if bbf, ok := c.(*bitBufFile); ok { if bbf, ok := c.(*bitBufFile); ok {
filename = bbf.filename opts.Filename = bbf.filename
if progress != "" { if opts.Progress != "" {
evalProgress := func(c interface{}) { evalProgress := func(c interface{}) {
_, _ = i.EvalFuncValues( _, _ = i.EvalFuncValues(
i.evalContext.ctx, i.evalContext.ctx,
CompletionMode,
c, c,
progress, opts.Progress,
nil, nil,
DiscardCtxWriter{Ctx: i.evalContext.ctx}, DiscardCtxWriter{Ctx: i.evalContext.ctx},
) )
@ -644,8 +630,8 @@ func (i *Interp) _decode(c interface{}, a []interface{}) interface{} {
dv, _, err := decode.Decode(i.evalContext.ctx, bb, decodeFormats, dv, _, err := decode.Decode(i.evalContext.ctx, bb, decodeFormats,
decode.DecodeOptions{ decode.DecodeOptions{
Description: filename, Description: opts.Filename,
FormatOptions: opts, FormatOptions: opts.Remain,
}, },
) )
if dv == nil { if dv == nil {

View File

@ -17,6 +17,7 @@ import (
"strings" "strings"
"time" "time"
"github.com/mitchellh/mapstructure"
"github.com/wader/fq/format/registry" "github.com/wader/fq/format/registry"
"github.com/wader/fq/internal/ansi" "github.com/wader/fq/internal/ansi"
"github.com/wader/fq/internal/colorjson" "github.com/wader/fq/internal/colorjson"
@ -241,44 +242,6 @@ type loadModule struct {
func (l loadModule) LoadInitModules() ([]*gojq.Query, error) { return l.init() } func (l loadModule) LoadInitModules() ([]*gojq.Query, error) { return l.init() }
func (l loadModule) LoadModule(name string) (*gojq.Query, error) { return l.load(name) } func (l loadModule) LoadModule(name string) (*gojq.Query, error) { return l.load(name) }
func toBool(v interface{}) (bool, error) {
switch v := v.(type) {
case bool:
return v, nil
case *big.Int:
return v.Int64() != 0, nil
case int:
return v != 0, nil
case float64:
return v != 0, nil
default:
return false, fmt.Errorf("value is not a number")
}
}
func toBoolZ(v interface{}) bool {
b, _ := toBool(v)
return b
}
func toInt(v interface{}) (int, error) {
switch v := v.(type) {
case *big.Int:
return int(v.Int64()), nil
case int:
return v, nil
case float64:
return int(v), nil
default:
return 0, fmt.Errorf("value is not a number")
}
}
func toIntZ(v interface{}) int {
n, _ := toInt(v)
return n
}
func toString(v interface{}) (string, error) { func toString(v interface{}) (string, error) {
switch v := v.(type) { switch v := v.(type) {
case string: case string:
@ -295,11 +258,6 @@ func toString(v interface{}) (string, error) {
} }
} }
func toStringZ(v interface{}) string {
s, _ := toString(v)
return s
}
func toBigInt(v interface{}) (*big.Int, error) { func toBigInt(v interface{}) (*big.Int, error) {
switch v := v.(type) { switch v := v.(type) {
case int: case int:
@ -451,7 +409,6 @@ type evalContext struct {
// structcheck has problems with embedding https://gitlab.com/opennota/check#known-limitations // structcheck has problems with embedding https://gitlab.com/opennota/check#known-limitations
ctx context.Context ctx context.Context
output io.Writer output io.Writer
mode RunMode
} }
type Interp struct { type Interp struct {
@ -500,8 +457,6 @@ func (i *Interp) Stop() {
} }
func (i *Interp) Main(ctx context.Context, output Output, version string) error { func (i *Interp) Main(ctx context.Context, output Output, version string) error {
runMode := ScriptMode
var args []interface{} var args []interface{}
for _, a := range i.os.Args() { for _, a := range i.os.Args() {
args = append(args, a) args = append(args, a)
@ -512,7 +467,7 @@ func (i *Interp) Main(ctx context.Context, output Output, version string) error
"version": version, "version": version,
} }
iter, err := i.EvalFunc(ctx, runMode, input, "_main", nil, output) iter, err := i.EvalFunc(ctx, input, "_main", nil, output)
if err != nil { if err != nil {
fmt.Fprintln(i.os.Stderr(), err) fmt.Fprintln(i.os.Stderr(), err)
return err return err
@ -542,7 +497,7 @@ func (i *Interp) Main(ctx context.Context, output Output, version string) error
return nil return nil
} }
func (i *Interp) Eval(ctx context.Context, mode RunMode, c interface{}, src string, srcFilename string, output io.Writer) (gojq.Iter, error) { func (i *Interp) Eval(ctx context.Context, c interface{}, src string, srcFilename string, output io.Writer) (gojq.Iter, error) {
gq, err := gojq.Parse(src) gq, err := gojq.Parse(src)
if err != nil { if err != nil {
p := queryErrorPosition(src, err) p := queryErrorPosition(src, err)
@ -557,10 +512,7 @@ func (i *Interp) Eval(ctx context.Context, mode RunMode, c interface{}, src stri
// make copy of interp // make copy of interp
ci := *i ci := *i
ni := &ci ni := &ci
ni.evalContext = evalContext{}
ni.evalContext = evalContext{
mode: mode,
}
var variableNames []string var variableNames []string
var variableValues []interface{} var variableValues []interface{}
@ -767,7 +719,7 @@ func (i *Interp) Eval(ctx context.Context, mode RunMode, c interface{}, src stri
return iterWrapper, nil return iterWrapper, nil
} }
func (i *Interp) EvalFunc(ctx context.Context, mode RunMode, c interface{}, name string, args []interface{}, output io.Writer) (gojq.Iter, error) { func (i *Interp) EvalFunc(ctx context.Context, c interface{}, name string, args []interface{}, output io.Writer) (gojq.Iter, error) {
var argsExpr []string var argsExpr []string
for i := range args { for i := range args {
argsExpr = append(argsExpr, fmt.Sprintf("$_args[%d]", i)) argsExpr = append(argsExpr, fmt.Sprintf("$_args[%d]", i))
@ -784,15 +736,15 @@ func (i *Interp) EvalFunc(ctx context.Context, mode RunMode, c interface{}, name
// _args to mark variable as internal and hide it from completion // _args to mark variable as internal and hide it from completion
// {input: ..., args: [...]} | .args as {args: $_args} | .input | name[($_args[0]; ...)] // {input: ..., args: [...]} | .args as {args: $_args} | .input | name[($_args[0]; ...)]
trampolineExpr := fmt.Sprintf(". as {args: $_args} | .input | %s%s", name, argExpr) trampolineExpr := fmt.Sprintf(". as {args: $_args} | .input | %s%s", name, argExpr)
iter, err := i.Eval(ctx, mode, trampolineInput, trampolineExpr, "", output) iter, err := i.Eval(ctx, trampolineInput, trampolineExpr, "", output)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return iter, nil return iter, nil
} }
func (i *Interp) EvalFuncValues(ctx context.Context, mode RunMode, c interface{}, name string, args []interface{}, output io.Writer) ([]interface{}, error) { func (i *Interp) EvalFuncValues(ctx context.Context, c interface{}, name string, args []interface{}, output io.Writer) ([]interface{}, error) {
iter, err := i.EvalFunc(ctx, mode, c, name, args, output) iter, err := i.EvalFunc(ctx, c, name, args, output)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -810,88 +762,29 @@ func (i *Interp) EvalFuncValues(ctx context.Context, mode RunMode, c interface{}
} }
type Options struct { type Options struct {
Depth int Depth int `mapstructure:"depth"`
ArrayTruncate int ArrayTruncate int `mapstructure:"array_truncate"`
Verbose bool Verbose bool `mapstructure:"verbose"`
DecodeProgress bool DecodeProgress bool `mapstructure:"decode_progress"`
Color bool Color bool `mapstructure:"color"`
Colors string Colors string `mapstructure:"colors"`
ByteColors string ByteColors string `mapstructure:"byte_colors"`
Unicode bool Unicode bool `mapstructure:"unicode"`
RawOutput bool RawOutput bool `mapstructure:"raw_output"`
REPL bool REPL bool `mapstructure:"repl"`
RawString bool RawString bool `mapstructure:"raw_string"`
JoinString string JoinString string `mapstructure:"join_string"`
Compact bool Compact bool `mapstructure:"compact"`
BitsFormat string BitsFormat string `mapstructure:"bits_format"`
LineBytes int `mapstructure:"line_bytes"`
LineBytes int DisplayBytes int `mapstructure:"display_bytes"`
DisplayBytes int AddrBase int `mapstructure:"addrbase"`
AddrBase int SizeBase int `mapstructure:"sizebase"`
SizeBase int
Decorator Decorator Decorator Decorator
BitsFormatFn func(bb *bitio.Buffer) (interface{}, error) BitsFormatFn func(bb *bitio.Buffer) (interface{}, error)
} }
func mapSetOptions(d *Options, m map[string]interface{}) {
if v, ok := m["depth"]; ok {
d.Depth = num.MaxInt(0, toIntZ(v))
}
if v, ok := m["array_truncate"]; ok {
d.ArrayTruncate = num.MaxInt(0, toIntZ(v))
}
if v, ok := m["verbose"]; ok {
d.Verbose = toBoolZ(v)
}
if v, ok := m["decode_progress"]; ok {
d.DecodeProgress = toBoolZ(v)
}
if v, ok := m["color"]; ok {
d.Color = toBoolZ(v)
}
if v, ok := m["colors"]; ok {
d.Colors = toStringZ(v)
}
if v, ok := m["byte_colors"]; ok {
d.ByteColors = toStringZ(v)
}
if v, ok := m["unicode"]; ok {
d.Unicode = toBoolZ(v)
}
if v, ok := m["raw_output"]; ok {
d.RawOutput = toBoolZ(v)
}
if v, ok := m["repl"]; ok {
d.REPL = toBoolZ(v)
}
if v, ok := m["raw_string"]; ok {
d.RawString = toBoolZ(v)
}
if v, ok := m["join_string"]; ok {
d.JoinString = toStringZ(v)
}
if v, ok := m["compact"]; ok {
d.Compact = toBoolZ(v)
}
if v, ok := m["bits_format"]; ok {
d.BitsFormat = toStringZ(v)
}
if v, ok := m["line_bytes"]; ok {
d.LineBytes = num.MaxInt(0, toIntZ(v))
}
if v, ok := m["display_bytes"]; ok {
d.DisplayBytes = num.MaxInt(0, toIntZ(v))
}
if v, ok := m["addrbase"]; ok {
d.AddrBase = num.ClampInt(2, 36, toIntZ(v))
}
if v, ok := m["sizebase"]; ok {
d.SizeBase = num.ClampInt(2, 36, toIntZ(v))
}
}
func bitsFormatFnFromOptions(opts Options) func(bb *bitio.Buffer) (interface{}, error) { func bitsFormatFnFromOptions(opts Options) func(bb *bitio.Buffer) (interface{}, error) {
switch opts.BitsFormat { switch opts.BitsFormat {
case "md5": case "md5":
@ -970,7 +863,7 @@ func (i *Interp) variables() map[string]interface{} {
} }
func (i *Interp) Options(fnOptsV ...interface{}) (Options, error) { func (i *Interp) Options(fnOptsV ...interface{}) (Options, error) {
vs, err := i.EvalFuncValues(i.evalContext.ctx, ScriptMode, nil, "options", []interface{}{fnOptsV}, DiscardCtxWriter{Ctx: i.evalContext.ctx}) vs, err := i.EvalFuncValues(i.evalContext.ctx, nil, "options", []interface{}{fnOptsV}, DiscardCtxWriter{Ctx: i.evalContext.ctx})
if err != nil { if err != nil {
return Options{}, err return Options{}, err
} }
@ -987,7 +880,13 @@ func (i *Interp) Options(fnOptsV ...interface{}) (Options, error) {
} }
var opts Options var opts Options
mapSetOptions(&opts, m) _ = mapstructure.Decode(m, &opts)
opts.Depth = num.MaxInt(0, opts.Depth)
opts.ArrayTruncate = num.MaxInt(0, opts.ArrayTruncate)
opts.AddrBase = num.ClampInt(2, 36, opts.AddrBase)
opts.SizeBase = num.ClampInt(2, 36, opts.SizeBase)
opts.LineBytes = num.MaxInt(0, opts.LineBytes)
opts.DisplayBytes = num.MaxInt(0, opts.DisplayBytes)
opts.Decorator = decoratorFromOptions(opts) opts.Decorator = decoratorFromOptions(opts)
opts.BitsFormatFn = bitsFormatFnFromOptions(opts) opts.BitsFormatFn = bitsFormatFnFromOptions(opts)

View File

@ -327,7 +327,7 @@ def _repl($opts): #:: a|(Opts) => @
def _read_expr: def _read_expr:
# both _prompt and _complete want arrays # both _prompt and _complete want arrays
( . as $c ( . as $c
| _readline(_prompt; "_complete") | _readline(_prompt; {complete: "_complete", timeout: 0.5})
| if trim == "" then | if trim == "" then
$c | _read_expr $c | _read_expr
end end