⬆️ Update dependencies
This commit is contained in:
+552
-116
@@ -1,13 +1,14 @@
|
||||
package kong
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// Path records the nodes and parsed values from the current command-line.
|
||||
@@ -26,6 +27,9 @@ type Path struct {
|
||||
|
||||
// True if this Path element was created as the result of a resolver.
|
||||
Resolved bool
|
||||
|
||||
// Remaining tokens after this node
|
||||
remainder []Token
|
||||
}
|
||||
|
||||
// Node returns the Node associated with this Path, or nil if Path is a non-Node.
|
||||
@@ -43,6 +47,36 @@ func (p *Path) Node() *Node {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Visitable returns the Visitable for this path element.
|
||||
func (p *Path) Visitable() Visitable {
|
||||
switch {
|
||||
case p.App != nil:
|
||||
return p.App
|
||||
|
||||
case p.Argument != nil:
|
||||
return p.Argument
|
||||
|
||||
case p.Command != nil:
|
||||
return p.Command
|
||||
|
||||
case p.Flag != nil:
|
||||
return p.Flag
|
||||
|
||||
case p.Positional != nil:
|
||||
return p.Positional
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Remainder returns the remaining unparsed args after this Path element.
|
||||
func (p *Path) Remainder() []string {
|
||||
args := []string{}
|
||||
for _, token := range p.remainder {
|
||||
args = append(args, token.String())
|
||||
}
|
||||
return args
|
||||
}
|
||||
|
||||
// Context contains the current parse context.
|
||||
type Context struct {
|
||||
*Kong
|
||||
@@ -66,14 +100,15 @@ type Context struct {
|
||||
// This just constructs a new trace. To fully apply the trace you must call Reset(), Resolve(),
|
||||
// Validate() and Apply().
|
||||
func Trace(k *Kong, args []string) (*Context, error) {
|
||||
s := Scan(args...).AllowHyphenPrefixedParameters(k.allowHyphenated)
|
||||
c := &Context{
|
||||
Kong: k,
|
||||
Args: args,
|
||||
Path: []*Path{
|
||||
{App: k.Model, Flags: k.Model.Flags},
|
||||
{App: k.Model, Flags: k.Model.Flags, remainder: s.PeekAll()},
|
||||
},
|
||||
values: map[*Value]reflect.Value{},
|
||||
scan: Scan(args...),
|
||||
scan: s,
|
||||
bindings: bindings{},
|
||||
}
|
||||
c.Error = c.trace(c.Model.Node)
|
||||
@@ -81,7 +116,7 @@ func Trace(k *Kong, args []string) (*Context, error) {
|
||||
}
|
||||
|
||||
// Bind adds bindings to the Context.
|
||||
func (c *Context) Bind(args ...interface{}) {
|
||||
func (c *Context) Bind(args ...any) {
|
||||
c.bindings.add(args...)
|
||||
}
|
||||
|
||||
@@ -89,10 +124,29 @@ func (c *Context) Bind(args ...interface{}) {
|
||||
//
|
||||
// This will typically have to be called like so:
|
||||
//
|
||||
// BindTo(impl, (*MyInterface)(nil))
|
||||
func (c *Context) BindTo(impl, iface interface{}) {
|
||||
valueOf := reflect.ValueOf(impl)
|
||||
c.bindings[reflect.TypeOf(iface).Elem()] = func() (reflect.Value, error) { return valueOf, nil }
|
||||
// BindTo(impl, (*MyInterface)(nil))
|
||||
func (c *Context) BindTo(impl, iface any) {
|
||||
c.bindings.addTo(impl, iface)
|
||||
}
|
||||
|
||||
// BindToProvider allows binding of provider functions.
|
||||
//
|
||||
// This is useful when the Run() function of different commands require different values that may
|
||||
// not all be initialisable from the main() function.
|
||||
//
|
||||
// "provider" must be a function with the signature func(...) (T, error) or func(...) T,
|
||||
// where ... will be recursively injected with bound values.
|
||||
func (c *Context) BindToProvider(provider any) error {
|
||||
return c.bindings.addProvider(provider, false /* singleton */)
|
||||
}
|
||||
|
||||
// BindSingletonProvider allows binding of provider functions.
|
||||
// The provider will be called once and the result cached.
|
||||
//
|
||||
// "provider" must be a function with the signature func(...) (T, error) or func(...) T,
|
||||
// where ... will be recursively injected with bound values.
|
||||
func (c *Context) BindSingletonProvider(provider any) error {
|
||||
return c.bindings.addProvider(provider, true /* singleton */)
|
||||
}
|
||||
|
||||
// Value returns the value for a particular path element.
|
||||
@@ -133,19 +187,53 @@ func (c *Context) Empty() bool {
|
||||
}
|
||||
|
||||
// Validate the current context.
|
||||
func (c *Context) Validate() error { // nolint: gocyclo
|
||||
err := Visit(c.Model, func(node Visitable, next Next) error {
|
||||
if value, ok := node.(*Value); ok {
|
||||
if value.Enum != "" && (!value.Required || value.Default != "") {
|
||||
func (c *Context) Validate() error { //nolint: gocyclo
|
||||
// Only check nodes on the selected command path: an envar shared with
|
||||
// another command may hold a value that is invalid there.
|
||||
for _, path := range c.Path {
|
||||
node := path.Node()
|
||||
if node == nil {
|
||||
continue
|
||||
}
|
||||
for _, value := range node.Values() {
|
||||
ok := atLeastOneEnvSet(value.Tag.Envs)
|
||||
if value.Enum != "" && (!value.Required || value.HasDefault || (len(value.Tag.Envs) != 0 && ok)) {
|
||||
if err := checkEnum(value, value.Target); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return next(nil)
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, el := range c.Path {
|
||||
var (
|
||||
value reflect.Value
|
||||
desc string
|
||||
)
|
||||
switch node := el.Visitable().(type) {
|
||||
case *Value:
|
||||
value = node.Target
|
||||
desc = node.ShortSummary()
|
||||
|
||||
case *Flag:
|
||||
value = node.Target
|
||||
desc = node.ShortSummary()
|
||||
|
||||
case *Application:
|
||||
value = node.Target
|
||||
desc = ""
|
||||
|
||||
case *Node:
|
||||
value = node.Target
|
||||
desc = node.Path()
|
||||
}
|
||||
for _, validate := range getValidators(value) {
|
||||
if err := validate.Validate(c); err != nil {
|
||||
if desc != "" {
|
||||
return fmt.Errorf("%s: %w", desc, err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, resolver := range c.combineResolvers() {
|
||||
if err := resolver.Validate(c.Model); err != nil {
|
||||
@@ -190,7 +278,7 @@ func (c *Context) Validate() error { // nolint: gocyclo
|
||||
if err := checkMissingPositionals(positionals, node.Positional); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := checkXorDuplicates(c.Path); err != nil {
|
||||
if err := checkXorDuplicatedAndAndMissing(c.Path); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -237,7 +325,7 @@ func (c *Context) AddResolver(resolver Resolver) {
|
||||
}
|
||||
|
||||
// FlagValue returns the set value of a flag if it was encountered and exists, or its default value.
|
||||
func (c *Context) FlagValue(flag *Flag) interface{} {
|
||||
func (c *Context) FlagValue(flag *Flag) any {
|
||||
for _, trace := range c.Path {
|
||||
if trace.Flag == flag {
|
||||
v, ok := c.values[trace.Flag.Value]
|
||||
@@ -255,22 +343,73 @@ func (c *Context) FlagValue(flag *Flag) interface{} {
|
||||
|
||||
// Reset recursively resets values to defaults (as specified in the grammar) or the zero value.
|
||||
func (c *Context) Reset() error {
|
||||
selected := c.selectedValues()
|
||||
return Visit(c.Model.Node, func(node Visitable, next Next) error {
|
||||
if value, ok := node.(*Value); ok {
|
||||
return next(value.Reset())
|
||||
value, ok := node.(*Value)
|
||||
if !ok {
|
||||
return next(nil)
|
||||
}
|
||||
return next(nil)
|
||||
err := value.Reset()
|
||||
if err != nil && !selected[value] {
|
||||
// An envar shared with a node outside the selected command path
|
||||
// may not parse there; that must not fail this parse.
|
||||
value.Target.Set(reflect.Zero(value.Target.Type()))
|
||||
err = nil
|
||||
}
|
||||
return next(err)
|
||||
})
|
||||
}
|
||||
|
||||
func (c *Context) trace(node *Node) (err error) { // nolint: gocyclo
|
||||
// selectedValues returns the set of values attached to nodes on the traced
|
||||
// command path.
|
||||
func (c *Context) selectedValues() map[*Value]bool {
|
||||
selected := map[*Value]bool{}
|
||||
for _, path := range c.Path {
|
||||
if node := path.Node(); node != nil {
|
||||
for _, value := range node.Values() {
|
||||
selected[value] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
return selected
|
||||
}
|
||||
|
||||
func (c *Context) endParsing() {
|
||||
args := []string{}
|
||||
for {
|
||||
token := c.scan.Pop()
|
||||
if token.Type == EOLToken {
|
||||
break
|
||||
}
|
||||
args = append(args, token.String())
|
||||
}
|
||||
// Note: tokens must be pushed in reverse order.
|
||||
for i := range args {
|
||||
c.scan.PushTyped(args[len(args)-1-i], PositionalArgumentToken)
|
||||
}
|
||||
}
|
||||
|
||||
//nolint:maintidx
|
||||
func (c *Context) trace(node *Node) (err error) { //nolint: gocyclo
|
||||
positional := 0
|
||||
node.Active = true
|
||||
|
||||
flags := []*Flag{}
|
||||
for _, group := range node.AllFlags(false) {
|
||||
flagNode := node
|
||||
if node.DefaultCmd != nil && node.DefaultCmd.Tag.Default == "withargs" {
|
||||
// Add flags of the default command if the current node has one
|
||||
// and that default command allows args / flags without explicitly
|
||||
// naming the command on the CLI.
|
||||
flagNode = node.DefaultCmd
|
||||
}
|
||||
for _, group := range flagNode.AllFlags(false) {
|
||||
flags = append(flags, group...)
|
||||
}
|
||||
|
||||
if node.Passthrough {
|
||||
c.endParsing()
|
||||
}
|
||||
|
||||
for !c.scan.Peek().IsEOL() {
|
||||
token := c.scan.Peek()
|
||||
switch token.Type {
|
||||
@@ -281,24 +420,17 @@ func (c *Context) trace(node *Node) (err error) { // nolint: gocyclo
|
||||
switch {
|
||||
case v == "-":
|
||||
fallthrough
|
||||
default: // nolint
|
||||
default: //nolint
|
||||
c.scan.Pop()
|
||||
c.scan.PushTyped(token.Value, PositionalArgumentToken)
|
||||
|
||||
// Indicates end of parsing. All remaining arguments are treated as positional arguments only.
|
||||
case v == "--":
|
||||
c.scan.Pop()
|
||||
args := []string{}
|
||||
for {
|
||||
token = c.scan.Pop()
|
||||
if token.Type == EOLToken {
|
||||
break
|
||||
}
|
||||
args = append(args, token.String())
|
||||
}
|
||||
// Note: tokens must be pushed in reverse order.
|
||||
for i := range args {
|
||||
c.scan.PushTyped(args[len(args)-1-i], PositionalArgumentToken)
|
||||
c.endParsing()
|
||||
|
||||
// Pop the -- token unless the next positional argument accepts passthrough arguments.
|
||||
if !(positional < len(node.Positional) && node.Positional[positional].Passthrough) {
|
||||
c.scan.Pop()
|
||||
}
|
||||
|
||||
// Long flag.
|
||||
@@ -335,12 +467,22 @@ func (c *Context) trace(node *Node) (err error) { // nolint: gocyclo
|
||||
|
||||
case FlagToken:
|
||||
if err := c.parseFlag(flags, token.String()); err != nil {
|
||||
return err
|
||||
if isUnknownFlagError(err) && positional < len(node.Positional) && node.Positional[positional].PassthroughMode == PassThroughModeAll {
|
||||
c.scan.Pop()
|
||||
c.scan.PushTyped(token.String(), PositionalArgumentToken)
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
case ShortFlagToken:
|
||||
if err := c.parseFlag(flags, token.String()); err != nil {
|
||||
return err
|
||||
if isUnknownFlagError(err) && positional < len(node.Positional) && node.Positional[positional].PassthroughMode == PassThroughModeAll {
|
||||
c.scan.Pop()
|
||||
c.scan.PushTyped(token.String(), PositionalArgumentToken)
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
case FlagValueToken:
|
||||
@@ -352,6 +494,12 @@ func (c *Context) trace(node *Node) (err error) { // nolint: gocyclo
|
||||
// Ensure we've consumed all positional arguments.
|
||||
if positional < len(node.Positional) {
|
||||
arg := node.Positional[positional]
|
||||
|
||||
if arg.Passthrough {
|
||||
c.endParsing()
|
||||
}
|
||||
|
||||
arg.Active = true
|
||||
err := arg.Parse(c.scan, c.getValue(arg))
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -359,11 +507,30 @@ func (c *Context) trace(node *Node) (err error) { // nolint: gocyclo
|
||||
c.Path = append(c.Path, &Path{
|
||||
Parent: node,
|
||||
Positional: arg,
|
||||
remainder: c.scan.PeekAll(),
|
||||
})
|
||||
positional++
|
||||
break
|
||||
}
|
||||
|
||||
// Assign token value to a branch name if tagged as an alias
|
||||
// An alias will be ignored in the case of an existing command
|
||||
cmds := make(map[string]bool)
|
||||
for _, branch := range node.Children {
|
||||
if branch.Type == CommandNode {
|
||||
cmds[branch.Name] = true
|
||||
}
|
||||
}
|
||||
for _, branch := range node.Children {
|
||||
for _, a := range branch.Aliases {
|
||||
_, ok := cmds[a]
|
||||
if token.Value == a && !ok {
|
||||
token.Value = branch.Name
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// After positional arguments have been consumed, check commands next...
|
||||
for _, branch := range node.Children {
|
||||
if branch.Type == CommandNode && !branch.Hidden {
|
||||
@@ -372,9 +539,10 @@ func (c *Context) trace(node *Node) (err error) { // nolint: gocyclo
|
||||
if branch.Type == CommandNode && branch.Name == token.Value {
|
||||
c.scan.Pop()
|
||||
c.Path = append(c.Path, &Path{
|
||||
Parent: node,
|
||||
Command: branch,
|
||||
Flags: branch.Flags,
|
||||
Parent: node,
|
||||
Command: branch,
|
||||
Flags: branch.Flags,
|
||||
remainder: c.scan.PeekAll(),
|
||||
})
|
||||
return c.trace(branch)
|
||||
}
|
||||
@@ -386,15 +554,28 @@ func (c *Context) trace(node *Node) (err error) { // nolint: gocyclo
|
||||
arg := branch.Argument
|
||||
if err := arg.Parse(c.scan, c.getValue(arg)); err == nil {
|
||||
c.Path = append(c.Path, &Path{
|
||||
Parent: node,
|
||||
Argument: branch,
|
||||
Flags: branch.Flags,
|
||||
Parent: node,
|
||||
Argument: branch,
|
||||
Flags: branch.Flags,
|
||||
remainder: c.scan.PeekAll(),
|
||||
})
|
||||
return c.trace(branch)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If there is a default command that allows args and nothing else
|
||||
// matches, take the branch of the default command
|
||||
if node.DefaultCmd != nil && node.DefaultCmd.Tag.Default == "withargs" {
|
||||
c.Path = append(c.Path, &Path{
|
||||
Parent: node,
|
||||
Command: node.DefaultCmd,
|
||||
Flags: node.DefaultCmd.Flags,
|
||||
remainder: c.scan.PeekAll(),
|
||||
})
|
||||
return c.trace(node.DefaultCmd)
|
||||
}
|
||||
|
||||
return findPotentialCandidates(token.String(), candidates, "unexpected argument %s", token)
|
||||
default:
|
||||
return fmt.Errorf("unexpected token %s", token)
|
||||
@@ -403,29 +584,26 @@ func (c *Context) trace(node *Node) (err error) { // nolint: gocyclo
|
||||
return c.maybeSelectDefault(flags, node)
|
||||
}
|
||||
|
||||
// IgnoreDefault can be implemented by flags that want to be applied before any default commands.
|
||||
type IgnoreDefault interface {
|
||||
IgnoreDefault()
|
||||
}
|
||||
|
||||
// End of the line, check for a default command, but only if we're not displaying help,
|
||||
// otherwise we'd only ever display the help for the default command.
|
||||
func (c *Context) maybeSelectDefault(flags []*Flag, node *Node) error {
|
||||
for _, flag := range flags {
|
||||
if flag.Name == "help" && flag.Set {
|
||||
if _, ok := flag.Target.Interface().(IgnoreDefault); ok && flag.Set {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
var defaultNode *Path
|
||||
for _, child := range node.Children {
|
||||
if child.Type == CommandNode && child.Tag.Default != "" {
|
||||
if defaultNode != nil {
|
||||
return fmt.Errorf("can't have more than one default command under %s", node.Summary())
|
||||
}
|
||||
defaultNode = &Path{
|
||||
Parent: child,
|
||||
Command: child,
|
||||
Flags: child.Flags,
|
||||
}
|
||||
}
|
||||
}
|
||||
if defaultNode != nil {
|
||||
c.Path = append(c.Path, defaultNode)
|
||||
if node.DefaultCmd != nil {
|
||||
c.Path = append(c.Path, &Path{
|
||||
Parent: node.DefaultCmd,
|
||||
Command: node.DefaultCmd,
|
||||
Flags: node.DefaultCmd.Flags,
|
||||
remainder: c.scan.PeekAll(),
|
||||
})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -446,11 +624,11 @@ func (c *Context) Resolve() error {
|
||||
}
|
||||
|
||||
// Pick the last resolved value.
|
||||
var selected interface{}
|
||||
var selected any
|
||||
for _, resolver := range resolvers {
|
||||
s, err := resolver.Resolve(c, path, flag)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, flag.ShortSummary())
|
||||
return fmt.Errorf("%s: %w", flag.ShortSummary(), err)
|
||||
}
|
||||
if s == nil {
|
||||
continue
|
||||
@@ -469,12 +647,13 @@ func (c *Context) Resolve() error {
|
||||
return err
|
||||
}
|
||||
inserted = append(inserted, &Path{
|
||||
Flag: flag,
|
||||
Resolved: true,
|
||||
Flag: flag,
|
||||
Resolved: true,
|
||||
remainder: c.scan.PeekAll(),
|
||||
})
|
||||
}
|
||||
}
|
||||
c.Path = append(inserted, c.Path...)
|
||||
c.Path = append(c.Path, inserted...)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -490,6 +669,15 @@ func (c *Context) getValue(value *Value) reflect.Value {
|
||||
v, ok := c.values[value]
|
||||
if !ok {
|
||||
v = reflect.New(value.Target.Type()).Elem()
|
||||
switch v.Kind() {
|
||||
case reflect.Ptr:
|
||||
v.Set(reflect.New(v.Type().Elem()))
|
||||
case reflect.Slice:
|
||||
v.Set(reflect.MakeSlice(v.Type(), 0, 0))
|
||||
case reflect.Map:
|
||||
v.Set(reflect.MakeMap(v.Type()))
|
||||
default:
|
||||
}
|
||||
c.values[value] = v
|
||||
}
|
||||
return v
|
||||
@@ -546,32 +734,92 @@ func (c *Context) Apply() (string, error) {
|
||||
return strings.Join(path, " "), nil
|
||||
}
|
||||
|
||||
func flipBoolValue(value reflect.Value) error {
|
||||
if value.Kind() == reflect.Bool {
|
||||
value.SetBool(!value.Bool())
|
||||
return nil
|
||||
}
|
||||
|
||||
if value.Kind() == reflect.Ptr {
|
||||
if !value.IsNil() {
|
||||
return flipBoolValue(value.Elem())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("cannot negate a value of %s", value.Type().String())
|
||||
}
|
||||
|
||||
func (c *Context) parseFlag(flags []*Flag, match string) (err error) {
|
||||
defer catch(&err)
|
||||
candidates := []string{}
|
||||
|
||||
for _, flag := range flags {
|
||||
long := "--" + flag.Name
|
||||
short := "-" + string(flag.Short)
|
||||
matched := long == match
|
||||
candidates = append(candidates, long)
|
||||
if flag.Short != 0 {
|
||||
short := "-" + string(flag.Short)
|
||||
matched = matched || (short == match)
|
||||
candidates = append(candidates, short)
|
||||
}
|
||||
if short != match && long != match {
|
||||
for _, alias := range flag.Aliases {
|
||||
aliasFlag := "--" + alias
|
||||
if utf8.RuneCountInString(alias) == 1 {
|
||||
aliasFlag = "-" + alias
|
||||
}
|
||||
matched = matched || (aliasFlag == match)
|
||||
candidates = append(candidates, aliasFlag)
|
||||
}
|
||||
|
||||
neg := negatableFlagName(flag.Name, flag.Tag.Negatable)
|
||||
if !matched && match != neg {
|
||||
continue
|
||||
}
|
||||
// Found a matching flag.
|
||||
c.scan.Pop()
|
||||
if match == neg && flag.Tag.Negatable != "" {
|
||||
flag.Negated = true
|
||||
}
|
||||
err := flag.Parse(c.scan, c.getValue(flag.Value))
|
||||
if err != nil {
|
||||
if e, ok := errors.Cause(err).(*expectedError); ok && e.token.InferredType().IsAny(FlagToken, ShortFlagToken) {
|
||||
return errors.Errorf("%s; perhaps try %s=%q?", err, flag.ShortSummary(), e.token)
|
||||
var expected *expectedError
|
||||
if errors.As(err, &expected) && expected.token.InferredType().IsAny(FlagToken, ShortFlagToken) {
|
||||
return fmt.Errorf("%s; perhaps try %s=%q?", err.Error(), flag.ShortSummary(), expected.token)
|
||||
}
|
||||
return err
|
||||
}
|
||||
c.Path = append(c.Path, &Path{Flag: flag})
|
||||
if flag.Negated {
|
||||
value := c.getValue(flag.Value)
|
||||
err := flipBoolValue(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
flag.Value.Apply(value)
|
||||
}
|
||||
c.Path = append(c.Path, &Path{
|
||||
Flag: flag,
|
||||
remainder: c.scan.PeekAll(),
|
||||
})
|
||||
return nil
|
||||
}
|
||||
return findPotentialCandidates(match, candidates, "unknown flag %s", match)
|
||||
return &unknownFlagError{Cause: findPotentialCandidates(match, candidates, "unknown flag %s", match)}
|
||||
}
|
||||
|
||||
func isUnknownFlagError(err error) bool {
|
||||
var unknown *unknownFlagError
|
||||
return errors.As(err, &unknown)
|
||||
}
|
||||
|
||||
type unknownFlagError struct{ Cause error }
|
||||
|
||||
func (e *unknownFlagError) Unwrap() error { return e.Cause }
|
||||
func (e *unknownFlagError) Error() string { return e.Cause.Error() }
|
||||
|
||||
// Call an arbitrary function filling arguments with bound values.
|
||||
func (c *Context) Call(fn any, binds ...any) (out []any, err error) {
|
||||
fv := reflect.ValueOf(fn)
|
||||
bindings := c.Kong.bindings.clone().add(binds...).add(c).merge(c.bindings)
|
||||
return callAnyFunction(fv, bindings)
|
||||
}
|
||||
|
||||
// RunNode calls the Run() method on an arbitrary node.
|
||||
@@ -580,7 +828,7 @@ func (c *Context) parseFlag(flags []*Flag, match string) (err error) {
|
||||
//
|
||||
// Any passed values will be bindable to arguments of the target Run() method. Additionally,
|
||||
// all parent nodes in the command structure will be bound.
|
||||
func (c *Context) RunNode(node *Node, binds ...interface{}) (err error) {
|
||||
func (c *Context) RunNode(node *Node, binds ...any) (err error) {
|
||||
type targetMethod struct {
|
||||
node *Node
|
||||
method reflect.Value
|
||||
@@ -593,6 +841,19 @@ func (c *Context) RunNode(node *Node, binds ...interface{}) (err error) {
|
||||
methodBinds = methodBinds.clone()
|
||||
for p := node; p != nil; p = p.Parent {
|
||||
methodBinds = methodBinds.add(p.Target.Addr().Interface())
|
||||
// Try value and pointer to value.
|
||||
for _, p := range []reflect.Value{p.Target, p.Target.Addr()} {
|
||||
t := p.Type()
|
||||
for i := 0; i < p.NumMethod(); i++ {
|
||||
methodt := t.Method(i)
|
||||
if strings.HasPrefix(methodt.Name, "Provide") {
|
||||
method := p.Method(i)
|
||||
if err := methodBinds.addProvider(method.Interface(), false /* singleton */); err != nil {
|
||||
return fmt.Errorf("%s.%s: %w", t.Name(), methodt.Name, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if method.IsValid() {
|
||||
methods = append(methods, targetMethod{node, method, methodBinds})
|
||||
@@ -601,13 +862,8 @@ func (c *Context) RunNode(node *Node, binds ...interface{}) (err error) {
|
||||
if len(methods) == 0 {
|
||||
return fmt.Errorf("no Run() method found in hierarchy of %s", c.Selected().Summary())
|
||||
}
|
||||
_, err = c.Apply()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, method := range methods {
|
||||
if err = callMethod("Run", method.node.Target, method.method, method.binds); err != nil {
|
||||
if err = callFunction(method.method, method.binds); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -618,13 +874,27 @@ func (c *Context) RunNode(node *Node, binds ...interface{}) (err error) {
|
||||
//
|
||||
// Any passed values will be bindable to arguments of the target Run() method. Additionally,
|
||||
// all parent nodes in the command structure will be bound.
|
||||
func (c *Context) Run(binds ...interface{}) (err error) {
|
||||
defer catch(&err)
|
||||
func (c *Context) Run(binds ...any) (err error) {
|
||||
node := c.Selected()
|
||||
if node == nil {
|
||||
return fmt.Errorf("no command selected")
|
||||
if len(c.Path) == 0 {
|
||||
return fmt.Errorf("no command selected")
|
||||
}
|
||||
selected := c.Path[0].Node()
|
||||
if selected.Type == ApplicationNode {
|
||||
method := getMethod(selected.Target, "Run")
|
||||
if method.IsValid() {
|
||||
node = selected
|
||||
}
|
||||
}
|
||||
|
||||
if node == nil {
|
||||
return fmt.Errorf("no command selected")
|
||||
}
|
||||
}
|
||||
return c.RunNode(node, binds...)
|
||||
runErr := c.RunNode(node, binds...)
|
||||
err = c.Kong.applyHook(c, "AfterRun")
|
||||
return errors.Join(runErr, err)
|
||||
}
|
||||
|
||||
// PrintUsage to Kong's stdout.
|
||||
@@ -633,25 +903,82 @@ func (c *Context) Run(binds ...interface{}) (err error) {
|
||||
func (c *Context) PrintUsage(summary bool) error {
|
||||
options := c.helpOptions
|
||||
options.Summary = summary
|
||||
_ = c.help(options, c)
|
||||
return nil
|
||||
return c.printHelp(options)
|
||||
}
|
||||
|
||||
func (c *Context) printHelp(options HelpOptions) error {
|
||||
options.ValueFormatter = c.Kong.helpFormatter
|
||||
return c.help(options, c)
|
||||
}
|
||||
|
||||
func checkMissingFlags(flags []*Flag) error {
|
||||
xorGroupSet := map[string]bool{}
|
||||
xorGroup := map[string][]string{}
|
||||
andGroupSet := map[string]bool{}
|
||||
andGroup := map[string][]string{}
|
||||
missing := []string{}
|
||||
andGroupRequired := getRequiredAndGroupMap(flags)
|
||||
for _, flag := range flags {
|
||||
for _, and := range flag.And {
|
||||
flag.Required = andGroupRequired[and]
|
||||
}
|
||||
if flag.Set {
|
||||
for _, xor := range flag.Xor {
|
||||
xorGroupSet[xor] = true
|
||||
}
|
||||
for _, and := range flag.And {
|
||||
andGroupSet[and] = true
|
||||
}
|
||||
}
|
||||
if !flag.Required || flag.Set {
|
||||
continue
|
||||
}
|
||||
missing = append(missing, flag.Summary())
|
||||
if len(flag.Xor) > 0 || len(flag.And) > 0 {
|
||||
for _, xor := range flag.Xor {
|
||||
if xorGroupSet[xor] {
|
||||
continue
|
||||
}
|
||||
xorGroup[xor] = append(xorGroup[xor], flag.Summary())
|
||||
}
|
||||
for _, and := range flag.And {
|
||||
andGroup[and] = append(andGroup[and], flag.Summary())
|
||||
}
|
||||
} else {
|
||||
missing = append(missing, flag.Summary())
|
||||
}
|
||||
}
|
||||
for xor, flags := range xorGroup {
|
||||
if !xorGroupSet[xor] && len(flags) > 1 {
|
||||
missing = append(missing, strings.Join(flags, " or "))
|
||||
}
|
||||
}
|
||||
for _, flags := range andGroup {
|
||||
if len(flags) > 1 {
|
||||
missing = append(missing, strings.Join(flags, " and "))
|
||||
}
|
||||
}
|
||||
|
||||
if len(missing) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
sort.Strings(missing)
|
||||
|
||||
return fmt.Errorf("missing flags: %s", strings.Join(missing, ", "))
|
||||
}
|
||||
|
||||
func getRequiredAndGroupMap(flags []*Flag) map[string]bool {
|
||||
andGroupRequired := map[string]bool{}
|
||||
for _, flag := range flags {
|
||||
for _, and := range flag.And {
|
||||
if flag.Required {
|
||||
andGroupRequired[and] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
return andGroupRequired
|
||||
}
|
||||
|
||||
func checkMissingChildren(node *Node) error {
|
||||
missing := []string{}
|
||||
|
||||
@@ -665,7 +992,10 @@ func checkMissingChildren(node *Node) error {
|
||||
missing = append(missing, strconv.Quote(strings.Join(missingArgs, " ")))
|
||||
}
|
||||
|
||||
haveDefault := 0
|
||||
// A node with a Run() method may run on its own, so it does not require one
|
||||
// of its subcommands to be selected.
|
||||
runnable := node.Target.IsValid() && getMethod(node.Target, "Run").IsValid()
|
||||
|
||||
for _, child := range node.Children {
|
||||
if child.Hidden {
|
||||
continue
|
||||
@@ -675,20 +1005,11 @@ func checkMissingChildren(node *Node) error {
|
||||
continue
|
||||
}
|
||||
missing = append(missing, strconv.Quote(child.Summary()))
|
||||
} else {
|
||||
if child.Tag.Default != "" {
|
||||
if len(child.Children) > 0 {
|
||||
return fmt.Errorf("default command %s must not have subcommands or arguments", child.Summary())
|
||||
}
|
||||
haveDefault++
|
||||
}
|
||||
} else if !runnable {
|
||||
missing = append(missing, strconv.Quote(child.Name))
|
||||
}
|
||||
}
|
||||
if haveDefault > 1 {
|
||||
return fmt.Errorf("more than one default command found under %s", node.Summary())
|
||||
}
|
||||
if len(missing) == 0 || haveDefault > 0 {
|
||||
if len(missing) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -698,7 +1019,7 @@ func checkMissingChildren(node *Node) error {
|
||||
if len(missing) == 1 {
|
||||
return fmt.Errorf("expected %s", missing[0])
|
||||
}
|
||||
return fmt.Errorf("expected one of %s", strings.Join(missing, ", "))
|
||||
return fmt.Errorf("expected one of %s", strings.Join(missing, ", "))
|
||||
}
|
||||
|
||||
// If we're missing any positionals and they're required, return an error.
|
||||
@@ -715,7 +1036,17 @@ func checkMissingPositionals(positional int, values []*Value) error {
|
||||
|
||||
missing := []string{}
|
||||
for ; positional < len(values); positional++ {
|
||||
missing = append(missing, "<"+values[positional].Name+">")
|
||||
arg := values[positional]
|
||||
// TODO(aat): Fix hardcoding of these env checks all over the place :\
|
||||
if len(arg.Tag.Envs) != 0 {
|
||||
if atLeastOneEnvSet(arg.Tag.Envs) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
missing = append(missing, "<"+arg.Name+">")
|
||||
}
|
||||
if len(missing) == 0 {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("missing positional arguments %s", strings.Join(missing, " "))
|
||||
}
|
||||
@@ -731,23 +1062,51 @@ func checkEnum(value *Value, target reflect.Value) error {
|
||||
return nil
|
||||
|
||||
case reflect.Map, reflect.Struct:
|
||||
return errors.Errorf("enum can only be applied to a slice or value")
|
||||
return errors.New("enum can only be applied to a slice or value")
|
||||
|
||||
default:
|
||||
enumMap := value.EnumMap()
|
||||
v := fmt.Sprintf("%v", target)
|
||||
if enumMap[v] {
|
||||
case reflect.Ptr:
|
||||
if target.IsNil() {
|
||||
return nil
|
||||
}
|
||||
return checkEnum(value, target.Elem())
|
||||
default:
|
||||
enumSlice := value.EnumSlice()
|
||||
v := fmt.Sprintf("%v", target)
|
||||
enums := []string{}
|
||||
for enum := range enumMap {
|
||||
for _, enum := range enumSlice {
|
||||
if enum == v {
|
||||
return nil
|
||||
}
|
||||
enums = append(enums, fmt.Sprintf("%q", enum))
|
||||
}
|
||||
sort.Strings(enums)
|
||||
return fmt.Errorf("%s must be one of %s but got %q", value.ShortSummary(), strings.Join(enums, ","), target.Interface())
|
||||
return fmt.Errorf("%s must be one of %s but got %q", value.ShortSummary(), strings.Join(enums, ","), fmt.Sprintf("%v", target.Interface()))
|
||||
}
|
||||
}
|
||||
|
||||
func checkPassthroughArg(target reflect.Value) bool {
|
||||
typ := target.Type()
|
||||
switch typ.Kind() {
|
||||
case reflect.Slice:
|
||||
return typ.Elem().Kind() == reflect.String
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func checkXorDuplicatedAndAndMissing(paths []*Path) error {
|
||||
errs := []string{}
|
||||
if err := checkXorDuplicates(paths); err != nil {
|
||||
errs = append(errs, err.Error())
|
||||
}
|
||||
if err := checkAndMissing(paths); err != nil {
|
||||
errs = append(errs, err.Error())
|
||||
}
|
||||
if len(errs) > 0 {
|
||||
return errors.New(strings.Join(errs, ", "))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkXorDuplicates(paths []*Path) error {
|
||||
for _, path := range paths {
|
||||
seen := map[string]*Flag{}
|
||||
@@ -755,19 +1114,50 @@ func checkXorDuplicates(paths []*Path) error {
|
||||
if !flag.Set {
|
||||
continue
|
||||
}
|
||||
if flag.Xor == "" {
|
||||
continue
|
||||
for _, xor := range flag.Xor {
|
||||
if seen[xor] != nil {
|
||||
return fmt.Errorf("--%s and --%s can't be used together", seen[xor].Name, flag.Name)
|
||||
}
|
||||
seen[xor] = flag
|
||||
}
|
||||
if seen[flag.Xor] != nil {
|
||||
return fmt.Errorf("--%s and --%s can't be used together", seen[flag.Xor].Name, flag.Name)
|
||||
}
|
||||
seen[flag.Xor] = flag
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func findPotentialCandidates(needle string, haystack []string, format string, args ...interface{}) error {
|
||||
func checkAndMissing(paths []*Path) error {
|
||||
for _, path := range paths {
|
||||
missingMsgs := []string{}
|
||||
andGroups := map[string][]*Flag{}
|
||||
for _, flag := range path.Flags {
|
||||
for _, and := range flag.And {
|
||||
andGroups[and] = append(andGroups[and], flag)
|
||||
}
|
||||
}
|
||||
for _, flags := range andGroups {
|
||||
oneSet := false
|
||||
notSet := []*Flag{}
|
||||
flagNames := []string{}
|
||||
for _, flag := range flags {
|
||||
flagNames = append(flagNames, flag.Name)
|
||||
if flag.Set {
|
||||
oneSet = true
|
||||
} else {
|
||||
notSet = append(notSet, flag)
|
||||
}
|
||||
}
|
||||
if len(notSet) > 0 && oneSet {
|
||||
missingMsgs = append(missingMsgs, fmt.Sprintf("--%s must be used together", strings.Join(flagNames, " and --")))
|
||||
}
|
||||
}
|
||||
if len(missingMsgs) > 0 {
|
||||
return fmt.Errorf("%s", strings.Join(missingMsgs, ", "))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func findPotentialCandidates(needle string, haystack []string, format string, args ...any) error {
|
||||
if len(haystack) == 0 {
|
||||
return fmt.Errorf(format, args...)
|
||||
}
|
||||
@@ -785,3 +1175,49 @@ func findPotentialCandidates(needle string, haystack []string, format string, ar
|
||||
}
|
||||
return fmt.Errorf("%s", prefix)
|
||||
}
|
||||
|
||||
type validatable interface{ Validate() error }
|
||||
type extendedValidatable interface {
|
||||
Validate(kctx *Context) error
|
||||
}
|
||||
|
||||
// Proxy a validatable function to the extendedValidatable interface
|
||||
type validatableFunc func() error
|
||||
|
||||
func (f validatableFunc) Validate(kctx *Context) error { return f() }
|
||||
|
||||
func isValidatable(v reflect.Value) extendedValidatable {
|
||||
if !v.IsValid() || (v.Kind() == reflect.Ptr || v.Kind() == reflect.Slice || v.Kind() == reflect.Map) && v.IsNil() {
|
||||
return nil
|
||||
}
|
||||
if validate, ok := v.Interface().(validatable); ok {
|
||||
return validatableFunc(validate.Validate)
|
||||
}
|
||||
if validate, ok := v.Interface().(extendedValidatable); ok {
|
||||
return validate
|
||||
}
|
||||
if v.CanAddr() {
|
||||
return isValidatable(v.Addr())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// getValidators returns validators implemented by v and by any embedded fields,
|
||||
// matching how hooks are discovered (see getMethods).
|
||||
func getValidators(v reflect.Value) (validators []extendedValidatable) {
|
||||
walkEmbedded(v, func(v reflect.Value) {
|
||||
if validate := isValidatable(v); validate != nil {
|
||||
validators = append(validators, validate)
|
||||
}
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
func atLeastOneEnvSet(envs []string) bool {
|
||||
for _, env := range envs {
|
||||
if _, ok := os.LookupEnv(env); ok {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user