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.

group.go 9.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406
  1. // Copyright 2012 Jesse van den Kieboom. All rights reserved.
  2. // Use of this source code is governed by a BSD-style
  3. // license that can be found in the LICENSE file.
  4. package flags
  5. import (
  6. "errors"
  7. "reflect"
  8. "strings"
  9. "unicode/utf8"
  10. )
  11. // ErrNotPointerToStruct indicates that a provided data container is not
  12. // a pointer to a struct. Only pointers to structs are valid data containers
  13. // for options.
  14. var ErrNotPointerToStruct = errors.New("provided data is not a pointer to struct")
  15. // Group represents an option group. Option groups can be used to logically
  16. // group options together under a description. Groups are only used to provide
  17. // more structure to options both for the user (as displayed in the help message)
  18. // and for you, since groups can be nested.
  19. type Group struct {
  20. // A short description of the group. The
  21. // short description is primarily used in the built-in generated help
  22. // message
  23. ShortDescription string
  24. // A long description of the group. The long
  25. // description is primarily used to present information on commands
  26. // (Command embeds Group) in the built-in generated help and man pages.
  27. LongDescription string
  28. // The namespace of the group
  29. Namespace string
  30. // If true, the group is not displayed in the help or man page
  31. Hidden bool
  32. // The parent of the group or nil if it has no parent
  33. parent interface{}
  34. // All the options in the group
  35. options []*Option
  36. // All the subgroups
  37. groups []*Group
  38. // Whether the group represents the built-in help group
  39. isBuiltinHelp bool
  40. data interface{}
  41. }
  42. type scanHandler func(reflect.Value, *reflect.StructField) (bool, error)
  43. // AddGroup adds a new group to the command with the given name and data. The
  44. // data needs to be a pointer to a struct from which the fields indicate which
  45. // options are in the group.
  46. func (g *Group) AddGroup(shortDescription string, longDescription string, data interface{}) (*Group, error) {
  47. group := newGroup(shortDescription, longDescription, data)
  48. group.parent = g
  49. if err := group.scan(); err != nil {
  50. return nil, err
  51. }
  52. g.groups = append(g.groups, group)
  53. return group, nil
  54. }
  55. // Groups returns the list of groups embedded in this group.
  56. func (g *Group) Groups() []*Group {
  57. return g.groups
  58. }
  59. // Options returns the list of options in this group.
  60. func (g *Group) Options() []*Option {
  61. return g.options
  62. }
  63. // Find locates the subgroup with the given short description and returns it.
  64. // If no such group can be found Find will return nil. Note that the description
  65. // is matched case insensitively.
  66. func (g *Group) Find(shortDescription string) *Group {
  67. lshortDescription := strings.ToLower(shortDescription)
  68. var ret *Group
  69. g.eachGroup(func(gg *Group) {
  70. if gg != g && strings.ToLower(gg.ShortDescription) == lshortDescription {
  71. ret = gg
  72. }
  73. })
  74. return ret
  75. }
  76. func (g *Group) findOption(matcher func(*Option) bool) (option *Option) {
  77. g.eachGroup(func(g *Group) {
  78. for _, opt := range g.options {
  79. if option == nil && matcher(opt) {
  80. option = opt
  81. }
  82. }
  83. })
  84. return option
  85. }
  86. // FindOptionByLongName finds an option that is part of the group, or any of its
  87. // subgroups, by matching its long name (including the option namespace).
  88. func (g *Group) FindOptionByLongName(longName string) *Option {
  89. return g.findOption(func(option *Option) bool {
  90. return option.LongNameWithNamespace() == longName
  91. })
  92. }
  93. // FindOptionByShortName finds an option that is part of the group, or any of
  94. // its subgroups, by matching its short name.
  95. func (g *Group) FindOptionByShortName(shortName rune) *Option {
  96. return g.findOption(func(option *Option) bool {
  97. return option.ShortName == shortName
  98. })
  99. }
  100. func newGroup(shortDescription string, longDescription string, data interface{}) *Group {
  101. return &Group{
  102. ShortDescription: shortDescription,
  103. LongDescription: longDescription,
  104. data: data,
  105. }
  106. }
  107. func (g *Group) optionByName(name string, namematch func(*Option, string) bool) *Option {
  108. prio := 0
  109. var retopt *Option
  110. g.eachGroup(func(g *Group) {
  111. for _, opt := range g.options {
  112. if namematch != nil && namematch(opt, name) && prio < 4 {
  113. retopt = opt
  114. prio = 4
  115. }
  116. if name == opt.field.Name && prio < 3 {
  117. retopt = opt
  118. prio = 3
  119. }
  120. if name == opt.LongNameWithNamespace() && prio < 2 {
  121. retopt = opt
  122. prio = 2
  123. }
  124. if opt.ShortName != 0 && name == string(opt.ShortName) && prio < 1 {
  125. retopt = opt
  126. prio = 1
  127. }
  128. }
  129. })
  130. return retopt
  131. }
  132. func (g *Group) eachGroup(f func(*Group)) {
  133. f(g)
  134. for _, gg := range g.groups {
  135. gg.eachGroup(f)
  136. }
  137. }
  138. func isStringFalsy(s string) bool {
  139. return s == "" || s == "false" || s == "no" || s == "0"
  140. }
  141. func (g *Group) scanStruct(realval reflect.Value, sfield *reflect.StructField, handler scanHandler) error {
  142. stype := realval.Type()
  143. if sfield != nil {
  144. if ok, err := handler(realval, sfield); err != nil {
  145. return err
  146. } else if ok {
  147. return nil
  148. }
  149. }
  150. for i := 0; i < stype.NumField(); i++ {
  151. field := stype.Field(i)
  152. // PkgName is set only for non-exported fields, which we ignore
  153. if field.PkgPath != "" && !field.Anonymous {
  154. continue
  155. }
  156. mtag := newMultiTag(string(field.Tag))
  157. if err := mtag.Parse(); err != nil {
  158. return err
  159. }
  160. // Skip fields with the no-flag tag
  161. if mtag.Get("no-flag") != "" {
  162. continue
  163. }
  164. // Dive deep into structs or pointers to structs
  165. kind := field.Type.Kind()
  166. fld := realval.Field(i)
  167. if kind == reflect.Struct {
  168. if err := g.scanStruct(fld, &field, handler); err != nil {
  169. return err
  170. }
  171. } else if kind == reflect.Ptr && field.Type.Elem().Kind() == reflect.Struct {
  172. flagCountBefore := len(g.options) + len(g.groups)
  173. if fld.IsNil() {
  174. fld = reflect.New(fld.Type().Elem())
  175. }
  176. if err := g.scanStruct(reflect.Indirect(fld), &field, handler); err != nil {
  177. return err
  178. }
  179. if len(g.options)+len(g.groups) != flagCountBefore {
  180. realval.Field(i).Set(fld)
  181. }
  182. }
  183. longname := mtag.Get("long")
  184. shortname := mtag.Get("short")
  185. // Need at least either a short or long name
  186. if longname == "" && shortname == "" && mtag.Get("ini-name") == "" {
  187. continue
  188. }
  189. short := rune(0)
  190. rc := utf8.RuneCountInString(shortname)
  191. if rc > 1 {
  192. return newErrorf(ErrShortNameTooLong,
  193. "short names can only be 1 character long, not `%s'",
  194. shortname)
  195. } else if rc == 1 {
  196. short, _ = utf8.DecodeRuneInString(shortname)
  197. }
  198. description := mtag.Get("description")
  199. def := mtag.GetMany("default")
  200. optionalValue := mtag.GetMany("optional-value")
  201. valueName := mtag.Get("value-name")
  202. defaultMask := mtag.Get("default-mask")
  203. optional := !isStringFalsy(mtag.Get("optional"))
  204. required := !isStringFalsy(mtag.Get("required"))
  205. choices := mtag.GetMany("choice")
  206. hidden := !isStringFalsy(mtag.Get("hidden"))
  207. option := &Option{
  208. Description: description,
  209. ShortName: short,
  210. LongName: longname,
  211. Default: def,
  212. EnvDefaultKey: mtag.Get("env"),
  213. EnvDefaultDelim: mtag.Get("env-delim"),
  214. OptionalArgument: optional,
  215. OptionalValue: optionalValue,
  216. Required: required,
  217. ValueName: valueName,
  218. DefaultMask: defaultMask,
  219. Choices: choices,
  220. Hidden: hidden,
  221. group: g,
  222. field: field,
  223. value: realval.Field(i),
  224. tag: mtag,
  225. }
  226. if option.isBool() && option.Default != nil {
  227. return newErrorf(ErrInvalidTag,
  228. "boolean flag `%s' may not have default values, they always default to `false' and can only be turned on",
  229. option.shortAndLongName())
  230. }
  231. g.options = append(g.options, option)
  232. }
  233. return nil
  234. }
  235. func (g *Group) checkForDuplicateFlags() *Error {
  236. shortNames := make(map[rune]*Option)
  237. longNames := make(map[string]*Option)
  238. var duplicateError *Error
  239. g.eachGroup(func(g *Group) {
  240. for _, option := range g.options {
  241. if option.LongName != "" {
  242. longName := option.LongNameWithNamespace()
  243. if otherOption, ok := longNames[longName]; ok {
  244. duplicateError = newErrorf(ErrDuplicatedFlag, "option `%s' uses the same long name as option `%s'", option, otherOption)
  245. return
  246. }
  247. longNames[longName] = option
  248. }
  249. if option.ShortName != 0 {
  250. if otherOption, ok := shortNames[option.ShortName]; ok {
  251. duplicateError = newErrorf(ErrDuplicatedFlag, "option `%s' uses the same short name as option `%s'", option, otherOption)
  252. return
  253. }
  254. shortNames[option.ShortName] = option
  255. }
  256. }
  257. })
  258. return duplicateError
  259. }
  260. func (g *Group) scanSubGroupHandler(realval reflect.Value, sfield *reflect.StructField) (bool, error) {
  261. mtag := newMultiTag(string(sfield.Tag))
  262. if err := mtag.Parse(); err != nil {
  263. return true, err
  264. }
  265. subgroup := mtag.Get("group")
  266. if len(subgroup) != 0 {
  267. var ptrval reflect.Value
  268. if realval.Kind() == reflect.Ptr {
  269. ptrval = realval
  270. if ptrval.IsNil() {
  271. ptrval.Set(reflect.New(ptrval.Type()))
  272. }
  273. } else {
  274. ptrval = realval.Addr()
  275. }
  276. description := mtag.Get("description")
  277. group, err := g.AddGroup(subgroup, description, ptrval.Interface())
  278. if err != nil {
  279. return true, err
  280. }
  281. group.Namespace = mtag.Get("namespace")
  282. group.Hidden = mtag.Get("hidden") != ""
  283. return true, nil
  284. }
  285. return false, nil
  286. }
  287. func (g *Group) scanType(handler scanHandler) error {
  288. // Get all the public fields in the data struct
  289. ptrval := reflect.ValueOf(g.data)
  290. if ptrval.Type().Kind() != reflect.Ptr {
  291. panic(ErrNotPointerToStruct)
  292. }
  293. stype := ptrval.Type().Elem()
  294. if stype.Kind() != reflect.Struct {
  295. panic(ErrNotPointerToStruct)
  296. }
  297. realval := reflect.Indirect(ptrval)
  298. if err := g.scanStruct(realval, nil, handler); err != nil {
  299. return err
  300. }
  301. if err := g.checkForDuplicateFlags(); err != nil {
  302. return err
  303. }
  304. return nil
  305. }
  306. func (g *Group) scan() error {
  307. return g.scanType(g.scanSubGroupHandler)
  308. }
  309. func (g *Group) groupByName(name string) *Group {
  310. if len(name) == 0 {
  311. return g
  312. }
  313. return g.Find(name)
  314. }