You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

tags.go 6.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315
  1. package structtag
  2. import (
  3. "bytes"
  4. "errors"
  5. "fmt"
  6. "strconv"
  7. "strings"
  8. )
  9. var (
  10. errTagSyntax = errors.New("bad syntax for struct tag pair")
  11. errTagKeySyntax = errors.New("bad syntax for struct tag key")
  12. errTagValueSyntax = errors.New("bad syntax for struct tag value")
  13. errKeyNotSet = errors.New("tag key does not exist")
  14. errTagNotExist = errors.New("tag does not exist")
  15. errTagKeyMismatch = errors.New("mismatch between key and tag.key")
  16. )
  17. // Tags represent a set of tags from a single struct field
  18. type Tags struct {
  19. tags []*Tag
  20. }
  21. // Tag defines a single struct's string literal tag
  22. type Tag struct {
  23. // Key is the tag key, such as json, xml, etc..
  24. // i.e: `json:"foo,omitempty". Here key is: "json"
  25. Key string
  26. // Name is a part of the value
  27. // i.e: `json:"foo,omitempty". Here name is: "foo"
  28. Name string
  29. // Options is a part of the value. It contains a slice of tag options i.e:
  30. // `json:"foo,omitempty". Here options is: ["omitempty"]
  31. Options []string
  32. }
  33. // Parse parses a single struct field tag and returns the set of tags.
  34. func Parse(tag string) (*Tags, error) {
  35. var tags []*Tag
  36. hasTag := tag != ""
  37. // NOTE(arslan) following code is from reflect and vet package with some
  38. // modifications to collect all necessary information and extend it with
  39. // usable methods
  40. for tag != "" {
  41. // Skip leading space.
  42. i := 0
  43. for i < len(tag) && tag[i] == ' ' {
  44. i++
  45. }
  46. tag = tag[i:]
  47. if tag == "" {
  48. break
  49. }
  50. // Scan to colon. A space, a quote or a control character is a syntax
  51. // error. Strictly speaking, control chars include the range [0x7f,
  52. // 0x9f], not just [0x00, 0x1f], but in practice, we ignore the
  53. // multi-byte control characters as it is simpler to inspect the tag's
  54. // bytes than the tag's runes.
  55. i = 0
  56. for i < len(tag) && tag[i] > ' ' && tag[i] != ':' && tag[i] != '"' && tag[i] != 0x7f {
  57. i++
  58. }
  59. if i == 0 {
  60. return nil, errTagKeySyntax
  61. }
  62. if i+1 >= len(tag) || tag[i] != ':' {
  63. return nil, errTagSyntax
  64. }
  65. if tag[i+1] != '"' {
  66. return nil, errTagValueSyntax
  67. }
  68. key := string(tag[:i])
  69. tag = tag[i+1:]
  70. // Scan quoted string to find value.
  71. i = 1
  72. for i < len(tag) && tag[i] != '"' {
  73. if tag[i] == '\\' {
  74. i++
  75. }
  76. i++
  77. }
  78. if i >= len(tag) {
  79. return nil, errTagValueSyntax
  80. }
  81. qvalue := string(tag[:i+1])
  82. tag = tag[i+1:]
  83. value, err := strconv.Unquote(qvalue)
  84. if err != nil {
  85. return nil, errTagValueSyntax
  86. }
  87. res := strings.Split(value, ",")
  88. name := res[0]
  89. options := res[1:]
  90. if len(options) == 0 {
  91. options = nil
  92. }
  93. tags = append(tags, &Tag{
  94. Key: key,
  95. Name: name,
  96. Options: options,
  97. })
  98. }
  99. if hasTag && len(tags) == 0 {
  100. return nil, nil
  101. }
  102. return &Tags{
  103. tags: tags,
  104. }, nil
  105. }
  106. // Get returns the tag associated with the given key. If the key is present
  107. // in the tag the value (which may be empty) is returned. Otherwise the
  108. // returned value will be the empty string. The ok return value reports whether
  109. // the tag exists or not (which the return value is nil).
  110. func (t *Tags) Get(key string) (*Tag, error) {
  111. for _, tag := range t.tags {
  112. if tag.Key == key {
  113. return tag, nil
  114. }
  115. }
  116. return nil, errTagNotExist
  117. }
  118. // Set sets the given tag. If the tag key already exists it'll override it
  119. func (t *Tags) Set(tag *Tag) error {
  120. if tag.Key == "" {
  121. return errKeyNotSet
  122. }
  123. added := false
  124. for i, tg := range t.tags {
  125. if tg.Key == tag.Key {
  126. added = true
  127. t.tags[i] = tag
  128. }
  129. }
  130. if !added {
  131. // this means this is a new tag, add it
  132. t.tags = append(t.tags, tag)
  133. }
  134. return nil
  135. }
  136. // AddOptions adds the given option for the given key. If the option already
  137. // exists it doesn't add it again.
  138. func (t *Tags) AddOptions(key string, options ...string) {
  139. for i, tag := range t.tags {
  140. if tag.Key != key {
  141. continue
  142. }
  143. for _, opt := range options {
  144. if !tag.HasOption(opt) {
  145. tag.Options = append(tag.Options, opt)
  146. }
  147. }
  148. t.tags[i] = tag
  149. }
  150. }
  151. // DeleteOptions deletes the given options for the given key
  152. func (t *Tags) DeleteOptions(key string, options ...string) {
  153. hasOption := func(option string) bool {
  154. for _, opt := range options {
  155. if opt == option {
  156. return true
  157. }
  158. }
  159. return false
  160. }
  161. for i, tag := range t.tags {
  162. if tag.Key != key {
  163. continue
  164. }
  165. var updated []string
  166. for _, opt := range tag.Options {
  167. if !hasOption(opt) {
  168. updated = append(updated, opt)
  169. }
  170. }
  171. tag.Options = updated
  172. t.tags[i] = tag
  173. }
  174. }
  175. // Delete deletes the tag for the given keys
  176. func (t *Tags) Delete(keys ...string) {
  177. hasKey := func(key string) bool {
  178. for _, k := range keys {
  179. if k == key {
  180. return true
  181. }
  182. }
  183. return false
  184. }
  185. var updated []*Tag
  186. for _, tag := range t.tags {
  187. if !hasKey(tag.Key) {
  188. updated = append(updated, tag)
  189. }
  190. }
  191. t.tags = updated
  192. }
  193. // Tags returns a slice of tags. The order is the original tag order unless it
  194. // was changed.
  195. func (t *Tags) Tags() []*Tag {
  196. return t.tags
  197. }
  198. // Tags returns a slice of tags. The order is the original tag order unless it
  199. // was changed.
  200. func (t *Tags) Keys() []string {
  201. var keys []string
  202. for _, tag := range t.tags {
  203. keys = append(keys, tag.Key)
  204. }
  205. return keys
  206. }
  207. // String reassembles the tags into a valid literal tag field representation
  208. func (t *Tags) String() string {
  209. tags := t.Tags()
  210. if len(tags) == 0 {
  211. return ""
  212. }
  213. var buf bytes.Buffer
  214. for i, tag := range t.Tags() {
  215. buf.WriteString(tag.String())
  216. if i != len(tags)-1 {
  217. buf.WriteString(" ")
  218. }
  219. }
  220. return buf.String()
  221. }
  222. // HasOption returns true if the given option is available in options
  223. func (t *Tag) HasOption(opt string) bool {
  224. for _, tagOpt := range t.Options {
  225. if tagOpt == opt {
  226. return true
  227. }
  228. }
  229. return false
  230. }
  231. // Value returns the raw value of the tag, i.e. if the tag is
  232. // `json:"foo,omitempty", the Value is "foo,omitempty"
  233. func (t *Tag) Value() string {
  234. options := strings.Join(t.Options, ",")
  235. if options != "" {
  236. return fmt.Sprintf(`%s,%s`, t.Name, options)
  237. }
  238. return t.Name
  239. }
  240. // String reassembles the tag into a valid tag field representation
  241. func (t *Tag) String() string {
  242. return fmt.Sprintf(`%s:%q`, t.Key, t.Value())
  243. }
  244. // GoString implements the fmt.GoStringer interface
  245. func (t *Tag) GoString() string {
  246. template := `{
  247. Key: '%s',
  248. Name: '%s',
  249. Option: '%s',
  250. }`
  251. if t.Options == nil {
  252. return fmt.Sprintf(template, t.Key, t.Name, "nil")
  253. }
  254. options := strings.Join(t.Options, ",")
  255. return fmt.Sprintf(template, t.Key, t.Name, options)
  256. }
  257. func (t *Tags) Len() int {
  258. return len(t.tags)
  259. }
  260. func (t *Tags) Less(i int, j int) bool {
  261. return t.tags[i].Key < t.tags[j].Key
  262. }
  263. func (t *Tags) Swap(i int, j int) {
  264. t.tags[i], t.tags[j] = t.tags[j], t.tags[i]
  265. }