mirror of
https://github.com/Threnklyn/jira.git
synced 2026-05-18 20:23:28 +02:00
add --jq option to run a json query against Jira service response json
This commit is contained in:
Generated
+4
-2
@@ -1,5 +1,5 @@
|
||||
hash: 4c3ae9c9421b17aae9987ea9566cac7d0a789750bb77c8d235b7be163aec8cae
|
||||
updated: 2017-09-09T17:13:22.172913984-07:00
|
||||
hash: b59fc2a2254d65db0a41057ab55bb571318587e7e4692309a41c912b1526880e
|
||||
updated: 2017-09-09T18:33:57.161504692-07:00
|
||||
imports:
|
||||
- name: github.com/alecthomas/template
|
||||
version: a0175ee3bccc567396460bf5acd36800cb10c49c
|
||||
@@ -35,6 +35,8 @@ imports:
|
||||
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
|
||||
|
||||
@@ -20,3 +20,4 @@ import:
|
||||
- package: gopkg.in/coryb/yaml.v2
|
||||
- package: gopkg.in/op/go-logging.v1
|
||||
version: ^1.0.0
|
||||
- package: github.com/savaki/jq
|
||||
|
||||
+34
-4
@@ -16,6 +16,7 @@ import (
|
||||
"github.com/coryb/oreo"
|
||||
"github.com/jinzhu/copier"
|
||||
shellquote "github.com/kballard/go-shellquote"
|
||||
"github.com/savaki/jq"
|
||||
"gopkg.in/AlecAivazis/survey.v1"
|
||||
kingpin "gopkg.in/alecthomas/kingpin.v2"
|
||||
yaml "gopkg.in/coryb/yaml.v2"
|
||||
@@ -38,10 +39,12 @@ type GlobalOptions struct {
|
||||
}
|
||||
|
||||
type CommonOptions struct {
|
||||
Browse figtree.BoolOption `yaml:"browse,omitempty" json:"browse,omitempty"`
|
||||
Editor figtree.StringOption `yaml:"editor,omitempty" json:"editor,omitempty"`
|
||||
SkipEditing figtree.BoolOption `yaml:"noedit,omitempty" json:"noedit,omitempty"`
|
||||
Template figtree.StringOption `yaml:"template,omitempty" json:"template,omitempty"`
|
||||
Browse figtree.BoolOption `yaml:"browse,omitempty" json:"browse,omitempty"`
|
||||
Editor figtree.StringOption `yaml:"editor,omitempty" json:"editor,omitempty"`
|
||||
JsonQuery figtree.StringOption `yaml:"jq,omitempty" json:"jq,omitempty"`
|
||||
JsonQueryRaw figtree.BoolOption `yaml:"jq-raw,omitempty" json":jq-raw,omitempty"`
|
||||
SkipEditing figtree.BoolOption `yaml:"noedit,omitempty" json:"noedit,omitempty"`
|
||||
Template figtree.StringOption `yaml:"template,omitempty" json:"template,omitempty"`
|
||||
}
|
||||
|
||||
type CommandRegistryEntry struct {
|
||||
@@ -169,6 +172,33 @@ 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 (o *CommonOptions) PrintTemplate(data interface{}) error {
|
||||
if o.JsonQuery.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)
|
||||
os.Stdout.Write([]byte{'\n'})
|
||||
return err
|
||||
}
|
||||
return RunTemplate(o.Template.Value, data, nil)
|
||||
}
|
||||
|
||||
func (o *CommonOptions) editFile(fileName string) (changes bool, err error) {
|
||||
var editor string
|
||||
for _, ed := range []string{o.Editor.Value, os.Getenv("JIRA_EDITOR"), os.Getenv("EDITOR"), "vim"} {
|
||||
|
||||
+18
-17
@@ -197,25 +197,26 @@ func RunTemplate(templateName string, data interface{}, out io.Writer) error {
|
||||
}
|
||||
|
||||
var AllTemplates = map[string]string{
|
||||
"component-add": defaultComponentAddTemplate,
|
||||
"debug": defaultDebugTemplate,
|
||||
"fields": defaultDebugTemplate,
|
||||
"editmeta": defaultDebugTemplate,
|
||||
"transmeta": defaultDebugTemplate,
|
||||
"createmeta": defaultDebugTemplate,
|
||||
"issuelinktypes": defaultDebugTemplate,
|
||||
"list": defaultListTemplate,
|
||||
"table": defaultTableTemplate,
|
||||
"view": defaultViewTemplate,
|
||||
"edit": defaultEditTemplate,
|
||||
"transitions": defaultTransitionsTemplate,
|
||||
"components": defaultComponentsTemplate,
|
||||
"issuetypes": defaultIssuetypesTemplate,
|
||||
"create": defaultCreateTemplate,
|
||||
"subtask": defaultSubtaskTemplate,
|
||||
"comment": defaultCommentTemplate,
|
||||
"transition": defaultTransitionTemplate,
|
||||
"component-add": defaultComponentAddTemplate,
|
||||
"components": defaultComponentsTemplate,
|
||||
"create": defaultCreateTemplate,
|
||||
"createmeta": defaultDebugTemplate,
|
||||
"debug": defaultDebugTemplate,
|
||||
"edit": defaultEditTemplate,
|
||||
"editmeta": defaultDebugTemplate,
|
||||
"fields": defaultDebugTemplate,
|
||||
"issuelinktypes": defaultDebugTemplate,
|
||||
"issuetypes": defaultIssuetypesTemplate,
|
||||
"json": defaultDebugTemplate,
|
||||
"list": defaultListTemplate,
|
||||
"request": defaultDebugTemplate,
|
||||
"subtask": defaultSubtaskTemplate,
|
||||
"table": defaultTableTemplate,
|
||||
"transition": defaultTransitionTemplate,
|
||||
"transitions": defaultTransitionsTemplate,
|
||||
"transmeta": defaultDebugTemplate,
|
||||
"view": defaultViewTemplate,
|
||||
"worklog": defaultWorklogTemplate,
|
||||
"worklogs": defaultWorklogsTemplate,
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ func CmdComponentsRegistry() *jiracli.CommandRegistryEntry {
|
||||
|
||||
func CmdComponentsUsage(cmd *kingpin.CmdClause, opts *ComponentsOptions) error {
|
||||
jiracli.TemplateUsage(cmd, &opts.CommonOptions)
|
||||
jiracli.JsonQueryUsage(cmd, &opts.CommonOptions)
|
||||
cmd.Flag("project", "project to list components").Short('p').StringVar(&opts.Project)
|
||||
|
||||
return nil
|
||||
@@ -51,5 +52,5 @@ func CmdComponents(o *oreo.Client, globals *jiracli.GlobalOptions, opts *Compone
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return jiracli.RunTemplate(opts.Template.Value, data, nil)
|
||||
return opts.PrintTemplate(data)
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ func CmdCreateMetaRegistry() *jiracli.CommandRegistryEntry {
|
||||
|
||||
func CmdCreateMetaUsage(cmd *kingpin.CmdClause, opts *CreateMetaOptions) error {
|
||||
jiracli.TemplateUsage(cmd, &opts.CommonOptions)
|
||||
jiracli.JsonQueryUsage(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
|
||||
@@ -49,5 +50,5 @@ func CmdCreateMeta(o *oreo.Client, globals *jiracli.GlobalOptions, opts *CreateM
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return jiracli.RunTemplate(opts.Template.Value, createMeta, nil)
|
||||
return opts.PrintTemplate(createMeta)
|
||||
}
|
||||
|
||||
+2
-1
@@ -36,6 +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)
|
||||
cmd.Arg("ISSUE", "edit metadata for issue id").Required().StringVar(&opts.Issue)
|
||||
return nil
|
||||
}
|
||||
@@ -46,7 +47,7 @@ func CmdEditMeta(o *oreo.Client, globals *jiracli.GlobalOptions, opts *EditMetaO
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := jiracli.RunTemplate(opts.Template.Value, editMeta, nil); err != nil {
|
||||
if err := opts.PrintTemplate(editMeta); err != nil {
|
||||
return err
|
||||
}
|
||||
if opts.Browse.Value {
|
||||
|
||||
+2
-1
@@ -17,6 +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)
|
||||
return nil
|
||||
},
|
||||
func(o *oreo.Client, globals *jiracli.GlobalOptions) error {
|
||||
@@ -31,5 +32,5 @@ func CmdFields(o *oreo.Client, globals *jiracli.GlobalOptions, opts *jiracli.Com
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return jiracli.RunTemplate(opts.Template.Value, data, nil)
|
||||
return opts.PrintTemplate(data)
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ func CmdIssueLinkTypesRegistry() *jiracli.CommandRegistryEntry {
|
||||
|
||||
func CmdIssueLinkTypesUsage(cmd *kingpin.CmdClause, opts *jiracli.CommonOptions) error {
|
||||
jiracli.TemplateUsage(cmd, opts)
|
||||
jiracli.JsonQueryUsage(cmd, opts)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -36,5 +37,5 @@ func CmdIssueLinkTypes(o *oreo.Client, globals *jiracli.GlobalOptions, opts *jir
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return jiracli.RunTemplate(opts.Template.Value, data, nil)
|
||||
return opts.PrintTemplate(data)
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ func CmdIssueTypesRegistry() *jiracli.CommandRegistryEntry {
|
||||
|
||||
func CmdIssueTypesUsage(cmd *kingpin.CmdClause, opts *IssueTypesOptions) error {
|
||||
jiracli.TemplateUsage(cmd, &opts.CommonOptions)
|
||||
jiracli.JsonQueryUsage(cmd, &opts.CommonOptions)
|
||||
cmd.Flag("project", "project to list issueTypes").Short('p').StringVar(&opts.Project)
|
||||
|
||||
return nil
|
||||
@@ -51,5 +52,5 @@ func CmdIssueTypes(o *oreo.Client, globals *jiracli.GlobalOptions, opts *IssueTy
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return jiracli.RunTemplate(opts.Template.Value, data, nil)
|
||||
return opts.PrintTemplate(data)
|
||||
}
|
||||
|
||||
+2
-1
@@ -43,6 +43,7 @@ func CmdListRegistry() *jiracli.CommandRegistryEntry {
|
||||
|
||||
func CmdListUsage(cmd *kingpin.CmdClause, opts *ListOptions) error {
|
||||
jiracli.TemplateUsage(cmd, &opts.CommonOptions)
|
||||
jiracli.JsonQueryUsage(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)
|
||||
@@ -62,5 +63,5 @@ func CmdList(o *oreo.Client, globals *jiracli.GlobalOptions, opts *ListOptions)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return jiracli.RunTemplate(opts.Template.Value, data, nil)
|
||||
return opts.PrintTemplate(data)
|
||||
}
|
||||
|
||||
+3
-1
@@ -35,6 +35,8 @@ func CmdRequestRegistry() *jiracli.CommandRegistryEntry {
|
||||
if opts.Method == "" {
|
||||
opts.Method = "GET"
|
||||
}
|
||||
jiracli.TemplateUsage(cmd, &opts.CommonOptions)
|
||||
jiracli.JsonQueryUsage(cmd, &opts.CommonOptions)
|
||||
return CmdRequestUsage(cmd, &opts)
|
||||
},
|
||||
func(o *oreo.Client, globals *jiracli.GlobalOptions) error {
|
||||
@@ -89,5 +91,5 @@ func CmdRequest(o *oreo.Client, globals *jiracli.GlobalOptions, opts *RequestOpt
|
||||
return fmt.Errorf("JSON Parse Error: %s from %q", err, content)
|
||||
}
|
||||
|
||||
return jiracli.RunTemplate(opts.Template.Value, &data, nil)
|
||||
return opts.PrintTemplate(&data)
|
||||
}
|
||||
|
||||
@@ -35,6 +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)
|
||||
cmd.Arg("ISSUE", "issue to list valid transitions").Required().StringVar(&opts.Issue)
|
||||
return nil
|
||||
}
|
||||
@@ -45,7 +46,7 @@ func CmdTransitions(o *oreo.Client, globals *jiracli.GlobalOptions, opts *Transi
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := jiracli.RunTemplate(opts.Template.Value, editMeta, nil); err != nil {
|
||||
if err := opts.PrintTemplate(editMeta); err != nil {
|
||||
return err
|
||||
}
|
||||
if opts.Browse.Value {
|
||||
|
||||
+2
-1
@@ -36,6 +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)
|
||||
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)
|
||||
@@ -49,7 +50,7 @@ func CmdView(o *oreo.Client, globals *jiracli.GlobalOptions, opts *ViewOptions)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := jiracli.RunTemplate(opts.Template.Value, data, nil); err != nil {
|
||||
if err := opts.PrintTemplate(data); err != nil {
|
||||
return err
|
||||
}
|
||||
if opts.Browse.Value {
|
||||
|
||||
@@ -35,6 +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)
|
||||
cmd.Arg("ISSUE", "issue id to fetch worklogs").Required().StringVar(&opts.Issue)
|
||||
return nil
|
||||
}
|
||||
@@ -45,9 +46,9 @@ func CmdWorklogList(o *oreo.Client, globals *jiracli.GlobalOptions, opts *Worklo
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := jiracli.RunTemplate(opts.Template.Value, struct {
|
||||
if err := opts.PrintTemplate(struct {
|
||||
Worklogs *jiradata.Worklogs `json:"worklogs,omitempty" yaml:"worklogs,omitempty"`
|
||||
}{data}, nil); err != nil {
|
||||
}{data}); err != nil {
|
||||
return err
|
||||
}
|
||||
if opts.Browse.Value {
|
||||
|
||||
Generated
+33
@@ -0,0 +1,33 @@
|
||||
# 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
@@ -0,0 +1,201 @@
|
||||
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
@@ -0,0 +1,95 @@
|
||||
# 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
@@ -0,0 +1,18 @@
|
||||
// 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
@@ -0,0 +1,83 @@
|
||||
// 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
@@ -0,0 +1,72 @@
|
||||
// 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
@@ -0,0 +1,78 @@
|
||||
// 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
@@ -0,0 +1,82 @@
|
||||
// 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
@@ -0,0 +1,75 @@
|
||||
// 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
@@ -0,0 +1,84 @@
|
||||
// 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
@@ -0,0 +1,49 @@
|
||||
// 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
@@ -0,0 +1,75 @@
|
||||
// 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
@@ -0,0 +1,58 @@
|
||||
// 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
@@ -0,0 +1,83 @@
|
||||
// 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
@@ -0,0 +1,71 @@
|
||||
// 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
@@ -0,0 +1,90 @@
|
||||
// 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
@@ -0,0 +1,32 @@
|
||||
// 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
@@ -0,0 +1,3 @@
|
||||
// Package scanner provides various utilities for parsing and extracting json data from []byte
|
||||
|
||||
package scanner
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
package scanner
|
||||
|
||||
type opErr struct {
|
||||
pos int
|
||||
msg string
|
||||
content string
|
||||
}
|
||||
|
||||
func (o opErr) Error() string {
|
||||
return o.msg + "; ..." + o.content
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
// 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
@@ -0,0 +1,81 @@
|
||||
// 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
@@ -0,0 +1,86 @@
|
||||
// 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
@@ -0,0 +1,76 @@
|
||||
// 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
@@ -0,0 +1,75 @@
|
||||
// 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
@@ -0,0 +1,97 @@
|
||||
// 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
@@ -0,0 +1,30 @@
|
||||
// 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
@@ -0,0 +1,34 @@
|
||||
// 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
@@ -0,0 +1,40 @@
|
||||
// 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
@@ -0,0 +1,82 @@
|
||||
// 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
@@ -0,0 +1,76 @@
|
||||
// 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
@@ -0,0 +1,78 @@
|
||||
// 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
@@ -0,0 +1,50 @@
|
||||
// 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
@@ -0,0 +1,99 @@
|
||||
// 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
@@ -0,0 +1,64 @@
|
||||
// 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
@@ -0,0 +1,79 @@
|
||||
// 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()
|
||||
}
|
||||
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user