mirror of
https://github.com/Threnklyn/jira.git
synced 2026-05-18 20:23:28 +02:00
use --gjq for GJson Query to filter json response data, remove --jq option
This commit is contained in:
Generated
+165
@@ -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
|
||||
+75
@@ -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"
|
||||
Generated
-66
@@ -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: []
|
||||
-23
@@ -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
|
||||
+7
-19
@@ -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"
|
||||
@@ -41,8 +41,7 @@ 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"`
|
||||
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"`
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
+1
-1
@@ -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
|
||||
}
|
||||
|
||||
+1
-1
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
+1
-1
@@ -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)
|
||||
|
||||
+1
-1
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
+1
-1
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Generated
-33
@@ -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
|
||||
-201
@@ -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.
|
||||
-95
@@ -1,95 +0,0 @@
|
||||
# jq
|
||||
|
||||
[](https://godoc.org/github.com/savaki/jq)
|
||||
[](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
|
||||
```
|
||||
|
||||
-18
@@ -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
|
||||
-83
@@ -1,83 +0,0 @@
|
||||
// Copyright (c) 2016 Matt Ho <matt.ho@gmail.com>
|
||||
//
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
-72
@@ -1,72 +0,0 @@
|
||||
// Copyright (c) 2016 Matt Ho <matt.ho@gmail.com>
|
||||
//
|
||||
// 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()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
-78
@@ -1,78 +0,0 @@
|
||||
// Copyright (c) 2016 Matt Ho <matt.ho@gmail.com>
|
||||
//
|
||||
// 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()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
-82
@@ -1,82 +0,0 @@
|
||||
// Copyright (c) 2016 Matt Ho <matt.ho@gmail.com>
|
||||
//
|
||||
// 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
|
||||
}
|
||||
-75
@@ -1,75 +0,0 @@
|
||||
// Copyright (c) 2016 Matt Ho <matt.ho@gmail.com>
|
||||
//
|
||||
// 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()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
-84
@@ -1,84 +0,0 @@
|
||||
// Copyright (c) 2016 Matt Ho <matt.ho@gmail.com>
|
||||
//
|
||||
// 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()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
-49
@@ -1,49 +0,0 @@
|
||||
// Copyright (c) 2016 Matt Ho <matt.ho@gmail.com>
|
||||
//
|
||||
// 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]),
|
||||
}
|
||||
}
|
||||
}
|
||||
-75
@@ -1,75 +0,0 @@
|
||||
// Copyright (c) 2016 Matt Ho <matt.ho@gmail.com>
|
||||
//
|
||||
// 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()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
-58
@@ -1,58 +0,0 @@
|
||||
// Copyright (c) 2016 Matt Ho <matt.ho@gmail.com>
|
||||
//
|
||||
// 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
|
||||
}
|
||||
}
|
||||
}
|
||||
-83
@@ -1,83 +0,0 @@
|
||||
// Copyright (c) 2016 Matt Ho <matt.ho@gmail.com>
|
||||
//
|
||||
// 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()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
-71
@@ -1,71 +0,0 @@
|
||||
// Copyright (c) 2016 Matt Ho <matt.ho@gmail.com>
|
||||
//
|
||||
// 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
|
||||
}
|
||||
}
|
||||
}
|
||||
-90
@@ -1,90 +0,0 @@
|
||||
// Copyright (c) 2016 Matt Ho <matt.ho@gmail.com>
|
||||
//
|
||||
// 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
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
-32
@@ -1,32 +0,0 @@
|
||||
// Copyright (c) 2016 Matt Ho <matt.ho@gmail.com>
|
||||
//
|
||||
// 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
|
||||
}
|
||||
}
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
// Package scanner provides various utilities for parsing and extracting json data from []byte
|
||||
|
||||
package scanner
|
||||
-11
@@ -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
|
||||
}
|
||||
-60
@@ -1,60 +0,0 @@
|
||||
// Copyright (c) 2016 Matt Ho <matt.ho@gmail.com>
|
||||
//
|
||||
// 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++
|
||||
}
|
||||
}
|
||||
-81
@@ -1,81 +0,0 @@
|
||||
// Copyright (c) 2016 Matt Ho <matt.ho@gmail.com>
|
||||
//
|
||||
// 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()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
-86
@@ -1,86 +0,0 @@
|
||||
// Copyright (c) 2016 Matt Ho <matt.ho@gmail.com>
|
||||
//
|
||||
// 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
|
||||
}
|
||||
}
|
||||
}
|
||||
-76
@@ -1,76 +0,0 @@
|
||||
// Copyright (c) 2016 Matt Ho <matt.ho@gmail.com>
|
||||
//
|
||||
// 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()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
-75
@@ -1,75 +0,0 @@
|
||||
// Copyright (c) 2016 Matt Ho <matt.ho@gmail.com>
|
||||
//
|
||||
// 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++
|
||||
}
|
||||
}
|
||||
-97
@@ -1,97 +0,0 @@
|
||||
// Copyright (c) 2016 Matt Ho <matt.ho@gmail.com>
|
||||
//
|
||||
// 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()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
-30
@@ -1,30 +0,0 @@
|
||||
// Copyright (c) 2016 Matt Ho <matt.ho@gmail.com>
|
||||
//
|
||||
// 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
|
||||
}
|
||||
}
|
||||
-34
@@ -1,34 +0,0 @@
|
||||
// Copyright (c) 2016 Matt Ho <matt.ho@gmail.com>
|
||||
//
|
||||
// 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()
|
||||
}
|
||||
}
|
||||
}
|
||||
-40
@@ -1,40 +0,0 @@
|
||||
// Copyright (c) 2016 Matt Ho <matt.ho@gmail.com>
|
||||
//
|
||||
// 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
|
||||
}
|
||||
-82
@@ -1,82 +0,0 @@
|
||||
// Copyright (c) 2016 Matt Ho <matt.ho@gmail.com>
|
||||
//
|
||||
// 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()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
-76
@@ -1,76 +0,0 @@
|
||||
// Copyright (c) 2016 Matt Ho <matt.ho@gmail.com>
|
||||
//
|
||||
// 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
|
||||
}
|
||||
}
|
||||
}
|
||||
-78
@@ -1,78 +0,0 @@
|
||||
// Copyright (c) 2016 Matt Ho <matt.ho@gmail.com>
|
||||
//
|
||||
// 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()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
-50
@@ -1,50 +0,0 @@
|
||||
// Copyright (c) 2016 Matt Ho <matt.ho@gmail.com>
|
||||
//
|
||||
// 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")
|
||||
}
|
||||
-99
@@ -1,99 +0,0 @@
|
||||
// Copyright (c) 2016 Matt Ho <matt.ho@gmail.com>
|
||||
//
|
||||
// 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()
|
||||
}
|
||||
}
|
||||
-64
@@ -1,64 +0,0 @@
|
||||
// Copyright (c) 2016 Matt Ho <matt.ho@gmail.com>
|
||||
//
|
||||
// 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}))
|
||||
}
|
||||
-79
@@ -1,79 +0,0 @@
|
||||
// Copyright (c) 2016 Matt Ho <matt.ho@gmail.com>
|
||||
//
|
||||
// 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()
|
||||
}
|
||||
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
language: go
|
||||
+20
@@ -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.
|
||||
+421
@@ -0,0 +1,421 @@
|
||||
<p align="center">
|
||||
<img
|
||||
src="logo.png"
|
||||
width="240" height="78" border="0" alt="GJSON">
|
||||
<br>
|
||||
<a href="https://travis-ci.org/tidwall/gjson"><img src="https://img.shields.io/travis/tidwall/gjson.svg?style=flat-square" alt="Build Status"></a>
|
||||
<a href="https://godoc.org/github.com/tidwall/gjson"><img src="https://img.shields.io/badge/api-reference-blue.svg?style=flat-square" alt="GoDoc"></a>
|
||||
<a href="http://tidwall.com/gjson-play"><img src="https://img.shields.io/badge/play-ground-orange.svg?style=flat-square" alt="GJSON Playground"></a>
|
||||
</p>
|
||||
|
||||
|
||||
|
||||
<p align="center">get a json value quickly</a></p>
|
||||
|
||||
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).
|
||||
+2442
File diff suppressed because it is too large
Load Diff
+1073
File diff suppressed because it is too large
Load Diff
BIN
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
+1
@@ -0,0 +1 @@
|
||||
language: go
|
||||
+20
@@ -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.
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
Match
|
||||
=====
|
||||
<a href="https://travis-ci.org/tidwall/match"><img src="https://img.shields.io/travis/tidwall/match.svg?style=flat-square" alt="Build Status"></a>
|
||||
<a href="https://godoc.org/github.com/tidwall/match"><img src="https://img.shields.io/badge/api-reference-blue.svg?style=flat-square" alt="GoDoc"></a>
|
||||
|
||||
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).
|
||||
+192
@@ -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
|
||||
*/
|
||||
}
|
||||
+408
@@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user