123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315 |
- package 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]
- }
|