gitea/vendor/github.com/fatih/structtag/tags.go

316 lines
6.5 KiB
Go

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