mirror of
https://github.com/NVIDIA/nvidia-container-toolkit
synced 2025-06-26 18:18:24 +00:00
348 lines
9.0 KiB
Go
348 lines
9.0 KiB
Go
package cli
|
|
|
|
import (
|
|
"bufio"
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"reflect"
|
|
"slices"
|
|
"unicode"
|
|
)
|
|
|
|
func (cmd *Command) parseArgsFromStdin() ([]string, error) {
|
|
type state int
|
|
const (
|
|
stateSearchForToken state = -1
|
|
stateSearchForString state = 0
|
|
)
|
|
|
|
st := stateSearchForToken
|
|
linenum := 1
|
|
token := ""
|
|
args := []string{}
|
|
|
|
breader := bufio.NewReader(cmd.Reader)
|
|
|
|
outer:
|
|
for {
|
|
ch, _, err := breader.ReadRune()
|
|
if err == io.EOF {
|
|
switch st {
|
|
case stateSearchForToken:
|
|
if token != "--" {
|
|
args = append(args, token)
|
|
}
|
|
case stateSearchForString:
|
|
// make sure string is not empty
|
|
for _, t := range token {
|
|
if !unicode.IsSpace(t) {
|
|
args = append(args, token)
|
|
}
|
|
}
|
|
}
|
|
break outer
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
switch st {
|
|
case stateSearchForToken:
|
|
if unicode.IsSpace(ch) || ch == '"' {
|
|
if ch == '\n' {
|
|
linenum++
|
|
}
|
|
if token != "" {
|
|
// end the processing here
|
|
if token == "--" {
|
|
break outer
|
|
}
|
|
args = append(args, token)
|
|
token = ""
|
|
}
|
|
if ch == '"' {
|
|
st = stateSearchForString
|
|
}
|
|
continue
|
|
}
|
|
token += string(ch)
|
|
case stateSearchForString:
|
|
if ch != '"' {
|
|
token += string(ch)
|
|
} else {
|
|
if token != "" {
|
|
args = append(args, token)
|
|
token = ""
|
|
}
|
|
/*else {
|
|
//TODO. Should we pass in empty strings ?
|
|
}*/
|
|
st = stateSearchForToken
|
|
}
|
|
}
|
|
}
|
|
|
|
tracef("parsed stdin args as %v (cmd=%[2]q)", args, cmd.Name)
|
|
|
|
return args, nil
|
|
}
|
|
|
|
// Run is the entry point to the command graph. The positional
|
|
// arguments are parsed according to the Flag and Command
|
|
// definitions and the matching Action functions are run.
|
|
func (cmd *Command) Run(ctx context.Context, osArgs []string) (deferErr error) {
|
|
_, deferErr = cmd.run(ctx, osArgs)
|
|
return
|
|
}
|
|
|
|
func (cmd *Command) run(ctx context.Context, osArgs []string) (_ context.Context, deferErr error) {
|
|
tracef("running with arguments %[1]q (cmd=%[2]q)", osArgs, cmd.Name)
|
|
cmd.setupDefaults(osArgs)
|
|
|
|
if v, ok := ctx.Value(commandContextKey).(*Command); ok {
|
|
tracef("setting parent (cmd=%[1]q) command from context.Context value (cmd=%[2]q)", v.Name, cmd.Name)
|
|
cmd.parent = v
|
|
}
|
|
|
|
if cmd.parent == nil {
|
|
if cmd.ReadArgsFromStdin {
|
|
if args, err := cmd.parseArgsFromStdin(); err != nil {
|
|
return ctx, err
|
|
} else {
|
|
osArgs = append(osArgs, args...)
|
|
}
|
|
}
|
|
// handle the completion flag separately from the flagset since
|
|
// completion could be attempted after a flag, but before its value was put
|
|
// on the command line. this causes the flagset to interpret the completion
|
|
// flag name as the value of the flag before it which is undesirable
|
|
// note that we can only do this because the shell autocomplete function
|
|
// always appends the completion flag at the end of the command
|
|
tracef("checking osArgs %v (cmd=%[2]q)", osArgs, cmd.Name)
|
|
cmd.shellCompletion, osArgs = checkShellCompleteFlag(cmd, osArgs)
|
|
|
|
tracef("setting cmd.shellCompletion=%[1]v from checkShellCompleteFlag (cmd=%[2]q)", cmd.shellCompletion && cmd.EnableShellCompletion, cmd.Name)
|
|
cmd.shellCompletion = cmd.EnableShellCompletion && cmd.shellCompletion
|
|
}
|
|
|
|
tracef("using post-checkShellCompleteFlag arguments %[1]q (cmd=%[2]q)", osArgs, cmd.Name)
|
|
|
|
tracef("setting self as cmd in context (cmd=%[1]q)", cmd.Name)
|
|
ctx = context.WithValue(ctx, commandContextKey, cmd)
|
|
|
|
if cmd.parent == nil {
|
|
cmd.setupCommandGraph()
|
|
}
|
|
|
|
var rargs Args = &stringSliceArgs{v: osArgs}
|
|
for _, f := range cmd.allFlags() {
|
|
if err := f.PreParse(); err != nil {
|
|
return ctx, err
|
|
}
|
|
}
|
|
|
|
var args Args = &stringSliceArgs{rargs.Tail()}
|
|
var err error
|
|
|
|
if cmd.SkipFlagParsing {
|
|
tracef("skipping flag parsing (cmd=%[1]q)", cmd.Name)
|
|
cmd.parsedArgs = args
|
|
} else {
|
|
cmd.parsedArgs, err = cmd.parseFlags(args)
|
|
}
|
|
|
|
tracef("using post-parse arguments %[1]q (cmd=%[2]q)", args, cmd.Name)
|
|
|
|
if checkCompletions(ctx, cmd) {
|
|
return ctx, nil
|
|
}
|
|
|
|
if err != nil {
|
|
tracef("setting deferErr from %[1]q (cmd=%[2]q)", err, cmd.Name)
|
|
deferErr = err
|
|
|
|
cmd.isInError = true
|
|
if cmd.OnUsageError != nil {
|
|
err = cmd.OnUsageError(ctx, cmd, err, cmd.parent != nil)
|
|
err = cmd.handleExitCoder(ctx, err)
|
|
return ctx, err
|
|
}
|
|
fmt.Fprintf(cmd.Root().ErrWriter, "Incorrect Usage: %s\n\n", err.Error())
|
|
if cmd.Suggest {
|
|
if suggestion, err := cmd.suggestFlagFromError(err, ""); err == nil {
|
|
fmt.Fprintf(cmd.Root().ErrWriter, "%s", suggestion)
|
|
}
|
|
}
|
|
if !cmd.hideHelp() {
|
|
if cmd.parent == nil {
|
|
tracef("running ShowAppHelp")
|
|
if err := ShowAppHelp(cmd); err != nil {
|
|
tracef("SILENTLY IGNORING ERROR running ShowAppHelp %[1]v (cmd=%[2]q)", err, cmd.Name)
|
|
}
|
|
} else {
|
|
tracef("running ShowCommandHelp with %[1]q", cmd.Name)
|
|
if err := ShowCommandHelp(ctx, cmd, cmd.Name); err != nil {
|
|
tracef("SILENTLY IGNORING ERROR running ShowCommandHelp with %[1]q %[2]v", cmd.Name, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
return ctx, err
|
|
}
|
|
|
|
if cmd.checkHelp() {
|
|
return ctx, helpCommandAction(ctx, cmd)
|
|
} else {
|
|
tracef("no help is wanted (cmd=%[1]q)", cmd.Name)
|
|
}
|
|
|
|
if cmd.parent == nil && !cmd.HideVersion && checkVersion(cmd) {
|
|
ShowVersion(cmd)
|
|
return ctx, nil
|
|
}
|
|
|
|
for _, flag := range cmd.allFlags() {
|
|
if err := flag.PostParse(); err != nil {
|
|
return ctx, err
|
|
}
|
|
}
|
|
|
|
if cmd.After != nil && !cmd.Root().shellCompletion {
|
|
defer func() {
|
|
if err := cmd.After(ctx, cmd); err != nil {
|
|
err = cmd.handleExitCoder(ctx, err)
|
|
|
|
if deferErr != nil {
|
|
deferErr = newMultiError(deferErr, err)
|
|
} else {
|
|
deferErr = err
|
|
}
|
|
}
|
|
}()
|
|
}
|
|
|
|
for _, grp := range cmd.MutuallyExclusiveFlags {
|
|
if err := grp.check(cmd); err != nil {
|
|
if cmd.OnUsageError != nil {
|
|
err = cmd.OnUsageError(ctx, cmd, err, cmd.parent != nil)
|
|
} else {
|
|
_ = ShowSubcommandHelp(cmd)
|
|
}
|
|
return ctx, err
|
|
}
|
|
}
|
|
|
|
var subCmd *Command
|
|
if cmd.parsedArgs.Present() {
|
|
tracef("checking positional args %[1]q (cmd=%[2]q)", cmd.parsedArgs, cmd.Name)
|
|
|
|
name := cmd.parsedArgs.First()
|
|
|
|
tracef("using first positional argument as sub-command name=%[1]q (cmd=%[2]q)", name, cmd.Name)
|
|
|
|
if cmd.SuggestCommandFunc != nil && name != "--" {
|
|
name = cmd.SuggestCommandFunc(cmd.Commands, name)
|
|
}
|
|
subCmd = cmd.Command(name)
|
|
if subCmd == nil {
|
|
hasDefault := cmd.DefaultCommand != ""
|
|
isFlagName := slices.Contains(cmd.FlagNames(), name)
|
|
|
|
if hasDefault {
|
|
tracef("using default command=%[1]q (cmd=%[2]q)", cmd.DefaultCommand, cmd.Name)
|
|
}
|
|
|
|
if isFlagName || hasDefault {
|
|
argsWithDefault := cmd.argsWithDefaultCommand(args)
|
|
tracef("using default command args=%[1]q (cmd=%[2]q)", argsWithDefault, cmd.Name)
|
|
if !reflect.DeepEqual(args, argsWithDefault) {
|
|
subCmd = cmd.Command(argsWithDefault.First())
|
|
}
|
|
}
|
|
}
|
|
} else if cmd.parent == nil && cmd.DefaultCommand != "" {
|
|
tracef("no positional args present; checking default command %[1]q (cmd=%[2]q)", cmd.DefaultCommand, cmd.Name)
|
|
|
|
if dc := cmd.Command(cmd.DefaultCommand); dc != cmd {
|
|
subCmd = dc
|
|
}
|
|
}
|
|
|
|
// If a subcommand has been resolved, let it handle the remaining execution.
|
|
if subCmd != nil {
|
|
tracef("running sub-command %[1]q with arguments %[2]q (cmd=%[3]q)", subCmd.Name, cmd.Args(), cmd.Name)
|
|
|
|
// It is important that we overwrite the ctx variable in the current
|
|
// function so any defer'd functions use the new context returned
|
|
// from the sub command.
|
|
ctx, err = subCmd.run(ctx, cmd.Args().Slice())
|
|
return ctx, err
|
|
}
|
|
|
|
// This code path is the innermost command execution. Here we actually
|
|
// perform the command action.
|
|
//
|
|
// First, resolve the chain of nested commands up to the parent.
|
|
var cmdChain []*Command
|
|
for p := cmd; p != nil; p = p.parent {
|
|
cmdChain = append(cmdChain, p)
|
|
}
|
|
slices.Reverse(cmdChain)
|
|
|
|
// Run Before actions in order.
|
|
for _, cmd := range cmdChain {
|
|
if cmd.Before == nil {
|
|
continue
|
|
}
|
|
if bctx, err := cmd.Before(ctx, cmd); err != nil {
|
|
deferErr = cmd.handleExitCoder(ctx, err)
|
|
return ctx, deferErr
|
|
} else if bctx != nil {
|
|
ctx = bctx
|
|
}
|
|
}
|
|
|
|
// Run flag actions in order.
|
|
// These take a context, so this has to happen after Before actions.
|
|
for _, cmd := range cmdChain {
|
|
tracef("running flag actions (cmd=%[1]q)", cmd.Name)
|
|
if err := cmd.runFlagActions(ctx); err != nil {
|
|
deferErr = cmd.handleExitCoder(ctx, err)
|
|
return ctx, deferErr
|
|
}
|
|
}
|
|
|
|
if err := cmd.checkAllRequiredFlags(); err != nil {
|
|
cmd.isInError = true
|
|
_ = ShowSubcommandHelp(cmd)
|
|
return ctx, err
|
|
}
|
|
|
|
// Run the command action.
|
|
if len(cmd.Arguments) > 0 {
|
|
rargs := cmd.Args().Slice()
|
|
tracef("calling argparse with %[1]v", rargs)
|
|
for _, arg := range cmd.Arguments {
|
|
var err error
|
|
rargs, err = arg.Parse(rargs)
|
|
if err != nil {
|
|
tracef("calling with %[1]v (cmd=%[2]q)", err, cmd.Name)
|
|
if cmd.OnUsageError != nil {
|
|
err = cmd.OnUsageError(ctx, cmd, err, cmd.parent != nil)
|
|
}
|
|
err = cmd.handleExitCoder(ctx, err)
|
|
return ctx, err
|
|
}
|
|
}
|
|
cmd.parsedArgs = &stringSliceArgs{v: rargs}
|
|
}
|
|
|
|
if err := cmd.Action(ctx, cmd); err != nil {
|
|
tracef("calling handleExitCoder with %[1]v (cmd=%[2]q)", err, cmd.Name)
|
|
deferErr = cmd.handleExitCoder(ctx, err)
|
|
}
|
|
|
|
tracef("returning deferErr (cmd=%[1]q) %[2]q", cmd.Name, deferErr)
|
|
return ctx, deferErr
|
|
}
|