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  }