pax_global_header00006660000000000000000000000064135723362720014524gustar00rootroot0000000000000052 comment=72c94723f1e6825195e0d8e9857fca28cd23d835 structtag-1.2.0/000077500000000000000000000000001357233627200135445ustar00rootroot00000000000000structtag-1.2.0/.github/000077500000000000000000000000001357233627200151045ustar00rootroot00000000000000structtag-1.2.0/.github/main.workflow000066400000000000000000000006341357233627200176270ustar00rootroot00000000000000workflow "Build and Test" { on = "pull_request" resolves = ["go test"] } action "go build" { uses = "docker://golang:1.12.7" runs = "go" args = "build ./..." env = { GOPROXY = "https://proxy.golang.org" } } action "go test" { uses = "docker://golang:1.12.7" needs = ["go build"] runs = "go" args = "test -v -cover -race ./..." env = { GOPROXY = "https://proxy.golang.org" } } structtag-1.2.0/LICENSE000066400000000000000000000063171357233627200145600ustar00rootroot00000000000000Copyright (c) 2017, Fatih Arslan All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of structtag nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. This software includes some portions from Go. Go is used under the terms of the BSD like license. Copyright (c) 2012 The Go Authors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Google Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. The Go gopher was designed by Renee French. http://reneefrench.blogspot.com/ The design is licensed under the Creative Commons 3.0 Attributions license. Read this article for more details: https://blog.golang.org/gopher structtag-1.2.0/README.md000066400000000000000000000030151357233627200150220ustar00rootroot00000000000000# structtag [![GoDoc](http://img.shields.io/badge/go-documentation-blue.svg?style=flat-square)](http://godoc.org/github.com/fatih/structtag) structtag provides an easy way of parsing and manipulating struct tag fields. Please vendor the library as it might change in future versions. # Install ```bash go get github.com/fatih/structtag ``` # Example ```go package main import ( "fmt" "reflect" "sort" "github.com/fatih/structtag" ) func main() { type t struct { t string `json:"foo,omitempty,string" xml:"foo"` } // get field tag tag := reflect.TypeOf(t{}).Field(0).Tag // ... and start using structtag by parsing the tag tags, err := structtag.Parse(string(tag)) if err != nil { panic(err) } // iterate over all tags for _, t := range tags.Tags() { fmt.Printf("tag: %+v\n", t) } // get a single tag jsonTag, err := tags.Get("json") if err != nil { panic(err) } fmt.Println(jsonTag) // Output: json:"foo,omitempty,string" fmt.Println(jsonTag.Key) // Output: json fmt.Println(jsonTag.Name) // Output: foo fmt.Println(jsonTag.Options) // Output: [omitempty string] // change existing tag jsonTag.Name = "foo_bar" jsonTag.Options = nil tags.Set(jsonTag) // add new tag tags.Set(&structtag.Tag{ Key: "hcl", Name: "foo", Options: []string{"squash"}, }) // print the tags fmt.Println(tags) // Output: json:"foo_bar" xml:"foo" hcl:"foo,squash" // sort tags according to keys sort.Sort(tags) fmt.Println(tags) // Output: hcl:"foo,squash" json:"foo_bar" xml:"foo" } ``` structtag-1.2.0/go.mod000066400000000000000000000000531357233627200146500ustar00rootroot00000000000000module github.com/fatih/structtag go 1.12 structtag-1.2.0/tags.go000066400000000000000000000150401357233627200150310ustar00rootroot00000000000000package structtag import ( "bytes" "errors" "fmt" "strconv" "strings" ) var ( errTagSyntax = errors.New("bad syntax for struct tag pair") errTagKeySyntax = errors.New("bad syntax for struct tag key") errTagValueSyntax = errors.New("bad syntax for struct tag value") errKeyNotSet = errors.New("tag key does not exist") errTagNotExist = errors.New("tag does not exist") errTagKeyMismatch = errors.New("mismatch between key and tag.key") ) // Tags represent a set of tags from a single struct field type Tags struct { tags []*Tag } // Tag defines a single struct's string literal tag type Tag struct { // Key is the tag key, such as json, xml, etc.. // i.e: `json:"foo,omitempty". Here key is: "json" Key string // Name is a part of the value // i.e: `json:"foo,omitempty". Here name is: "foo" Name string // Options is a part of the value. It contains a slice of tag options i.e: // `json:"foo,omitempty". Here options is: ["omitempty"] Options []string } // Parse parses a single struct field tag and returns the set of tags. func Parse(tag string) (*Tags, error) { var tags []*Tag hasTag := tag != "" // NOTE(arslan) following code is from reflect and vet package with some // modifications to collect all necessary information and extend it with // usable methods for tag != "" { // Skip leading space. i := 0 for i < len(tag) && tag[i] == ' ' { i++ } tag = tag[i:] if tag == "" { break } // Scan to colon. A space, a quote or a control character is a syntax // error. Strictly speaking, control chars include the range [0x7f, // 0x9f], not just [0x00, 0x1f], but in practice, we ignore the // multi-byte control characters as it is simpler to inspect the tag's // bytes than the tag's runes. i = 0 for i < len(tag) && tag[i] > ' ' && tag[i] != ':' && tag[i] != '"' && tag[i] != 0x7f { i++ } if i == 0 { return nil, errTagKeySyntax } if i+1 >= len(tag) || tag[i] != ':' { return nil, errTagSyntax } if tag[i+1] != '"' { return nil, errTagValueSyntax } key := string(tag[:i]) tag = tag[i+1:] // Scan quoted string to find value. i = 1 for i < len(tag) && tag[i] != '"' { if tag[i] == '\\' { i++ } i++ } if i >= len(tag) { return nil, errTagValueSyntax } qvalue := string(tag[:i+1]) tag = tag[i+1:] value, err := strconv.Unquote(qvalue) if err != nil { return nil, errTagValueSyntax } res := strings.Split(value, ",") name := res[0] options := res[1:] if len(options) == 0 { options = nil } tags = append(tags, &Tag{ Key: key, Name: name, Options: options, }) } if hasTag && len(tags) == 0 { return nil, nil } return &Tags{ tags: tags, }, nil } // Get returns the tag associated with the given key. If the key is present // in the tag the value (which may be empty) is returned. Otherwise the // returned value will be the empty string. The ok return value reports whether // the tag exists or not (which the return value is nil). func (t *Tags) Get(key string) (*Tag, error) { for _, tag := range t.tags { if tag.Key == key { return tag, nil } } return nil, errTagNotExist } // Set sets the given tag. If the tag key already exists it'll override it func (t *Tags) Set(tag *Tag) error { if tag.Key == "" { return errKeyNotSet } added := false for i, tg := range t.tags { if tg.Key == tag.Key { added = true t.tags[i] = tag } } if !added { // this means this is a new tag, add it t.tags = append(t.tags, tag) } return nil } // AddOptions adds the given option for the given key. If the option already // exists it doesn't add it again. func (t *Tags) AddOptions(key string, options ...string) { for i, tag := range t.tags { if tag.Key != key { continue } for _, opt := range options { if !tag.HasOption(opt) { tag.Options = append(tag.Options, opt) } } t.tags[i] = tag } } // DeleteOptions deletes the given options for the given key func (t *Tags) DeleteOptions(key string, options ...string) { hasOption := func(option string) bool { for _, opt := range options { if opt == option { return true } } return false } for i, tag := range t.tags { if tag.Key != key { continue } var updated []string for _, opt := range tag.Options { if !hasOption(opt) { updated = append(updated, opt) } } tag.Options = updated t.tags[i] = tag } } // Delete deletes the tag for the given keys func (t *Tags) Delete(keys ...string) { hasKey := func(key string) bool { for _, k := range keys { if k == key { return true } } return false } var updated []*Tag for _, tag := range t.tags { if !hasKey(tag.Key) { updated = append(updated, tag) } } t.tags = updated } // Tags returns a slice of tags. The order is the original tag order unless it // was changed. func (t *Tags) Tags() []*Tag { return t.tags } // Tags returns a slice of tags. The order is the original tag order unless it // was changed. func (t *Tags) Keys() []string { var keys []string for _, tag := range t.tags { keys = append(keys, tag.Key) } return keys } // String reassembles the tags into a valid literal tag field representation func (t *Tags) String() string { tags := t.Tags() if len(tags) == 0 { return "" } var buf bytes.Buffer for i, tag := range t.Tags() { buf.WriteString(tag.String()) if i != len(tags)-1 { buf.WriteString(" ") } } return buf.String() } // HasOption returns true if the given option is available in options func (t *Tag) HasOption(opt string) bool { for _, tagOpt := range t.Options { if tagOpt == opt { return true } } return false } // Value returns the raw value of the tag, i.e. if the tag is // `json:"foo,omitempty", the Value is "foo,omitempty" func (t *Tag) Value() string { options := strings.Join(t.Options, ",") if options != "" { return fmt.Sprintf(`%s,%s`, t.Name, options) } return t.Name } // String reassembles the tag into a valid tag field representation func (t *Tag) String() string { return fmt.Sprintf(`%s:%q`, t.Key, t.Value()) } // GoString implements the fmt.GoStringer interface func (t *Tag) GoString() string { template := `{ Key: '%s', Name: '%s', Option: '%s', }` if t.Options == nil { return fmt.Sprintf(template, t.Key, t.Name, "nil") } options := strings.Join(t.Options, ",") return fmt.Sprintf(template, t.Key, t.Name, options) } func (t *Tags) Len() int { return len(t.tags) } func (t *Tags) Less(i int, j int) bool { return t.tags[i].Key < t.tags[j].Key } func (t *Tags) Swap(i int, j int) { t.tags[i], t.tags[j] = t.tags[j], t.tags[i] } structtag-1.2.0/tags_test.go000066400000000000000000000177251357233627200161040ustar00rootroot00000000000000package structtag import ( "reflect" "sort" "strings" "testing" ) func TestParse(t *testing.T) { test := []struct { name string tag string exp []*Tag invalid bool }{ { name: "empty tag", tag: "", }, { name: "tag with one key (invalid)", tag: "json", invalid: true, }, { name: "tag with one key (valid)", tag: `json:""`, exp: []*Tag{ { Key: "json", }, }, }, { name: "tag with one key and dash name", tag: `json:"-"`, exp: []*Tag{ { Key: "json", Name: "-", }, }, }, { name: "tag with key and name", tag: `json:"foo"`, exp: []*Tag{ { Key: "json", Name: "foo", }, }, }, { name: "tag with key, name and option", tag: `json:"foo,omitempty"`, exp: []*Tag{ { Key: "json", Name: "foo", Options: []string{"omitempty"}, }, }, }, { name: "tag with multiple keys", tag: `json:"" hcl:""`, exp: []*Tag{ { Key: "json", }, { Key: "hcl", }, }, }, { name: "tag with multiple keys and names", tag: `json:"foo" hcl:"foo"`, exp: []*Tag{ { Key: "json", Name: "foo", }, { Key: "hcl", Name: "foo", }, }, }, { name: "tag with multiple keys and names", tag: `json:"foo" hcl:"foo"`, exp: []*Tag{ { Key: "json", Name: "foo", }, { Key: "hcl", Name: "foo", }, }, }, { name: "tag with multiple keys and different names", tag: `json:"foo" hcl:"bar"`, exp: []*Tag{ { Key: "json", Name: "foo", }, { Key: "hcl", Name: "bar", }, }, }, { name: "tag with multiple keys, different names and options", tag: `json:"foo,omitempty" structs:"bar,omitnested"`, exp: []*Tag{ { Key: "json", Name: "foo", Options: []string{"omitempty"}, }, { Key: "structs", Name: "bar", Options: []string{"omitnested"}, }, }, }, { name: "tag with multiple keys, different names and options", tag: `json:"foo" structs:"bar,omitnested" hcl:"-"`, exp: []*Tag{ { Key: "json", Name: "foo", }, { Key: "structs", Name: "bar", Options: []string{"omitnested"}, }, { Key: "hcl", Name: "-", }, }, }, { name: "tag with quoted name", tag: `json:"foo,bar:\"baz\""`, exp: []*Tag{ { Key: "json", Name: "foo", Options: []string{`bar:"baz"`}, }, }, }, { name: "tag with trailing space", tag: `json:"foo" `, exp: []*Tag{ { Key: "json", Name: "foo", }, }, }, } for _, ts := range test { t.Run(ts.name, func(t *testing.T) { tags, err := Parse(ts.tag) invalid := err != nil if invalid != ts.invalid { t.Errorf("invalid case\n\twant: %+v\n\tgot : %+v\n\terr : %s", ts.invalid, invalid, err) } if invalid { return } got := tags.Tags() if !reflect.DeepEqual(ts.exp, got) { t.Errorf("parse\n\twant: %#v\n\tgot : %#v", ts.exp, got) } trimmedInput := strings.TrimSpace(ts.tag) if trimmedInput != tags.String() { t.Errorf("parse string\n\twant: %#v\n\tgot : %#v", trimmedInput, tags.String()) } }) } } func TestTags_Get(t *testing.T) { tag := `json:"foo,omitempty" structs:"bar,omitnested"` tags, err := Parse(tag) if err != nil { t.Fatal(err) } found, err := tags.Get("json") if err != nil { t.Fatal(err) } t.Run("String", func(t *testing.T) { want := `json:"foo,omitempty"` if found.String() != want { t.Errorf("get\n\twant: %#v\n\tgot : %#v", want, found.String()) } }) t.Run("Value", func(t *testing.T) { want := `foo,omitempty` if found.Value() != want { t.Errorf("get\n\twant: %#v\n\tgot : %#v", want, found.Value()) } }) } func TestTags_Set(t *testing.T) { tag := `json:"foo,omitempty" structs:"bar,omitnested"` tags, err := Parse(tag) if err != nil { t.Fatal(err) } err = tags.Set(&Tag{ Key: "json", Name: "bar", Options: []string{}, }) if err != nil { t.Fatal(err) } found, err := tags.Get("json") if err != nil { t.Fatal(err) } want := `json:"bar"` if found.String() != want { t.Errorf("set\n\twant: %#v\n\tgot : %#v", want, found.String()) } } func TestTags_Set_Append(t *testing.T) { tag := `json:"foo,omitempty"` tags, err := Parse(tag) if err != nil { t.Fatal(err) } err = tags.Set(&Tag{ Key: "structs", Name: "bar", Options: []string{"omitnested"}, }) if err != nil { t.Fatal(err) } found, err := tags.Get("structs") if err != nil { t.Fatal(err) } want := `structs:"bar,omitnested"` if found.String() != want { t.Errorf("set append\n\twant: %#v\n\tgot : %#v", want, found.String()) } wantFull := `json:"foo,omitempty" structs:"bar,omitnested"` if tags.String() != wantFull { t.Errorf("set append\n\twant: %#v\n\tgot : %#v", wantFull, tags.String()) } } func TestTags_Set_KeyDoesNotExist(t *testing.T) { tag := `json:"foo,omitempty" structs:"bar,omitnested"` tags, err := Parse(tag) if err != nil { t.Fatal(err) } err = tags.Set(&Tag{ Key: "", Name: "bar", Options: []string{}, }) if err == nil { t.Fatal("setting tag with a nonexisting key should error") } if err != errKeyNotSet { t.Errorf("set\n\twant: %#v\n\tgot : %#v", errTagKeyMismatch, err) } } func TestTags_Delete(t *testing.T) { tag := `json:"foo,omitempty" structs:"bar,omitnested" hcl:"-"` tags, err := Parse(tag) if err != nil { t.Fatal(err) } tags.Delete("structs") if tags.Len() != 2 { t.Fatalf("tag length should be 2, have %d", tags.Len()) } found, err := tags.Get("json") if err != nil { t.Fatal(err) } want := `json:"foo,omitempty"` if found.String() != want { t.Errorf("delete\n\twant: %#v\n\tgot : %#v", want, found.String()) } wantFull := `json:"foo,omitempty" hcl:"-"` if tags.String() != wantFull { t.Errorf("delete\n\twant: %#v\n\tgot : %#v", wantFull, tags.String()) } } func TestTags_DeleteOptions(t *testing.T) { tag := `json:"foo,omitempty" structs:"bar,omitnested,omitempty" hcl:"-"` tags, err := Parse(tag) if err != nil { t.Fatal(err) } tags.DeleteOptions("json", "omitempty") want := `json:"foo" structs:"bar,omitnested,omitempty" hcl:"-"` if tags.String() != want { t.Errorf("delete option\n\twant: %#v\n\tgot : %#v", want, tags.String()) } tags.DeleteOptions("structs", "omitnested") want = `json:"foo" structs:"bar,omitempty" hcl:"-"` if tags.String() != want { t.Errorf("delete option\n\twant: %#v\n\tgot : %#v", want, tags.String()) } } func TestTags_AddOption(t *testing.T) { tag := `json:"foo" structs:"bar,omitempty" hcl:"-"` tags, err := Parse(tag) if err != nil { t.Fatal(err) } tags.AddOptions("json", "omitempty") want := `json:"foo,omitempty" structs:"bar,omitempty" hcl:"-"` if tags.String() != want { t.Errorf("add options\n\twant: %#v\n\tgot : %#v", want, tags.String()) } // this shouldn't change anything tags.AddOptions("structs", "omitempty") want = `json:"foo,omitempty" structs:"bar,omitempty" hcl:"-"` if tags.String() != want { t.Errorf("add options\n\twant: %#v\n\tgot : %#v", want, tags.String()) } // this should append to the existing tags.AddOptions("structs", "omitnested", "flatten") want = `json:"foo,omitempty" structs:"bar,omitempty,omitnested,flatten" hcl:"-"` if tags.String() != want { t.Errorf("add options\n\twant: %#v\n\tgot : %#v", want, tags.String()) } } func TestTags_String(t *testing.T) { tag := `json:"foo" structs:"bar,omitnested" hcl:"-"` tags, err := Parse(tag) if err != nil { t.Fatal(err) } if tags.String() != tag { t.Errorf("string\n\twant: %#v\n\tgot : %#v", tag, tags.String()) } } func TestTags_Sort(t *testing.T) { tag := `json:"foo" structs:"bar,omitnested" hcl:"-"` tags, err := Parse(tag) if err != nil { t.Fatal(err) } sort.Sort(tags) want := `hcl:"-" json:"foo" structs:"bar,omitnested"` if tags.String() != want { t.Errorf("string\n\twant: %#v\n\tgot : %#v", want, tags.String()) } }