package kong import ( "fmt" "math" "os" "reflect" "strconv" "strings" "github.com/pkg/errors" ) // A Visitable component in the model. type Visitable interface { node() } // Application is the root of the Kong model. type Application struct { *Node // Help flag, if the NoDefaultHelp() option is not specified. HelpFlag *Flag } // Argument represents a branching positional argument. type Argument = Node // Command represents a command in the CLI. type Command = Node // NodeType is an enum representing the type of a Node. type NodeType int // Node type enumerations. const ( ApplicationNode NodeType = iota CommandNode ArgumentNode ) // Node is a branch in the CLI. ie. a command or positional argument. type Node struct { Type NodeType Parent *Node Name string Help string // Short help displayed in summaries. Detail string // Detailed help displayed when describing command/arg alone. Group string Hidden bool Flags []*Flag Positional []*Positional Children []*Node Target reflect.Value // Pointer to the value in the grammar that this Node is associated with. Tag *Tag Argument *Value // Populated when Type is ArgumentNode. } func (*Node) node() {} // Leaf returns true if this Node is a leaf node. func (n *Node) Leaf() bool { return len(n.Children) == 0 } // Find a command/argument/flag by pointer to its field. // // Returns nil if not found. Panics if ptr is not a pointer. func (n *Node) Find(ptr interface{}) *Node { key := reflect.ValueOf(ptr) if key.Kind() != reflect.Ptr { panic("expected a pointer") } return n.findNode(key) } func (n *Node) findNode(key reflect.Value) *Node { if n.Target == key { return n } for _, child := range n.Children { if found := child.findNode(key); found != nil { return found } } return nil } // AllFlags returns flags from all ancestor branches encountered. // // If "hide" is true hidden flags will be omitted. func (n *Node) AllFlags(hide bool) (out [][]*Flag) { if n.Parent != nil { out = append(out, n.Parent.AllFlags(hide)...) } group := []*Flag{} for _, flag := range n.Flags { if !hide || !flag.Hidden { group = append(group, flag) } } if len(group) > 0 { out = append(out, group) } return } // Leaves returns the leaf commands/arguments under Node. // // If "hidden" is true hidden leaves will be omitted. func (n *Node) Leaves(hide bool) (out []*Node) { _ = Visit(n, func(nd Visitable, next Next) error { if nd == n { return next(nil) } if node, ok := nd.(*Node); ok { if hide && node.Hidden { return nil } if len(node.Children) == 0 && node.Type != ApplicationNode { out = append(out, node) } } return next(nil) }) return } // Depth of the command from the application root. func (n *Node) Depth() int { depth := 0 p := n.Parent for p != nil && p.Type != ApplicationNode { depth++ p = p.Parent } return depth } // Summary help string for the node (not including application name). func (n *Node) Summary() string { summary := n.Path() if flags := n.FlagSummary(true); flags != "" { summary += " " + flags } args := []string{} for _, arg := range n.Positional { args = append(args, arg.Summary()) } if len(args) != 0 { summary += " " + strings.Join(args, " ") } else if len(n.Children) > 0 { summary += " " } return summary } // FlagSummary for the node. func (n *Node) FlagSummary(hide bool) string { required := []string{} count := 0 for _, group := range n.AllFlags(hide) { for _, flag := range group { count++ if flag.Required { required = append(required, flag.Summary()) } } } return strings.Join(required, " ") } // FullPath is like Path() but includes the Application root node. func (n *Node) FullPath() string { root := n for root.Parent != nil { root = root.Parent } return strings.TrimSpace(root.Name + " " + n.Path()) } // Vars returns the combined Vars defined by all ancestors of this Node. func (n *Node) Vars() Vars { if n == nil { return Vars{} } return n.Parent.Vars().CloneWith(n.Tag.Vars) } // Path through ancestors to this Node. func (n *Node) Path() (out string) { if n.Parent != nil { out += " " + n.Parent.Path() } switch n.Type { case CommandNode: out += " " + n.Name case ArgumentNode: out += " " + "<" + n.Name + ">" } return strings.TrimSpace(out) } // A Value is either a flag or a variable positional argument. type Value struct { Flag *Flag // Nil if positional argument. Name string Help string Default string DefaultValue reflect.Value Enum string Mapper Mapper Tag *Tag Target reflect.Value Required bool Set bool // Set to true when this value is set through some mechanism. Format string // Formatting directive, if applicable. Position int // Position (for positional arguments). } // EnumMap returns a map of the enums in this value. func (v *Value) EnumMap() map[string]bool { parts := strings.Split(v.Enum, ",") out := make(map[string]bool, len(parts)) for _, part := range parts { out[strings.TrimSpace(part)] = true } return out } // ShortSummary returns a human-readable summary of the value, not including any placeholders/defaults. func (v *Value) ShortSummary() string { if v.Flag != nil { return fmt.Sprintf("--%s", v.Name) } argText := "<" + v.Name + ">" if v.IsCumulative() { argText += " ..." } if !v.Required { argText = "[" + argText + "]" } return argText } // Summary returns a human-readable summary of the value. func (v *Value) Summary() string { if v.Flag != nil { if v.IsBool() { return fmt.Sprintf("--%s", v.Name) } return fmt.Sprintf("--%s=%s", v.Name, v.Flag.FormatPlaceHolder()) } argText := "<" + v.Name + ">" if v.IsCumulative() { argText += " ..." } if !v.Required { argText = "[" + argText + "]" } return argText } // IsCumulative returns true if the type can be accumulated into. func (v *Value) IsCumulative() bool { return v.IsSlice() || v.IsMap() } // IsSlice returns true if the value is a slice. func (v *Value) IsSlice() bool { return v.Target.Type().Name() == "" && v.Target.Kind() == reflect.Slice } // IsMap returns true if the value is a map. func (v *Value) IsMap() bool { return v.Target.Kind() == reflect.Map } // IsBool returns true if the underlying value is a boolean. func (v *Value) IsBool() bool { if m, ok := v.Mapper.(BoolMapper); ok && m.IsBool() { return true } return v.Target.Kind() == reflect.Bool } // Parse tokens into value, parse, and validate, but do not write to the field. func (v *Value) Parse(scan *Scanner, target reflect.Value) (err error) { defer func() { if rerr := recover(); rerr != nil { switch rerr := rerr.(type) { case Error: err = errors.Wrap(rerr, v.ShortSummary()) default: panic(fmt.Sprintf("mapper %T failed to apply to %s: %s", v.Mapper, v.Summary(), rerr)) } } }() err = v.Mapper.Decode(&DecodeContext{Value: v, Scan: scan}, target) if err != nil { return errors.Wrap(err, v.ShortSummary()) } v.Set = true return nil } // Apply value to field. func (v *Value) Apply(value reflect.Value) { v.Target.Set(value) v.Set = true } // ApplyDefault value to field if it is not already set. func (v *Value) ApplyDefault() error { if reflectValueIsZero(v.Target) { return v.Reset() } v.Set = true return nil } // Reset this value to its default, either the zero value or the parsed result of its envar, // or its "default" tag. // // Does not include resolvers. func (v *Value) Reset() error { v.Target.Set(reflect.Zero(v.Target.Type())) if v.Tag.Env != "" { envar := os.Getenv(v.Tag.Env) if envar != "" { err := v.Parse(ScanFromTokens(Token{Type: FlagValueToken, Value: envar}), v.Target) if err != nil { return fmt.Errorf("%s (from envar %s=%q)", err, v.Tag.Env, envar) } return nil } } if v.Default != "" { return v.Parse(ScanFromTokens(Token{Type: FlagValueToken, Value: v.Default}), v.Target) } return nil } func (*Value) node() {} // A Positional represents a non-branching command-line positional argument. type Positional = Value // A Flag represents a command-line flag. type Flag struct { *Value Group string // Logical grouping when displaying. May also be used by configuration loaders to group options logically. Xor string PlaceHolder string Env string Short rune Hidden bool } func (f *Flag) String() string { out := "--" + f.Name if f.Short != 0 { out = fmt.Sprintf("-%c, %s", f.Short, out) } if !f.IsBool() { out += "=" + f.FormatPlaceHolder() } return out } // FormatPlaceHolder formats the placeholder string for a Flag. func (f *Flag) FormatPlaceHolder() string { tail := "" if f.Value.IsSlice() && f.Value.Tag.Sep != -1 { tail += string(f.Value.Tag.Sep) + "..." } if f.Default != "" { if f.Value.Target.Kind() == reflect.String { return strconv.Quote(f.Default) + tail } return f.Default + tail } if f.PlaceHolder != "" { return f.PlaceHolder + tail } if f.Value.IsMap() { if f.Value.Tag.MapSep != -1 { tail = string(f.Value.Tag.MapSep) + "..." } return "KEY=VALUE" + tail } return strings.ToUpper(f.Name) + tail } // This is directly from the Go 1.13 source code. func reflectValueIsZero(v reflect.Value) bool { switch v.Kind() { case reflect.Bool: return !v.Bool() case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: return v.Int() == 0 case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: return v.Uint() == 0 case reflect.Float32, reflect.Float64: return math.Float64bits(v.Float()) == 0 case reflect.Complex64, reflect.Complex128: c := v.Complex() return math.Float64bits(real(c)) == 0 && math.Float64bits(imag(c)) == 0 case reflect.Array: for i := 0; i < v.Len(); i++ { if !reflectValueIsZero(v.Index(i)) { return false } } return true case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice, reflect.UnsafePointer: return v.IsNil() case reflect.String: return v.Len() == 0 case reflect.Struct: for i := 0; i < v.NumField(); i++ { if !reflectValueIsZero(v.Field(i)) { return false } } return true default: // This should never happens, but will act as a safeguard for // later, as a default value doesn't makes sense here. panic(&reflect.ValueError{"reflect.Value.IsZero", v.Kind()}) } }