diff --git a/README.md b/README.md index 05a9fcfa..6b22d79b 100644 --- a/README.md +++ b/README.md @@ -102,7 +102,7 @@ func main() { [`gojq.Compile`](https://pkg.go.dev/github.com/itchyny/gojq?tab=doc#Compile) allows to configure the following compiler options. -- [`gojq.WithModuleLoader`](https://pkg.go.dev/github.com/itchyny/gojq?tab=doc#WithModuleLoader) allows to load modules. By default, the module feature is disabled. +- [`gojq.WithModuleLoader`](https://pkg.go.dev/github.com/itchyny/gojq?tab=doc#WithModuleLoader) allows to load modules. By default, the module feature is disabled. If you want to load modules from the filesystem, use [`gojq.NewModuleLoader`](https://pkg.go.dev/github.com/itchyny/gojq?tab=doc#NewModuleLoader). - [`gojq.WithEnvironLoader`](https://pkg.go.dev/github.com/itchyny/gojq?tab=doc#WithEnvironLoader) allows to configure the environment variables referenced by `env` and `$ENV`. By default, OS environment variables are not accessible due to security reason. You can use `gojq.WithEnvironLoader(os.Environ)` if you want. - [`gojq.WithVariables`](https://pkg.go.dev/github.com/itchyny/gojq?tab=doc#WithVariables) allows to configure the variables which can be used in the query. Pass the values of the variables to [`code.Run`](https://pkg.go.dev/github.com/itchyny/gojq?tab=doc#Code.Run) in the same order. - [`gojq.WithInputIter`](https://pkg.go.dev/github.com/itchyny/gojq?tab=doc#WithInputIter) allows to use `input` and `inputs` functions. By default, these functions are disabled. diff --git a/cli/cli.go b/cli/cli.go index 4d2b6926..753bd46e 100644 --- a/cli/cli.go +++ b/cli/cli.go @@ -199,7 +199,7 @@ Synopsis: iter := cli.createInputIter(args) defer iter.Close() code, err := gojq.Compile(query, - gojq.WithModuleLoader(&moduleLoader{modulePaths}), + gojq.WithModuleLoader(gojq.NewModuleLoader(modulePaths)), gojq.WithEnvironLoader(os.Environ), gojq.WithVariables(cli.argnames), gojq.WithInputIter(iter), @@ -209,7 +209,16 @@ Synopsis: QueryParseError() (string, string, string, error) }); ok { typ, name, query, err := err.QueryParseError() - return &queryParseError{typ, fname + ":" + name, query, err} + if _, err := os.Stat(name); os.IsNotExist(err) { + name = fname + ":" + name + } + return &queryParseError{typ, name, query, err} + } + if err, ok := err.(interface { + JSONParseError() (string, string, error) + }); ok { + fname, query, err := err.JSONParseError() + return &compileError{&jsonParseError{fname, query, err}} } return &compileError{err} } diff --git a/cli/test.yaml b/cli/test.yaml index 66c6b2d2..edb96bad 100644 --- a/cli/test.yaml +++ b/cli/test.yaml @@ -5119,7 +5119,7 @@ - 'import "4" as $x; $x' input: 0 error: | - invalid json: testdata/4.json + compile error: invalid json: testdata/4.json bar ^ invalid character 'b' looking for beginning of value exit_code: 3 diff --git a/error.go b/error.go index 970bd322..fb72bd9c 100644 --- a/error.go +++ b/error.go @@ -264,6 +264,32 @@ func (err *getpathError) Error() string { return fmt.Sprintf("cannot getpath with %s against: %s", previewValue(err.path), typeErrorPreview(err.v)) } +type queryParseError struct { + typ, fname, contents string + err error +} + +func (err *queryParseError) QueryParseError() (string, string, string, error) { + return err.typ, err.fname, err.contents, err.err +} + +func (err *queryParseError) Error() string { + return fmt.Sprintf("invalid %s: %s: %s", err.typ, err.fname, err.err) +} + +type jsonParseError struct { + fname, contents string + err error +} + +func (err *jsonParseError) JSONParseError() (string, string, error) { + return err.fname, err.contents, err.err +} + +func (err *jsonParseError) Error() string { + return fmt.Sprintf("invalid json: %s: %s", err.fname, err.err) +} + func typeErrorPreview(v interface{}) string { p := preview(v) if p != "" { diff --git a/module_loader.go b/module_loader.go index 492dc402..2b589fb0 100644 --- a/module_loader.go +++ b/module_loader.go @@ -1,21 +1,27 @@ -package cli +package gojq import ( + "bytes" + "encoding/json" "fmt" + "io" "io/ioutil" "os" "path/filepath" "strconv" - - "github.com/itchyny/gojq" ) type moduleLoader struct { paths []string } -func (l *moduleLoader) LoadInitModules() ([]*gojq.Module, error) { - var ms []*gojq.Module +// NewModuleLoader creates a new ModuleLoader reading local modules in the paths. +func NewModuleLoader(paths []string) ModuleLoader { + return &moduleLoader{paths} +} + +func (l *moduleLoader) LoadInitModules() ([]*Module, error) { + var ms []*Module for _, path := range l.paths { if filepath.Base(path) != ".jq" { continue @@ -43,11 +49,11 @@ func (l *moduleLoader) LoadInitModules() ([]*gojq.Module, error) { return ms, nil } -func (l *moduleLoader) LoadModule(string) (*gojq.Module, error) { - panic("moduleLoader#LoadModule: unreachable") +func (l *moduleLoader) LoadModule(string) (*Module, error) { + panic("LocalModuleLoader#LoadModule: unreachable") } -func (l *moduleLoader) LoadModuleWithMeta(name string, meta map[string]interface{}) (*gojq.Module, error) { +func (l *moduleLoader) LoadModuleWithMeta(name string, meta map[string]interface{}) (*Module, error) { path, err := l.lookupModule(name, ".jq", meta) if err != nil { return nil, err @@ -68,7 +74,25 @@ func (l *moduleLoader) LoadJSONWithMeta(name string, meta map[string]interface{} if err != nil { return nil, err } - return slurpFile(path) + f, err := os.Open(path) + if err != nil { + return nil, err + } + defer f.Close() + var vals []interface{} + var buf bytes.Buffer + dec := json.NewDecoder(io.TeeReader(f, &buf)) + for { + var val interface{} + if err := dec.Decode(&val); err != nil { + if err == io.EOF { + break + } + return nil, &jsonParseError{path, buf.String(), err} + } + vals = append(vals, val) + } + return vals, nil } func (l *moduleLoader) lookupModule(name, extension string, meta map[string]interface{}) (string, error) { @@ -90,9 +114,8 @@ func (l *moduleLoader) lookupModule(name, extension string, meta map[string]inte } // This is a dirty hack to implement the "search" field. -// Note that gojq package should not depend on the filesystem. -func parseModule(path, cnt string) (*gojq.Module, error) { - m, err := gojq.ParseModule(cnt) +func parseModule(path, cnt string) (*Module, error) { + m, err := ParseModule(cnt) if err != nil { return nil, err } @@ -102,9 +125,9 @@ func parseModule(path, cnt string) (*gojq.Module, error) { } i.Meta.KeyVals = append( i.Meta.KeyVals, - gojq.ConstObjectKeyVal{ + ConstObjectKeyVal{ Key: "$$path", - Val: &gojq.ConstTerm{Str: strconv.Quote(path)}, + Val: &ConstTerm{Str: strconv.Quote(path)}, }, ) } diff --git a/option.go b/option.go index 205aca57..d4ac541b 100644 --- a/option.go +++ b/option.go @@ -4,6 +4,7 @@ package gojq type CompilerOption func(*compiler) // WithModuleLoader is a compiler option for module loader. +// If you want to load modules from the filesystem, use NewModuleLoader. func WithModuleLoader(moduleLoader ModuleLoader) CompilerOption { return func(c *compiler) { c.moduleLoader = moduleLoader