Files
kong/model.go
T
Alec Thomas 6408010083 Clean up disparity between Context and Kong.
Previously, there was a confusing mix of functionality shared between
the two wherein you would need to use the Kong type for printing errors,
etc. but it did not have access to the context in order to print
context-sensitive usage information. This has been fixed.

Additionally, there are now fuzzy correction suggestions for flags and
commands

Also added a server example which shows how Kong can be used for parsing
in interactive shells. Run with:

    $ go run ./_examples/server/*.go

Then interact with:

    $ ssh -p 6740 127.0.0.1
2018-06-27 09:11:11 +10:00

311 lines
6.8 KiB
Go

package kong
import (
"fmt"
"reflect"
"strconv"
"strings"
)
// 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
Hidden bool
Flags []*Flag
Positional []*Positional
Children []*Node
Target reflect.Value // Pointer to the value in the grammar that this Node is associated with.
Argument *Value // Populated when Type is ArgumentNode.
}
// 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) {
var walk func(n *Node)
walk = func(n *Node) {
if hide && n.Hidden {
return
}
if len(n.Children) == 0 && n.Type != ApplicationNode {
out = append(out, n)
}
for _, child := range n.Children {
if child.Type == CommandNode || child.Type == ArgumentNode {
walk(child)
}
}
}
for _, child := range n.Children {
walk(child)
}
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 += " <command>"
}
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())
}
// 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
Name string
Help string
Default 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).
}
// 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.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) error {
err := v.Mapper.Decode(&DecodeContext{Value: v, Scan: scan}, target)
if err == nil {
v.Set = true
}
return err
}
// Apply value to field.
func (v *Value) Apply(value reflect.Value) {
v.Target.Set(value)
v.Set = true
}
// Reset this value to its default, either the zero value or the parsed result of its "default" tag.
//
// Does not include resolvers.
func (v *Value) Reset() error {
v.Target.Set(reflect.Zero(v.Target.Type()))
if v.Default != "" {
return v.Parse(Scan(v.Default), v.Target)
}
return nil
}
// A Positional represents a non-branching command-line positional argument.
type Positional = Value
// A Flag represents a command-line flag.
type Flag struct {
*Value
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() {
tail += ",..."
}
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() {
return "KEY=VALUE" + tail
}
return strings.ToUpper(f.Name) + tail
}