mirror of
https://github.com/crazy-max/diun.git
synced 2024-12-22 19:38:28 +00:00
9a00af98bc
Bumps [github.com/alecthomas/kong](https://github.com/alecthomas/kong) from 0.9.0 to 1.6.0. - [Commits](https://github.com/alecthomas/kong/compare/v0.9.0...v1.6.0) --- updated-dependencies: - dependency-name: github.com/alecthomas/kong dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com>
379 lines
10 KiB
Go
379 lines
10 KiB
Go
package kong
|
|
|
|
import (
|
|
"fmt"
|
|
"reflect"
|
|
"strings"
|
|
)
|
|
|
|
// Plugins are dynamically embedded command-line structures.
|
|
//
|
|
// Each element in the Plugins list *must* be a pointer to a structure.
|
|
type Plugins []interface{}
|
|
|
|
func build(k *Kong, ast interface{}) (app *Application, err error) {
|
|
v := reflect.ValueOf(ast)
|
|
iv := reflect.Indirect(v)
|
|
if v.Kind() != reflect.Ptr || iv.Kind() != reflect.Struct {
|
|
return nil, fmt.Errorf("expected a pointer to a struct but got %T", ast)
|
|
}
|
|
|
|
app = &Application{}
|
|
extraFlags := k.extraFlags()
|
|
seenFlags := map[string]bool{}
|
|
for _, flag := range extraFlags {
|
|
seenFlags[flag.Name] = true
|
|
}
|
|
|
|
node, err := buildNode(k, iv, ApplicationNode, newEmptyTag(), seenFlags)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(node.Positional) > 0 && len(node.Children) > 0 {
|
|
return nil, fmt.Errorf("can't mix positional arguments and branching arguments on %T", ast)
|
|
}
|
|
app.Node = node
|
|
app.Node.Flags = append(extraFlags, app.Node.Flags...)
|
|
app.Tag = newEmptyTag()
|
|
app.Tag.Vars = k.vars
|
|
return app, nil
|
|
}
|
|
|
|
func dashedString(s string) string {
|
|
return strings.Join(camelCase(s), "-")
|
|
}
|
|
|
|
type flattenedField struct {
|
|
field reflect.StructField
|
|
value reflect.Value
|
|
tag *Tag
|
|
}
|
|
|
|
func flattenedFields(v reflect.Value, ptag *Tag) (out []flattenedField, err error) {
|
|
v = reflect.Indirect(v)
|
|
if v.Kind() != reflect.Struct {
|
|
return out, nil
|
|
}
|
|
for i := 0; i < v.NumField(); i++ {
|
|
ft := v.Type().Field(i)
|
|
fv := v.Field(i)
|
|
tag, err := parseTag(v, ft)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if tag.Ignored {
|
|
continue
|
|
}
|
|
// Assign group if it's not already set.
|
|
if tag.Group == "" {
|
|
tag.Group = ptag.Group
|
|
}
|
|
// Accumulate prefixes.
|
|
tag.Prefix = ptag.Prefix + tag.Prefix
|
|
tag.EnvPrefix = ptag.EnvPrefix + tag.EnvPrefix
|
|
// Combine parent vars.
|
|
tag.Vars = ptag.Vars.CloneWith(tag.Vars)
|
|
// Command and embedded structs can be pointers, so we hydrate them now.
|
|
if (tag.Cmd || tag.Embed) && ft.Type.Kind() == reflect.Ptr {
|
|
fv = reflect.New(ft.Type.Elem()).Elem()
|
|
v.FieldByIndex(ft.Index).Set(fv.Addr())
|
|
}
|
|
if !ft.Anonymous && !tag.Embed {
|
|
if fv.CanSet() {
|
|
field := flattenedField{field: ft, value: fv, tag: tag}
|
|
out = append(out, field)
|
|
}
|
|
continue
|
|
}
|
|
|
|
// Embedded type.
|
|
if fv.Kind() == reflect.Interface {
|
|
fv = fv.Elem()
|
|
} else if fv.Type() == reflect.TypeOf(Plugins{}) {
|
|
for i := 0; i < fv.Len(); i++ {
|
|
fields, ferr := flattenedFields(fv.Index(i).Elem(), tag)
|
|
if ferr != nil {
|
|
return nil, ferr
|
|
}
|
|
out = append(out, fields...)
|
|
}
|
|
continue
|
|
}
|
|
sub, err := flattenedFields(fv, tag)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
out = append(out, sub...)
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
// Build a Node in the Kong data model.
|
|
//
|
|
// "v" is the value to create the node from, "typ" is the output Node type.
|
|
func buildNode(k *Kong, v reflect.Value, typ NodeType, tag *Tag, seenFlags map[string]bool) (*Node, error) {
|
|
node := &Node{
|
|
Type: typ,
|
|
Target: v,
|
|
Tag: tag,
|
|
}
|
|
fields, err := flattenedFields(v, tag)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
MAIN:
|
|
for _, field := range fields {
|
|
for _, r := range k.ignoreFields {
|
|
if r.MatchString(v.Type().Name() + "." + field.field.Name) {
|
|
continue MAIN
|
|
}
|
|
}
|
|
|
|
ft := field.field
|
|
fv := field.value
|
|
|
|
tag := field.tag
|
|
name := tag.Name
|
|
if name == "" {
|
|
name = tag.Prefix + k.flagNamer(ft.Name)
|
|
} else {
|
|
name = tag.Prefix + name
|
|
}
|
|
|
|
if len(tag.Envs) != 0 {
|
|
for i := range tag.Envs {
|
|
tag.Envs[i] = tag.EnvPrefix + tag.Envs[i]
|
|
}
|
|
}
|
|
|
|
// Nested structs are either commands or args, unless they implement the Mapper interface.
|
|
if field.value.Kind() == reflect.Struct && (tag.Cmd || tag.Arg) && k.registry.ForValue(fv) == nil {
|
|
typ := CommandNode
|
|
if tag.Arg {
|
|
typ = ArgumentNode
|
|
}
|
|
err = buildChild(k, node, typ, v, ft, fv, tag, name, seenFlags)
|
|
} else {
|
|
err = buildField(k, node, v, ft, fv, tag, name, seenFlags)
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
// Validate if there are no duplicate names
|
|
if err := checkDuplicateNames(node, v); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// "Unsee" flags.
|
|
for _, flag := range node.Flags {
|
|
delete(seenFlags, "--"+flag.Name)
|
|
if flag.Short != 0 {
|
|
delete(seenFlags, "-"+string(flag.Short))
|
|
}
|
|
if negFlag := negatableFlagName(flag.Name, flag.Tag.Negatable); negFlag != "" {
|
|
delete(seenFlags, negFlag)
|
|
}
|
|
for _, aflag := range flag.Aliases {
|
|
delete(seenFlags, "--"+aflag)
|
|
}
|
|
}
|
|
|
|
if err := validatePositionalArguments(node); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return node, nil
|
|
}
|
|
|
|
func validatePositionalArguments(node *Node) error {
|
|
var last *Value
|
|
for i, curr := range node.Positional {
|
|
if last != nil {
|
|
// Scan through argument positionals to ensure optional is never before a required.
|
|
if !last.Required && curr.Required {
|
|
return fmt.Errorf("%s: required %q cannot come after optional %q", node.FullPath(), curr.Name, last.Name)
|
|
}
|
|
|
|
// Cumulative argument needs to be last.
|
|
if last.IsCumulative() {
|
|
return fmt.Errorf("%s: argument %q cannot come after cumulative %q", node.FullPath(), curr.Name, last.Name)
|
|
}
|
|
}
|
|
|
|
last = curr
|
|
curr.Position = i
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
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 {
|
|
child, err := buildNode(k, fv, typ, newEmptyTag(), seenFlags)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
child.Name = name
|
|
child.Tag = tag
|
|
child.Parent = node
|
|
child.Help = tag.Help
|
|
child.Hidden = tag.Hidden
|
|
child.Group = buildGroupForKey(k, tag.Group)
|
|
child.Aliases = tag.Aliases
|
|
|
|
if provider, ok := fv.Addr().Interface().(HelpProvider); ok {
|
|
child.Detail = provider.Help()
|
|
}
|
|
|
|
// A branching argument. This is a bit hairy, as we let buildNode() do the parsing, then check that
|
|
// a positional argument is provided to the child, and move it to the branching argument field.
|
|
if tag.Arg {
|
|
if len(child.Positional) == 0 {
|
|
return failField(v, ft, "positional branch must have at least one child positional argument named %q", name)
|
|
}
|
|
if child.Positional[0].Name != name {
|
|
return failField(v, ft, "first field in positional branch must have the same name as the parent field (%s).", child.Name)
|
|
}
|
|
|
|
child.Argument = child.Positional[0]
|
|
child.Positional = child.Positional[1:]
|
|
if child.Help == "" {
|
|
child.Help = child.Argument.Help
|
|
}
|
|
} else {
|
|
if tag.HasDefault {
|
|
if node.DefaultCmd != nil {
|
|
return failField(v, ft, "can't have more than one default command under %s", node.Summary())
|
|
}
|
|
if tag.Default != "withargs" && (len(child.Children) > 0 || len(child.Positional) > 0) {
|
|
return failField(v, ft, "default command %s must not have subcommands or arguments", child.Summary())
|
|
}
|
|
node.DefaultCmd = child
|
|
}
|
|
if tag.Passthrough {
|
|
if len(child.Children) > 0 || len(child.Flags) > 0 {
|
|
return failField(v, ft, "passthrough command %s must not have subcommands or flags", child.Summary())
|
|
}
|
|
if len(child.Positional) != 1 {
|
|
return failField(v, ft, "passthrough command %s must contain exactly one positional argument", child.Summary())
|
|
}
|
|
if !checkPassthroughArg(child.Positional[0].Target) {
|
|
return failField(v, ft, "passthrough command %s must contain exactly one positional argument of []string type", child.Summary())
|
|
}
|
|
child.Passthrough = true
|
|
}
|
|
}
|
|
node.Children = append(node.Children, child)
|
|
|
|
if len(child.Positional) > 0 && len(child.Children) > 0 {
|
|
return failField(v, ft, "can't mix positional arguments and branching arguments")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func buildField(k *Kong, node *Node, v reflect.Value, ft reflect.StructField, fv reflect.Value, tag *Tag, name string, seenFlags map[string]bool) error {
|
|
mapper := k.registry.ForNamedValue(tag.Type, fv)
|
|
if mapper == nil {
|
|
return failField(v, ft, "unsupported field type %s, perhaps missing a cmd:\"\" tag?", ft.Type)
|
|
}
|
|
|
|
value := &Value{
|
|
Name: name,
|
|
Help: tag.Help,
|
|
OrigHelp: tag.Help,
|
|
HasDefault: tag.HasDefault,
|
|
Default: tag.Default,
|
|
DefaultValue: reflect.New(fv.Type()).Elem(),
|
|
Mapper: mapper,
|
|
Tag: tag,
|
|
Target: fv,
|
|
Enum: tag.Enum,
|
|
Passthrough: tag.Passthrough,
|
|
PassthroughMode: tag.PassthroughMode,
|
|
|
|
// Flags are optional by default, and args are required by default.
|
|
Required: (!tag.Arg && tag.Required) || (tag.Arg && !tag.Optional),
|
|
Format: tag.Format,
|
|
}
|
|
|
|
if tag.Arg {
|
|
node.Positional = append(node.Positional, value)
|
|
} else {
|
|
if seenFlags["--"+value.Name] {
|
|
return failField(v, ft, "duplicate flag --%s", value.Name)
|
|
}
|
|
seenFlags["--"+value.Name] = true
|
|
for _, alias := range tag.Aliases {
|
|
aliasFlag := "--" + alias
|
|
if seenFlags[aliasFlag] {
|
|
return failField(v, ft, "duplicate flag %s", aliasFlag)
|
|
}
|
|
seenFlags[aliasFlag] = true
|
|
}
|
|
if tag.Short != 0 {
|
|
if seenFlags["-"+string(tag.Short)] {
|
|
return failField(v, ft, "duplicate short flag -%c", tag.Short)
|
|
}
|
|
seenFlags["-"+string(tag.Short)] = true
|
|
}
|
|
if tag.Negatable != "" {
|
|
negFlag := negatableFlagName(value.Name, tag.Negatable)
|
|
if seenFlags[negFlag] {
|
|
return failField(v, ft, "duplicate negation flag %s", negFlag)
|
|
}
|
|
seenFlags[negFlag] = true
|
|
}
|
|
flag := &Flag{
|
|
Value: value,
|
|
Aliases: tag.Aliases,
|
|
Short: tag.Short,
|
|
PlaceHolder: tag.PlaceHolder,
|
|
Envs: tag.Envs,
|
|
Group: buildGroupForKey(k, tag.Group),
|
|
Xor: tag.Xor,
|
|
And: tag.And,
|
|
Hidden: tag.Hidden,
|
|
}
|
|
value.Flag = flag
|
|
node.Flags = append(node.Flags, flag)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func buildGroupForKey(k *Kong, key string) *Group {
|
|
if key == "" {
|
|
return nil
|
|
}
|
|
for _, group := range k.groups {
|
|
if group.Key == key {
|
|
return &group
|
|
}
|
|
}
|
|
|
|
// No group provided with kong.ExplicitGroups. We create one ad-hoc for this key.
|
|
return &Group{
|
|
Key: key,
|
|
Title: key,
|
|
}
|
|
}
|
|
|
|
func checkDuplicateNames(node *Node, v reflect.Value) error {
|
|
seenNames := make(map[string]struct{})
|
|
for _, node := range node.Children {
|
|
if _, ok := seenNames[node.Name]; ok {
|
|
name := v.Type().Name()
|
|
if name == "" {
|
|
name = "<anonymous struct>"
|
|
}
|
|
return fmt.Errorf("duplicate command name %q in command %q", node.Name, name)
|
|
}
|
|
|
|
seenNames[node.Name] = struct{}{}
|
|
}
|
|
|
|
return nil
|
|
}
|