comments.go 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  1. /*
  2. Copyright 2015 The Kubernetes Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package gengo
  14. import (
  15. "bytes"
  16. "fmt"
  17. "slices"
  18. "strings"
  19. "k8s.io/gengo/v2/codetags"
  20. )
  21. // ExtractCommentTags parses comments for lines of the form:
  22. //
  23. // 'marker' + "key=value".
  24. //
  25. // Values are optional; "" is the default. A tag can be specified more than
  26. // one time and all values are returned. If the resulting map has an entry for
  27. // a key, the value (a slice) is guaranteed to have at least 1 element.
  28. //
  29. // Example: if you pass "+" for 'marker', and the following lines are in
  30. // the comments:
  31. //
  32. // +foo=value1
  33. // +bar
  34. // +foo=value2
  35. // +baz="qux"
  36. //
  37. // Then this function will return:
  38. //
  39. // map[string][]string{"foo":{"value1, "value2"}, "bar": {""}, "baz": {`"qux"`}}
  40. //
  41. // Deprecated: Prefer codetags.Extract and codetags.Parse.
  42. func ExtractCommentTags(marker string, lines []string) map[string][]string {
  43. out := map[string][]string{}
  44. for _, line := range lines {
  45. line = strings.Trim(line, " ")
  46. if len(line) == 0 {
  47. continue
  48. }
  49. if !strings.HasPrefix(line, marker) {
  50. continue
  51. }
  52. kv := strings.SplitN(line[len(marker):], "=", 2)
  53. if len(kv) == 2 {
  54. out[kv[0]] = append(out[kv[0]], kv[1])
  55. } else if len(kv) == 1 {
  56. out[kv[0]] = append(out[kv[0]], "")
  57. }
  58. }
  59. return out
  60. }
  61. // ExtractSingleBoolCommentTag parses comments for lines of the form:
  62. //
  63. // 'marker' + "key=value1"
  64. //
  65. // If the tag is not found, the default value is returned. Values are asserted
  66. // to be boolean ("true" or "false"), and any other value will cause an error
  67. // to be returned. If the key has multiple values, the first one will be used.
  68. //
  69. // This function is a wrapper around codetags.Extract and codetags.Parse, but only supports tags with
  70. // a single position arg of type string, and a value of type bool.
  71. func ExtractSingleBoolCommentTag(marker string, key string, defaultVal bool, lines []string) (bool, error) {
  72. tags, err := ExtractFunctionStyleCommentTags(marker, []string{key}, lines, ParseValues(true))
  73. if err != nil {
  74. return false, err
  75. }
  76. values := tags[key]
  77. if values == nil {
  78. return defaultVal, nil
  79. }
  80. if values[0].Value == "true" {
  81. return true, nil
  82. }
  83. if values[0].Value == "false" {
  84. return false, nil
  85. }
  86. return false, fmt.Errorf("tag value for %q is not boolean: %q", key, values[0])
  87. }
  88. // ExtractFunctionStyleCommentTags parses comments for special metadata tags.
  89. //
  90. // This function is a wrapper around codetags.Extract and codetags.Parse, but only supports tags with
  91. // a single position arg of type string.
  92. func ExtractFunctionStyleCommentTags(marker string, tagNames []string, lines []string, options ...TagOption) (map[string][]Tag, error) {
  93. opts := tagOpts{}
  94. for _, o := range options {
  95. o(&opts)
  96. }
  97. out := map[string][]Tag{}
  98. tags := codetags.Extract(marker, lines)
  99. for tagName, tagLines := range tags {
  100. if len(tagNames) > 0 && !slices.Contains(tagNames, tagName) {
  101. continue
  102. }
  103. for _, line := range tagLines {
  104. typedTag, err := codetags.Parse(line, codetags.RawValues(!opts.parseValues))
  105. if err != nil {
  106. return nil, err
  107. }
  108. tag, err := toStringArgs(typedTag)
  109. if err != nil {
  110. return nil, err
  111. }
  112. out[tagName] = append(out[tagName], tag)
  113. }
  114. }
  115. return out, nil
  116. }
  117. // TagOption provides an option for extracting tags.
  118. type TagOption func(opts *tagOpts)
  119. // ParseValues enables parsing of tag values. When enabled, tag values must
  120. // be valid quoted strings, ints, booleans, identifiers, or tags. Otherwise, a
  121. // parse error will be returned. Also, when enabled, trailing comments are
  122. // ignored.
  123. // Default: disabled
  124. func ParseValues(enabled bool) TagOption {
  125. return func(opts *tagOpts) {
  126. opts.parseValues = enabled
  127. }
  128. }
  129. type tagOpts struct {
  130. parseValues bool
  131. }
  132. func toStringArgs(tag codetags.Tag) (Tag, error) {
  133. var stringArgs []string
  134. if len(tag.Args) > 1 {
  135. return Tag{}, fmt.Errorf("expected one argument, got: %v", tag.Args)
  136. }
  137. for _, arg := range tag.Args {
  138. if len(arg.Name) > 0 {
  139. return Tag{}, fmt.Errorf("unexpected named argument: %q", arg.Name)
  140. }
  141. if arg.Type != codetags.ArgTypeString {
  142. return Tag{}, fmt.Errorf("unexpected argument type: %s", arg.Type)
  143. } else {
  144. stringArgs = append(stringArgs, arg.Value)
  145. }
  146. }
  147. return Tag{
  148. Name: tag.Name,
  149. Args: stringArgs,
  150. Value: tag.Value,
  151. }, nil
  152. }
  153. // Tag represents a single comment tag.
  154. type Tag struct {
  155. // Name is the name of the tag with no arguments.
  156. Name string
  157. // Args is a list of optional arguments to the tag.
  158. Args []string
  159. // Value is the value of the tag.
  160. Value string
  161. }
  162. func (t Tag) String() string {
  163. buf := bytes.Buffer{}
  164. buf.WriteString(t.Name)
  165. if len(t.Args) > 0 {
  166. buf.WriteString("(")
  167. for i, a := range t.Args {
  168. if i > 0 {
  169. buf.WriteString(", ")
  170. }
  171. buf.WriteString(a)
  172. }
  173. buf.WriteString(")")
  174. }
  175. return buf.String()
  176. }