github.com/maresnic/mr-kong@v1.0.0/build.go (about) 1 package kong 2 3 import ( 4 "fmt" 5 "reflect" 6 "strings" 7 ) 8 9 // Plugins are dynamically embedded command-line structures. 10 // 11 // Each element in the Plugins list *must* be a pointer to a structure. 12 type Plugins []interface{} 13 14 func build(k *Kong, ast interface{}) (app *Application, err error) { 15 v := reflect.ValueOf(ast) 16 iv := reflect.Indirect(v) 17 if v.Kind() != reflect.Ptr || iv.Kind() != reflect.Struct { 18 return nil, fmt.Errorf("expected a pointer to a struct but got %T", ast) 19 } 20 21 app = &Application{} 22 extraFlags := k.extraFlags() 23 seenFlags := map[string]bool{} 24 for _, flag := range extraFlags { 25 seenFlags[flag.Name] = true 26 } 27 28 node, err := buildNode(k, iv, ApplicationNode, newEmptyTag(), seenFlags) 29 if err != nil { 30 return nil, err 31 } 32 if len(node.Positional) > 0 && len(node.Children) > 0 { 33 return nil, fmt.Errorf("can't mix positional arguments and branching arguments on %T", ast) 34 } 35 app.Node = node 36 app.Node.Flags = append(extraFlags, app.Node.Flags...) 37 app.Tag = newEmptyTag() 38 app.Tag.Vars = k.vars 39 return app, nil 40 } 41 42 func dashedString(s string) string { 43 return strings.Join(camelCase(s), "-") 44 } 45 46 type flattenedField struct { 47 field reflect.StructField 48 value reflect.Value 49 tag *Tag 50 } 51 52 func flattenedFields(v reflect.Value, ptag *Tag) (out []flattenedField, err error) { 53 v = reflect.Indirect(v) 54 for i := 0; i < v.NumField(); i++ { 55 ft := v.Type().Field(i) 56 fv := v.Field(i) 57 tag, err := parseTag(v, ft) 58 if err != nil { 59 return nil, err 60 } 61 if tag.Ignored { 62 continue 63 } 64 // Assign group if it's not already set. 65 if tag.Group == "" { 66 tag.Group = ptag.Group 67 } 68 // Accumulate prefixes. 69 tag.Prefix = ptag.Prefix + tag.Prefix 70 tag.EnvPrefix = ptag.EnvPrefix + tag.EnvPrefix 71 // Combine parent vars. 72 tag.Vars = ptag.Vars.CloneWith(tag.Vars) 73 // Command and embedded structs can be pointers, so we hydrate them now. 74 if (tag.Cmd || tag.Embed) && ft.Type.Kind() == reflect.Ptr { 75 fv = reflect.New(ft.Type.Elem()).Elem() 76 v.FieldByIndex(ft.Index).Set(fv.Addr()) 77 } 78 if !ft.Anonymous && !tag.Embed { 79 if fv.CanSet() { 80 field := flattenedField{field: ft, value: fv, tag: tag} 81 out = append(out, field) 82 } 83 continue 84 } 85 86 // Embedded type. 87 if fv.Kind() == reflect.Interface { 88 fv = fv.Elem() 89 } else if fv.Type() == reflect.TypeOf(Plugins{}) { 90 for i := 0; i < fv.Len(); i++ { 91 fields, ferr := flattenedFields(fv.Index(i).Elem(), tag) 92 if ferr != nil { 93 return nil, ferr 94 } 95 out = append(out, fields...) 96 } 97 continue 98 } 99 sub, err := flattenedFields(fv, tag) 100 if err != nil { 101 return nil, err 102 } 103 out = append(out, sub...) 104 } 105 return out, nil 106 } 107 108 // Build a Node in the Kong data model. 109 // 110 // "v" is the value to create the node from, "typ" is the output Node type. 111 func buildNode(k *Kong, v reflect.Value, typ NodeType, tag *Tag, seenFlags map[string]bool) (*Node, error) { 112 node := &Node{ 113 Type: typ, 114 Target: v, 115 Tag: tag, 116 } 117 fields, err := flattenedFields(v, tag) 118 if err != nil { 119 return nil, err 120 } 121 122 MAIN: 123 for _, field := range fields { 124 for _, r := range k.ignoreFields { 125 if r.MatchString(v.Type().Name() + "." + field.field.Name) { 126 continue MAIN 127 } 128 } 129 130 ft := field.field 131 fv := field.value 132 133 tag := field.tag 134 name := tag.Name 135 if name == "" { 136 name = tag.Prefix + k.flagNamer(ft.Name) 137 } else { 138 name = tag.Prefix + name 139 } 140 141 if len(tag.Envs) != 0 { 142 for i := range tag.Envs { 143 tag.Envs[i] = tag.EnvPrefix + tag.Envs[i] 144 } 145 } 146 147 // Nested structs are either commands or args, unless they implement the Mapper interface. 148 if field.value.Kind() == reflect.Struct && (tag.Cmd || tag.Arg) && k.registry.ForValue(fv) == nil { 149 typ := CommandNode 150 if tag.Arg { 151 typ = ArgumentNode 152 } 153 err = buildChild(k, node, typ, v, ft, fv, tag, name, seenFlags) 154 } else { 155 err = buildField(k, node, v, ft, fv, tag, name, seenFlags) 156 } 157 if err != nil { 158 return nil, err 159 } 160 } 161 162 // Validate if there are no duplicate names 163 if err := checkDuplicateNames(node, v); err != nil { 164 return nil, err 165 } 166 167 // "Unsee" flags. 168 for _, flag := range node.Flags { 169 delete(seenFlags, "--"+flag.Name) 170 if flag.Short != 0 { 171 delete(seenFlags, "-"+string(flag.Short)) 172 } 173 for _, aflag := range flag.Aliases { 174 delete(seenFlags, "--"+aflag) 175 } 176 } 177 178 if err := validatePositionalArguments(node); err != nil { 179 return nil, err 180 } 181 182 return node, nil 183 } 184 185 func validatePositionalArguments(node *Node) error { 186 var last *Value 187 for i, curr := range node.Positional { 188 if last != nil { 189 // Scan through argument positionals to ensure optional is never before a required. 190 if !last.Required && curr.Required { 191 return fmt.Errorf("%s: required %q cannot come after optional %q", node.FullPath(), curr.Name, last.Name) 192 } 193 194 // Cumulative argument needs to be last. 195 if last.IsCumulative() { 196 return fmt.Errorf("%s: argument %q cannot come after cumulative %q", node.FullPath(), curr.Name, last.Name) 197 } 198 } 199 200 last = curr 201 curr.Position = i 202 } 203 204 return nil 205 } 206 207 func buildChild(k *Kong, node *Node, typ NodeType, v reflect.Value, ft reflect.StructField, fv reflect.Value, tag *Tag, name string, seenFlags map[string]bool) error { 208 child, err := buildNode(k, fv, typ, newEmptyTag(), seenFlags) 209 if err != nil { 210 return err 211 } 212 child.Name = name 213 child.Tag = tag 214 child.Parent = node 215 child.Help = tag.Help 216 child.Hidden = tag.Hidden 217 child.Group = buildGroupForKey(k, tag.Group) 218 child.Aliases = tag.Aliases 219 220 if provider, ok := fv.Addr().Interface().(HelpProvider); ok { 221 child.Detail = provider.Help() 222 } 223 224 // A branching argument. This is a bit hairy, as we let buildNode() do the parsing, then check that 225 // a positional argument is provided to the child, and move it to the branching argument field. 226 if tag.Arg { 227 if len(child.Positional) == 0 { 228 return failField(v, ft, "positional branch must have at least one child positional argument named %q", name) 229 } 230 if child.Positional[0].Name != name { 231 return failField(v, ft, "first field in positional branch must have the same name as the parent field (%s).", child.Name) 232 } 233 234 child.Argument = child.Positional[0] 235 child.Positional = child.Positional[1:] 236 if child.Help == "" { 237 child.Help = child.Argument.Help 238 } 239 } else { 240 if tag.HasDefault { 241 if node.DefaultCmd != nil { 242 return failField(v, ft, "can't have more than one default command under %s", node.Summary()) 243 } 244 if tag.Default != "withargs" && (len(child.Children) > 0 || len(child.Positional) > 0) { 245 return failField(v, ft, "default command %s must not have subcommands or arguments", child.Summary()) 246 } 247 node.DefaultCmd = child 248 } 249 if tag.Passthrough { 250 if len(child.Children) > 0 || len(child.Flags) > 0 { 251 return failField(v, ft, "passthrough command %s must not have subcommands or flags", child.Summary()) 252 } 253 if len(child.Positional) != 1 { 254 return failField(v, ft, "passthrough command %s must contain exactly one positional argument", child.Summary()) 255 } 256 if !checkPassthroughArg(child.Positional[0].Target) { 257 return failField(v, ft, "passthrough command %s must contain exactly one positional argument of []string type", child.Summary()) 258 } 259 child.Passthrough = true 260 } 261 } 262 node.Children = append(node.Children, child) 263 264 if len(child.Positional) > 0 && len(child.Children) > 0 { 265 return failField(v, ft, "can't mix positional arguments and branching arguments") 266 } 267 268 return nil 269 } 270 271 func buildField(k *Kong, node *Node, v reflect.Value, ft reflect.StructField, fv reflect.Value, tag *Tag, name string, seenFlags map[string]bool) error { 272 mapper := k.registry.ForNamedValue(tag.Type, fv) 273 if mapper == nil { 274 return failField(v, ft, "unsupported field type %s, perhaps missing a cmd:\"\" tag?", ft.Type) 275 } 276 277 value := &Value{ 278 Name: name, 279 Help: tag.Help, 280 OrigHelp: tag.Help, 281 HasDefault: tag.HasDefault, 282 Default: tag.Default, 283 DefaultValue: reflect.New(fv.Type()).Elem(), 284 Mapper: mapper, 285 Tag: tag, 286 Target: fv, 287 Enum: tag.Enum, 288 Passthrough: tag.Passthrough, 289 290 // Flags are optional by default, and args are required by default. 291 Required: (!tag.Arg && tag.Required) || (tag.Arg && !tag.Optional), 292 Format: tag.Format, 293 } 294 295 if tag.Arg { 296 node.Positional = append(node.Positional, value) 297 } else { 298 if seenFlags["--"+value.Name] { 299 return failField(v, ft, "duplicate flag --%s", value.Name) 300 } 301 seenFlags["--"+value.Name] = true 302 for _, alias := range tag.Aliases { 303 aliasFlag := "--" + alias 304 if seenFlags[aliasFlag] { 305 return failField(v, ft, "duplicate flag %s", aliasFlag) 306 } 307 seenFlags[aliasFlag] = true 308 } 309 if tag.Short != 0 { 310 if seenFlags["-"+string(tag.Short)] { 311 return failField(v, ft, "duplicate short flag -%c", tag.Short) 312 } 313 seenFlags["-"+string(tag.Short)] = true 314 } 315 flag := &Flag{ 316 Value: value, 317 Aliases: tag.Aliases, 318 Short: tag.Short, 319 PlaceHolder: tag.PlaceHolder, 320 Envs: tag.Envs, 321 Group: buildGroupForKey(k, tag.Group), 322 Xor: tag.Xor, 323 Hidden: tag.Hidden, 324 } 325 value.Flag = flag 326 node.Flags = append(node.Flags, flag) 327 } 328 return nil 329 } 330 331 func buildGroupForKey(k *Kong, key string) *Group { 332 if key == "" { 333 return nil 334 } 335 for _, group := range k.groups { 336 if group.Key == key { 337 return &group 338 } 339 } 340 341 // No group provided with kong.ExplicitGroups. We create one ad-hoc for this key. 342 return &Group{ 343 Key: key, 344 Title: key, 345 } 346 } 347 348 func checkDuplicateNames(node *Node, v reflect.Value) error { 349 seenNames := make(map[string]struct{}) 350 for _, node := range node.Children { 351 if _, ok := seenNames[node.Name]; ok { 352 name := v.Type().Name() 353 if name == "" { 354 name = "<anonymous struct>" 355 } 356 return fmt.Errorf("duplicate command name %q in command %q", node.Name, name) 357 } 358 359 seenNames[node.Name] = struct{}{} 360 } 361 362 return nil 363 }