diff --git a/Gopkg.lock b/Gopkg.lock new file mode 100644 index 0000000..7c345e7 --- /dev/null +++ b/Gopkg.lock @@ -0,0 +1,165 @@ +# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. + + +[[projects]] + branch = "master" + name = "github.com/alecthomas/template" + packages = [".","parse"] + revision = "a0175ee3bccc567396460bf5acd36800cb10c49c" + +[[projects]] + branch = "master" + name = "github.com/alecthomas/units" + packages = ["."] + revision = "2efee857e7cfd4f3d0138cc3cbb1b4966962b93a" + +[[projects]] + branch = "master" + name = "github.com/cheekybits/genny" + packages = ["generic"] + revision = "9127e812e1e9e501ce899a18121d316ecb52e4ba" + +[[projects]] + branch = "master" + name = "github.com/coryb/figtree" + packages = ["."] + revision = "86e7c859d0326621c45ba7be2c32e3b3ae203213" + +[[projects]] + branch = "master" + name = "github.com/coryb/kingpeon" + packages = ["."] + revision = "64b561ae2d0f895b94719c486bed798f4236a4b3" + +[[projects]] + branch = "master" + name = "github.com/coryb/oreo" + packages = ["."] + revision = "95687d61c95ee1522c1140e2af59b0c1846abfc1" + +[[projects]] + branch = "master" + name = "github.com/fatih/camelcase" + packages = ["."] + revision = "f6a740d52f961c60348ebb109adde9f4635d7540" + +[[projects]] + branch = "master" + name = "github.com/guelfey/go.dbus" + packages = ["."] + revision = "f6a3a2366cc39b8479cadc499d3c735fb10fbdda" + +[[projects]] + branch = "master" + name = "github.com/jinzhu/copier" + packages = ["."] + revision = "32e0d0db1dcd4373fb9eb0f9d727b1fe1a723e54" + +[[projects]] + branch = "master" + name = "github.com/kballard/go-shellquote" + packages = ["."] + revision = "cd60e84ee657ff3dc51de0b4f55dd299a3e136f2" + +[[projects]] + branch = "master" + name = "github.com/mattn/go-colorable" + packages = ["."] + revision = "ad5389df28cdac544c99bd7b9161a0b5b6ca9d1b" + +[[projects]] + name = "github.com/mattn/go-isatty" + packages = ["."] + revision = "fc9e8d8ef48496124e79ae0df75490096eccf6fe" + version = "v0.0.2" + +[[projects]] + branch = "master" + name = "github.com/mgutz/ansi" + packages = ["."] + revision = "9520e82c474b0a04dd04f8a40959027271bab992" + +[[projects]] + branch = "master" + name = "github.com/pkg/browser" + packages = ["."] + revision = "c90ca0c84f15f81c982e32665bffd8d7aac8f097" + +[[projects]] + name = "github.com/pkg/errors" + packages = ["."] + revision = "645ef00459ed84a119197bfb8d8205042c6df63d" + version = "v0.8.0" + +[[projects]] + branch = "master" + name = "github.com/sethgrid/pester" + packages = ["."] + revision = "a86a2d88f4dc3c7dbf3a6a6bbbfb095690b834b6" + +[[projects]] + branch = "master" + name = "github.com/theckman/go-flock" + packages = ["."] + revision = "6de226b0d5f040ed85b88c82c381709b98277f3d" + +[[projects]] + branch = "master" + name = "github.com/tidwall/gjson" + packages = ["."] + revision = "be96719f990978a867f52c48f29d43f6b591da28" + +[[projects]] + branch = "master" + name = "github.com/tidwall/match" + packages = ["."] + revision = "173748da739a410c5b0b813b956f89ff94730b4c" + +[[projects]] + branch = "master" + name = "github.com/tmc/keyring" + packages = ["."] + revision = "06e6283d50adc5f8fcdb3cdf33ee1244d4400ae1" + +[[projects]] + branch = "master" + name = "golang.org/x/crypto" + packages = ["ssh/terminal"] + revision = "9ba3862cf6a5452ae579de98f9364dd2e544844c" + +[[projects]] + branch = "master" + name = "golang.org/x/sys" + packages = ["unix","windows"] + revision = "a5054c7c1385fd50d9394475365355a87a7873ec" + +[[projects]] + name = "gopkg.in/AlecAivazis/survey.v1" + packages = [".","core","terminal"] + revision = "9d910423e24aa6d7c7950160658c295e0734c7e0" + version = "1.3.1" + +[[projects]] + name = "gopkg.in/alecthomas/kingpin.v2" + packages = ["."] + revision = "1087e65c9441605df944fb12c33f0fe7072d18ca" + version = "v2.2.5" + +[[projects]] + branch = "v2" + name = "gopkg.in/coryb/yaml.v2" + packages = ["."] + revision = "fb7cb9628c6e3bdd76c29fb91798d51a09832470" + +[[projects]] + name = "gopkg.in/op/go-logging.v1" + packages = ["."] + revision = "b2cb9fa56473e98db8caba80237377e83fe44db5" + version = "v1" + +[solve-meta] + analyzer-name = "dep" + analyzer-version = 1 + inputs-digest = "1f4b97fcf898a5ef03af5e222686a09328b393e71797d21bf0c37b74d1e74a8e" + solver-name = "gps-cdcl" + solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml new file mode 100644 index 0000000..c910b31 --- /dev/null +++ b/Gopkg.toml @@ -0,0 +1,75 @@ + +# Gopkg.toml example +# +# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md +# for detailed Gopkg.toml documentation. +# +# required = ["github.com/user/thing/cmd/thing"] +# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] +# +# [[constraint]] +# name = "github.com/user/project" +# version = "1.0.0" +# +# [[constraint]] +# name = "github.com/user/project2" +# branch = "dev" +# source = "github.com/myfork/project2" +# +# [[override]] +# name = "github.com/x/y" +# version = "2.4.0" + + +[[constraint]] + name = "github.com/coryb/figtree" + +[[constraint]] + name = "github.com/coryb/kingpeon" + +[[constraint]] + name = "github.com/coryb/oreo" + +[[constraint]] + name = "github.com/jinzhu/copier" + +[[constraint]] + name = "github.com/kballard/go-shellquote" + +[[constraint]] + name = "github.com/mgutz/ansi" + +[[constraint]] + name = "github.com/pkg/browser" + +[[constraint]] + name = "github.com/pkg/errors" + version = "0.8.0" + +[[constraint]] + name = "github.com/savaki/jq" + +[[constraint]] + name = "github.com/tmc/keyring" + +[[constraint]] + name = "golang.org/x/crypto" + +[[constraint]] + name = "gopkg.in/AlecAivazis/survey.v1" + version = "1.3.1" + +[[constraint]] + name = "gopkg.in/alecthomas/kingpin.v2" + version = "2.2.5" + +[[constraint]] + name = "gopkg.in/coryb/yaml.v2" + +[[constraint]] + name = "gopkg.in/op/go-logging.v1" + version = "1.0.0" + +[[constraint]] + branch = "master" + name = "github.com/tidwall/gjson" diff --git a/glide.lock b/glide.lock deleted file mode 100644 index 4e84598..0000000 --- a/glide.lock +++ /dev/null @@ -1,66 +0,0 @@ -hash: b59fc2a2254d65db0a41057ab55bb571318587e7e4692309a41c912b1526880e -updated: 2017-09-09T18:33:57.161504692-07:00 -imports: -- name: github.com/alecthomas/template - version: a0175ee3bccc567396460bf5acd36800cb10c49c - subpackages: - - parse -- name: github.com/alecthomas/units - version: 2efee857e7cfd4f3d0138cc3cbb1b4966962b93a -- name: github.com/cheekybits/genny - version: 9127e812e1e9e501ce899a18121d316ecb52e4ba - subpackages: - - generic -- name: github.com/coryb/figtree - version: 86e7c859d0326621c45ba7be2c32e3b3ae203213 -- name: github.com/coryb/kingpeon - version: 64b561ae2d0f895b94719c486bed798f4236a4b3 -- name: github.com/coryb/oreo - version: 95687d61c95ee1522c1140e2af59b0c1846abfc1 -- name: github.com/fatih/camelcase - version: f6a740d52f961c60348ebb109adde9f4635d7540 -- name: github.com/guelfey/go.dbus - version: f6a3a2366cc39b8479cadc499d3c735fb10fbdda -- name: github.com/jinzhu/copier - version: 32e0d0db1dcd4373fb9eb0f9d727b1fe1a723e54 -- name: github.com/kballard/go-shellquote - version: cd60e84ee657ff3dc51de0b4f55dd299a3e136f2 -- name: github.com/mattn/go-colorable - version: ad5389df28cdac544c99bd7b9161a0b5b6ca9d1b -- name: github.com/mattn/go-isatty - version: fc9e8d8ef48496124e79ae0df75490096eccf6fe -- name: github.com/mgutz/ansi - version: 9520e82c474b0a04dd04f8a40959027271bab992 -- name: github.com/pkg/browser - version: c90ca0c84f15f81c982e32665bffd8d7aac8f097 -- name: github.com/pkg/errors - version: 645ef00459ed84a119197bfb8d8205042c6df63d -- name: github.com/savaki/jq - version: 0e6baecebbf8a24a6590d3fc8232165008d6e5d8 -- name: github.com/sethgrid/pester - version: a86a2d88f4dc3c7dbf3a6a6bbbfb095690b834b6 -- name: github.com/theckman/go-flock - version: 6de226b0d5f040ed85b88c82c381709b98277f3d -- name: github.com/tmc/keyring - version: 06e6283d50adc5f8fcdb3cdf33ee1244d4400ae1 -- name: golang.org/x/crypto - version: 9ba3862cf6a5452ae579de98f9364dd2e544844c - subpackages: - - ssh/terminal -- name: golang.org/x/sys - version: a5054c7c1385fd50d9394475365355a87a7873ec - subpackages: - - unix - - windows -- name: gopkg.in/AlecAivazis/survey.v1 - version: 9d910423e24aa6d7c7950160658c295e0734c7e0 - subpackages: - - core - - terminal -- name: gopkg.in/alecthomas/kingpin.v2 - version: 1087e65c9441605df944fb12c33f0fe7072d18ca -- name: gopkg.in/coryb/yaml.v2 - version: fb7cb9628c6e3bdd76c29fb91798d51a09832470 -- name: gopkg.in/op/go-logging.v1 - version: b2cb9fa56473e98db8caba80237377e83fe44db5 -testImports: [] diff --git a/glide.yaml b/glide.yaml deleted file mode 100644 index 46ce6ee..0000000 --- a/glide.yaml +++ /dev/null @@ -1,23 +0,0 @@ -package: gopkg.in/Netflix-Skunkworks/go-jira.v1 -import: -- package: github.com/coryb/figtree -- package: github.com/coryb/kingpeon -- package: github.com/coryb/oreo -- package: github.com/jinzhu/copier -- package: github.com/kballard/go-shellquote -- package: github.com/mgutz/ansi -- package: github.com/pkg/browser -- package: github.com/pkg/errors - version: ^0.8.0 -- package: github.com/tmc/keyring -- package: golang.org/x/crypto - subpackages: - - ssh/terminal -- package: gopkg.in/AlecAivazis/survey.v1 - version: ^1.3.1 -- package: gopkg.in/alecthomas/kingpin.v2 - version: ^2.2.5 -- package: gopkg.in/coryb/yaml.v2 -- package: gopkg.in/op/go-logging.v1 - version: ^1.0.0 -- package: github.com/savaki/jq diff --git a/jiracli/cli.go b/jiracli/cli.go index 032638f..3ec03f7 100644 --- a/jiracli/cli.go +++ b/jiracli/cli.go @@ -16,7 +16,7 @@ import ( "github.com/coryb/oreo" "github.com/jinzhu/copier" shellquote "github.com/kballard/go-shellquote" - "github.com/savaki/jq" + "github.com/tidwall/gjson" "gopkg.in/AlecAivazis/survey.v1" kingpin "gopkg.in/alecthomas/kingpin.v2" yaml "gopkg.in/coryb/yaml.v2" @@ -39,12 +39,11 @@ type GlobalOptions struct { } type CommonOptions struct { - Browse figtree.BoolOption `yaml:"browse,omitempty" json:"browse,omitempty"` - Editor figtree.StringOption `yaml:"editor,omitempty" json:"editor,omitempty"` - JsonQuery figtree.StringOption `yaml:"jq,omitempty" json:"jq,omitempty"` - JsonQueryRaw figtree.BoolOption `yaml:"jq-raw,omitempty" json:"jq-raw,omitempty"` - SkipEditing figtree.BoolOption `yaml:"noedit,omitempty" json:"noedit,omitempty"` - Template figtree.StringOption `yaml:"template,omitempty" json:"template,omitempty"` + Browse figtree.BoolOption `yaml:"browse,omitempty" json:"browse,omitempty"` + Editor figtree.StringOption `yaml:"editor,omitempty" json:"editor,omitempty"` + GJsonQuery figtree.StringOption `yaml:"gjq,omitempty" json:"gjq,omitempty"` + SkipEditing figtree.BoolOption `yaml:"noedit,omitempty" json:"noedit,omitempty"` + Template figtree.StringOption `yaml:"template,omitempty" json:"template,omitempty"` } type CommandRegistryEntry struct { @@ -172,27 +171,16 @@ func TemplateUsage(cmd *kingpin.CmdClause, opts *CommonOptions) { cmd.Flag("template", "Template to use for output").Short('t').SetValue(&opts.Template) } -func JsonQueryUsage(cmd *kingpin.CmdClause, opts *CommonOptions) { - cmd.Flag("jq", "JSON Query to filter output").SetValue(&opts.JsonQuery) - cmd.Flag("raw", "Return unquoted raw data from JSON Query").Hidden().SetValue(&opts.JsonQueryRaw) +func GJsonQueryUsage(cmd *kingpin.CmdClause, opts *CommonOptions) { + cmd.Flag("gjq", "GJSON Query to filter output, see https://goo.gl/iaYwJ5").SetValue(&opts.GJsonQuery) } func (o *CommonOptions) PrintTemplate(data interface{}) error { - if o.JsonQuery.Value != "" { + if o.GJsonQuery.Value != "" { buf := bytes.NewBufferString("") RunTemplate("json", data, buf) - op, err := jq.Parse(o.JsonQuery.Value) - if err != nil { - return err - } - value, err := op.Apply(buf.Bytes()) - if err != nil { - return err - } - if o.JsonQueryRaw.Value { - value = []byte(strings.TrimPrefix(strings.TrimSuffix(string(value), "\""), "\"")) - } - _, err = os.Stdout.Write(value) + results := gjson.GetBytes(buf.Bytes(), o.GJsonQuery.Value) + _, err := os.Stdout.Write([]byte(results.String())) os.Stdout.Write([]byte{'\n'}) return err } diff --git a/jiracmd/components.go b/jiracmd/components.go index 5cd647b..c4076db 100644 --- a/jiracmd/components.go +++ b/jiracmd/components.go @@ -37,7 +37,7 @@ func CmdComponentsRegistry() *jiracli.CommandRegistryEntry { func CmdComponentsUsage(cmd *kingpin.CmdClause, opts *ComponentsOptions) error { jiracli.TemplateUsage(cmd, &opts.CommonOptions) - jiracli.JsonQueryUsage(cmd, &opts.CommonOptions) + jiracli.GJsonQueryUsage(cmd, &opts.CommonOptions) cmd.Flag("project", "project to list components").Short('p').StringVar(&opts.Project) return nil diff --git a/jiracmd/createmeta.go b/jiracmd/createmeta.go index f08ed2b..33080aa 100644 --- a/jiracmd/createmeta.go +++ b/jiracmd/createmeta.go @@ -35,7 +35,7 @@ func CmdCreateMetaRegistry() *jiracli.CommandRegistryEntry { func CmdCreateMetaUsage(cmd *kingpin.CmdClause, opts *CreateMetaOptions) error { jiracli.TemplateUsage(cmd, &opts.CommonOptions) - jiracli.JsonQueryUsage(cmd, &opts.CommonOptions) + jiracli.GJsonQueryUsage(cmd, &opts.CommonOptions) cmd.Flag("project", "project to fetch create metadata").Short('p').StringVar(&opts.Project) cmd.Flag("issuetype", "issuetype in project to fetch create metadata").Short('i').StringVar(&opts.IssueType) return nil diff --git a/jiracmd/editmeta.go b/jiracmd/editmeta.go index 68eec3a..a4f07db 100644 --- a/jiracmd/editmeta.go +++ b/jiracmd/editmeta.go @@ -36,7 +36,7 @@ func CmdEditMetaRegistry() *jiracli.CommandRegistryEntry { func CmdEditMetaUsage(cmd *kingpin.CmdClause, opts *EditMetaOptions) error { jiracli.BrowseUsage(cmd, &opts.CommonOptions) jiracli.TemplateUsage(cmd, &opts.CommonOptions) - jiracli.JsonQueryUsage(cmd, &opts.CommonOptions) + jiracli.GJsonQueryUsage(cmd, &opts.CommonOptions) cmd.Arg("ISSUE", "edit metadata for issue id").Required().StringVar(&opts.Issue) return nil } diff --git a/jiracmd/fields.go b/jiracmd/fields.go index 872a6d0..75a8186 100644 --- a/jiracmd/fields.go +++ b/jiracmd/fields.go @@ -17,7 +17,7 @@ func CmdFieldsRegistry() *jiracli.CommandRegistryEntry { func(fig *figtree.FigTree, cmd *kingpin.CmdClause) error { jiracli.LoadConfigs(cmd, fig, &opts) jiracli.TemplateUsage(cmd, &opts) - jiracli.JsonQueryUsage(cmd, &opts) + jiracli.GJsonQueryUsage(cmd, &opts) return nil }, func(o *oreo.Client, globals *jiracli.GlobalOptions) error { diff --git a/jiracmd/issuelinktypes.go b/jiracmd/issuelinktypes.go index 52c1b4d..29fdc07 100644 --- a/jiracmd/issuelinktypes.go +++ b/jiracmd/issuelinktypes.go @@ -27,7 +27,7 @@ func CmdIssueLinkTypesRegistry() *jiracli.CommandRegistryEntry { func CmdIssueLinkTypesUsage(cmd *kingpin.CmdClause, opts *jiracli.CommonOptions) error { jiracli.TemplateUsage(cmd, opts) - jiracli.JsonQueryUsage(cmd, opts) + jiracli.GJsonQueryUsage(cmd, opts) return nil } diff --git a/jiracmd/issuetypes.go b/jiracmd/issuetypes.go index 800fd46..e6c005b 100644 --- a/jiracmd/issuetypes.go +++ b/jiracmd/issuetypes.go @@ -37,7 +37,7 @@ func CmdIssueTypesRegistry() *jiracli.CommandRegistryEntry { func CmdIssueTypesUsage(cmd *kingpin.CmdClause, opts *IssueTypesOptions) error { jiracli.TemplateUsage(cmd, &opts.CommonOptions) - jiracli.JsonQueryUsage(cmd, &opts.CommonOptions) + jiracli.GJsonQueryUsage(cmd, &opts.CommonOptions) cmd.Flag("project", "project to list issueTypes").Short('p').StringVar(&opts.Project) return nil diff --git a/jiracmd/list.go b/jiracmd/list.go index adf8a11..5d5ccad 100644 --- a/jiracmd/list.go +++ b/jiracmd/list.go @@ -43,7 +43,7 @@ func CmdListRegistry() *jiracli.CommandRegistryEntry { func CmdListUsage(cmd *kingpin.CmdClause, opts *ListOptions) error { jiracli.TemplateUsage(cmd, &opts.CommonOptions) - jiracli.JsonQueryUsage(cmd, &opts.CommonOptions) + jiracli.GJsonQueryUsage(cmd, &opts.CommonOptions) cmd.Flag("assignee", "User assigned the issue").Short('a').StringVar(&opts.Assignee) cmd.Flag("component", "Component to search for").Short('c').StringVar(&opts.Component) cmd.Flag("issuetype", "Issue type to search for").Short('i').StringVar(&opts.IssueType) diff --git a/jiracmd/request.go b/jiracmd/request.go index 166e89b..57ffe50 100644 --- a/jiracmd/request.go +++ b/jiracmd/request.go @@ -36,7 +36,7 @@ func CmdRequestRegistry() *jiracli.CommandRegistryEntry { opts.Method = "GET" } jiracli.TemplateUsage(cmd, &opts.CommonOptions) - jiracli.JsonQueryUsage(cmd, &opts.CommonOptions) + jiracli.GJsonQueryUsage(cmd, &opts.CommonOptions) return CmdRequestUsage(cmd, &opts) }, func(o *oreo.Client, globals *jiracli.GlobalOptions) error { diff --git a/jiracmd/transitions.go b/jiracmd/transitions.go index 0a9b30f..90a3de8 100644 --- a/jiracmd/transitions.go +++ b/jiracmd/transitions.go @@ -35,7 +35,7 @@ func CmdTransitionsRegistry(defaultTemplate string) *jiracli.CommandRegistryEntr func CmdTransitionsUsage(cmd *kingpin.CmdClause, opts *TransitionsOptions) error { jiracli.BrowseUsage(cmd, &opts.CommonOptions) jiracli.TemplateUsage(cmd, &opts.CommonOptions) - jiracli.JsonQueryUsage(cmd, &opts.CommonOptions) + jiracli.GJsonQueryUsage(cmd, &opts.CommonOptions) cmd.Arg("ISSUE", "issue to list valid transitions").Required().StringVar(&opts.Issue) return nil } diff --git a/jiracmd/view.go b/jiracmd/view.go index 4d576b5..034f10f 100644 --- a/jiracmd/view.go +++ b/jiracmd/view.go @@ -36,7 +36,7 @@ func CmdViewRegistry() *jiracli.CommandRegistryEntry { func CmdViewUsage(cmd *kingpin.CmdClause, opts *ViewOptions) error { jiracli.BrowseUsage(cmd, &opts.CommonOptions) jiracli.TemplateUsage(cmd, &opts.CommonOptions) - jiracli.JsonQueryUsage(cmd, &opts.CommonOptions) + jiracli.GJsonQueryUsage(cmd, &opts.CommonOptions) cmd.Flag("expand", "field to expand for the issue").StringsVar(&opts.Expand) cmd.Flag("field", "field to return for the issue").StringsVar(&opts.Fields) cmd.Flag("property", "property to return for issue").StringsVar(&opts.Properties) diff --git a/jiracmd/worklogList.go b/jiracmd/worklogList.go index 58a0e03..8ef49b6 100644 --- a/jiracmd/worklogList.go +++ b/jiracmd/worklogList.go @@ -35,7 +35,7 @@ func CmdWorklogListRegistry() *jiracli.CommandRegistryEntry { func CmdWorklogListUsage(cmd *kingpin.CmdClause, opts *WorklogListOptions) error { jiracli.BrowseUsage(cmd, &opts.CommonOptions) jiracli.TemplateUsage(cmd, &opts.CommonOptions) - jiracli.JsonQueryUsage(cmd, &opts.CommonOptions) + jiracli.GJsonQueryUsage(cmd, &opts.CommonOptions) cmd.Arg("ISSUE", "issue id to fetch worklogs").Required().StringVar(&opts.Issue) return nil } diff --git a/vendor/github.com/savaki/jq/.gitignore b/vendor/github.com/savaki/jq/.gitignore deleted file mode 100644 index 434825c..0000000 --- a/vendor/github.com/savaki/jq/.gitignore +++ /dev/null @@ -1,33 +0,0 @@ -# Compiled Object files, Static and Dynamic libs (Shared Objects) -*.o -*.a -*.so - -# Folders -_obj -_test - -# Architecture specific extensions/prefixes -*.[568vq] -[568vq].out - -*.cgo1.go -*.cgo2.c -_cgo_defun.c -_cgo_gotypes.go -_cgo_export.* - -_testmain.go - -*.exe -*.test -*.prof - -# ignore intellij files -.idea -*.iml -*.ipr -*.iws - -# ignore scratch files -sample.json diff --git a/vendor/github.com/savaki/jq/LICENSE b/vendor/github.com/savaki/jq/LICENSE deleted file mode 100644 index 8dada3e..0000000 --- a/vendor/github.com/savaki/jq/LICENSE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "{}" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright {yyyy} {name of copyright owner} - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/vendor/github.com/savaki/jq/README.md b/vendor/github.com/savaki/jq/README.md deleted file mode 100644 index 4f74983..0000000 --- a/vendor/github.com/savaki/jq/README.md +++ /dev/null @@ -1,95 +0,0 @@ -# jq - -[![GoDoc](https://godoc.org/github.com/savaki/jq?status.svg)](https://godoc.org/github.com/savaki/jq) -[![Build Status](https://snap-ci.com/savaki/jq/branch/master/build_image)](https://snap-ci.com/savaki/jq/branch/master) - -A high performance Golang implementation of the incredibly useful jq command line tool. - -Rather than marshalling json elements into go instances, jq opts to manipulate the json elements as raw []byte. This -is especially useful for apps that need to handle dynamic json data. - -Using jq consists of creation an ```Op``` and then calling ```Apply``` on the ```Op``` to transform one []byte into the -desired []byte. Ops may be chained together to form a transformation chain similar to how the command line jq works. - -## Installation - -``` -go get github.com/savaki/jq -``` - -## Example - -```go -package main - -import ( - "fmt" - - "github.com/savaki/jq" -) - -func main() { - op, _ := jq.Parse(".hello") // create an Op - data := []byte(`{"hello":"world"}`) // sample input - value, _ := op.Apply(data) // value == '"world"' - fmt.Println(string(value)) -} -``` - -## Syntax - -The initial goal is to support all the selectors the original jq command line supports. - -| syntax | meaning| -| :--- | :--- | -| . | unchanged input | -| .foo | value at key | -| .foo.bar | value at nested key | -| .[0] | value at specified element of array | -| .[0:1] | array of specified elements of array, inclusive | -| .foo.[0] | nested value | - -## Examples - -### Data -```json -{ - "string": "a", - "number": 1.23, - "simple": ["a", "b", "c"], - "mixed": [ - "a", - 1, - {"hello":"world"} - ], - "object": { - "first": "joe", - "array": [1,2,3] - } -} -``` - -| syntax | value | -| :--- | :--- | -| .string | "a" | -| .number| 1.23 | -| .simple | ["a", "b", "c"] | -| .simple.[0] | "a" | -| .simple.[0:1] | ["a","b"] | -| .mixed.[1] | 1 -| .object.first | "joe" | -| .object.array.[2] | 3 | - -## Performance - -``` -BenchmarkAny-8 20000000 80.8 ns/op 0 B/op 0 allocs/op -BenchmarkArray-8 20000000 108 ns/op 0 B/op 0 allocs/op -BenchmarkFindIndex-8 10000000 125 ns/op 0 B/op 0 allocs/op -BenchmarkFindKey-8 10000000 125 ns/op 0 B/op 0 allocs/op -BenchmarkFindRange-8 10000000 186 ns/op 16 B/op 1 allocs/op -BenchmarkNumber-8 50000000 28.9 ns/op 0 B/op 0 allocs/op -BenchmarkObject-8 20000000 98.5 ns/op 0 B/op 0 allocs/op -BenchmarkString-8 30000000 40.4 ns/op 0 B/op 0 allocs/op -``` - diff --git a/vendor/github.com/savaki/jq/doc.go b/vendor/github.com/savaki/jq/doc.go deleted file mode 100644 index 9f40d27..0000000 --- a/vendor/github.com/savaki/jq/doc.go +++ /dev/null @@ -1,18 +0,0 @@ -// Package jp offers a highly performant json selector in the style of the jq command line -// -// Usage of this package involves the concept of an Op. An Op is a transformation that converts a []byte into a []byte. -// To get started, use the Parse function to obtain an Op. -// -// op, err := jq.Parse(".key") -// -// This will create an Op that will accept a JSON object in []byte format and return the value associated with "key." -// For example: -// -// in := []byte(`{"key":"value"}`) -// data, _ := op.Apply(in)) -// fmt.Println(string(data)) -// -// Will print the string "value". The goal is to support all the select operations supported by jq's command line -// namesake. -// -package jq diff --git a/vendor/github.com/savaki/jq/op.go b/vendor/github.com/savaki/jq/op.go deleted file mode 100644 index 7d92316..0000000 --- a/vendor/github.com/savaki/jq/op.go +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright (c) 2016 Matt Ho -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package jq - -import ( - "strings" - - "github.com/savaki/jq/scanner" -) - -// Op defines a single transformation to be applied to a []byte -type Op interface { - Apply([]byte) ([]byte, error) -} - -// OpFunc provides a convenient func type wrapper on Op -type OpFunc func([]byte) ([]byte, error) - -// Apply executes the transformation defined by OpFunc -func (fn OpFunc) Apply(in []byte) ([]byte, error) { - return fn(in) -} - -// Dot extract the specific key from the map provided; to extract a nested value, use the Dot Op in conjunction with the -// Chain Op -func Dot(key string) OpFunc { - key = strings.TrimSpace(key) - if key == "" { - return func(in []byte) ([]byte, error) { return in, nil } - } - - k := []byte(key) - - return func(in []byte) ([]byte, error) { - return scanner.FindKey(in, 0, k) - } -} - -// Chain executes a series of operations in the order provided -func Chain(filters ...Op) OpFunc { - return func(in []byte) ([]byte, error) { - if filters == nil { - return in, nil - } - - var err error - data := in - for _, filter := range filters { - data, err = filter.Apply(data) - if err != nil { - return nil, err - } - } - - return data, nil - } -} - -// Index extracts a specific element from the array provided -func Index(index int) OpFunc { - return func(in []byte) ([]byte, error) { - return scanner.FindIndex(in, 0, index) - } -} - -// Range extracts a selection of elements from the array provided, inclusive -func Range(from, to int) OpFunc { - return func(in []byte) ([]byte, error) { - return scanner.FindRange(in, 0, from, to) - } -} diff --git a/vendor/github.com/savaki/jq/op_chain_test.go b/vendor/github.com/savaki/jq/op_chain_test.go deleted file mode 100644 index 543cb30..0000000 --- a/vendor/github.com/savaki/jq/op_chain_test.go +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright (c) 2016 Matt Ho -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package jq_test - -import ( - "testing" - - "github.com/savaki/jq" -) - -func BenchmarkChain(t *testing.B) { - op := jq.Chain(jq.Dot("a"), jq.Dot("b")) - data := []byte(`{"a":{"b":"value"}}`) - - for i := 0; i < t.N; i++ { - _, err := op.Apply(data) - if err != nil { - t.FailNow() - return - } - } -} - -func TestChain(t *testing.T) { - testCases := map[string]struct { - In string - Op jq.Op - Expected string - HasError bool - }{ - "simple": { - In: `{"hello":"world"}`, - Op: jq.Chain(jq.Dot("hello")), - Expected: `"world"`, - }, - "nested": { - In: `{"a":{"b":"world"}}`, - Op: jq.Chain(jq.Dot("a"), jq.Dot("b")), - Expected: `"world"`, - }, - } - - for label, tc := range testCases { - t.Run(label, func(t *testing.T) { - data, err := tc.Op.Apply([]byte(tc.In)) - if tc.HasError { - if err == nil { - t.FailNow() - } - } else { - if string(data) != tc.Expected { - t.FailNow() - } - if err != nil { - t.FailNow() - } - } - }) - } -} diff --git a/vendor/github.com/savaki/jq/op_dot_test.go b/vendor/github.com/savaki/jq/op_dot_test.go deleted file mode 100644 index efb1a7e..0000000 --- a/vendor/github.com/savaki/jq/op_dot_test.go +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright (c) 2016 Matt Ho -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package jq_test - -import ( - "testing" - - "github.com/savaki/jq" -) - -func BenchmarkDot(t *testing.B) { - op := jq.Dot("hello") - data := []byte(`{"hello":"world"}`) - - for i := 0; i < t.N; i++ { - _, err := op.Apply(data) - if err != nil { - t.FailNow() - return - } - } -} - -func TestDot(t *testing.T) { - testCases := map[string]struct { - In string - Key string - Expected string - HasError bool - }{ - "simple": { - In: `{"hello":"world"}`, - Key: "hello", - Expected: `"world"`, - }, - "key not found": { - In: `{"hello":"world"}`, - Key: "junk", - HasError: true, - }, - "unclosed value": { - In: `{"hello":"world`, - Key: "hello", - HasError: true, - }, - } - - for label, tc := range testCases { - t.Run(label, func(t *testing.T) { - op := jq.Dot(tc.Key) - data, err := op.Apply([]byte(tc.In)) - if tc.HasError { - if err == nil { - t.FailNow() - } - } else { - if string(data) != tc.Expected { - t.FailNow() - } - if err != nil { - t.FailNow() - } - } - }) - } -} diff --git a/vendor/github.com/savaki/jq/parse.go b/vendor/github.com/savaki/jq/parse.go deleted file mode 100644 index 3c21e06..0000000 --- a/vendor/github.com/savaki/jq/parse.go +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright (c) 2016 Matt Ho -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package jq - -import ( - "fmt" - "regexp" - "strconv" - "strings" -) - -var ( - reArray = regexp.MustCompile(`^\s*\[\s*(\d+)(\s*:\s*(\d+))?\s*]\s*$`) -) - -// Must is a convenience method similar to template.Must -func Must(op Op, err error) Op { - if err != nil { - panic(fmt.Errorf("unable to parse selector; %v", err.Error())) - } - - return op -} - -// Parse takes a string representation of a selector and returns the corresponding Op definition -func Parse(selector string) (Op, error) { - segments := strings.Split(selector, ".") - - ops := make([]Op, 0, len(segments)) - for _, segment := range segments { - key := strings.TrimSpace(segment) - if key == "" { - continue - } - - if op, ok := parseArray(key); ok { - ops = append(ops, op) - continue - } - - ops = append(ops, Dot(key)) - } - - return Chain(ops...), nil -} - -func parseArray(key string) (Op, bool) { - match := reArray.FindAllStringSubmatch(key, -1) - if len(match) != 1 { - return nil, false - } - - fromStr := match[0][1] - from, err := strconv.Atoi(fromStr) - if err != nil { - return nil, false - } - - toStr := match[0][3] - if toStr == "" { - return Index(from), true - } - - to, err := strconv.Atoi(toStr) - if err != nil { - return nil, false - } - - return Range(from, to), true -} diff --git a/vendor/github.com/savaki/jq/parse_regexp_test.go b/vendor/github.com/savaki/jq/parse_regexp_test.go deleted file mode 100644 index 4f09743..0000000 --- a/vendor/github.com/savaki/jq/parse_regexp_test.go +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright (c) 2016 Matt Ho -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package jq - -import ( - "testing" -) - -func TestRegexp(t *testing.T) { - testCases := map[string]struct { - In string - From string - To string - }{ - "simple": { - In: `[0]`, - From: "0", - }, - "range": { - In: `[0:1]`, - From: "0", - To: "1", - }, - "space before": { - In: ` [0:1]`, - From: "0", - To: "1", - }, - "space after": { - In: `[0:1] `, - From: "0", - To: "1", - }, - "space from": { - In: `[ 0 :1] `, - From: "0", - To: "1", - }, - "space to": { - In: `[0: 1 ] `, - From: "0", - To: "1", - }, - } - - for label, tc := range testCases { - t.Run(label, func(t *testing.T) { - matches := reArray.FindAllStringSubmatch(tc.In, -1) - if len(matches) != 1 { - t.FailNow() - } - if len(matches[0]) != 4 { - t.FailNow() - } - if matches[0][1] != tc.From { - t.FailNow() - } - if matches[0][3] != tc.To { - t.FailNow() - } - }) - } -} diff --git a/vendor/github.com/savaki/jq/parse_test.go b/vendor/github.com/savaki/jq/parse_test.go deleted file mode 100644 index fa78e38..0000000 --- a/vendor/github.com/savaki/jq/parse_test.go +++ /dev/null @@ -1,84 +0,0 @@ -// Copyright (c) 2016 Matt Ho -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package jq_test - -import ( - "testing" - - "github.com/savaki/jq" -) - -func TestParse(t *testing.T) { - testCases := map[string]struct { - In string - Op string - Expected string - HasError bool - }{ - "simple": { - In: `{"hello":"world"}`, - Op: ".hello", - Expected: `"world"`, - }, - "nested": { - In: `{"a":{"b":"world"}}`, - Op: ".a.b", - Expected: `"world"`, - }, - "index": { - In: `["a","b","c"]`, - Op: ".[1]", - Expected: `"b"`, - }, - "range": { - In: `["a","b","c"]`, - Op: ".[1:2]", - Expected: `["b","c"]`, - }, - "nested index": { - In: `{"abc":"-","def":["a","b","c"]}`, - Op: ".def.[1]", - Expected: `"b"`, - }, - "nested range": { - In: `{"abc":"-","def":["a","b","c"]}`, - Op: ".def.[1:2]", - Expected: `["b","c"]`, - }, - } - - for label, tc := range testCases { - t.Run(label, func(t *testing.T) { - op, err := jq.Parse(tc.Op) - if err != nil { - t.FailNow() - } - - data, err := op.Apply([]byte(tc.In)) - if tc.HasError { - if err == nil { - t.FailNow() - } - } else { - if string(data) != tc.Expected { - t.FailNow() - } - if err != nil { - t.FailNow() - } - } - }) - } -} diff --git a/vendor/github.com/savaki/jq/scanner/any.go b/vendor/github.com/savaki/jq/scanner/any.go deleted file mode 100644 index eeee0a8..0000000 --- a/vendor/github.com/savaki/jq/scanner/any.go +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright (c) 2016 Matt Ho -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package scanner - -// Any returns the position of the end of the current element that begins at pos; handles any valid json element -func Any(in []byte, pos int) (int, error) { - pos, err := skipSpace(in, pos) - if err != nil { - return 0, err - } - - switch in[pos] { - case '"': - return String(in, pos) - case '{': - return Object(in, pos) - case '.', '-', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0': - return Number(in, pos) - case '[': - return Array(in, pos) - case 't', 'f': - return Boolean(in, pos) - case 'n': - return Null(in, pos) - default: - max := len(in) - pos - if max > 20 { - max = 20 - } - - return 0, opErr{ - pos: pos, - msg: "invalid object", - content: string(in[pos : pos+max]), - } - } -} diff --git a/vendor/github.com/savaki/jq/scanner/any_test.go b/vendor/github.com/savaki/jq/scanner/any_test.go deleted file mode 100644 index 352011a..0000000 --- a/vendor/github.com/savaki/jq/scanner/any_test.go +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright (c) 2016 Matt Ho -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package scanner_test - -import ( - "testing" - - "github.com/savaki/jq/scanner" -) - -func BenchmarkAny(t *testing.B) { - data := []byte(`"Hello, 世界 - 生日快乐"`) - - for i := 0; i < t.N; i++ { - end, err := scanner.Any(data, 0) - if err != nil { - t.FailNow() - return - } - - if end == 0 { - t.FailNow() - return - } - } -} - -func TestAny(t *testing.T) { - testCases := map[string]struct { - In string - Out string - }{ - "string": { - In: `"hello"`, - Out: `"hello"`, - }, - "array": { - In: `["a","b","c"]`, - Out: `["a","b","c"]`, - }, - "object": { - In: `{"a":"b"}`, - Out: `{"a":"b"}`, - }, - "number": { - In: `1.234e+10`, - Out: `1.234e+10`, - }, - } - - for label, tc := range testCases { - t.Run(label, func(t *testing.T) { - end, err := scanner.Any([]byte(tc.In), 0) - if err != nil { - t.FailNow() - } - data := tc.In[0:end] - if string(data) != tc.Out { - t.FailNow() - } - }) - } -} diff --git a/vendor/github.com/savaki/jq/scanner/array.go b/vendor/github.com/savaki/jq/scanner/array.go deleted file mode 100644 index 040ab3a..0000000 --- a/vendor/github.com/savaki/jq/scanner/array.go +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright (c) 2016 Matt Ho -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package scanner - -// Array returns the position of the end of the array that begins at the position specified -func Array(in []byte, pos int) (int, error) { - pos, err := skipSpace(in, pos) - if err != nil { - return 0, err - } - - if v := in[pos]; v != '[' { - return 0, newError(pos, v) - } - pos++ - - // clean initial spaces - pos, err = skipSpace(in, pos) - if err != nil { - return 0, err - } - - if in[pos] == ']' { - return pos + 1, nil - } - - for { - // data - pos, err = Any(in, pos) - if err != nil { - return 0, err - } - - pos, err = skipSpace(in, pos) - if err != nil { - return 0, err - } - - switch in[pos] { - case ',': - pos++ - case ']': - return pos + 1, nil - } - } -} diff --git a/vendor/github.com/savaki/jq/scanner/array_test.go b/vendor/github.com/savaki/jq/scanner/array_test.go deleted file mode 100644 index c0a99ab..0000000 --- a/vendor/github.com/savaki/jq/scanner/array_test.go +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright (c) 2016 Matt Ho -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package scanner_test - -import ( - "testing" - - "github.com/savaki/jq/scanner" -) - -func BenchmarkArray(t *testing.B) { - data := []byte(`["hello","world"]`) - - for i := 0; i < t.N; i++ { - end, err := scanner.Array(data, 0) - if err != nil { - t.FailNow() - return - } - - if end == 0 { - t.FailNow() - return - } - } -} - -func TestArray(t *testing.T) { - testCases := map[string]struct { - In string - Out string - HasErr bool - }{ - "simple": { - In: `["hello","world"]`, - Out: `["hello","world"]`, - }, - "empty": { - In: `[]`, - Out: `[]`, - }, - "spaced": { - In: ` [ "hello" , "world" ] `, - Out: ` [ "hello" , "world" ]`, - }, - "all types": { - In: ` [ "hello" , 123, {"hello":"world"} ] `, - Out: ` [ "hello" , 123, {"hello":"world"} ]`, - }, - } - - for label, tc := range testCases { - t.Run(label, func(t *testing.T) { - end, err := scanner.Array([]byte(tc.In), 0) - if tc.HasErr { - if err == nil { - t.FailNow() - } - - } else { - data := tc.In[0:end] - if string(data) != tc.Out { - t.FailNow() - } - if err != nil { - t.FailNow() - } - } - }) - } -} diff --git a/vendor/github.com/savaki/jq/scanner/as.go b/vendor/github.com/savaki/jq/scanner/as.go deleted file mode 100644 index 88019d8..0000000 --- a/vendor/github.com/savaki/jq/scanner/as.go +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright (c) 2016 Matt Ho -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package scanner - -// AsArray accepts an []byte encoded json array as an input and returns the array's elements -func AsArray(in []byte, pos int) ([][]byte, error) { - pos, err := skipSpace(in, pos) - if err != nil { - return nil, err - } - - if v := in[pos]; v != '[' { - return nil, newError(pos, v) - } - pos++ - - // clean initial spaces - pos, err = skipSpace(in, pos) - if err != nil { - return nil, err - } - - if in[pos] == ']' { - return [][]byte{}, nil - } - - // 1. Count the number of elements in the array - - start := pos - - elements := make([][]byte, 0, 256) - for { - pos, err = skipSpace(in, pos) - if err != nil { - return nil, err - } - - start = pos - - // data - pos, err = Any(in, pos) - if err != nil { - return nil, err - } - elements = append(elements, in[start:pos]) - - pos, err = skipSpace(in, pos) - if err != nil { - return nil, err - } - - switch in[pos] { - case ',': - pos++ - case ']': - return elements, nil - } - } -} diff --git a/vendor/github.com/savaki/jq/scanner/as_array_test.go b/vendor/github.com/savaki/jq/scanner/as_array_test.go deleted file mode 100644 index 77c20e5..0000000 --- a/vendor/github.com/savaki/jq/scanner/as_array_test.go +++ /dev/null @@ -1,90 +0,0 @@ -// Copyright (c) 2016 Matt Ho -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package scanner_test - -import ( - "bytes" - "testing" - - "github.com/savaki/jq/scanner" -) - -func BenchmarkAsArray(t *testing.B) { - data := []byte(`["hello","world"]`) - - for i := 0; i < t.N; i++ { - out, err := scanner.AsArray(data, 0) - if err != nil { - t.Errorf("expected nil err; got %v", err) - return - } - if v := len(out); v != 2 { - t.Errorf("want %v, got %v", 2, v) - return - } - } -} - -func TestAsArray(t *testing.T) { - testCases := map[string]struct { - In string - Out []string - HasErr bool - }{ - "simple": { - In: `["hello","world"]`, - Out: []string{`"hello"`, `"world"`}, - }, - "empty": { - In: `[]`, - Out: []string{}, - }, - "spaced": { - In: ` [ "hello" , "world" ] `, - Out: []string{`"hello"`, `"world"`}, - }, - "all types": { - In: ` [ "hello" , 123, {"hello":"world"} ] `, - Out: []string{`"hello"`, `123`, `{"hello":"world"}`}, - }, - } - - for label, tc := range testCases { - t.Run(label, func(t *testing.T) { - out, err := scanner.AsArray([]byte(tc.In), 0) - if tc.HasErr { - if err == nil { - t.FailNow() - } - - } else { - if err != nil { - t.Errorf("expected nil err; got %v", err) - return - } - if len(out) != len(tc.Out) { - t.Errorf("expected output lengths to match; want %v, got %v", len(tc.Out), len(out)) - return - } - for index, item := range tc.Out { - if v := out[index]; bytes.Compare(v, []byte(item)) != 0 { - t.Errorf("expected content at index %v to match; want %v, got %v", index, item, string(v)) - return - } - } - } - }) - } -} diff --git a/vendor/github.com/savaki/jq/scanner/bool.go b/vendor/github.com/savaki/jq/scanner/bool.go deleted file mode 100644 index 8748cae..0000000 --- a/vendor/github.com/savaki/jq/scanner/bool.go +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) 2016 Matt Ho -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package scanner - -var ( - t = []byte("true") - f = []byte("false") -) - -// Boolean matches a boolean at the specified position -func Boolean(in []byte, pos int) (int, error) { - switch in[pos] { - case 't': - return expect(in, pos, t...) - case 'f': - return expect(in, pos, f...) - default: - return 0, errUnexpectedValue - } -} diff --git a/vendor/github.com/savaki/jq/scanner/doc.go b/vendor/github.com/savaki/jq/scanner/doc.go deleted file mode 100644 index 5ca8842..0000000 --- a/vendor/github.com/savaki/jq/scanner/doc.go +++ /dev/null @@ -1,3 +0,0 @@ -// Package scanner provides various utilities for parsing and extracting json data from []byte - -package scanner diff --git a/vendor/github.com/savaki/jq/scanner/err.go b/vendor/github.com/savaki/jq/scanner/err.go deleted file mode 100644 index e6fbb47..0000000 --- a/vendor/github.com/savaki/jq/scanner/err.go +++ /dev/null @@ -1,11 +0,0 @@ -package scanner - -type opErr struct { - pos int - msg string - content string -} - -func (o opErr) Error() string { - return o.msg + "; ..." + o.content -} diff --git a/vendor/github.com/savaki/jq/scanner/find_index.go b/vendor/github.com/savaki/jq/scanner/find_index.go deleted file mode 100644 index 061c8be..0000000 --- a/vendor/github.com/savaki/jq/scanner/find_index.go +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright (c) 2016 Matt Ho -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package scanner - -// FindIndex accepts a JSON array and return the value of the element at the specified index -func FindIndex(in []byte, pos, index int) ([]byte, error) { - pos, err := skipSpace(in, pos) - if err != nil { - return nil, err - } - - if v := in[pos]; v != '[' { - return nil, newError(pos, v) - } - pos++ - - idx := 0 - for { - pos, err = skipSpace(in, pos) - if err != nil { - return nil, err - } - - itemStart := pos - // data - pos, err = Any(in, pos) - if err != nil { - return nil, err - } - if index == idx { - return in[itemStart:pos], nil - } - - pos, err = skipSpace(in, pos) - if err != nil { - return nil, err - } - - switch in[pos] { - case ',': - pos++ - case ']': - return nil, errIndexOutOfBounds - } - - idx++ - } -} diff --git a/vendor/github.com/savaki/jq/scanner/find_index_test.go b/vendor/github.com/savaki/jq/scanner/find_index_test.go deleted file mode 100644 index ed6dbee..0000000 --- a/vendor/github.com/savaki/jq/scanner/find_index_test.go +++ /dev/null @@ -1,81 +0,0 @@ -// Copyright (c) 2016 Matt Ho -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package scanner_test - -import ( - "testing" - - "github.com/savaki/jq/scanner" -) - -func BenchmarkFindIndex(t *testing.B) { - data := []byte(`["hello","world"]`) - - for i := 0; i < t.N; i++ { - data, err := scanner.FindIndex(data, 0, 1) - if err != nil { - t.FailNow() - return - } - - if string(data) != `"world"` { - t.FailNow() - return - } - } -} - -func TestFindIndex(t *testing.T) { - testCases := map[string]struct { - In string - Index int - Expected string - HasErr bool - }{ - "simple": { - In: `["hello","world"]`, - Index: 1, - Expected: `"world"`, - }, - "spaced": { - In: ` [ "hello" , "world" ] `, - Index: 1, - Expected: `"world"`, - }, - "all types": { - In: ` [ "hello" , 123, {"hello":"world"} ] `, - Index: 2, - Expected: `{"hello":"world"}`, - }, - } - - for label, tc := range testCases { - t.Run(label, func(t *testing.T) { - data, err := scanner.FindIndex([]byte(tc.In), 0, tc.Index) - if tc.HasErr { - if err == nil { - t.FailNow() - } - } else { - if string(data) != tc.Expected { - t.FailNow() - } - if err != nil { - t.FailNow() - } - } - }) - } -} diff --git a/vendor/github.com/savaki/jq/scanner/find_key.go b/vendor/github.com/savaki/jq/scanner/find_key.go deleted file mode 100644 index 6477030..0000000 --- a/vendor/github.com/savaki/jq/scanner/find_key.go +++ /dev/null @@ -1,86 +0,0 @@ -// Copyright (c) 2016 Matt Ho -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package scanner - -import "bytes" - -// FindKey accepts a JSON object and returns the value associated with the key specified -func FindKey(in []byte, pos int, k []byte) ([]byte, error) { - pos, err := skipSpace(in, pos) - if err != nil { - return nil, err - } - - if v := in[pos]; v != '{' { - return nil, newError(pos, v) - } - pos++ - - for { - pos, err = skipSpace(in, pos) - if err != nil { - return nil, err - } - - keyStart := pos - // key - pos, err = String(in, pos) - if err != nil { - return nil, err - } - key := in[keyStart+1 : pos-1] - match := bytes.Equal(k, key) - - // leading spaces - pos, err = skipSpace(in, pos) - if err != nil { - return nil, err - } - - // colon - pos, err = expect(in, pos, ':') - if err != nil { - return nil, err - } - - pos, err = skipSpace(in, pos) - if err != nil { - return nil, err - } - - valueStart := pos - // data - pos, err = Any(in, pos) - if err != nil { - return nil, err - } - - if match { - return in[valueStart:pos], nil - } - - pos, err = skipSpace(in, pos) - if err != nil { - return nil, err - } - - switch in[pos] { - case ',': - pos++ - case '}': - return nil, errKeyNotFound - } - } -} diff --git a/vendor/github.com/savaki/jq/scanner/find_key_test.go b/vendor/github.com/savaki/jq/scanner/find_key_test.go deleted file mode 100644 index 0c1a1be..0000000 --- a/vendor/github.com/savaki/jq/scanner/find_key_test.go +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright (c) 2016 Matt Ho -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package scanner_test - -import ( - "testing" - - "github.com/savaki/jq/scanner" -) - -func BenchmarkFindKey(t *testing.B) { - data := []byte(`{"hello":"world"}`) - - for i := 0; i < t.N; i++ { - out, err := scanner.FindKey(data, 0, []byte("hello")) - if err != nil { - t.FailNow() - return - } - - if string(out) != `"world"` { - t.FailNow() - return - } - } -} - -func TestFindKey(t *testing.T) { - testCases := map[string]struct { - In string - Key string - Expected string - HasErr bool - }{ - "simple": { - In: `{"hello":"world"}`, - Key: "hello", - Expected: `"world"`, - }, - "spaced": { - In: ` { "hello" : "world" } `, - Key: "hello", - Expected: `"world"`, - }, - } - - for label, tc := range testCases { - t.Run(label, func(t *testing.T) { - data, err := scanner.FindKey([]byte(tc.In), 0, []byte(tc.Key)) - if tc.HasErr { - if err == nil { - t.FailNow() - } - } else { - if string(data) != tc.Expected { - t.FailNow() - } - if err != nil { - t.FailNow() - } - } - }) - } -} diff --git a/vendor/github.com/savaki/jq/scanner/find_range.go b/vendor/github.com/savaki/jq/scanner/find_range.go deleted file mode 100644 index 0d28d28..0000000 --- a/vendor/github.com/savaki/jq/scanner/find_range.go +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright (c) 2016 Matt Ho -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package scanner - -// FindRange finds the elements of an array between the specified indexes; inclusive -func FindRange(in []byte, pos, from, to int) ([]byte, error) { - if to < from { - return nil, errToLessThanFrom - } - - pos, err := skipSpace(in, pos) - if err != nil { - return nil, err - } - - if v := in[pos]; v != '[' { - return nil, newError(pos, v) - } - pos++ - - idx := 0 - itemStart := pos - - for { - pos, err = skipSpace(in, pos) - if err != nil { - return nil, err - } - - if idx == from { - itemStart = pos - } - - // data - pos, err = Any(in, pos) - if err != nil { - return nil, err - } - - if idx == to { - data := in[itemStart:pos] - result := make([]byte, 0, len(data)+2) - result = append(result, '[') - result = append(result, data...) - result = append(result, ']') - return result, nil - } - - pos, err = skipSpace(in, pos) - if err != nil { - return nil, err - } - - switch in[pos] { - case ',': - pos++ - case ']': - return nil, errIndexOutOfBounds - } - - idx++ - } -} diff --git a/vendor/github.com/savaki/jq/scanner/find_range_test.go b/vendor/github.com/savaki/jq/scanner/find_range_test.go deleted file mode 100644 index c2b7887..0000000 --- a/vendor/github.com/savaki/jq/scanner/find_range_test.go +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright (c) 2016 Matt Ho -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package scanner_test - -import ( - "testing" - - "github.com/savaki/jq/scanner" -) - -func BenchmarkFindRange(t *testing.B) { - data := []byte(`["a","b","c","d","e"]`) - - for i := 0; i < t.N; i++ { - out, err := scanner.FindRange(data, 0, 1, 2) - if err != nil { - t.FailNow() - return - } - - if string(out) != `["b","c"]` { - t.FailNow() - return - } - } -} - -func TestFindRange(t *testing.T) { - testCases := map[string]struct { - In string - From int - To int - Expected string - HasErr bool - }{ - "simple": { - In: `["a","b","c","d","e"]`, - From: 1, - To: 2, - Expected: `["b","c"]`, - }, - "single": { - In: `["a","b","c","d","e"]`, - From: 1, - To: 1, - Expected: `["b"]`, - }, - "mixed": { - In: `["a",{"hello":"world"},"c","d","e"]`, - From: 1, - To: 1, - Expected: `[{"hello":"world"}]`, - }, - "ordering": { - In: `["a",{"hello":"world"},"c","d","e"]`, - From: 1, - To: 0, - HasErr: true, - }, - "out of bounds": { - In: `["a",{"hello":"world"},"c","d","e"]`, - From: 1, - To: 20, - HasErr: true, - }, - } - - for label, tc := range testCases { - t.Run(label, func(t *testing.T) { - data, err := scanner.FindRange([]byte(tc.In), 0, tc.From, tc.To) - if tc.HasErr { - if err == nil { - t.FailNow() - } - } else { - if string(data) != tc.Expected { - t.FailNow() - } - if err != nil { - t.FailNow() - } - } - }) - } -} diff --git a/vendor/github.com/savaki/jq/scanner/null.go b/vendor/github.com/savaki/jq/scanner/null.go deleted file mode 100644 index b947664..0000000 --- a/vendor/github.com/savaki/jq/scanner/null.go +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) 2016 Matt Ho -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package scanner - -var ( - n = []byte("null") -) - -// Null verifies the contents of bytes provided is a null starting as pos -func Null(in []byte, pos int) (int, error) { - switch in[pos] { - case 'n': - return expect(in, pos, n...) - return pos + 4, nil - default: - return 0, errUnexpectedValue - } -} diff --git a/vendor/github.com/savaki/jq/scanner/null_test.go b/vendor/github.com/savaki/jq/scanner/null_test.go deleted file mode 100644 index c3a8f81..0000000 --- a/vendor/github.com/savaki/jq/scanner/null_test.go +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) 2016 Matt Ho -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package scanner_test - -import ( - "testing" - - "github.com/savaki/jq/scanner" -) - -func BenchmarkNull(t *testing.B) { - data := []byte("null") - for i := 0; i < t.N; i++ { - pos, err := scanner.Null(data, 0) - if err != nil { - t.FailNow() - } - if pos != 4 { - t.FailNow() - } - } -} diff --git a/vendor/github.com/savaki/jq/scanner/number.go b/vendor/github.com/savaki/jq/scanner/number.go deleted file mode 100644 index 43c0dbc..0000000 --- a/vendor/github.com/savaki/jq/scanner/number.go +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright (c) 2016 Matt Ho -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package scanner - -// Number returns the end position of the number that begins at the specified pos -func Number(in []byte, pos int) (int, error) { - pos, err := skipSpace(in, pos) - if err != nil { - return 0, err - } - - max := len(in) - for { - v := in[pos] - switch v { - case '-', '+', '.', 'e', 'E', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0': - pos++ - default: - return pos, nil - } - - if pos >= max { - return pos, nil - } - } - - return pos, nil -} diff --git a/vendor/github.com/savaki/jq/scanner/number_test.go b/vendor/github.com/savaki/jq/scanner/number_test.go deleted file mode 100644 index f0d9540..0000000 --- a/vendor/github.com/savaki/jq/scanner/number_test.go +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright (c) 2016 Matt Ho -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package scanner_test - -import ( - "testing" - - "github.com/savaki/jq/scanner" -) - -func BenchmarkNumber(t *testing.B) { - data := []byte(`12.34e+9`) - - for i := 0; i < t.N; i++ { - end, err := scanner.Number(data, 0) - if err != nil { - t.FailNow() - return - } - - if end == 0 { - t.FailNow() - return - } - } -} - -func TestNumber(t *testing.T) { - testCases := map[string]struct { - In string - Out string - HasErr bool - }{ - "simple": { - In: `1234`, - Out: `1234`, - }, - "decimal": { - In: `1.234`, - Out: `1.234`, - }, - "spaced": { - In: ` 1.234 `, - Out: ` 1.234`, - }, - "kitchen-sink": { - In: ` +-123.25eE10 `, - Out: ` +-123.25eE10`, - }, - } - - for label, tc := range testCases { - t.Run(label, func(t *testing.T) { - end, err := scanner.Number([]byte(tc.In), 0) - if tc.HasErr { - if err == nil { - t.FailNow() - } - } else { - data := tc.In[0:end] - if string(data) != tc.Out { - t.FailNow() - } - if err != nil { - t.FailNow() - } - } - }) - } -} diff --git a/vendor/github.com/savaki/jq/scanner/object.go b/vendor/github.com/savaki/jq/scanner/object.go deleted file mode 100644 index b290652..0000000 --- a/vendor/github.com/savaki/jq/scanner/object.go +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright (c) 2016 Matt Ho -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package scanner - -// Object returns the position of the end of the object that begins at the specified pos -func Object(in []byte, pos int) (int, error) { - pos, err := skipSpace(in, pos) - if err != nil { - return 0, err - } - - if v := in[pos]; v != '{' { - return 0, newError(pos, v) - } - pos++ - - // clean initial spaces - pos, err = skipSpace(in, pos) - if err != nil { - return 0, err - } - - if in[pos] == '}' { - return pos + 1, nil - } - - for { - // key - pos, err = String(in, pos) - if err != nil { - return 0, err - } - - // leading spaces - pos, err = skipSpace(in, pos) - if err != nil { - return 0, err - } - - // colon - pos, err = expect(in, pos, ':') - if err != nil { - return 0, err - } - - // data - pos, err = Any(in, pos) - if err != nil { - return 0, err - } - - pos, err = skipSpace(in, pos) - if err != nil { - return 0, err - } - - switch in[pos] { - case ',': - pos++ - case '}': - return pos + 1, nil - } - } -} diff --git a/vendor/github.com/savaki/jq/scanner/object_test.go b/vendor/github.com/savaki/jq/scanner/object_test.go deleted file mode 100644 index 1f87399..0000000 --- a/vendor/github.com/savaki/jq/scanner/object_test.go +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright (c) 2016 Matt Ho -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package scanner_test - -import ( - "testing" - - "github.com/savaki/jq/scanner" -) - -func BenchmarkObject(t *testing.B) { - data := []byte(`{"hello":"world"}`) - - for i := 0; i < t.N; i++ { - end, err := scanner.Object(data, 0) - if err != nil { - t.FailNow() - return - } - - if end == 0 { - t.FailNow() - return - } - } -} - -func TestObject(t *testing.T) { - testCases := map[string]struct { - In string - Out string - HasErr bool - }{ - "simple": { - In: `{"hello":"world"}`, - Out: `{"hello":"world"}`, - }, - "empty": { - In: `{}`, - Out: `{}`, - }, - "spaced": { - In: ` { "hello" : "world" } `, - Out: ` { "hello" : "world" }`, - }, - } - - for label, tc := range testCases { - t.Run(label, func(t *testing.T) { - end, err := scanner.Object([]byte(tc.In), 0) - if tc.HasErr { - if err == nil { - t.FailNow() - } - } else { - data := tc.In[0:end] - if string(data) != tc.Out { - t.FailNow() - } - if err != nil { - t.FailNow() - } - } - }) - } -} diff --git a/vendor/github.com/savaki/jq/scanner/string.go b/vendor/github.com/savaki/jq/scanner/string.go deleted file mode 100644 index 6b2232c..0000000 --- a/vendor/github.com/savaki/jq/scanner/string.go +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright (c) 2016 Matt Ho -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package scanner - -import "errors" - -// String returns the position of the string that begins at the specified pos -func String(in []byte, pos int) (int, error) { - pos, err := skipSpace(in, pos) - if err != nil { - return 0, err - } - - max := len(in) - - if v := in[pos]; v != '"' { - return 0, newError(pos, v) - } - pos++ - - for { - switch in[pos] { - case '\\': - if in[pos+1] == '"' { - pos++ - } - case '"': - return pos + 1, nil - } - pos++ - - if pos >= max { - break - } - } - - return 0, errors.New("unclosed string") -} diff --git a/vendor/github.com/savaki/jq/scanner/string_test.go b/vendor/github.com/savaki/jq/scanner/string_test.go deleted file mode 100644 index fcce039..0000000 --- a/vendor/github.com/savaki/jq/scanner/string_test.go +++ /dev/null @@ -1,99 +0,0 @@ -// Copyright (c) 2016 Matt Ho -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package scanner_test - -import ( - "testing" - "unicode/utf8" - - "github.com/savaki/jq/scanner" -) - -func BenchmarkString(t *testing.B) { - data := []byte(`"hello world"`) - - for i := 0; i < t.N; i++ { - end, err := scanner.String(data, 0) - if err != nil { - t.FailNow() - return - } - - if end == 0 { - t.FailNow() - return - } - } -} - -func TestString(t *testing.T) { - testCases := map[string]struct { - In string - Out string - HasErr bool - }{ - "simple": { - In: `"hello"`, - Out: `"hello"`, - }, - "array": { - In: `"hello", "world"`, - Out: `"hello"`, - }, - "escaped": { - In: `"hello\"\"world"`, - Out: `"hello\"\"world"`, - }, - "unclosed": { - In: `"hello`, - HasErr: true, - }, - "unclosed escape": { - In: `"hello\"`, - HasErr: true, - }, - "utf8": { - In: `"生日快乐"`, - Out: `"生日快乐"`, - }, - } - - for label, tc := range testCases { - t.Run(label, func(t *testing.T) { - end, err := scanner.String([]byte(tc.In), 0) - if tc.HasErr { - if err == nil { - t.FailNow() - } - } else { - data := tc.In[0:end] - if string(data) != tc.Out { - t.FailNow() - } - if err != nil { - t.FailNow() - } - } - }) - } -} - -func TestDecode(t *testing.T) { - v := "" - _, size := utf8.DecodeRune([]byte(v)) - if size != 0 { - t.FailNow() - } -} diff --git a/vendor/github.com/savaki/jq/scanner/util.go b/vendor/github.com/savaki/jq/scanner/util.go deleted file mode 100644 index 0ab36d2..0000000 --- a/vendor/github.com/savaki/jq/scanner/util.go +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright (c) 2016 Matt Ho -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package scanner - -import ( - "errors" - "fmt" - "unicode" - "unicode/utf8" -) - -var ( - errUnexpectedEOF = errors.New("unexpected EOF") - errKeyNotFound = errors.New("key not found") - errIndexOutOfBounds = errors.New("index out of bounds") - errToLessThanFrom = errors.New("to index less than from index") - errUnexpectedValue = errors.New("unexpected value") -) - -func skipSpace(in []byte, pos int) (int, error) { - for { - r, size := utf8.DecodeRune(in[pos:]) - if size == 0 { - return 0, errUnexpectedEOF - } - if !unicode.IsSpace(r) { - break - } - pos += size - } - - return pos, nil -} - -func expect(in []byte, pos int, content ...byte) (int, error) { - if pos+len(content) > len(in) { - return 0, errUnexpectedEOF - } - - for _, b := range content { - if v := in[pos]; v != b { - return 0, errUnexpectedValue - } - pos++ - } - - return pos, nil -} - -func newError(pos int, b byte) error { - return fmt.Errorf("invalid character at position, %v; %v", pos, string([]byte{b})) -} diff --git a/vendor/github.com/savaki/jq/scanner/util_test.go b/vendor/github.com/savaki/jq/scanner/util_test.go deleted file mode 100644 index d4d240f..0000000 --- a/vendor/github.com/savaki/jq/scanner/util_test.go +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright (c) 2016 Matt Ho -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package scanner - -import ( - "fmt" - "testing" -) - -func TestSkipSpace(t *testing.T) { - content := []byte(" \t\n\r!") - end, err := skipSpace(content, 0) - if err != nil { - t.FailNow() - } - if end+1 != len(content) { - t.FailNow() - } -} - -func TestExpect(t *testing.T) { - testCases := map[string]struct { - In string - Expected string - HasError bool - }{ - "simple": { - In: "abc", - Expected: "abc", - }, - "extra": { - In: "abcdef", - Expected: "abc", - }, - "no match": { - In: "abc", - Expected: "def", - HasError: true, - }, - "unexpected EOF": { - In: "ab", - Expected: "abc", - HasError: true, - }, - } - - for label, tc := range testCases { - t.Run(label, func(t *testing.T) { - pos, err := expect([]byte(tc.In), 0, []byte(tc.Expected)...) - if tc.HasError { - if err == nil { - t.FailNow() - } - - } else { - if err != nil { - fmt.Println(err) - t.FailNow() - } - if pos != len([]byte(tc.Expected)) { - t.FailNow() - } - - } - }) - } -} diff --git a/vendor/github.com/tidwall/gjson/.travis.yml b/vendor/github.com/tidwall/gjson/.travis.yml new file mode 100644 index 0000000..4f2ee4d --- /dev/null +++ b/vendor/github.com/tidwall/gjson/.travis.yml @@ -0,0 +1 @@ +language: go diff --git a/vendor/github.com/tidwall/gjson/LICENSE b/vendor/github.com/tidwall/gjson/LICENSE new file mode 100644 index 0000000..58f5819 --- /dev/null +++ b/vendor/github.com/tidwall/gjson/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2016 Josh Baker + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/github.com/tidwall/gjson/README.md b/vendor/github.com/tidwall/gjson/README.md new file mode 100644 index 0000000..5dbfed3 --- /dev/null +++ b/vendor/github.com/tidwall/gjson/README.md @@ -0,0 +1,421 @@ +

+GJSON +
+Build Status +GoDoc +GJSON Playground +

+ + + +

get a json value quickly

+ +GJSON is a Go package that provides a [fast](#performance) and [simple](#get-a-value) way to get values from a json document. +It has features such as [one line retrieval](#get-a-value), [dot notation paths](#path-syntax), [iteration](#iterate-through-an-object-or-array). + +Getting Started +=============== + +## Installing + +To start using GJSON, install Go and run `go get`: + +```sh +$ go get -u github.com/tidwall/gjson +``` + +This will retrieve the library. + +## Get a value +Get searches json for the specified path. A path is in dot syntax, such as "name.last" or "age". This function expects that the json is well-formed. Bad json will not panic, but it may return back unexpected results. When the value is found it's returned immediately. + +```go +package main + +import "github.com/tidwall/gjson" + +const json = `{"name":{"first":"Janet","last":"Prichard"},"age":47}` + +func main() { + value := gjson.Get(json, "name.last") + println(value.String()) +} +``` + +This will print: + +``` +Prichard +``` +*There's also the [GetMany](#get-multiple-values-at-once) function to get multiple values at once, and [GetBytes](#working-with-bytes) for working with JSON byte slices.* + +## Path Syntax + +A path is a series of keys separated by a dot. +A key may contain special wildcard characters '\*' and '?'. +To access an array value use the index as the key. +To get the number of elements in an array or to access a child path, use the '#' character. +The dot and wildcard characters can be escaped with '\\'. + +```json +{ + "name": {"first": "Tom", "last": "Anderson"}, + "age":37, + "children": ["Sara","Alex","Jack"], + "fav.movie": "Deer Hunter", + "friends": [ + {"first": "Dale", "last": "Murphy", "age": 44}, + {"first": "Roger", "last": "Craig", "age": 68}, + {"first": "Jane", "last": "Murphy", "age": 47} + ] +} +``` +``` +"name.last" >> "Anderson" +"age" >> 37 +"children" >> ["Sara","Alex","Jack"] +"children.#" >> 3 +"children.1" >> "Alex" +"child*.2" >> "Jack" +"c?ildren.0" >> "Sara" +"fav\.movie" >> "Deer Hunter" +"friends.#.first" >> ["Dale","Roger","Jane"] +"friends.1.last" >> "Craig" +``` + +You can also query an array for the first match by using `#[...]`, or find all matches with `#[...]#`. +Queries support the `==`, `!=`, `<`, `<=`, `>`, `>=` comparison operators and the simple pattern matching `%` operator. + +``` +friends.#[last=="Murphy"].first >> "Dale" +friends.#[last=="Murphy"]#.first >> ["Dale","Jane"] +friends.#[age>45]#.last >> ["Craig","Murphy"] +friends.#[first%"D*"].last >> "Murphy" +``` + +## Result Type + +GJSON supports the json types `string`, `number`, `bool`, and `null`. +Arrays and Objects are returned as their raw json types. + +The `Result` type holds one of these: + +``` +bool, for JSON booleans +float64, for JSON numbers +string, for JSON string literals +nil, for JSON null +``` + +To directly access the value: + +```go +result.Type // can be String, Number, True, False, Null, or JSON +result.Str // holds the string +result.Num // holds the float64 number +result.Raw // holds the raw json +result.Index // index of raw value in original json, zero means index unknown +``` + +There are a variety of handy functions that work on a result: + +```go +result.Exists() bool +result.Value() interface{} +result.Int() int64 +result.Uint() uint64 +result.Float() float64 +result.String() string +result.Bool() bool +result.Time() time.Time +result.Array() []gjson.Result +result.Map() map[string]gjson.Result +result.Get(path string) Result +result.ForEach(iterator func(key, value Result) bool) +result.Less(token Result, caseSensitive bool) bool +``` + +The `result.Value()` function returns an `interface{}` which requires type assertion and is one of the following Go types: + +The `result.Array()` function returns back an array of values. +If the result represents a non-existent value, then an empty array will be returned. +If the result is not a JSON array, the return value will be an array containing one result. + +```go +boolean >> bool +number >> float64 +string >> string +null >> nil +array >> []interface{} +object >> map[string]interface{} +``` + +## Get nested array values + +Suppose you want all the last names from the following json: + +```json +{ + "programmers": [ + { + "firstName": "Janet", + "lastName": "McLaughlin", + }, { + "firstName": "Elliotte", + "lastName": "Hunter", + }, { + "firstName": "Jason", + "lastName": "Harold", + } + ] +} +``` + +You would use the path "programmers.#.lastName" like such: + +```go +result := gjson.Get(json, "programmers.#.lastName") +for _, name := range result.Array() { + println(name.String()) +} +``` + +You can also query an object inside an array: + +```go +name := gjson.Get(json, `programmers.#[lastName="Hunter"].firstName`) +println(name.String()) // prints "Elliotte" +``` + +## Iterate through an object or array + +The `ForEach` function allows for quickly iterating through an object or array. +The key and value are passed to the iterator function for objects. +Only the value is passed for arrays. +Returning `false` from an iterator will stop iteration. + +```go +result := gjson.Get(json, "programmers") +result.ForEach(func(key, value gjson.Result) bool { + println(value.String()) + return true // keep iterating +}) +``` + +## Simple Parse and Get + +There's a `Parse(json)` function that will do a simple parse, and `result.Get(path)` that will search a result. + +For example, all of these will return the same result: + +```go +gjson.Parse(json).Get("name").Get("last") +gjson.Get(json, "name").Get("last") +gjson.Get(json, "name.last") +``` + +## Check for the existence of a value + +Sometimes you just want to know if a value exists. + +```go +value := gjson.Get(json, "name.last") +if !value.Exists() { + println("no last name") +} else { + println(value.String()) +} + +// Or as one step +if gjson.Get(json, "name.last").Exists() { + println("has a last name") +} +``` + +## Unmarshalling + +There's a `gjson.Unmarshal` function which loads json data into a value. +It's a general replacement for `json.Unmarshal` and you can typically +see a 2-3x boost in performance without the need for external generators. + +This function works almost identically to `json.Unmarshal` except that +`gjson.Unmarshal` will automatically attempt to convert JSON values to any +Go type. For example, the JSON string "100" or the JSON number 100 can be +equally assigned to Go string, int, byte, uint64, etc. This rule applies to +all types. + + +```go +package main + +import ( + "fmt" + + "github.com/tidwall/gjson" +) + +type Animal struct { + Type string `json:"type"` + Sound string `json:"sound"` + Age int `json:"age"` +} + +var json = `{ + "type": "Dog", + "Sound": "Bark", + "Age": "11" +}` + +func main() { + var dog Animal + gjson.Unmarshal([]byte(json), &dog) + fmt.Printf("type: %s, sound: %s, age: %d\n", dog.Type, dog.Sound, dog.Age) +} +``` + +This will print: + +``` +type: Dog, sound: Bark, age: 11 +``` + +## Unmarshal to a map + +To unmarshal to a `map[string]interface{}`: + +```go +m, ok := gjson.Parse(json).Value().(map[string]interface{}) +if !ok { + // not a map +} +``` + +## Working with Bytes + +If your JSON is contained in a `[]byte` slice, there's the [GetBytes](https://godoc.org/github.com/tidwall/gjson#GetBytes) function. This is preferred over `Get(string(data), path)`. + +```go +var json []byte = ... +result := gjson.GetBytes(json, path) +``` + +If you are using the `gjson.GetBytes(json, path)` function and you want to avoid converting `result.Raw` to a `[]byte`, then you can use this pattern: + +```go +var json []byte = ... +result := gjson.GetBytes(json, path) +var raw []byte +if result.Index > 0 { + raw = json[result.Index:result.Index+len(result.Raw)] +} else { + raw = []byte(result.Raw) +} +``` + +This is a best-effort no allocation sub slice of the original json. This method utilizes the `result.Index` field, which is the position of the raw data in the original json. It's possible that the value of `result.Index` equals zero, in which case the `result.Raw` is converted to a `[]byte`. + +## Get multiple values at once + +The `GetMany` function can be used to get multiple values at the same time, and is optimized to scan over a JSON payload once. + +```go +results := gjson.GetMany(json, "name.first", "name.last", "age") +``` + +The return value is a `[]Result`, which will always contain exactly the same number of items as the input paths. + +## Performance + +Benchmarks of GJSON alongside [encoding/json](https://golang.org/pkg/encoding/json/), +[ffjson](https://github.com/pquerna/ffjson), +[EasyJSON](https://github.com/mailru/easyjson), +[jsonparser](https://github.com/buger/jsonparser), +and [json-iterator](https://github.com/json-iterator/go) + +``` +BenchmarkGJSONGet-8 3000000 372 ns/op 0 B/op 0 allocs/op +BenchmarkGJSONUnmarshalMap-8 900000 4154 ns/op 1920 B/op 26 allocs/op +BenchmarkJSONUnmarshalMap-8 600000 9019 ns/op 3048 B/op 69 allocs/op +BenchmarkJSONUnmarshalStruct-8 600000 9268 ns/op 1832 B/op 69 allocs/op +BenchmarkJSONDecoder-8 300000 14120 ns/op 4224 B/op 184 allocs/op +BenchmarkFFJSONLexer-8 1500000 3111 ns/op 896 B/op 8 allocs/op +BenchmarkEasyJSONLexer-8 3000000 887 ns/op 613 B/op 6 allocs/op +BenchmarkJSONParserGet-8 3000000 499 ns/op 21 B/op 0 allocs/op +BenchmarkJSONIterator-8 3000000 812 ns/op 544 B/op 9 allocs/op +``` + +Benchmarks for the `GetMany` function: + +``` +BenchmarkGJSONGetMany4Paths-8 4000000 303 ns/op 112 B/op 0 allocs/op +BenchmarkGJSONGetMany8Paths-8 8000000 208 ns/op 56 B/op 0 allocs/op +BenchmarkGJSONGetMany16Paths-8 16000000 156 ns/op 56 B/op 0 allocs/op +BenchmarkGJSONGetMany32Paths-8 32000000 127 ns/op 64 B/op 0 allocs/op +BenchmarkGJSONGetMany64Paths-8 64000000 117 ns/op 64 B/op 0 allocs/op +BenchmarkGJSONGetMany128Paths-8 128000000 109 ns/op 64 B/op 0 allocs/op +``` + +JSON document used: + +```json +{ + "widget": { + "debug": "on", + "window": { + "title": "Sample Konfabulator Widget", + "name": "main_window", + "width": 500, + "height": 500 + }, + "image": { + "src": "Images/Sun.png", + "hOffset": 250, + "vOffset": 250, + "alignment": "center" + }, + "text": { + "data": "Click Here", + "size": 36, + "style": "bold", + "vOffset": 100, + "alignment": "center", + "onMouseUp": "sun1.opacity = (sun1.opacity / 100) * 90;" + } + } +} +``` + +Each operation was rotated though one of the following search paths: + +``` +widget.window.name +widget.image.hOffset +widget.text.onMouseUp +``` + +For the `GetMany` benchmarks these paths are used: + +``` +widget.window.name +widget.image.hOffset +widget.text.onMouseUp +widget.window.title +widget.image.alignment +widget.text.style +widget.window.height +widget.image.src +widget.text.data +widget.text.size +``` + +*These benchmarks were run on a MacBook Pro 15" 2.8 GHz Intel Core i7 using Go 1.8 and can be be found [here](https://github.com/tidwall/gjson-benchmarks).* + + +## Contact +Josh Baker [@tidwall](http://twitter.com/tidwall) + +## License + +GJSON source code is available under the MIT [License](/LICENSE). diff --git a/vendor/github.com/tidwall/gjson/gjson.go b/vendor/github.com/tidwall/gjson/gjson.go new file mode 100644 index 0000000..f9fe002 --- /dev/null +++ b/vendor/github.com/tidwall/gjson/gjson.go @@ -0,0 +1,2442 @@ +// Package gjson provides searching for json strings. +package gjson + +import ( + "encoding/base64" + "encoding/json" + "errors" + "reflect" + "strconv" + "strings" + "sync" + "sync/atomic" + "time" + "unicode/utf16" + "unicode/utf8" + "unsafe" + + "github.com/tidwall/match" +) + +// Type is Result type +type Type int + +const ( + // Null is a null json value + Null Type = iota + // False is a json false boolean + False + // Number is json number + Number + // String is a json string + String + // True is a json true boolean + True + // JSON is a raw block of JSON + JSON +) + +// String returns a string representation of the type. +func (t Type) String() string { + switch t { + default: + return "" + case Null: + return "Null" + case False: + return "False" + case Number: + return "Number" + case String: + return "String" + case True: + return "True" + case JSON: + return "JSON" + } +} + +// Result represents a json value that is returned from Get(). +type Result struct { + // Type is the json type + Type Type + // Raw is the raw json + Raw string + // Str is the json string + Str string + // Num is the json number + Num float64 + // Index of raw value in original json, zero means index unknown + Index int +} + +// String returns a string representation of the value. +func (t Result) String() string { + switch t.Type { + default: + return "" + case False: + return "false" + case Number: + return strconv.FormatFloat(t.Num, 'f', -1, 64) + case String: + return t.Str + case JSON: + return t.Raw + case True: + return "true" + } +} + +// Bool returns an boolean representation. +func (t Result) Bool() bool { + switch t.Type { + default: + return false + case True: + return true + case String: + return t.Str != "" && t.Str != "0" + case Number: + return t.Num != 0 + } +} + +// Int returns an integer representation. +func (t Result) Int() int64 { + switch t.Type { + default: + return 0 + case True: + return 1 + case String: + n, _ := parseInt(t.Str) + return n + case Number: + // try to directly convert the float64 to int64 + n, ok := floatToInt(t.Num) + if !ok { + // now try to parse the raw string + n, ok = parseInt(t.Raw) + if !ok { + // fallback to a standard conversion + return int64(t.Num) + } + } + return n + } +} + +// Uint returns an unsigned integer representation. +func (t Result) Uint() uint64 { + switch t.Type { + default: + return 0 + case True: + return 1 + case String: + n, _ := parseUint(t.Str) + return n + case Number: + // try to directly convert the float64 to uint64 + n, ok := floatToUint(t.Num) + if !ok { + // now try to parse the raw string + n, ok = parseUint(t.Raw) + if !ok { + // fallback to a standard conversion + return uint64(t.Num) + } + } + return n + } +} + +// Float returns an float64 representation. +func (t Result) Float() float64 { + switch t.Type { + default: + return 0 + case True: + return 1 + case String: + n, _ := strconv.ParseFloat(t.Str, 64) + return n + case Number: + return t.Num + } +} + +// Time returns a time.Time representation. +func (t Result) Time() time.Time { + res, _ := time.Parse(time.RFC3339, t.String()) + return res +} + +// Array returns back an array of values. +// If the result represents a non-existent value, then an empty array will be returned. +// If the result is not a JSON array, the return value will be an array containing one result. +func (t Result) Array() []Result { + if !t.Exists() { + return nil + } + if t.Type != JSON { + return []Result{t} + } + r := t.arrayOrMap('[', false) + return r.a +} + +// IsObject returns true if the result value is a JSON object. +func (t Result) IsObject() bool { + return t.Type == JSON && len(t.Raw) > 0 && t.Raw[0] == '{' +} + +// IsObject returns true if the result value is a JSON array. +func (t Result) IsArray() bool { + return t.Type == JSON && len(t.Raw) > 0 && t.Raw[0] == '[' +} + +// ForEach iterates through values. +// If the result represents a non-existent value, then no values will be iterated. +// If the result is an Object, the iterator will pass the key and value of each item. +// If the result is an Array, the iterator will only pass the value of each item. +// If the result is not a JSON array or object, the iterator will pass back one value equal to the result. +func (t Result) ForEach(iterator func(key, value Result) bool) { + if !t.Exists() { + return + } + if t.Type != JSON { + iterator(Result{}, t) + return + } + json := t.Raw + var keys bool + var i int + var key, value Result + for ; i < len(json); i++ { + if json[i] == '{' { + i++ + key.Type = String + keys = true + break + } else if json[i] == '[' { + i++ + break + } + if json[i] > ' ' { + return + } + } + var str string + var vesc bool + var ok bool + for ; i < len(json); i++ { + if keys { + if json[i] != '"' { + continue + } + s := i + i, str, vesc, ok = parseString(json, i+1) + if !ok { + return + } + if vesc { + key.Str = unescape(str[1 : len(str)-1]) + } else { + key.Str = str[1 : len(str)-1] + } + key.Raw = str + key.Index = s + } + for ; i < len(json); i++ { + if json[i] <= ' ' || json[i] == ',' || json[i] == ':' { + continue + } + break + } + s := i + i, value, ok = parseAny(json, i, true) + if !ok { + return + } + value.Index = s + if !iterator(key, value) { + return + } + } +} + +// Map returns back an map of values. The result should be a JSON array. +func (t Result) Map() map[string]Result { + if t.Type != JSON { + return map[string]Result{} + } + r := t.arrayOrMap('{', false) + return r.o +} + +// Get searches result for the specified path. +// The result should be a JSON array or object. +func (t Result) Get(path string) Result { + return Get(t.Raw, path) +} + +type arrayOrMapResult struct { + a []Result + ai []interface{} + o map[string]Result + oi map[string]interface{} + vc byte +} + +func (t Result) arrayOrMap(vc byte, valueize bool) (r arrayOrMapResult) { + var json = t.Raw + var i int + var value Result + var count int + var key Result + if vc == 0 { + for ; i < len(json); i++ { + if json[i] == '{' || json[i] == '[' { + r.vc = json[i] + i++ + break + } + if json[i] > ' ' { + goto end + } + } + } else { + for ; i < len(json); i++ { + if json[i] == vc { + i++ + break + } + if json[i] > ' ' { + goto end + } + } + r.vc = vc + } + if r.vc == '{' { + if valueize { + r.oi = make(map[string]interface{}) + } else { + r.o = make(map[string]Result) + } + } else { + if valueize { + r.ai = make([]interface{}, 0) + } else { + r.a = make([]Result, 0) + } + } + for ; i < len(json); i++ { + if json[i] <= ' ' { + continue + } + // get next value + if json[i] == ']' || json[i] == '}' { + break + } + switch json[i] { + default: + if (json[i] >= '0' && json[i] <= '9') || json[i] == '-' { + value.Type = Number + value.Raw, value.Num = tonum(json[i:]) + } else { + continue + } + case '{', '[': + value.Type = JSON + value.Raw = squash(json[i:]) + case 'n': + value.Type = Null + value.Raw = tolit(json[i:]) + case 't': + value.Type = True + value.Raw = tolit(json[i:]) + case 'f': + value.Type = False + value.Raw = tolit(json[i:]) + case '"': + value.Type = String + value.Raw, value.Str = tostr(json[i:]) + } + i += len(value.Raw) - 1 + + if r.vc == '{' { + if count%2 == 0 { + key = value + } else { + if valueize { + r.oi[key.Str] = value.Value() + } else { + r.o[key.Str] = value + } + } + count++ + } else { + if valueize { + r.ai = append(r.ai, value.Value()) + } else { + r.a = append(r.a, value) + } + } + } +end: + return +} + +// Parse parses the json and returns a result. +func Parse(json string) Result { + var value Result + for i := 0; i < len(json); i++ { + if json[i] == '{' || json[i] == '[' { + value.Type = JSON + value.Raw = json[i:] // just take the entire raw + break + } + if json[i] <= ' ' { + continue + } + switch json[i] { + default: + if (json[i] >= '0' && json[i] <= '9') || json[i] == '-' { + value.Type = Number + value.Raw, value.Num = tonum(json[i:]) + } else { + return Result{} + } + case 'n': + value.Type = Null + value.Raw = tolit(json[i:]) + case 't': + value.Type = True + value.Raw = tolit(json[i:]) + case 'f': + value.Type = False + value.Raw = tolit(json[i:]) + case '"': + value.Type = String + value.Raw, value.Str = tostr(json[i:]) + } + break + } + return value +} + +// ParseBytes parses the json and returns a result. +// If working with bytes, this method preferred over Parse(string(data)) +func ParseBytes(json []byte) Result { + return Parse(string(json)) +} + +func squash(json string) string { + // expects that the lead character is a '[' or '{' + // squash the value, ignoring all nested arrays and objects. + // the first '[' or '{' has already been read + depth := 1 + for i := 1; i < len(json); i++ { + if json[i] >= '"' && json[i] <= '}' { + switch json[i] { + case '"': + i++ + s2 := i + for ; i < len(json); i++ { + if json[i] > '\\' { + continue + } + if json[i] == '"' { + // look for an escaped slash + if json[i-1] == '\\' { + n := 0 + for j := i - 2; j > s2-1; j-- { + if json[j] != '\\' { + break + } + n++ + } + if n%2 == 0 { + continue + } + } + break + } + } + case '{', '[': + depth++ + case '}', ']': + depth-- + if depth == 0 { + return json[:i+1] + } + } + } + } + return json +} + +func tonum(json string) (raw string, num float64) { + for i := 1; i < len(json); i++ { + // less than dash might have valid characters + if json[i] <= '-' { + if json[i] <= ' ' || json[i] == ',' { + // break on whitespace and comma + raw = json[:i] + num, _ = strconv.ParseFloat(raw, 64) + return + } + // could be a '+' or '-'. let's assume so. + continue + } + if json[i] < ']' { + // probably a valid number + continue + } + if json[i] == 'e' || json[i] == 'E' { + // allow for exponential numbers + continue + } + // likely a ']' or '}' + raw = json[:i] + num, _ = strconv.ParseFloat(raw, 64) + return + } + raw = json + num, _ = strconv.ParseFloat(raw, 64) + return +} + +func tolit(json string) (raw string) { + for i := 1; i < len(json); i++ { + if json[i] <= 'a' || json[i] >= 'z' { + return json[:i] + } + } + return json +} + +func tostr(json string) (raw string, str string) { + // expects that the lead character is a '"' + for i := 1; i < len(json); i++ { + if json[i] > '\\' { + continue + } + if json[i] == '"' { + return json[:i+1], json[1:i] + } + if json[i] == '\\' { + i++ + for ; i < len(json); i++ { + if json[i] > '\\' { + continue + } + if json[i] == '"' { + // look for an escaped slash + if json[i-1] == '\\' { + n := 0 + for j := i - 2; j > 0; j-- { + if json[j] != '\\' { + break + } + n++ + } + if n%2 == 0 { + continue + } + } + break + } + } + var ret string + if i+1 < len(json) { + ret = json[:i+1] + } else { + ret = json[:i] + } + return ret, unescape(json[1:i]) + } + } + return json, json[1:] +} + +// Exists returns true if value exists. +// +// if gjson.Get(json, "name.last").Exists(){ +// println("value exists") +// } +func (t Result) Exists() bool { + return t.Type != Null || len(t.Raw) != 0 +} + +// Value returns one of these types: +// +// bool, for JSON booleans +// float64, for JSON numbers +// Number, for JSON numbers +// string, for JSON string literals +// nil, for JSON null +// +func (t Result) Value() interface{} { + if t.Type == String { + return t.Str + } + switch t.Type { + default: + return nil + case False: + return false + case Number: + return t.Num + case JSON: + r := t.arrayOrMap(0, true) + if r.vc == '{' { + return r.oi + } else if r.vc == '[' { + return r.ai + } + return nil + case True: + return true + } +} + +func parseString(json string, i int) (int, string, bool, bool) { + var s = i + for ; i < len(json); i++ { + if json[i] > '\\' { + continue + } + if json[i] == '"' { + return i + 1, json[s-1 : i+1], false, true + } + if json[i] == '\\' { + i++ + for ; i < len(json); i++ { + if json[i] > '\\' { + continue + } + if json[i] == '"' { + // look for an escaped slash + if json[i-1] == '\\' { + n := 0 + for j := i - 2; j > 0; j-- { + if json[j] != '\\' { + break + } + n++ + } + if n%2 == 0 { + continue + } + } + return i + 1, json[s-1 : i+1], true, true + } + } + break + } + } + return i, json[s-1:], false, false +} + +func parseNumber(json string, i int) (int, string) { + var s = i + i++ + for ; i < len(json); i++ { + if json[i] <= ' ' || json[i] == ',' || json[i] == ']' || json[i] == '}' { + return i, json[s:i] + } + } + return i, json[s:] +} + +func parseLiteral(json string, i int) (int, string) { + var s = i + i++ + for ; i < len(json); i++ { + if json[i] < 'a' || json[i] > 'z' { + return i, json[s:i] + } + } + return i, json[s:] +} + +type arrayPathResult struct { + part string + path string + more bool + alogok bool + arrch bool + alogkey string + query struct { + on bool + path string + op string + value string + all bool + } +} + +func parseArrayPath(path string) (r arrayPathResult) { + for i := 0; i < len(path); i++ { + if path[i] == '.' { + r.part = path[:i] + r.path = path[i+1:] + r.more = true + return + } + if path[i] == '#' { + r.arrch = true + if i == 0 && len(path) > 1 { + if path[1] == '.' { + r.alogok = true + r.alogkey = path[2:] + r.path = path[:1] + } else if path[1] == '[' { + r.query.on = true + // query + i += 2 + // whitespace + for ; i < len(path); i++ { + if path[i] > ' ' { + break + } + } + s := i + for ; i < len(path); i++ { + if path[i] <= ' ' || + path[i] == '!' || + path[i] == '=' || + path[i] == '<' || + path[i] == '>' || + path[i] == '%' || + path[i] == ']' { + break + } + } + r.query.path = path[s:i] + // whitespace + for ; i < len(path); i++ { + if path[i] > ' ' { + break + } + } + if i < len(path) { + s = i + if path[i] == '!' { + if i < len(path)-1 && path[i+1] == '=' { + i++ + } + } else if path[i] == '<' || path[i] == '>' { + if i < len(path)-1 && path[i+1] == '=' { + i++ + } + } else if path[i] == '=' { + if i < len(path)-1 && path[i+1] == '=' { + s++ + i++ + } + } + i++ + r.query.op = path[s:i] + // whitespace + for ; i < len(path); i++ { + if path[i] > ' ' { + break + } + } + s = i + for ; i < len(path); i++ { + if path[i] == '"' { + i++ + s2 := i + for ; i < len(path); i++ { + if path[i] > '\\' { + continue + } + if path[i] == '"' { + // look for an escaped slash + if path[i-1] == '\\' { + n := 0 + for j := i - 2; j > s2-1; j-- { + if path[j] != '\\' { + break + } + n++ + } + if n%2 == 0 { + continue + } + } + break + } + } + } else if path[i] == ']' { + if i+1 < len(path) && path[i+1] == '#' { + r.query.all = true + } + break + } + } + if i > len(path) { + i = len(path) + } + v := path[s:i] + for len(v) > 0 && v[len(v)-1] <= ' ' { + v = v[:len(v)-1] + } + r.query.value = v + } + } + } + continue + } + } + r.part = path + r.path = "" + return +} + +type objectPathResult struct { + part string + path string + wild bool + more bool +} + +func parseObjectPath(path string) (r objectPathResult) { + for i := 0; i < len(path); i++ { + if path[i] == '.' { + r.part = path[:i] + r.path = path[i+1:] + r.more = true + return + } + if path[i] == '*' || path[i] == '?' { + r.wild = true + continue + } + if path[i] == '\\' { + // go into escape mode. this is a slower path that + // strips off the escape character from the part. + epart := []byte(path[:i]) + i++ + if i < len(path) { + epart = append(epart, path[i]) + i++ + for ; i < len(path); i++ { + if path[i] == '\\' { + i++ + if i < len(path) { + epart = append(epart, path[i]) + } + continue + } else if path[i] == '.' { + r.part = string(epart) + r.path = path[i+1:] + r.more = true + return + } else if path[i] == '*' || path[i] == '?' { + r.wild = true + } + epart = append(epart, path[i]) + } + } + // append the last part + r.part = string(epart) + return + } + } + r.part = path + return +} + +func parseSquash(json string, i int) (int, string) { + // expects that the lead character is a '[' or '{' + // squash the value, ignoring all nested arrays and objects. + // the first '[' or '{' has already been read + s := i + i++ + depth := 1 + for ; i < len(json); i++ { + if json[i] >= '"' && json[i] <= '}' { + switch json[i] { + case '"': + i++ + s2 := i + for ; i < len(json); i++ { + if json[i] > '\\' { + continue + } + if json[i] == '"' { + // look for an escaped slash + if json[i-1] == '\\' { + n := 0 + for j := i - 2; j > s2-1; j-- { + if json[j] != '\\' { + break + } + n++ + } + if n%2 == 0 { + continue + } + } + break + } + } + case '{', '[': + depth++ + case '}', ']': + depth-- + if depth == 0 { + i++ + return i, json[s:i] + } + } + } + } + return i, json[s:] +} + +func parseObject(c *parseContext, i int, path string) (int, bool) { + var pmatch, kesc, vesc, ok, hit bool + var key, val string + rp := parseObjectPath(path) + for i < len(c.json) { + for ; i < len(c.json); i++ { + if c.json[i] == '"' { + // parse_key_string + // this is slightly different from getting s string value + // because we don't need the outer quotes. + i++ + var s = i + for ; i < len(c.json); i++ { + if c.json[i] > '\\' { + continue + } + if c.json[i] == '"' { + i, key, kesc, ok = i+1, c.json[s:i], false, true + goto parse_key_string_done + } + if c.json[i] == '\\' { + i++ + for ; i < len(c.json); i++ { + if c.json[i] > '\\' { + continue + } + if c.json[i] == '"' { + // look for an escaped slash + if c.json[i-1] == '\\' { + n := 0 + for j := i - 2; j > 0; j-- { + if c.json[j] != '\\' { + break + } + n++ + } + if n%2 == 0 { + continue + } + } + i, key, kesc, ok = i+1, c.json[s:i], true, true + goto parse_key_string_done + } + } + break + } + } + key, kesc, ok = c.json[s:], false, false + parse_key_string_done: + break + } + if c.json[i] == '}' { + return i + 1, false + } + } + if !ok { + return i, false + } + if rp.wild { + if kesc { + pmatch = match.Match(unescape(key), rp.part) + } else { + pmatch = match.Match(key, rp.part) + } + } else { + if kesc { + pmatch = rp.part == unescape(key) + } else { + pmatch = rp.part == key + } + } + hit = pmatch && !rp.more + for ; i < len(c.json); i++ { + switch c.json[i] { + default: + continue + case '"': + i++ + i, val, vesc, ok = parseString(c.json, i) + if !ok { + return i, false + } + if hit { + if vesc { + c.value.Str = unescape(val[1 : len(val)-1]) + } else { + c.value.Str = val[1 : len(val)-1] + } + c.value.Raw = val + c.value.Type = String + return i, true + } + case '{': + if pmatch && !hit { + i, hit = parseObject(c, i+1, rp.path) + if hit { + return i, true + } + } else { + i, val = parseSquash(c.json, i) + if hit { + c.value.Raw = val + c.value.Type = JSON + return i, true + } + } + case '[': + if pmatch && !hit { + i, hit = parseArray(c, i+1, rp.path) + if hit { + return i, true + } + } else { + i, val = parseSquash(c.json, i) + if hit { + c.value.Raw = val + c.value.Type = JSON + return i, true + } + } + case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + i, val = parseNumber(c.json, i) + if hit { + c.value.Raw = val + c.value.Type = Number + c.value.Num, _ = strconv.ParseFloat(val, 64) + return i, true + } + case 't', 'f', 'n': + vc := c.json[i] + i, val = parseLiteral(c.json, i) + if hit { + c.value.Raw = val + switch vc { + case 't': + c.value.Type = True + case 'f': + c.value.Type = False + } + return i, true + } + } + break + } + } + return i, false +} +func queryMatches(rp *arrayPathResult, value Result) bool { + rpv := rp.query.value + if len(rpv) > 2 && rpv[0] == '"' && rpv[len(rpv)-1] == '"' { + rpv = rpv[1 : len(rpv)-1] + } + switch value.Type { + case String: + switch rp.query.op { + case "=": + return value.Str == rpv + case "!=": + return value.Str != rpv + case "<": + return value.Str < rpv + case "<=": + return value.Str <= rpv + case ">": + return value.Str > rpv + case ">=": + return value.Str >= rpv + case "%": + return match.Match(value.Str, rpv) + } + case Number: + rpvn, _ := strconv.ParseFloat(rpv, 64) + switch rp.query.op { + case "=": + return value.Num == rpvn + case "!=": + return value.Num == rpvn + case "<": + return value.Num < rpvn + case "<=": + return value.Num <= rpvn + case ">": + return value.Num > rpvn + case ">=": + return value.Num >= rpvn + } + case True: + switch rp.query.op { + case "=": + return rpv == "true" + case "!=": + return rpv != "true" + case ">": + return rpv == "false" + case ">=": + return true + } + case False: + switch rp.query.op { + case "=": + return rpv == "false" + case "!=": + return rpv != "false" + case "<": + return rpv == "true" + case "<=": + return true + } + } + return false +} +func parseArray(c *parseContext, i int, path string) (int, bool) { + var pmatch, vesc, ok, hit bool + var val string + var h int + var alog []int + var partidx int + var multires []byte + rp := parseArrayPath(path) + if !rp.arrch { + n, ok := parseUint(rp.part) + if !ok { + partidx = -1 + } else { + partidx = int(n) + } + } + for i < len(c.json) { + if !rp.arrch { + pmatch = partidx == h + hit = pmatch && !rp.more + } + h++ + if rp.alogok { + alog = append(alog, i) + } + for ; i < len(c.json); i++ { + switch c.json[i] { + default: + continue + case '"': + i++ + i, val, vesc, ok = parseString(c.json, i) + if !ok { + return i, false + } + if hit { + if rp.alogok { + break + } + if vesc { + c.value.Str = unescape(val[1 : len(val)-1]) + } else { + c.value.Str = val[1 : len(val)-1] + } + c.value.Raw = val + c.value.Type = String + return i, true + } + case '{': + if pmatch && !hit { + i, hit = parseObject(c, i+1, rp.path) + if hit { + if rp.alogok { + break + } + return i, true + } + } else { + i, val = parseSquash(c.json, i) + if rp.query.on { + res := Get(val, rp.query.path) + if queryMatches(&rp, res) { + if rp.more { + res = Get(val, rp.path) + } else { + res = Result{Raw: val, Type: JSON} + } + if rp.query.all { + if len(multires) == 0 { + multires = append(multires, '[') + } else { + multires = append(multires, ',') + } + multires = append(multires, res.Raw...) + } else { + c.value = res + return i, true + } + } + } else if hit { + if rp.alogok { + break + } + c.value.Raw = val + c.value.Type = JSON + return i, true + } + } + case '[': + if pmatch && !hit { + i, hit = parseArray(c, i+1, rp.path) + if hit { + if rp.alogok { + break + } + return i, true + } + } else { + i, val = parseSquash(c.json, i) + if hit { + if rp.alogok { + break + } + c.value.Raw = val + c.value.Type = JSON + return i, true + } + } + case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + i, val = parseNumber(c.json, i) + if hit { + if rp.alogok { + break + } + c.value.Raw = val + c.value.Type = Number + c.value.Num, _ = strconv.ParseFloat(val, 64) + return i, true + } + case 't', 'f', 'n': + vc := c.json[i] + i, val = parseLiteral(c.json, i) + if hit { + if rp.alogok { + break + } + c.value.Raw = val + switch vc { + case 't': + c.value.Type = True + case 'f': + c.value.Type = False + } + return i, true + } + case ']': + if rp.arrch && rp.part == "#" { + if rp.alogok { + var jsons = make([]byte, 0, 64) + jsons = append(jsons, '[') + for j, k := 0, 0; j < len(alog); j++ { + res := Get(c.json[alog[j]:], rp.alogkey) + if res.Exists() { + if k > 0 { + jsons = append(jsons, ',') + } + jsons = append(jsons, []byte(res.Raw)...) + k++ + } + } + jsons = append(jsons, ']') + c.value.Type = JSON + c.value.Raw = string(jsons) + return i + 1, true + } + if rp.alogok { + break + } + c.value.Raw = val + c.value.Type = Number + c.value.Num = float64(h - 1) + c.calcd = true + return i + 1, true + } + if len(multires) > 0 && !c.value.Exists() { + c.value = Result{ + Raw: string(append(multires, ']')), + Type: JSON, + } + } + return i + 1, false + } + break + } + } + return i, false +} + +type parseContext struct { + json string + value Result + calcd bool +} + +// Get searches json for the specified path. +// A path is in dot syntax, such as "name.last" or "age". +// This function expects that the json is well-formed, and does not validate. +// Invalid json will not panic, but it may return back unexpected results. +// When the value is found it's returned immediately. +// +// A path is a series of keys searated by a dot. +// A key may contain special wildcard characters '*' and '?'. +// To access an array value use the index as the key. +// To get the number of elements in an array or to access a child path, use the '#' character. +// The dot and wildcard character can be escaped with '\'. +// +// { +// "name": {"first": "Tom", "last": "Anderson"}, +// "age":37, +// "children": ["Sara","Alex","Jack"], +// "friends": [ +// {"first": "James", "last": "Murphy"}, +// {"first": "Roger", "last": "Craig"} +// ] +// } +// "name.last" >> "Anderson" +// "age" >> 37 +// "children" >> ["Sara","Alex","Jack"] +// "children.#" >> 3 +// "children.1" >> "Alex" +// "child*.2" >> "Jack" +// "c?ildren.0" >> "Sara" +// "friends.#.first" >> ["James","Roger"] +// +func Get(json, path string) Result { + var i int + var c = &parseContext{json: json} + for ; i < len(c.json); i++ { + if c.json[i] == '{' { + i++ + parseObject(c, i, path) + break + } + if c.json[i] == '[' { + i++ + parseArray(c, i, path) + break + } + } + if len(c.value.Raw) > 0 && !c.calcd { + jhdr := *(*reflect.StringHeader)(unsafe.Pointer(&json)) + rhdr := *(*reflect.StringHeader)(unsafe.Pointer(&(c.value.Raw))) + c.value.Index = int(rhdr.Data - jhdr.Data) + if c.value.Index < 0 || c.value.Index >= len(json) { + c.value.Index = 0 + } + } + return c.value +} +func fromBytesGet(result Result) Result { + // safely get the string headers + rawhi := *(*reflect.StringHeader)(unsafe.Pointer(&result.Raw)) + strhi := *(*reflect.StringHeader)(unsafe.Pointer(&result.Str)) + // create byte slice headers + rawh := reflect.SliceHeader{Data: rawhi.Data, Len: rawhi.Len} + strh := reflect.SliceHeader{Data: strhi.Data, Len: strhi.Len} + if strh.Data == 0 { + // str is nil + if rawh.Data == 0 { + // raw is nil + result.Raw = "" + } else { + // raw has data, safely copy the slice header to a string + result.Raw = string(*(*[]byte)(unsafe.Pointer(&rawh))) + } + result.Str = "" + } else if rawh.Data == 0 { + // raw is nil + result.Raw = "" + // str has data, safely copy the slice header to a string + result.Str = string(*(*[]byte)(unsafe.Pointer(&strh))) + } else if strh.Data >= rawh.Data && + int(strh.Data)+strh.Len <= int(rawh.Data)+rawh.Len { + // Str is a substring of Raw. + start := int(strh.Data - rawh.Data) + // safely copy the raw slice header + result.Raw = string(*(*[]byte)(unsafe.Pointer(&rawh))) + // substring the raw + result.Str = result.Raw[start : start+strh.Len] + } else { + // safely copy both the raw and str slice headers to strings + result.Raw = string(*(*[]byte)(unsafe.Pointer(&rawh))) + result.Str = string(*(*[]byte)(unsafe.Pointer(&strh))) + } + return result +} + +// GetBytes searches json for the specified path. +// If working with bytes, this method preferred over Get(string(data), path) +func GetBytes(json []byte, path string) Result { + var result Result + if json != nil { + // unsafe cast to string + result = Get(*(*string)(unsafe.Pointer(&json)), path) + result = fromBytesGet(result) + } + return result +} + +// runeit returns the rune from the the \uXXXX +func runeit(json string) rune { + n, _ := strconv.ParseUint(json[:4], 16, 64) + return rune(n) +} + +// unescape unescapes a string +func unescape(json string) string { //, error) { + var str = make([]byte, 0, len(json)) + for i := 0; i < len(json); i++ { + switch { + default: + str = append(str, json[i]) + case json[i] < ' ': + return string(str) + case json[i] == '\\': + i++ + if i >= len(json) { + return string(str) + } + switch json[i] { + default: + return string(str) + case '\\': + str = append(str, '\\') + case '/': + str = append(str, '/') + case 'b': + str = append(str, '\b') + case 'f': + str = append(str, '\f') + case 'n': + str = append(str, '\n') + case 'r': + str = append(str, '\r') + case 't': + str = append(str, '\t') + case '"': + str = append(str, '"') + case 'u': + if i+5 > len(json) { + return string(str) + } + r := runeit(json[i+1:]) + i += 5 + if utf16.IsSurrogate(r) { + // need another code + if len(json[i:]) >= 6 && json[i] == '\\' && json[i+1] == 'u' { + // we expect it to be correct so just consume it + r = utf16.DecodeRune(r, runeit(json[i+2:])) + i += 6 + } + } + // provide enough space to encode the largest utf8 possible + str = append(str, 0, 0, 0, 0, 0, 0, 0, 0) + n := utf8.EncodeRune(str[len(str)-8:], r) + str = str[:len(str)-8+n] + i-- // backtrack index by one + } + } + } + return string(str) +} + +// Less return true if a token is less than another token. +// The caseSensitive paramater is used when the tokens are Strings. +// The order when comparing two different type is: +// +// Null < False < Number < String < True < JSON +// +func (t Result) Less(token Result, caseSensitive bool) bool { + if t.Type < token.Type { + return true + } + if t.Type > token.Type { + return false + } + if t.Type == String { + if caseSensitive { + return t.Str < token.Str + } + return stringLessInsensitive(t.Str, token.Str) + } + if t.Type == Number { + return t.Num < token.Num + } + return t.Raw < token.Raw +} + +func stringLessInsensitive(a, b string) bool { + for i := 0; i < len(a) && i < len(b); i++ { + if a[i] >= 'A' && a[i] <= 'Z' { + if b[i] >= 'A' && b[i] <= 'Z' { + // both are uppercase, do nothing + if a[i] < b[i] { + return true + } else if a[i] > b[i] { + return false + } + } else { + // a is uppercase, convert a to lowercase + if a[i]+32 < b[i] { + return true + } else if a[i]+32 > b[i] { + return false + } + } + } else if b[i] >= 'A' && b[i] <= 'Z' { + // b is uppercase, convert b to lowercase + if a[i] < b[i]+32 { + return true + } else if a[i] > b[i]+32 { + return false + } + } else { + // neither are uppercase + if a[i] < b[i] { + return true + } else if a[i] > b[i] { + return false + } + } + } + return len(a) < len(b) +} + +// parseAny parses the next value from a json string. +// A Result is returned when the hit param is set. +// The return values are (i int, res Result, ok bool) +func parseAny(json string, i int, hit bool) (int, Result, bool) { + var res Result + var val string + for ; i < len(json); i++ { + if json[i] == '{' || json[i] == '[' { + i, val = parseSquash(json, i) + if hit { + res.Raw = val + res.Type = JSON + } + return i, res, true + } + if json[i] <= ' ' { + continue + } + switch json[i] { + case '"': + i++ + var vesc bool + var ok bool + i, val, vesc, ok = parseString(json, i) + if !ok { + return i, res, false + } + if hit { + res.Type = String + res.Raw = val + if vesc { + res.Str = unescape(val[1 : len(val)-1]) + } else { + res.Str = val[1 : len(val)-1] + } + } + return i, res, true + case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + i, val = parseNumber(json, i) + if hit { + res.Raw = val + res.Type = Number + res.Num, _ = strconv.ParseFloat(val, 64) + } + return i, res, true + case 't', 'f', 'n': + vc := json[i] + i, val = parseLiteral(json, i) + if hit { + res.Raw = val + switch vc { + case 't': + res.Type = True + case 'f': + res.Type = False + } + return i, res, true + } + } + } + return i, res, false +} + +var ( // used for testing + testWatchForFallback bool + testLastWasFallback bool +) + +// areSimplePaths returns true if all the paths are simple enough +// to parse quickly for GetMany(). Allows alpha-numeric, dots, +// underscores, and the dollar sign. It does not allow non-alnum, +// escape characters, or keys which start with a numbers. +// For example: +// "name.last" == OK +// "user.id0" == OK +// "user.ID" == OK +// "user.first_name" == OK +// "user.firstName" == OK +// "user.0item" == BAD +// "user.#id" == BAD +// "user\.name" == BAD +func areSimplePaths(paths []string) bool { + for _, path := range paths { + var fi int // first key index, for keys with numeric prefix + for i := 0; i < len(path); i++ { + if path[i] >= 'a' && path[i] <= 'z' { + // a-z is likely to be the highest frequency charater. + continue + } + if path[i] == '.' { + fi = i + 1 + continue + } + if path[i] >= 'A' && path[i] <= 'Z' { + continue + } + if path[i] == '_' || path[i] == '$' { + continue + } + if i > fi && path[i] >= '0' && path[i] <= '9' { + continue + } + return false + } + } + return true +} + +// GetMany searches json for the multiple paths. +// The return value is a Result array where the number of items +// will be equal to the number of input paths. +func GetMany(json string, paths ...string) []Result { + if len(paths) < 4 { + if testWatchForFallback { + testLastWasFallback = false + } + switch len(paths) { + case 0: + // return nil when no paths are specified. + return nil + case 1: + return []Result{Get(json, paths[0])} + case 2: + return []Result{Get(json, paths[0]), Get(json, paths[1])} + case 3: + return []Result{Get(json, paths[0]), Get(json, paths[1]), Get(json, paths[2])} + } + } + var results []Result + var ok bool + var i int + if len(paths) > 512 { + // we can only support up to 512 paths. Is that too many? + goto fallback + } + if !areSimplePaths(paths) { + // If there is even one path that is not considered "simple" then + // we need to use the fallback method. + goto fallback + } + // locate the object token. + for ; i < len(json); i++ { + if json[i] == '{' { + i++ + break + } + if json[i] <= ' ' { + continue + } + goto fallback + } + // use the call function table. + if len(paths) <= 8 { + results, ok = getMany8(json, i, paths) + } else if len(paths) <= 16 { + results, ok = getMany16(json, i, paths) + } else if len(paths) <= 32 { + results, ok = getMany32(json, i, paths) + } else if len(paths) <= 64 { + results, ok = getMany64(json, i, paths) + } else if len(paths) <= 128 { + results, ok = getMany128(json, i, paths) + } else if len(paths) <= 256 { + results, ok = getMany256(json, i, paths) + } else if len(paths) <= 512 { + results, ok = getMany512(json, i, paths) + } + if !ok { + // there was some fault while parsing. we should try the + // fallback method. This could result in performance + // degregation in some cases. + goto fallback + } + if testWatchForFallback { + testLastWasFallback = false + } + return results +fallback: + results = results[:0] + for i := 0; i < len(paths); i++ { + results = append(results, Get(json, paths[i])) + } + if testWatchForFallback { + testLastWasFallback = true + } + return results +} + +// GetManyBytes searches json for the specified path. +// If working with bytes, this method preferred over +// GetMany(string(data), paths...) +func GetManyBytes(json []byte, paths ...string) []Result { + if json == nil { + return GetMany("", paths...) + } + results := GetMany(*(*string)(unsafe.Pointer(&json)), paths...) + for i := range results { + results[i] = fromBytesGet(results[i]) + } + return results +} + +// parseGetMany parses a json object for keys that match against the callers +// paths. It's a best-effort attempt and quickly locating and assigning the +// values to the []Result array. If there are failures such as bad json, or +// invalid input paths, or too much recursion, the function will exit with a +// return value of 'false'. +func parseGetMany( + json string, i int, + level uint, kplen int, + paths []string, completed []bool, matches []uint64, results []Result, +) (int, bool) { + if level > 62 { + // The recursion level is limited because the matches []uint64 + // array cannot handle more the 64-bits. + return i, false + } + // At this point the last character read was a '{'. + // Read all object keys and try to match against the paths. + var key string + var val string + var vesc, ok bool +next_key: + for ; i < len(json); i++ { + if json[i] == '"' { + // read the key + i, val, vesc, ok = parseString(json, i+1) + if !ok { + return i, false + } + if vesc { + // the value is escaped + key = unescape(val[1 : len(val)-1]) + } else { + // just a plain old ascii key + key = val[1 : len(val)-1] + } + var hasMatch bool + var parsedVal bool + var valOrgIndex int + var valPathIndex int + for j := 0; j < len(key); j++ { + if key[j] == '.' { + // we need to look for keys with dot and ignore them. + if i, _, ok = parseAny(json, i, false); !ok { + return i, false + } + continue next_key + } + } + var usedPaths int + // loop through paths and look for matches + for j := 0; j < len(paths); j++ { + if completed[j] { + usedPaths++ + // ignore completed paths + continue + } + if level > 0 && (matches[j]>>(level-1))&1 == 0 { + // ignore unmatched paths + usedPaths++ + continue + } + + // try to match the key to the path + // this is spaghetti code but the idea is to minimize + // calls and variable assignments when comparing the + // key to paths + if len(paths[j])-kplen >= len(key) { + i, k := kplen, 0 + for ; k < len(key); k, i = k+1, i+1 { + if key[k] != paths[j][i] { + // no match + goto nomatch + } + } + if i < len(paths[j]) { + if paths[j][i] == '.' { + // matched, but there are still more keys in path + goto match_not_atend + } + } + if len(paths[j]) <= len(key) || kplen != 0 { + // matched and at the end of the path + goto match_atend + } + } + // no match, jump to the nomatch label + goto nomatch + match_atend: + // found a match + // at the end of the path. we must take the value. + usedPaths++ + if !parsedVal { + // the value has not been parsed yet. let's do so. + valOrgIndex = i // keep track of the current position. + i, results[j], ok = parseAny(json, i, true) + if !ok { + return i, false + } + parsedVal = true + valPathIndex = j + } else { + results[j] = results[valPathIndex] + } + // mark as complete + completed[j] = true + // jump over the match_not_atend label + goto nomatch + match_not_atend: + // found a match + // still in the middle of the path. + usedPaths++ + // mark the path as matched + matches[j] |= 1 << level + if !hasMatch { + hasMatch = true + } + nomatch: // noop label + } + + if !parsedVal { + if hasMatch { + // we found a match and the value has not been parsed yet. + // let's find out if the next value type is an object. + for ; i < len(json); i++ { + if json[i] <= ' ' || json[i] == ':' { + continue + } + break + } + if i < len(json) { + if json[i] == '{' { + // it's an object. let's go deeper + i, ok = parseGetMany(json, i+1, level+1, kplen+len(key)+1, paths, completed, matches, results) + if !ok { + return i, false + } + } else { + // not an object. just parse and ignore. + if i, _, ok = parseAny(json, i, false); !ok { + return i, false + } + } + } + } else { + // Since there was no matches we can just parse the value and + // ignore the result. + if i, _, ok = parseAny(json, i, false); !ok { + return i, false + } + } + } else if hasMatch && len(results[valPathIndex].Raw) > 0 && results[valPathIndex].Raw[0] == '{' { + // The value was already parsed and the value type is an object. + // Rewind the json index and let's parse deeper. + i = valOrgIndex + for ; i < len(json); i++ { + if json[i] == '{' { + break + } + } + i, ok = parseGetMany(json, i+1, level+1, kplen+len(key)+1, paths, completed, matches, results) + if !ok { + return i, false + } + } + if usedPaths == len(paths) { + // all paths have been used, either completed or matched. + // we should stop parsing this object to save CPU cycles. + if level > 0 && i < len(json) { + i, _ = parseSquash(json, i) + } + return i, true + } + } else if json[i] == '}' { + // reached the end of the object. end it here. + return i + 1, true + } + } + return i, true +} + +// Call table for GetMany. Using an isolated function allows for allocating +// arrays with know capacities on the stack, as opposed to dynamically +// allocating on the heap. This can provide a tremendous performance boost +// by avoiding the GC. +func getMany8(json string, i int, paths []string) ([]Result, bool) { + const max = 8 + var completed = make([]bool, 0, max) + var matches = make([]uint64, 0, max) + var results = make([]Result, 0, max) + completed = completed[0:len(paths):max] + matches = matches[0:len(paths):max] + results = results[0:len(paths):max] + _, ok := parseGetMany(json, i, 0, 0, paths, completed, matches, results) + return results, ok +} +func getMany16(json string, i int, paths []string) ([]Result, bool) { + const max = 16 + var completed = make([]bool, 0, max) + var matches = make([]uint64, 0, max) + var results = make([]Result, 0, max) + completed = completed[0:len(paths):max] + matches = matches[0:len(paths):max] + results = results[0:len(paths):max] + _, ok := parseGetMany(json, i, 0, 0, paths, completed, matches, results) + return results, ok +} +func getMany32(json string, i int, paths []string) ([]Result, bool) { + const max = 32 + var completed = make([]bool, 0, max) + var matches = make([]uint64, 0, max) + var results = make([]Result, 0, max) + completed = completed[0:len(paths):max] + matches = matches[0:len(paths):max] + results = results[0:len(paths):max] + _, ok := parseGetMany(json, i, 0, 0, paths, completed, matches, results) + return results, ok +} +func getMany64(json string, i int, paths []string) ([]Result, bool) { + const max = 64 + var completed = make([]bool, 0, max) + var matches = make([]uint64, 0, max) + var results = make([]Result, 0, max) + completed = completed[0:len(paths):max] + matches = matches[0:len(paths):max] + results = results[0:len(paths):max] + _, ok := parseGetMany(json, i, 0, 0, paths, completed, matches, results) + return results, ok +} +func getMany128(json string, i int, paths []string) ([]Result, bool) { + const max = 128 + var completed = make([]bool, 0, max) + var matches = make([]uint64, 0, max) + var results = make([]Result, 0, max) + completed = completed[0:len(paths):max] + matches = matches[0:len(paths):max] + results = results[0:len(paths):max] + _, ok := parseGetMany(json, i, 0, 0, paths, completed, matches, results) + return results, ok +} +func getMany256(json string, i int, paths []string) ([]Result, bool) { + const max = 256 + var completed = make([]bool, 0, max) + var matches = make([]uint64, 0, max) + var results = make([]Result, 0, max) + completed = completed[0:len(paths):max] + matches = matches[0:len(paths):max] + results = results[0:len(paths):max] + _, ok := parseGetMany(json, i, 0, 0, paths, completed, matches, results) + return results, ok +} +func getMany512(json string, i int, paths []string) ([]Result, bool) { + const max = 512 + var completed = make([]bool, 0, max) + var matches = make([]uint64, 0, max) + var results = make([]Result, 0, max) + completed = completed[0:len(paths):max] + matches = matches[0:len(paths):max] + results = results[0:len(paths):max] + _, ok := parseGetMany(json, i, 0, 0, paths, completed, matches, results) + return results, ok +} + +var fieldsmu sync.RWMutex +var fields = make(map[string]map[string]int) + +func assign(jsval Result, goval reflect.Value) { + if jsval.Type == Null { + return + } + switch goval.Kind() { + default: + case reflect.Ptr: + if !goval.IsNil() { + newval := reflect.New(goval.Elem().Type()) + assign(jsval, newval.Elem()) + goval.Elem().Set(newval.Elem()) + } else { + newval := reflect.New(goval.Type().Elem()) + assign(jsval, newval.Elem()) + goval.Set(newval) + } + case reflect.Struct: + fieldsmu.RLock() + sf := fields[goval.Type().String()] + fieldsmu.RUnlock() + if sf == nil { + fieldsmu.Lock() + sf = make(map[string]int) + for i := 0; i < goval.Type().NumField(); i++ { + f := goval.Type().Field(i) + tag := strings.Split(f.Tag.Get("json"), ",")[0] + if tag != "-" { + if tag != "" { + sf[tag] = i + sf[f.Name] = i + } else { + sf[f.Name] = i + } + } + } + fields[goval.Type().String()] = sf + fieldsmu.Unlock() + } + jsval.ForEach(func(key, value Result) bool { + if idx, ok := sf[key.Str]; ok { + f := goval.Field(idx) + if f.CanSet() { + assign(value, f) + } + } + return true + }) + case reflect.Slice: + if goval.Type().Elem().Kind() == reflect.Uint8 && jsval.Type == String { + data, _ := base64.StdEncoding.DecodeString(jsval.String()) + goval.Set(reflect.ValueOf(data)) + } else { + jsvals := jsval.Array() + slice := reflect.MakeSlice(goval.Type(), len(jsvals), len(jsvals)) + for i := 0; i < len(jsvals); i++ { + assign(jsvals[i], slice.Index(i)) + } + goval.Set(slice) + } + case reflect.Array: + i, n := 0, goval.Len() + jsval.ForEach(func(_, value Result) bool { + if i == n { + return false + } + assign(value, goval.Index(i)) + i++ + return true + }) + case reflect.Map: + if goval.Type().Key().Kind() == reflect.String && goval.Type().Elem().Kind() == reflect.Interface { + goval.Set(reflect.ValueOf(jsval.Value())) + } + case reflect.Interface: + goval.Set(reflect.ValueOf(jsval.Value())) + case reflect.Bool: + goval.SetBool(jsval.Bool()) + case reflect.Float32, reflect.Float64: + goval.SetFloat(jsval.Float()) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + goval.SetInt(jsval.Int()) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + goval.SetUint(jsval.Uint()) + case reflect.String: + goval.SetString(jsval.String()) + } + if len(goval.Type().PkgPath()) > 0 { + v := goval.Addr() + if v.Type().NumMethod() > 0 { + if u, ok := v.Interface().(json.Unmarshaler); ok { + u.UnmarshalJSON([]byte(jsval.Raw)) + } + } + } +} + +var validate uintptr = 1 + +// UnmarshalValidationEnabled provides the option to disable JSON validation +// during the Unmarshal routine. Validation is enabled by default. +func UnmarshalValidationEnabled(enabled bool) { + if enabled { + atomic.StoreUintptr(&validate, 1) + } else { + atomic.StoreUintptr(&validate, 0) + } +} + +// Unmarshal loads the JSON data into the value pointed to by v. +// +// This function works almost identically to json.Unmarshal except that +// gjson.Unmarshal will automatically attempt to convert JSON values to any Go +// type. For example, the JSON string "100" or the JSON number 100 can be equally +// assigned to Go string, int, byte, uint64, etc. This rule applies to all types. +func Unmarshal(data []byte, v interface{}) error { + if atomic.LoadUintptr(&validate) == 1 { + _, ok := validpayload(data, 0) + if !ok { + return errors.New("invalid json") + } + } + if v := reflect.ValueOf(v); v.Kind() == reflect.Ptr { + assign(ParseBytes(data), v) + } + return nil +} + +func validpayload(data []byte, i int) (outi int, ok bool) { + for ; i < len(data); i++ { + switch data[i] { + default: + i, ok = validany(data, i) + if !ok { + return i, false + } + for ; i < len(data); i++ { + switch data[i] { + default: + return i, false + case ' ', '\t', '\n', '\r': + continue + } + } + return i, true + case ' ', '\t', '\n', '\r': + continue + } + } + return i, false +} +func validany(data []byte, i int) (outi int, ok bool) { + for ; i < len(data); i++ { + switch data[i] { + default: + return i, false + case ' ', '\t', '\n', '\r': + continue + case '{': + return validobject(data, i+1) + case '[': + return validarray(data, i+1) + case '"': + return validstring(data, i+1) + case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + return validnumber(data, i+1) + case 't': + return validtrue(data, i+1) + case 'f': + return validfalse(data, i+1) + case 'n': + return validnull(data, i+1) + } + } + return i, false +} +func validobject(data []byte, i int) (outi int, ok bool) { + for ; i < len(data); i++ { + switch data[i] { + default: + return i, false + case ' ', '\t', '\n', '\r': + continue + case '}': + return i + 1, true + case '"': + key: + if i, ok = validstring(data, i+1); !ok { + return i, false + } + if i, ok = validcolon(data, i); !ok { + return i, false + } + if i, ok = validany(data, i); !ok { + return i, false + } + if i, ok = validcomma(data, i, '}'); !ok { + return i, false + } + if data[i] == '}' { + return i + 1, true + } + for ; i < len(data); i++ { + if data[i] == '"' { + goto key + } + } + return i, false + } + } + return i, false +} +func validcolon(data []byte, i int) (outi int, ok bool) { + for ; i < len(data); i++ { + switch data[i] { + default: + return i, false + case ' ', '\t', '\n', '\r': + continue + case ':': + return i + 1, true + } + } + return i, false +} +func validcomma(data []byte, i int, end byte) (outi int, ok bool) { + for ; i < len(data); i++ { + switch data[i] { + default: + return i, false + case ' ', '\t', '\n', '\r': + continue + case ',': + return i, true + case end: + return i, true + } + } + return i, false +} +func validarray(data []byte, i int) (outi int, ok bool) { + for ; i < len(data); i++ { + switch data[i] { + default: + for ; i < len(data); i++ { + if i, ok = validany(data, i); !ok { + return i, false + } + if i, ok = validcomma(data, i, ']'); !ok { + return i, false + } + if data[i] == ']' { + return i + 1, true + } + } + case ' ', '\t', '\n', '\r': + continue + case ']': + return i + 1, true + } + } + return i, false +} +func validstring(data []byte, i int) (outi int, ok bool) { + for ; i < len(data); i++ { + if data[i] < ' ' { + return i, false + } else if data[i] == '\\' { + i++ + if i == len(data) { + return i, false + } + switch data[i] { + default: + return i, false + case '"', '\\', '/', 'b', 'f', 'n', 'r', 't': + case 'u': + for j := 0; j < 4; j++ { + i++ + if i >= len(data) { + return i, false + } + if !((data[i] >= '0' && data[i] <= '9') || + (data[i] >= 'a' && data[i] <= 'f') || + (data[i] >= 'A' && data[i] <= 'F')) { + return i, false + } + } + } + } else if data[i] == '"' { + return i + 1, true + } + } + return i, false +} +func validnumber(data []byte, i int) (outi int, ok bool) { + i-- + // sign + if data[i] == '-' { + i++ + } + // int + if i == len(data) { + return i, false + } + if data[i] == '0' { + i++ + } else { + for ; i < len(data); i++ { + if data[i] >= '0' && data[i] <= '9' { + continue + } + break + } + } + // frac + if i == len(data) { + return i, true + } + if data[i] == '.' { + i++ + if i == len(data) { + return i, false + } + if data[i] < '0' || data[i] > '9' { + return i, false + } + i++ + for ; i < len(data); i++ { + if data[i] >= '0' && data[i] <= '9' { + continue + } + break + } + } + // exp + if i == len(data) { + return i, true + } + if data[i] == 'e' || data[i] == 'E' { + i++ + if i == len(data) { + return i, false + } + if data[i] == '+' || data[i] == '-' { + i++ + } + if i == len(data) { + return i, false + } + if data[i] < '0' || data[i] > '9' { + return i, false + } + i++ + for ; i < len(data); i++ { + if data[i] >= '0' && data[i] <= '9' { + continue + } + break + } + } + return i, true +} + +func validtrue(data []byte, i int) (outi int, ok bool) { + if i+3 <= len(data) && data[i] == 'r' && data[i+1] == 'u' && data[i+2] == 'e' { + return i + 3, true + } + return i, false +} +func validfalse(data []byte, i int) (outi int, ok bool) { + if i+4 <= len(data) && data[i] == 'a' && data[i+1] == 'l' && data[i+2] == 's' && data[i+3] == 'e' { + return i + 4, true + } + return i, false +} +func validnull(data []byte, i int) (outi int, ok bool) { + if i+3 <= len(data) && data[i] == 'u' && data[i+1] == 'l' && data[i+2] == 'l' { + return i + 3, true + } + return i, false +} + +// Valid returns true if the input is valid json. +func Valid(json string) bool { + _, ok := validpayload([]byte(json), 0) + return ok +} + +func parseUint(s string) (n uint64, ok bool) { + var i int + if i == len(s) { + return 0, false + } + for ; i < len(s); i++ { + if s[i] >= '0' && s[i] <= '9' { + n = n*10 + uint64(s[i]-'0') + } else { + return 0, false + } + } + return n, true +} + +func parseInt(s string) (n int64, ok bool) { + var i int + var sign bool + if len(s) > 0 && s[0] == '-' { + sign = true + i++ + } + if i == len(s) { + return 0, false + } + for ; i < len(s); i++ { + if s[i] >= '0' && s[i] <= '9' { + n = n*10 + int64(s[i]-'0') + } else { + return 0, false + } + } + if sign { + return n * -1, true + } + return n, true +} + +const minUint53 = 0 +const maxUint53 = 4503599627370495 +const minInt53 = -2251799813685248 +const maxInt53 = 2251799813685247 + +func floatToUint(f float64) (n uint64, ok bool) { + n = uint64(f) + if float64(n) == f && n >= minUint53 && n <= maxUint53 { + return n, true + } + return 0, false +} + +func floatToInt(f float64) (n int64, ok bool) { + n = int64(f) + if float64(n) == f && n >= minInt53 && n <= maxInt53 { + return n, true + } + return 0, false +} diff --git a/vendor/github.com/tidwall/gjson/gjson_test.go b/vendor/github.com/tidwall/gjson/gjson_test.go new file mode 100644 index 0000000..57f0425 --- /dev/null +++ b/vendor/github.com/tidwall/gjson/gjson_test.go @@ -0,0 +1,1073 @@ +package gjson + +import ( + "bytes" + "encoding/hex" + "encoding/json" + "fmt" + "math/rand" + "reflect" + "strings" + "testing" + "time" +) + +// TestRandomData is a fuzzing test that throws random data at the Parse +// function looking for panics. +func TestRandomData(t *testing.T) { + var lstr string + defer func() { + if v := recover(); v != nil { + println("'" + hex.EncodeToString([]byte(lstr)) + "'") + println("'" + lstr + "'") + panic(v) + } + }() + rand.Seed(time.Now().UnixNano()) + b := make([]byte, 200) + for i := 0; i < 2000000; i++ { + n, err := rand.Read(b[:rand.Int()%len(b)]) + if err != nil { + t.Fatal(err) + } + lstr = string(b[:n]) + GetBytes([]byte(lstr), "zzzz") + Parse(lstr) + } +} + +func TestRandomValidStrings(t *testing.T) { + rand.Seed(time.Now().UnixNano()) + b := make([]byte, 200) + for i := 0; i < 100000; i++ { + n, err := rand.Read(b[:rand.Int()%len(b)]) + if err != nil { + t.Fatal(err) + } + sm, err := json.Marshal(string(b[:n])) + if err != nil { + t.Fatal(err) + } + var su string + if err := json.Unmarshal([]byte(sm), &su); err != nil { + t.Fatal(err) + } + token := Get(`{"str":`+string(sm)+`}`, "str") + if token.Type != String || token.Str != su { + println("["+token.Raw+"]", "["+token.Str+"]", "["+su+"]", "["+string(sm)+"]") + t.Fatal("string mismatch") + } + } +} + +func TestEmoji(t *testing.T) { + const input = `{"utf8":"Example emoji, KO: \ud83d\udd13, \ud83c\udfc3 OK: \u2764\ufe0f "}` + value := Get(input, "utf8") + var s string + json.Unmarshal([]byte(value.Raw), &s) + if value.String() != s { + t.Fatalf("expected '%v', got '%v'", s, value.String()) + } +} + +func testEscapePath(t *testing.T, json, path, expect string) { + if Get(json, path).String() != expect { + t.Fatalf("expected '%v', got '%v'", expect, Get(json, path).String()) + } +} + +func TestEscapePath(t *testing.T) { + json := `{ + "test":{ + "*":"valZ", + "*v":"val0", + "keyv*":"val1", + "key*v":"val2", + "keyv?":"val3", + "key?v":"val4", + "keyv.":"val5", + "key.v":"val6", + "keyk*":{"key?":"val7"} + } + }` + + testEscapePath(t, json, "test.\\*", "valZ") + testEscapePath(t, json, "test.\\*v", "val0") + testEscapePath(t, json, "test.keyv\\*", "val1") + testEscapePath(t, json, "test.key\\*v", "val2") + testEscapePath(t, json, "test.keyv\\?", "val3") + testEscapePath(t, json, "test.key\\?v", "val4") + testEscapePath(t, json, "test.keyv\\.", "val5") + testEscapePath(t, json, "test.key\\.v", "val6") + testEscapePath(t, json, "test.keyk\\*.key\\?", "val7") +} + +// this json block is poorly formed on purpose. +var basicJSON = `{"age":100, "name":{"here":"B\\\"R"}, + "noop":{"what is a wren?":"a bird"}, + "happy":true,"immortal":false, + "items":[1,2,3,{"tags":[1,2,3],"points":[[1,2],[3,4]]},4,5,6,7], + "arr":["1",2,"3",{"hello":"world"},"4",5], + "vals":[1,2,3,{"sadf":sdf"asdf"}],"name":{"first":"tom","last":null}, + "created":"2014-05-16T08:28:06.989Z", + "loggy":{ + "programmers": [ + { + "firstName": "Brett", + "lastName": "McLaughlin", + "email": "aaaa", + "tag": "good" + }, + { + "firstName": "Jason", + "lastName": "Hunter", + "email": "bbbb", + "tag": "bad" + }, + { + "firstName": "Elliotte", + "lastName": "Harold", + "email": "cccc", + "tag":, "good" + }, + { + "firstName": 1002.3, + "age": 101 + } + ] + }, + "lastly":{"yay":"final"} +}` +var basicJSONB = []byte(basicJSON) + +func TestTimeResult(t *testing.T) { + assert(t, Get(basicJSON, "created").String() == Get(basicJSON, "created").Time().Format(time.RFC3339Nano)) +} + +func TestParseAny(t *testing.T) { + assert(t, Parse("100").Float() == 100) + assert(t, Parse("true").Bool()) + assert(t, Parse("valse").Bool() == false) +} + +func TestManyVariousPathCounts(t *testing.T) { + json := `{"a":"a","b":"b","c":"c"}` + counts := []int{3, 4, 7, 8, 9, 15, 16, 17, 31, 32, 33, 63, 64, 65, 127, 128, 129, 255, 256, 257, 511, 512, 513} + paths := []string{"a", "b", "c"} + expects := []string{"a", "b", "c"} + for _, count := range counts { + var gpaths []string + var gexpects []string + for i := 0; i < count; i++ { + if i < len(paths) { + gpaths = append(gpaths, paths[i]) + gexpects = append(gexpects, expects[i]) + } else { + gpaths = append(gpaths, fmt.Sprintf("not%d", i)) + gexpects = append(gexpects, "null") + } + } + results := GetMany(json, gpaths...) + for i := 0; i < len(paths); i++ { + if results[i].String() != expects[i] { + t.Fatalf("expected '%v', got '%v'", expects[i], results[i].String()) + } + } + } +} +func TestManyRecursion(t *testing.T) { + var json string + var path string + for i := 0; i < 100; i++ { + json += `{"a":` + path += ".a" + } + json += `"b"` + for i := 0; i < 100; i++ { + json += `}` + } + path = path[1:] + assert(t, GetMany(json, path)[0].String() == "b") +} +func TestByteSafety(t *testing.T) { + jsonb := []byte(`{"name":"Janet","age":38}`) + mtok := GetBytes(jsonb, "name") + if mtok.String() != "Janet" { + t.Fatalf("expected %v, got %v", "Jason", mtok.String()) + } + mtok2 := GetBytes(jsonb, "age") + if mtok2.Raw != "38" { + t.Fatalf("expected %v, got %v", "Jason", mtok2.Raw) + } + jsonb[9] = 'T' + jsonb[12] = 'd' + jsonb[13] = 'y' + if mtok.String() != "Janet" { + t.Fatalf("expected %v, got %v", "Jason", mtok.String()) + } +} + +func get(json, path string) Result { + return GetBytes([]byte(json), path) +} + +func TestBasic(t *testing.T) { + var mtok Result + mtok = get(basicJSON, `loggy.programmers.#[tag="good"].firstName`) + if mtok.String() != "Brett" { + t.Fatalf("expected %v, got %v", "Brett", mtok.String()) + } + mtok = get(basicJSON, `loggy.programmers.#[tag="good"]#.firstName`) + if mtok.String() != `["Brett","Elliotte"]` { + t.Fatalf("expected %v, got %v", `["Brett","Elliotte"]`, mtok.String()) + } +} + +func TestIsArrayIsObject(t *testing.T) { + mtok := get(basicJSON, "loggy") + assert(t, mtok.IsObject()) + assert(t, !mtok.IsArray()) + + mtok = get(basicJSON, "loggy.programmers") + assert(t, !mtok.IsObject()) + assert(t, mtok.IsArray()) + + mtok = get(basicJSON, `loggy.programmers.#[tag="good"]#.firstName`) + assert(t, mtok.IsArray()) + + mtok = get(basicJSON, `loggy.programmers.0.firstName`) + assert(t, !mtok.IsObject()) + assert(t, !mtok.IsArray()) +} + +func TestPlus53BitInts(t *testing.T) { + json := `{"IdentityData":{"GameInstanceId":634866135153775564}}` + value := Get(json, "IdentityData.GameInstanceId") + assert(t, value.Uint() == 634866135153775564) + assert(t, value.Int() == 634866135153775564) + assert(t, value.Float() == 634866135153775616) + + json = `{"IdentityData":{"GameInstanceId":634866135153775564.88172}}` + value = Get(json, "IdentityData.GameInstanceId") + assert(t, value.Uint() == 634866135153775616) + assert(t, value.Int() == 634866135153775616) + assert(t, value.Float() == 634866135153775616.88172) + + json = `{ + "min_uint64": 0, + "max_uint64": 18446744073709551615, + "overflow_uint64": 18446744073709551616, + "min_int64": -9223372036854775808, + "max_int64": 9223372036854775807, + "overflow_int64": 9223372036854775808, + "min_uint53": 0, + "max_uint53": 4503599627370495, + "overflow_uint53": 4503599627370496, + "min_int53": -2251799813685248, + "max_int53": 2251799813685247, + "overflow_int53": 2251799813685248 + }` + + assert(t, Get(json, "min_uint53").Uint() == 0) + assert(t, Get(json, "max_uint53").Uint() == 4503599627370495) + assert(t, Get(json, "overflow_uint53").Int() == 4503599627370496) + assert(t, Get(json, "min_int53").Int() == -2251799813685248) + assert(t, Get(json, "max_int53").Int() == 2251799813685247) + assert(t, Get(json, "overflow_int53").Int() == 2251799813685248) + assert(t, Get(json, "min_uint64").Uint() == 0) + assert(t, Get(json, "max_uint64").Uint() == 18446744073709551615) + // this next value overflows the max uint64 by one which will just + // flip the number to zero + assert(t, Get(json, "overflow_uint64").Int() == 0) + assert(t, Get(json, "min_int64").Int() == -9223372036854775808) + assert(t, Get(json, "max_int64").Int() == 9223372036854775807) + // this next value overflows the max int64 by one which will just + // flip the number to the negative sign. + assert(t, Get(json, "overflow_int64").Int() == -9223372036854775808) +} +func TestIssue38(t *testing.T) { + // These should not fail, even though the unicode is invalid. + Get(`["S3O PEDRO DO BUTI\udf93"]`, "0") + Get(`["S3O PEDRO DO BUTI\udf93asdf"]`, "0") + Get(`["S3O PEDRO DO BUTI\udf93\u"]`, "0") + Get(`["S3O PEDRO DO BUTI\udf93\u1"]`, "0") + Get(`["S3O PEDRO DO BUTI\udf93\u13"]`, "0") + Get(`["S3O PEDRO DO BUTI\udf93\u134"]`, "0") + Get(`["S3O PEDRO DO BUTI\udf93\u1345"]`, "0") + Get(`["S3O PEDRO DO BUTI\udf93\u1345asd"]`, "0") +} +func TestTypes(t *testing.T) { + assert(t, (Result{Type: String}).Type.String() == "String") + assert(t, (Result{Type: Number}).Type.String() == "Number") + assert(t, (Result{Type: Null}).Type.String() == "Null") + assert(t, (Result{Type: False}).Type.String() == "False") + assert(t, (Result{Type: True}).Type.String() == "True") + assert(t, (Result{Type: JSON}).Type.String() == "JSON") + assert(t, (Result{Type: 100}).Type.String() == "") + // bool + assert(t, (Result{Type: String, Str: "true"}).Bool()) + assert(t, (Result{Type: True}).Bool()) + assert(t, (Result{Type: False}).Bool() == false) + assert(t, (Result{Type: Number, Num: 1}).Bool()) + // int + assert(t, (Result{Type: String, Str: "1"}).Int() == 1) + assert(t, (Result{Type: True}).Int() == 1) + assert(t, (Result{Type: False}).Int() == 0) + assert(t, (Result{Type: Number, Num: 1}).Int() == 1) + // uint + assert(t, (Result{Type: String, Str: "1"}).Uint() == 1) + assert(t, (Result{Type: True}).Uint() == 1) + assert(t, (Result{Type: False}).Uint() == 0) + assert(t, (Result{Type: Number, Num: 1}).Uint() == 1) + // float + assert(t, (Result{Type: String, Str: "1"}).Float() == 1) + assert(t, (Result{Type: True}).Float() == 1) + assert(t, (Result{Type: False}).Float() == 0) + assert(t, (Result{Type: Number, Num: 1}).Float() == 1) +} +func TestForEach(t *testing.T) { + Result{}.ForEach(nil) + Result{Type: String, Str: "Hello"}.ForEach(func(_, value Result) bool { + assert(t, value.String() == "Hello") + return false + }) + Result{Type: JSON, Raw: "*invalid*"}.ForEach(nil) + + json := ` {"name": {"first": "Janet","last": "Prichard"}, + "asd\nf":"\ud83d\udd13","age": 47}` + var count int + ParseBytes([]byte(json)).ForEach(func(key, value Result) bool { + count++ + return true + }) + assert(t, count == 3) + ParseBytes([]byte(`{"bad`)).ForEach(nil) + ParseBytes([]byte(`{"ok":"bad`)).ForEach(nil) +} +func TestMap(t *testing.T) { + assert(t, len(ParseBytes([]byte(`"asdf"`)).Map()) == 0) + assert(t, ParseBytes([]byte(`{"asdf":"ghjk"`)).Map()["asdf"].String() == "ghjk") + assert(t, len(Result{Type: JSON, Raw: "**invalid**"}.Map()) == 0) + assert(t, Result{Type: JSON, Raw: "**invalid**"}.Value() == nil) + assert(t, Result{Type: JSON, Raw: "{"}.Map() != nil) +} +func TestBasic1(t *testing.T) { + mtok := get(basicJSON, `loggy.programmers`) + var count int + mtok.ForEach(func(key, value Result) bool { + if key.Exists() { + t.Fatalf("expected %v, got %v", false, key.Exists()) + } + count++ + if count == 3 { + return false + } + if count == 1 { + i := 0 + value.ForEach(func(key, value Result) bool { + switch i { + case 0: + if key.String() != "firstName" || value.String() != "Brett" { + t.Fatalf("expected %v/%v got %v/%v", "firstName", "Brett", key.String(), value.String()) + } + case 1: + if key.String() != "lastName" || value.String() != "McLaughlin" { + t.Fatalf("expected %v/%v got %v/%v", "lastName", "McLaughlin", key.String(), value.String()) + } + case 2: + if key.String() != "email" || value.String() != "aaaa" { + t.Fatalf("expected %v/%v got %v/%v", "email", "aaaa", key.String(), value.String()) + } + } + i++ + return true + }) + } + return true + }) + if count != 3 { + t.Fatalf("expected %v, got %v", 3, count) + } +} +func TestBasic2(t *testing.T) { + mtok := get(basicJSON, `loggy.programmers.#[age=101].firstName`) + if mtok.String() != "1002.3" { + t.Fatalf("expected %v, got %v", "1002.3", mtok.String()) + } + mtok = get(basicJSON, `loggy.programmers.#[firstName != "Brett"].firstName`) + if mtok.String() != "Jason" { + t.Fatalf("expected %v, got %v", "Jason", mtok.String()) + } + mtok = get(basicJSON, `loggy.programmers.#[firstName % "Bre*"].email`) + if mtok.String() != "aaaa" { + t.Fatalf("expected %v, got %v", "aaaa", mtok.String()) + } + mtok = get(basicJSON, `loggy.programmers.#[firstName == "Brett"].email`) + if mtok.String() != "aaaa" { + t.Fatalf("expected %v, got %v", "aaaa", mtok.String()) + } + mtok = get(basicJSON, "loggy") + if mtok.Type != JSON { + t.Fatalf("expected %v, got %v", JSON, mtok.Type) + } + if len(mtok.Map()) != 1 { + t.Fatalf("expected %v, got %v", 1, len(mtok.Map())) + } + programmers := mtok.Map()["programmers"] + if programmers.Array()[1].Map()["firstName"].Str != "Jason" { + t.Fatalf("expected %v, got %v", "Jason", mtok.Map()["programmers"].Array()[1].Map()["firstName"].Str) + } +} +func TestBasic3(t *testing.T) { + var mtok Result + if Parse(basicJSON).Get("loggy.programmers").Get("1").Get("firstName").Str != "Jason" { + t.Fatalf("expected %v, got %v", "Jason", Parse(basicJSON).Get("loggy.programmers").Get("1").Get("firstName").Str) + } + var token Result + if token = Parse("-102"); token.Num != -102 { + t.Fatalf("expected %v, got %v", -102, token.Num) + } + if token = Parse("102"); token.Num != 102 { + t.Fatalf("expected %v, got %v", 102, token.Num) + } + if token = Parse("102.2"); token.Num != 102.2 { + t.Fatalf("expected %v, got %v", 102.2, token.Num) + } + if token = Parse(`"hello"`); token.Str != "hello" { + t.Fatalf("expected %v, got %v", "hello", token.Str) + } + if token = Parse(`"\"he\nllo\""`); token.Str != "\"he\nllo\"" { + t.Fatalf("expected %v, got %v", "\"he\nllo\"", token.Str) + } + mtok = get(basicJSON, "loggy.programmers.#.firstName") + if len(mtok.Array()) != 4 { + t.Fatalf("expected 4, got %v", len(mtok.Array())) + } + for i, ex := range []string{"Brett", "Jason", "Elliotte", "1002.3"} { + if mtok.Array()[i].String() != ex { + t.Fatalf("expected '%v', got '%v'", ex, mtok.Array()[i].String()) + } + } + mtok = get(basicJSON, "loggy.programmers.#.asd") + if mtok.Type != JSON { + t.Fatalf("expected %v, got %v", JSON, mtok.Type) + } + if len(mtok.Array()) != 0 { + t.Fatalf("expected 0, got %v", len(mtok.Array())) + } +} +func TestBasic4(t *testing.T) { + if get(basicJSON, "items.3.tags.#").Num != 3 { + t.Fatalf("expected 3, got %v", get(basicJSON, "items.3.tags.#").Num) + } + if get(basicJSON, "items.3.points.1.#").Num != 2 { + t.Fatalf("expected 2, got %v", get(basicJSON, "items.3.points.1.#").Num) + } + if get(basicJSON, "items.#").Num != 8 { + t.Fatalf("expected 6, got %v", get(basicJSON, "items.#").Num) + } + if get(basicJSON, "vals.#").Num != 4 { + t.Fatalf("expected 4, got %v", get(basicJSON, "vals.#").Num) + } + if !get(basicJSON, "name.last").Exists() { + t.Fatal("expected true, got false") + } + token := get(basicJSON, "name.here") + if token.String() != "B\\\"R" { + t.Fatal("expecting 'B\\\"R'", "got", token.String()) + } + token = get(basicJSON, "arr.#") + if token.String() != "6" { + t.Fatal("expecting '6'", "got", token.String()) + } + token = get(basicJSON, "arr.3.hello") + if token.String() != "world" { + t.Fatal("expecting 'world'", "got", token.String()) + } + _ = token.Value().(string) + token = get(basicJSON, "name.first") + if token.String() != "tom" { + t.Fatal("expecting 'tom'", "got", token.String()) + } + _ = token.Value().(string) + token = get(basicJSON, "name.last") + if token.String() != "" { + t.Fatal("expecting ''", "got", token.String()) + } + if token.Value() != nil { + t.Fatal("should be nil") + } +} +func TestBasic5(t *testing.T) { + token := get(basicJSON, "age") + if token.String() != "100" { + t.Fatal("expecting '100'", "got", token.String()) + } + _ = token.Value().(float64) + token = get(basicJSON, "happy") + if token.String() != "true" { + t.Fatal("expecting 'true'", "got", token.String()) + } + _ = token.Value().(bool) + token = get(basicJSON, "immortal") + if token.String() != "false" { + t.Fatal("expecting 'false'", "got", token.String()) + } + _ = token.Value().(bool) + token = get(basicJSON, "noop") + if token.String() != `{"what is a wren?":"a bird"}` { + t.Fatal("expecting '"+`{"what is a wren?":"a bird"}`+"'", "got", token.String()) + } + _ = token.Value().(map[string]interface{}) + + if get(basicJSON, "").Value() != nil { + t.Fatal("should be nil") + } + + get(basicJSON, "vals.hello") + + mm := Parse(basicJSON).Value().(map[string]interface{}) + fn := mm["loggy"].(map[string]interface{})["programmers"].([]interface{})[1].(map[string]interface{})["firstName"].(string) + if fn != "Jason" { + t.Fatalf("expecting %v, got %v", "Jason", fn) + } +} +func TestUnicode(t *testing.T) { + var json = `{"key":0,"的情况下解":{"key":1,"的情况":2}}` + if Get(json, "的情况下解.key").Num != 1 { + t.Fatal("fail") + } + if Get(json, "的情况下解.的情况").Num != 2 { + t.Fatal("fail") + } + if Get(json, "的情况下解.的?况").Num != 2 { + t.Fatal("fail") + } + if Get(json, "的情况下解.的?*").Num != 2 { + t.Fatal("fail") + } + if Get(json, "的情况下解.*?况").Num != 2 { + t.Fatal("fail") + } + if Get(json, "的情?下解.*?况").Num != 2 { + t.Fatal("fail") + } + if Get(json, "的情下解.*?况").Num != 0 { + t.Fatal("fail") + } +} + +func TestUnescape(t *testing.T) { + unescape(string([]byte{'\\', '\\', 0})) + unescape(string([]byte{'\\', '/', '\\', 'b', '\\', 'f'})) +} +func assert(t testing.TB, cond bool) { + if !cond { + panic("assert failed") + } +} +func TestLess(t *testing.T) { + assert(t, !Result{Type: Null}.Less(Result{Type: Null}, true)) + assert(t, Result{Type: Null}.Less(Result{Type: False}, true)) + assert(t, Result{Type: Null}.Less(Result{Type: True}, true)) + assert(t, Result{Type: Null}.Less(Result{Type: JSON}, true)) + assert(t, Result{Type: Null}.Less(Result{Type: Number}, true)) + assert(t, Result{Type: Null}.Less(Result{Type: String}, true)) + assert(t, !Result{Type: False}.Less(Result{Type: Null}, true)) + assert(t, Result{Type: False}.Less(Result{Type: True}, true)) + assert(t, Result{Type: String, Str: "abc"}.Less(Result{Type: String, Str: "bcd"}, true)) + assert(t, Result{Type: String, Str: "ABC"}.Less(Result{Type: String, Str: "abc"}, true)) + assert(t, !Result{Type: String, Str: "ABC"}.Less(Result{Type: String, Str: "abc"}, false)) + assert(t, Result{Type: Number, Num: 123}.Less(Result{Type: Number, Num: 456}, true)) + assert(t, !Result{Type: Number, Num: 456}.Less(Result{Type: Number, Num: 123}, true)) + assert(t, !Result{Type: Number, Num: 456}.Less(Result{Type: Number, Num: 456}, true)) + assert(t, stringLessInsensitive("abcde", "BBCDE")) + assert(t, stringLessInsensitive("abcde", "bBCDE")) + assert(t, stringLessInsensitive("Abcde", "BBCDE")) + assert(t, stringLessInsensitive("Abcde", "bBCDE")) + assert(t, !stringLessInsensitive("bbcde", "aBCDE")) + assert(t, !stringLessInsensitive("bbcde", "ABCDE")) + assert(t, !stringLessInsensitive("Bbcde", "aBCDE")) + assert(t, !stringLessInsensitive("Bbcde", "ABCDE")) + assert(t, !stringLessInsensitive("abcde", "ABCDE")) + assert(t, !stringLessInsensitive("Abcde", "ABCDE")) + assert(t, !stringLessInsensitive("abcde", "ABCDE")) + assert(t, !stringLessInsensitive("ABCDE", "ABCDE")) + assert(t, !stringLessInsensitive("abcde", "abcde")) + assert(t, !stringLessInsensitive("123abcde", "123Abcde")) + assert(t, !stringLessInsensitive("123Abcde", "123Abcde")) + assert(t, !stringLessInsensitive("123Abcde", "123abcde")) + assert(t, !stringLessInsensitive("123abcde", "123abcde")) + assert(t, !stringLessInsensitive("124abcde", "123abcde")) + assert(t, !stringLessInsensitive("124Abcde", "123Abcde")) + assert(t, !stringLessInsensitive("124Abcde", "123abcde")) + assert(t, !stringLessInsensitive("124abcde", "123abcde")) + assert(t, stringLessInsensitive("124abcde", "125abcde")) + assert(t, stringLessInsensitive("124Abcde", "125Abcde")) + assert(t, stringLessInsensitive("124Abcde", "125abcde")) + assert(t, stringLessInsensitive("124abcde", "125abcde")) +} + +func TestIssue6(t *testing.T) { + data := `{ + "code": 0, + "msg": "", + "data": { + "sz002024": { + "qfqday": [ + [ + "2014-01-02", + "8.93", + "9.03", + "9.17", + "8.88", + "621143.00" + ], + [ + "2014-01-03", + "9.03", + "9.30", + "9.47", + "8.98", + "1624438.00" + ] + ] + } + } + }` + + var num []string + for _, v := range Get(data, "data.sz002024.qfqday.0").Array() { + num = append(num, v.String()) + } + if fmt.Sprintf("%v", num) != "[2014-01-02 8.93 9.03 9.17 8.88 621143.00]" { + t.Fatalf("invalid result") + } +} + +var exampleJSON = `{ + "widget": { + "debug": "on", + "window": { + "title": "Sample Konfabulator Widget", + "name": "main_window", + "width": 500, + "height": 500 + }, + "image": { + "src": "Images/Sun.png", + "hOffset": 250, + "vOffset": 250, + "alignment": "center" + }, + "text": { + "data": "Click Here", + "size": 36, + "style": "bold", + "vOffset": 100, + "alignment": "center", + "onMouseUp": "sun1.opacity = (sun1.opacity / 100) * 90;" + } + } +}` + +func TestNewParse(t *testing.T) { + //fmt.Printf("%v\n", parse2(exampleJSON, "widget").String()) +} + +func TestUnmarshalMap(t *testing.T) { + var m1 = Parse(exampleJSON).Value().(map[string]interface{}) + var m2 map[string]interface{} + if err := json.Unmarshal([]byte(exampleJSON), &m2); err != nil { + t.Fatal(err) + } + b1, err := json.Marshal(m1) + if err != nil { + t.Fatal(err) + } + b2, err := json.Marshal(m2) + if err != nil { + t.Fatal(err) + } + if bytes.Compare(b1, b2) != 0 { + t.Fatal("b1 != b2") + } +} + +func TestSingleArrayValue(t *testing.T) { + var json = `{"key": "value","key2":[1,2,3,4,"A"]}` + var result = Get(json, "key") + var array = result.Array() + if len(array) != 1 { + t.Fatal("array is empty") + } + if array[0].String() != "value" { + t.Fatalf("got %s, should be %s", array[0].String(), "value") + } + + array = Get(json, "key2.#").Array() + if len(array) != 1 { + t.Fatalf("got '%v', expected '%v'", len(array), 1) + } + + array = Get(json, "key3").Array() + if len(array) != 0 { + t.Fatalf("got '%v', expected '%v'", len(array), 0) + } + +} + +var manyJSON = ` { + "a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{ + "a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{ + "a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{ + "a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{ + "a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{ + "a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{ + "a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"hello":"world" + }}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}} + "position":{"type":"Point","coordinates":[-115.24,33.09]}, + "loves":["world peace"], + "name":{"last":"Anderson","first":"Nancy"}, + "age":31 + "":{"a":"emptya","b":"emptyb"}, + "name.last":"Yellow", + "name.first":"Cat", +}` + +func combine(results []Result) string { + return fmt.Sprintf("%v", results) +} +func TestManyBasic(t *testing.T) { + testWatchForFallback = true + defer func() { + testWatchForFallback = false + }() + testMany := func(shouldFallback bool, expect string, paths ...string) { + results := GetManyBytes( + []byte(manyJSON), + paths..., + ) + if len(results) != len(paths) { + t.Fatalf("expected %v, got %v", len(paths), len(results)) + } + if fmt.Sprintf("%v", results) != expect { + fmt.Printf("%v\n", paths) + t.Fatalf("expected %v, got %v", expect, results) + } + //if testLastWasFallback != shouldFallback { + // t.Fatalf("expected %v, got %v", shouldFallback, testLastWasFallback) + //} + } + testMany(false, "[Point]", "position.type") + testMany(false, `[emptya ["world peace"] 31]`, ".a", "loves", "age") + testMany(false, `[["world peace"]]`, "loves") + testMany(false, `[{"last":"Anderson","first":"Nancy"} Nancy]`, "name", "name.first") + testMany(true, `[]`, strings.Repeat("a.", 40)+"hello") + res := Get(manyJSON, strings.Repeat("a.", 48)+"a") + testMany(true, `[`+res.String()+`]`, strings.Repeat("a.", 48)+"a") + // these should fallback + testMany(true, `[Cat Nancy]`, "name\\.first", "name.first") + testMany(true, `[world]`, strings.Repeat("a.", 70)+"hello") +} +func testMany(t *testing.T, json string, paths, expected []string) { + testManyAny(t, json, paths, expected, true) + testManyAny(t, json, paths, expected, false) +} +func testManyAny(t *testing.T, json string, paths, expected []string, bytes bool) { + var result []Result + for i := 0; i < 2; i++ { + var which string + if i == 0 { + which = "Get" + result = nil + for j := 0; j < len(expected); j++ { + if bytes { + result = append(result, GetBytes([]byte(json), paths[j])) + } else { + result = append(result, Get(json, paths[j])) + } + } + } else if i == 1 { + which = "GetMany" + if bytes { + result = GetManyBytes([]byte(json), paths...) + } else { + result = GetMany(json, paths...) + } + } + for j := 0; j < len(expected); j++ { + if result[j].String() != expected[j] { + t.Fatalf("Using key '%s' for '%s'\nexpected '%v', got '%v'", paths[j], which, expected[j], result[j].String()) + } + } + } +} +func TestIssue20(t *testing.T) { + json := `{ "name": "FirstName", "name1": "FirstName1", "address": "address1", "addressDetails": "address2", }` + paths := []string{"name", "name1", "address", "addressDetails"} + expected := []string{"FirstName", "FirstName1", "address1", "address2"} + t.Run("SingleMany", func(t *testing.T) { testMany(t, json, paths, expected) }) +} + +func TestIssue21(t *testing.T) { + json := `{ "Level1Field1":3, + "Level1Field4":4, + "Level1Field2":{ "Level2Field1":[ "value1", "value2" ], + "Level2Field2":{ "Level3Field1":[ { "key1":"value1" } ] } } }` + paths := []string{"Level1Field1", "Level1Field2.Level2Field1", "Level1Field2.Level2Field2.Level3Field1", "Level1Field4"} + expected := []string{"3", `[ "value1", "value2" ]`, `[ { "key1":"value1" } ]`, "4"} + t.Run("SingleMany", func(t *testing.T) { testMany(t, json, paths, expected) }) +} + +func TestRandomMany(t *testing.T) { + var lstr string + defer func() { + if v := recover(); v != nil { + println("'" + hex.EncodeToString([]byte(lstr)) + "'") + println("'" + lstr + "'") + panic(v) + } + }() + rand.Seed(time.Now().UnixNano()) + b := make([]byte, 512) + for i := 0; i < 50000; i++ { + n, err := rand.Read(b[:rand.Int()%len(b)]) + if err != nil { + t.Fatal(err) + } + lstr = string(b[:n]) + paths := make([]string, rand.Int()%64) + for i := range paths { + var b []byte + n := rand.Int() % 5 + for j := 0; j < n; j++ { + if j > 0 { + b = append(b, '.') + } + nn := rand.Int() % 10 + for k := 0; k < nn; k++ { + b = append(b, 'a'+byte(rand.Int()%26)) + } + } + paths[i] = string(b) + } + GetMany(lstr, paths...) + } +} + +type ComplicatedType struct { + unsettable int + Tagged string `json:"tagged"` + NotTagged bool + Nested struct { + Yellow string `json:"yellow"` + } + NestedTagged struct { + Green string + Map map[string]interface{} + Ints struct { + Int int `json:"int"` + Int8 int8 + Int16 int16 + Int32 int32 + Int64 int64 `json:"int64"` + } + Uints struct { + Uint uint + Uint8 uint8 + Uint16 uint16 + Uint32 uint32 + Uint64 uint64 + } + Floats struct { + Float64 float64 + Float32 float32 + } + Byte byte + Bool bool + } `json:"nestedTagged"` + LeftOut string `json:"-"` + SelfPtr *ComplicatedType + SelfSlice []ComplicatedType + SelfSlicePtr []*ComplicatedType + SelfPtrSlice *[]ComplicatedType + Interface interface{} `json:"interface"` + Array [3]int + Time time.Time `json:"time"` + Binary []byte + NonBinary []byte +} + +var complicatedJSON = ` +{ + "tagged": "OK", + "Tagged": "KO", + "NotTagged": true, + "unsettable": 101, + "Nested": { + "Yellow": "Green", + "yellow": "yellow" + }, + "nestedTagged": { + "Green": "Green", + "Map": { + "this": "that", + "and": "the other thing" + }, + "Ints": { + "Uint": 99, + "Uint16": 16, + "Uint32": 32, + "Uint64": 65 + }, + "Uints": { + "int": -99, + "Int": -98, + "Int16": -16, + "Int32": -32, + "int64": -64, + "Int64": -65 + }, + "Uints": { + "Float32": 32.32, + "Float64": 64.64 + }, + "Byte": 254, + "Bool": true + }, + "LeftOut": "you shouldn't be here", + "SelfPtr": {"tagged":"OK","nestedTagged":{"Ints":{"Uint32":32}}}, + "SelfSlice": [{"tagged":"OK","nestedTagged":{"Ints":{"Uint32":32}}}], + "SelfSlicePtr": [{"tagged":"OK","nestedTagged":{"Ints":{"Uint32":32}}}], + "SelfPtrSlice": [{"tagged":"OK","nestedTagged":{"Ints":{"Uint32":32}}}], + "interface": "Tile38 Rocks!", + "Interface": "Please Download", + "Array": [0,2,3,4,5], + "time": "2017-05-07T13:24:43-07:00", + "Binary": "R0lGODlhPQBEAPeo", + "NonBinary": [9,3,100,115] +} +` + +func TestUnmarshal(t *testing.T) { + var s1 ComplicatedType + var s2 ComplicatedType + if err := json.Unmarshal([]byte(complicatedJSON), &s1); err != nil { + t.Fatal(err) + } + if err := Unmarshal([]byte(complicatedJSON), &s2); err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(&s1, &s2) { + t.Fatal("not equal") + } + var str string + if err := json.Unmarshal([]byte(Get(complicatedJSON, "LeftOut").Raw), &str); err != nil { + t.Fatal(err) + } + assert(t, str == Get(complicatedJSON, "LeftOut").String()) +} + +func testvalid(json string, expect bool) { + _, ok := validpayload([]byte(json), 0) + if ok != expect { + panic("mismatch") + } +} + +func TestValidBasic(t *testing.T) { + testvalid("0", true) + testvalid("00", false) + testvalid("-00", false) + testvalid("-.", false) + testvalid("0.0", true) + testvalid("10.0", true) + testvalid("10e1", true) + testvalid("10EE", false) + testvalid("10E-", false) + testvalid("10E+", false) + testvalid("10E123", true) + testvalid("10E-123", true) + testvalid("10E-0123", true) + testvalid("", false) + testvalid(" ", false) + testvalid("{}", true) + testvalid("{", false) + testvalid("-", false) + testvalid("-1", true) + testvalid("-1.", false) + testvalid("-1.0", true) + testvalid(" -1.0", true) + testvalid(" -1.0 ", true) + testvalid("-1.0 ", true) + testvalid("-1.0 i", false) + testvalid("-1.0 i", false) + testvalid("true", true) + testvalid(" true", true) + testvalid(" true ", true) + testvalid(" True ", false) + testvalid(" tru", false) + testvalid("false", true) + testvalid(" false", true) + testvalid(" false ", true) + testvalid(" False ", false) + testvalid(" fals", false) + testvalid("null", true) + testvalid(" null", true) + testvalid(" null ", true) + testvalid(" Null ", false) + testvalid(" nul", false) + testvalid(" []", true) + testvalid(" [true]", true) + testvalid(" [ true, null ]", true) + testvalid(" [ true,]", false) + testvalid(`{"hello":"world"}`, true) + testvalid(`{ "hello": "world" }`, true) + testvalid(`{ "hello": "world", }`, false) + testvalid(`{"a":"b",}`, false) + testvalid(`{"a":"b","a"}`, false) + testvalid(`{"a":"b","a":}`, false) + testvalid(`{"a":"b","a":1}`, true) + testvalid(`{"a":"b","a": 1, "c":{"hi":"there"} }`, true) + testvalid(`{"a":"b","a": 1, "c":{"hi":"there", "easy":["going",{"mixed":"bag"}]} }`, true) + testvalid(`""`, true) + testvalid(`"`, false) + testvalid(`"\n"`, true) + testvalid(`"\"`, false) + testvalid(`"\\"`, true) + testvalid(`"a\\b"`, true) + testvalid(`"a\\b\\\"a"`, true) + testvalid(`"a\\b\\\uFFAAa"`, true) + testvalid(`"a\\b\\\uFFAZa"`, false) + testvalid(`"a\\b\\\uFFA"`, false) + testvalid(string(complicatedJSON), true) + testvalid(string(exampleJSON), true) +} + +var jsonchars = []string{"{", "[", ",", ":", "}", "]", "1", "0", "true", "false", "null", `""`, `"\""`, `"a"`} + +func makeRandomJSONChars(b []byte) { + var bb []byte + for len(bb) < len(b) { + bb = append(bb, jsonchars[rand.Int()%len(jsonchars)]...) + } + copy(b, bb[:len(b)]) +} +func TestValidRandom(t *testing.T) { + rand.Seed(time.Now().UnixNano()) + b := make([]byte, 100000) + start := time.Now() + for time.Since(start) < time.Second*3 { + n := rand.Int() % len(b) + rand.Read(b[:n]) + validpayload(b[:n], 0) + } + + start = time.Now() + for time.Since(start) < time.Second*3 { + n := rand.Int() % len(b) + makeRandomJSONChars(b[:n]) + validpayload(b[:n], 0) + } +} diff --git a/vendor/github.com/tidwall/gjson/logo.png b/vendor/github.com/tidwall/gjson/logo.png new file mode 100644 index 0000000..17a8bbe Binary files /dev/null and b/vendor/github.com/tidwall/gjson/logo.png differ diff --git a/vendor/github.com/tidwall/match/.travis.yml b/vendor/github.com/tidwall/match/.travis.yml new file mode 100644 index 0000000..4f2ee4d --- /dev/null +++ b/vendor/github.com/tidwall/match/.travis.yml @@ -0,0 +1 @@ +language: go diff --git a/vendor/github.com/tidwall/match/LICENSE b/vendor/github.com/tidwall/match/LICENSE new file mode 100644 index 0000000..58f5819 --- /dev/null +++ b/vendor/github.com/tidwall/match/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2016 Josh Baker + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/github.com/tidwall/match/README.md b/vendor/github.com/tidwall/match/README.md new file mode 100644 index 0000000..04b0aaa --- /dev/null +++ b/vendor/github.com/tidwall/match/README.md @@ -0,0 +1,31 @@ +Match +===== +Build Status +GoDoc + +Match is a very simple pattern matcher where '*' matches on any +number characters and '?' matches on any one character. +Installing +---------- + +``` +go get -u github.com/tidwall/match +``` + +Example +------- + +```go +match.Match("hello", "*llo") +match.Match("jello", "?ello") +match.Match("hello", "h*o") +``` + + +Contact +------- +Josh Baker [@tidwall](http://twitter.com/tidwall) + +License +------- +Redcon source code is available under the MIT [License](/LICENSE). diff --git a/vendor/github.com/tidwall/match/match.go b/vendor/github.com/tidwall/match/match.go new file mode 100644 index 0000000..8885add --- /dev/null +++ b/vendor/github.com/tidwall/match/match.go @@ -0,0 +1,192 @@ +// Match provides a simple pattern matcher with unicode support. +package match + +import "unicode/utf8" + +// Match returns true if str matches pattern. This is a very +// simple wildcard match where '*' matches on any number characters +// and '?' matches on any one character. + +// pattern: +// { term } +// term: +// '*' matches any sequence of non-Separator characters +// '?' matches any single non-Separator character +// c matches character c (c != '*', '?', '\\') +// '\\' c matches character c +// +func Match(str, pattern string) bool { + if pattern == "*" { + return true + } + return deepMatch(str, pattern) +} +func deepMatch(str, pattern string) bool { + for len(pattern) > 0 { + if pattern[0] > 0x7f { + return deepMatchRune(str, pattern) + } + switch pattern[0] { + default: + if len(str) == 0 { + return false + } + if str[0] > 0x7f { + return deepMatchRune(str, pattern) + } + if str[0] != pattern[0] { + return false + } + case '?': + if len(str) == 0 { + return false + } + case '*': + return deepMatch(str, pattern[1:]) || + (len(str) > 0 && deepMatch(str[1:], pattern)) + } + str = str[1:] + pattern = pattern[1:] + } + return len(str) == 0 && len(pattern) == 0 +} + +func deepMatchRune(str, pattern string) bool { + var sr, pr rune + var srsz, prsz int + + // read the first rune ahead of time + if len(str) > 0 { + if str[0] > 0x7f { + sr, srsz = utf8.DecodeRuneInString(str) + } else { + sr, srsz = rune(str[0]), 1 + } + } else { + sr, srsz = utf8.RuneError, 0 + } + if len(pattern) > 0 { + if pattern[0] > 0x7f { + pr, prsz = utf8.DecodeRuneInString(pattern) + } else { + pr, prsz = rune(pattern[0]), 1 + } + } else { + pr, prsz = utf8.RuneError, 0 + } + // done reading + for pr != utf8.RuneError { + switch pr { + default: + if srsz == utf8.RuneError { + return false + } + if sr != pr { + return false + } + case '?': + if srsz == utf8.RuneError { + return false + } + case '*': + return deepMatchRune(str, pattern[prsz:]) || + (srsz > 0 && deepMatchRune(str[srsz:], pattern)) + } + str = str[srsz:] + pattern = pattern[prsz:] + // read the next runes + if len(str) > 0 { + if str[0] > 0x7f { + sr, srsz = utf8.DecodeRuneInString(str) + } else { + sr, srsz = rune(str[0]), 1 + } + } else { + sr, srsz = utf8.RuneError, 0 + } + if len(pattern) > 0 { + if pattern[0] > 0x7f { + pr, prsz = utf8.DecodeRuneInString(pattern) + } else { + pr, prsz = rune(pattern[0]), 1 + } + } else { + pr, prsz = utf8.RuneError, 0 + } + // done reading + } + + return srsz == 0 && prsz == 0 +} + +var maxRuneBytes = func() []byte { + b := make([]byte, 4) + if utf8.EncodeRune(b, '\U0010FFFF') != 4 { + panic("invalid rune encoding") + } + return b +}() + +// Allowable parses the pattern and determines the minimum and maximum allowable +// values that the pattern can represent. +// When the max cannot be determined, 'true' will be returned +// for infinite. +func Allowable(pattern string) (min, max string) { + if pattern == "" || pattern[0] == '*' { + return "", "" + } + + minb := make([]byte, 0, len(pattern)) + maxb := make([]byte, 0, len(pattern)) + var wild bool + for i := 0; i < len(pattern); i++ { + if pattern[i] == '*' { + wild = true + break + } + if pattern[i] == '?' { + minb = append(minb, 0) + maxb = append(maxb, maxRuneBytes...) + } else { + minb = append(minb, pattern[i]) + maxb = append(maxb, pattern[i]) + } + } + if wild { + r, n := utf8.DecodeLastRune(maxb) + if r != utf8.RuneError { + if r < utf8.MaxRune { + r++ + if r > 0x7f { + b := make([]byte, 4) + nn := utf8.EncodeRune(b, r) + maxb = append(maxb[:len(maxb)-n], b[:nn]...) + } else { + maxb = append(maxb[:len(maxb)-n], byte(r)) + } + } + } + } + return string(minb), string(maxb) + /* + return + if wild { + r, n := utf8.DecodeLastRune(maxb) + if r != utf8.RuneError { + if r < utf8.MaxRune { + infinite = true + } else { + r++ + if r > 0x7f { + b := make([]byte, 4) + nn := utf8.EncodeRune(b, r) + maxb = append(maxb[:len(maxb)-n], b[:nn]...) + } else { + maxb = append(maxb[:len(maxb)-n], byte(r)) + } + } + } + } + return string(minb), string(maxb), infinite + */ +} diff --git a/vendor/github.com/tidwall/match/match_test.go b/vendor/github.com/tidwall/match/match_test.go new file mode 100644 index 0000000..032ee59 --- /dev/null +++ b/vendor/github.com/tidwall/match/match_test.go @@ -0,0 +1,408 @@ +package match + +import ( + "fmt" + "math/rand" + "testing" + "time" + "unicode/utf8" +) + +func TestMatch(t *testing.T) { + if !Match("hello world", "hello world") { + t.Fatal("fail") + } + if Match("hello world", "jello world") { + t.Fatal("fail") + } + if !Match("hello world", "hello*") { + t.Fatal("fail") + } + if Match("hello world", "jello*") { + t.Fatal("fail") + } + if !Match("hello world", "hello?world") { + t.Fatal("fail") + } + if Match("hello world", "jello?world") { + t.Fatal("fail") + } + if !Match("hello world", "he*o?world") { + t.Fatal("fail") + } + if !Match("hello world", "he*o?wor*") { + t.Fatal("fail") + } + if !Match("hello world", "he*o?*r*") { + t.Fatal("fail") + } + if !Match("的情况下解析一个", "*") { + t.Fatal("fail") + } + if !Match("的情况下解析一个", "*况下*") { + t.Fatal("fail") + } + if !Match("的情况下解析一个", "*况?*") { + t.Fatal("fail") + } + if !Match("的情况下解析一个", "的情况?解析一个") { + t.Fatal("fail") + } +} + +// TestWildcardMatch - Tests validate the logic of wild card matching. +// `WildcardMatch` supports '*' and '?' wildcards. +// Sample usage: In resource matching for folder policy validation. +func TestWildcardMatch(t *testing.T) { + testCases := []struct { + pattern string + text string + matched bool + }{ + // Test case - 1. + // Test case with pattern containing key name with a prefix. Should accept the same text without a "*". + { + pattern: "my-folder/oo*", + text: "my-folder/oo", + matched: true, + }, + // Test case - 2. + // Test case with "*" at the end of the pattern. + { + pattern: "my-folder/In*", + text: "my-folder/India/Karnataka/", + matched: true, + }, + // Test case - 3. + // Test case with prefixes shuffled. + // This should fail. + { + pattern: "my-folder/In*", + text: "my-folder/Karnataka/India/", + matched: false, + }, + // Test case - 4. + // Test case with text expanded to the wildcards in the pattern. + { + pattern: "my-folder/In*/Ka*/Ban", + text: "my-folder/India/Karnataka/Ban", + matched: true, + }, + // Test case - 5. + // Test case with the keyname part is repeated as prefix several times. + // This is valid. + { + pattern: "my-folder/In*/Ka*/Ban", + text: "my-folder/India/Karnataka/Ban/Ban/Ban/Ban/Ban", + matched: true, + }, + // Test case - 6. + // Test case to validate that `*` can be expanded into multiple prefixes. + { + pattern: "my-folder/In*/Ka*/Ban", + text: "my-folder/India/Karnataka/Area1/Area2/Area3/Ban", + matched: true, + }, + // Test case - 7. + // Test case to validate that `*` can be expanded into multiple prefixes. + { + pattern: "my-folder/In*/Ka*/Ban", + text: "my-folder/India/State1/State2/Karnataka/Area1/Area2/Area3/Ban", + matched: true, + }, + // Test case - 8. + // Test case where the keyname part of the pattern is expanded in the text. + { + pattern: "my-folder/In*/Ka*/Ban", + text: "my-folder/India/Karnataka/Bangalore", + matched: false, + }, + // Test case - 9. + // Test case with prefixes and wildcard expanded for all "*". + { + pattern: "my-folder/In*/Ka*/Ban*", + text: "my-folder/India/Karnataka/Bangalore", + matched: true, + }, + // Test case - 10. + // Test case with keyname part being a wildcard in the pattern. + {pattern: "my-folder/*", + text: "my-folder/India", + matched: true, + }, + // Test case - 11. + { + pattern: "my-folder/oo*", + text: "my-folder/odo", + matched: false, + }, + + // Test case with pattern containing wildcard '?'. + // Test case - 12. + // "my-folder?/" matches "my-folder1/", "my-folder2/", "my-folder3" etc... + // doesn't match "myfolder/". + { + pattern: "my-folder?/abc*", + text: "myfolder/abc", + matched: false, + }, + // Test case - 13. + { + pattern: "my-folder?/abc*", + text: "my-folder1/abc", + matched: true, + }, + // Test case - 14. + { + pattern: "my-?-folder/abc*", + text: "my--folder/abc", + matched: false, + }, + // Test case - 15. + { + pattern: "my-?-folder/abc*", + text: "my-1-folder/abc", + matched: true, + }, + // Test case - 16. + { + pattern: "my-?-folder/abc*", + text: "my-k-folder/abc", + matched: true, + }, + // Test case - 17. + { + pattern: "my??folder/abc*", + text: "myfolder/abc", + matched: false, + }, + // Test case - 18. + { + pattern: "my??folder/abc*", + text: "my4afolder/abc", + matched: true, + }, + // Test case - 19. + { + pattern: "my-folder?abc*", + text: "my-folder/abc", + matched: true, + }, + // Test case 20-21. + // '?' matches '/' too. (works with s3). + // This is because the namespace is considered flat. + // "abc?efg" matches both "abcdefg" and "abc/efg". + { + pattern: "my-folder/abc?efg", + text: "my-folder/abcdefg", + matched: true, + }, + { + pattern: "my-folder/abc?efg", + text: "my-folder/abc/efg", + matched: true, + }, + // Test case - 22. + { + pattern: "my-folder/abc????", + text: "my-folder/abc", + matched: false, + }, + // Test case - 23. + { + pattern: "my-folder/abc????", + text: "my-folder/abcde", + matched: false, + }, + // Test case - 24. + { + pattern: "my-folder/abc????", + text: "my-folder/abcdefg", + matched: true, + }, + // Test case 25-26. + // test case with no '*'. + { + pattern: "my-folder/abc?", + text: "my-folder/abc", + matched: false, + }, + { + pattern: "my-folder/abc?", + text: "my-folder/abcd", + matched: true, + }, + { + pattern: "my-folder/abc?", + text: "my-folder/abcde", + matched: false, + }, + // Test case 27. + { + pattern: "my-folder/mnop*?", + text: "my-folder/mnop", + matched: false, + }, + // Test case 28. + { + pattern: "my-folder/mnop*?", + text: "my-folder/mnopqrst/mnopqr", + matched: true, + }, + // Test case 29. + { + pattern: "my-folder/mnop*?", + text: "my-folder/mnopqrst/mnopqrs", + matched: true, + }, + // Test case 30. + { + pattern: "my-folder/mnop*?", + text: "my-folder/mnop", + matched: false, + }, + // Test case 31. + { + pattern: "my-folder/mnop*?", + text: "my-folder/mnopq", + matched: true, + }, + // Test case 32. + { + pattern: "my-folder/mnop*?", + text: "my-folder/mnopqr", + matched: true, + }, + // Test case 33. + { + pattern: "my-folder/mnop*?and", + text: "my-folder/mnopqand", + matched: true, + }, + // Test case 34. + { + pattern: "my-folder/mnop*?and", + text: "my-folder/mnopand", + matched: false, + }, + // Test case 35. + { + pattern: "my-folder/mnop*?and", + text: "my-folder/mnopqand", + matched: true, + }, + // Test case 36. + { + pattern: "my-folder/mnop*?", + text: "my-folder/mn", + matched: false, + }, + // Test case 37. + { + pattern: "my-folder/mnop*?", + text: "my-folder/mnopqrst/mnopqrs", + matched: true, + }, + // Test case 38. + { + pattern: "my-folder/mnop*??", + text: "my-folder/mnopqrst", + matched: true, + }, + // Test case 39. + { + pattern: "my-folder/mnop*qrst", + text: "my-folder/mnopabcdegqrst", + matched: true, + }, + // Test case 40. + { + pattern: "my-folder/mnop*?and", + text: "my-folder/mnopqand", + matched: true, + }, + // Test case 41. + { + pattern: "my-folder/mnop*?and", + text: "my-folder/mnopand", + matched: false, + }, + // Test case 42. + { + pattern: "my-folder/mnop*?and?", + text: "my-folder/mnopqanda", + matched: true, + }, + // Test case 43. + { + pattern: "my-folder/mnop*?and", + text: "my-folder/mnopqanda", + matched: false, + }, + // Test case 44. + + { + pattern: "my-?-folder/abc*", + text: "my-folder/mnopqanda", + matched: false, + }, + } + // Iterating over the test cases, call the function under test and asert the output. + for i, testCase := range testCases { + actualResult := Match(testCase.text, testCase.pattern) + if testCase.matched != actualResult { + t.Errorf("Test %d: Expected the result to be `%v`, but instead found it to be `%v`", i+1, testCase.matched, actualResult) + } + } +} +func TestRandomInput(t *testing.T) { + rand.Seed(time.Now().UnixNano()) + b1 := make([]byte, 100) + b2 := make([]byte, 100) + for i := 0; i < 1000000; i++ { + if _, err := rand.Read(b1); err != nil { + t.Fatal(err) + } + if _, err := rand.Read(b2); err != nil { + t.Fatal(err) + } + Match(string(b1), string(b2)) + } +} +func testAllowable(pattern, exmin, exmax string) error { + min, max := Allowable(pattern) + if min != exmin || max != exmax { + return fmt.Errorf("expected '%v'/'%v', got '%v'/'%v'", + exmin, exmax, min, max) + } + return nil +} +func TestAllowable(t *testing.T) { + if err := testAllowable("hell*", "hell", "helm"); err != nil { + t.Fatal(err) + } + if err := testAllowable("hell?", "hell"+string(0), "hell"+string(utf8.MaxRune)); err != nil { + t.Fatal(err) + } + if err := testAllowable("h解析ell*", "h解析ell", "h解析elm"); err != nil { + t.Fatal(err) + } + if err := testAllowable("h解*ell*", "h解", "h觤"); err != nil { + t.Fatal(err) + } +} +func BenchmarkAscii(t *testing.B) { + for i := 0; i < t.N; i++ { + if !Match("hello", "hello") { + t.Fatal("fail") + } + } +} + +func BenchmarkUnicode(t *testing.B) { + for i := 0; i < t.N; i++ { + if !Match("h情llo", "h情llo") { + t.Fatal("fail") + } + } +}