github.com/sleungcy/cli@v7.1.0+incompatible/util/command_parser/command_parser.go (about) 1 package command_parser 2 3 import ( 4 "errors" 5 "fmt" 6 "os" 7 "reflect" 8 "strings" 9 10 "code.cloudfoundry.org/cli/cf/cmd" 11 "code.cloudfoundry.org/cli/command" 12 "code.cloudfoundry.org/cli/command/common" 13 "code.cloudfoundry.org/cli/command/flag" 14 "code.cloudfoundry.org/cli/command/translatableerror" 15 "code.cloudfoundry.org/cli/util/configv3" 16 "code.cloudfoundry.org/cli/util/ui" 17 "github.com/jessevdk/go-flags" 18 log "github.com/sirupsen/logrus" 19 "golang.org/x/crypto/ssh" 20 ) 21 22 var ErrFailed = errors.New("command failed") 23 var ParseErr = errors.New("incorrect type for arg") 24 25 type UI interface { 26 DisplayError(err error) 27 DisplayWarning(template string, templateValues ...map[string]interface{}) 28 DisplayText(template string, templateValues ...map[string]interface{}) 29 FlushDeferred() 30 } 31 32 type DisplayUsage interface { 33 DisplayUsage() 34 } 35 36 type TriggerLegacyMain interface { 37 LegacyMain() 38 error 39 } 40 41 type CommandParser struct { 42 Config *configv3.Config 43 UI *ui.UI 44 } 45 46 func NewCommandParser() (CommandParser, error) { 47 cfConfig, err := configv3.GetCFConfig() 48 if err != nil { 49 return CommandParser{}, err 50 } 51 52 return CommandParser{Config: cfConfig}, nil 53 } 54 55 func (p *CommandParser) ParseCommandFromArgs(ui *ui.UI, args []string) (int, error) { 56 p.UI = ui 57 return p.parse(args, &common.Commands) 58 } 59 60 func (p *CommandParser) executionWrapper(cmd flags.Commander, args []string) error { 61 cfConfig := p.Config 62 cfConfig.Flags = configv3.FlagOverride{ 63 Verbose: common.Commands.VerboseOrVersion, 64 } 65 defer p.UI.FlushDeferred() 66 67 err := preventExtraArgs(args) 68 if err != nil { 69 return p.handleError(err) 70 } 71 72 err = cfConfig.CreatePluginHome() 73 if err != nil { 74 return p.handleError(err) 75 } 76 77 defer func() { 78 configWriteErr := cfConfig.WriteConfig() 79 if configWriteErr != nil { 80 fmt.Fprintf(os.Stderr, "Error writing config: %s", configWriteErr.Error()) 81 } 82 }() 83 84 if extendedCmd, ok := cmd.(command.ExtendedCommander); ok { 85 log.SetOutput(os.Stderr) 86 log.SetLevel(log.Level(cfConfig.LogLevel())) 87 88 err = extendedCmd.Setup(cfConfig, p.UI) 89 if err != nil { 90 return p.handleError(err) 91 } 92 93 err = extendedCmd.Execute(args) 94 return p.handleError(err) 95 } 96 97 return fmt.Errorf("command does not conform to ExtendedCommander") 98 } 99 100 func (p *CommandParser) handleError(passedErr error) error { 101 if passedErr == nil { 102 return nil 103 } 104 105 translatedErr := translatableerror.ConvertToTranslatableError(passedErr) 106 107 switch typedErr := translatedErr.(type) { 108 case translatableerror.V3V2SwitchError: 109 log.Info("Received a V3V2SwitchError - switch to the V2 version of the command") 110 return passedErr 111 case TriggerLegacyMain: 112 if typedErr.Error() != "" { 113 p.UI.DisplayWarning("") 114 p.UI.DisplayWarning(typedErr.Error()) 115 } 116 cmd.Main(os.Getenv("CF_TRACE"), os.Args) 117 case *ssh.ExitError: 118 exitStatus := typedErr.ExitStatus() 119 if sig := typedErr.Signal(); sig != "" { 120 p.UI.DisplayText("Process terminated by signal: {{.Signal}}. Exited with {{.ExitCode}}", map[string]interface{}{ 121 "Signal": sig, 122 "ExitCode": exitStatus, 123 }) 124 } 125 return passedErr 126 } 127 128 p.UI.DisplayError(translatedErr) 129 130 if _, ok := translatedErr.(DisplayUsage); ok { 131 return ParseErr 132 } 133 134 return ErrFailed 135 } 136 137 func (p *CommandParser) handleFlagErrorAndCommandHelp(flagErr *flags.Error, flagsParser *flags.Parser, extraArgs []string, originalArgs []string, commandList interface{}) (int, error) { 138 switch flagErr.Type { 139 case flags.ErrHelp, flags.ErrUnknownFlag, flags.ErrExpectedArgument, flags.ErrInvalidChoice: 140 _, commandExists := reflect.TypeOf(common.Commands).FieldByNameFunc( 141 func(fieldName string) bool { 142 field, _ := reflect.TypeOf(common.Commands).FieldByName(fieldName) 143 return flagsParser.Active != nil && flagsParser.Active.Name == field.Tag.Get("command") 144 }, 145 ) 146 147 var helpExitCode int 148 var err error 149 if commandExists && flagErr.Type == flags.ErrUnknownFlag && (flagsParser.Active.Name == "set-env" || flagsParser.Active.Name == "v3-set-env") { 150 newArgs := []string{} 151 for _, arg := range originalArgs { 152 if arg[0] == '-' { 153 newArgs = append(newArgs, fmt.Sprintf("%s%s", flag.WorkAroundPrefix, arg)) 154 } else { 155 newArgs = append(newArgs, arg) 156 } 157 } 158 return p.parse(newArgs, commandList) 159 } 160 161 if flagErr.Type == flags.ErrUnknownFlag || flagErr.Type == flags.ErrExpectedArgument || flagErr.Type == flags.ErrInvalidChoice { 162 fmt.Fprintf(os.Stderr, "Incorrect Usage: %s\n\n", flagErr.Error()) 163 } 164 165 if commandExists { 166 helpExitCode, err = p.parse([]string{"help", flagsParser.Active.Name}, commandList) 167 } else { 168 switch len(extraArgs) { 169 case 0: 170 helpExitCode, err = p.parse([]string{"help"}, commandList) 171 case 1: 172 if !isOption(extraArgs[0]) || (len(originalArgs) > 1 && extraArgs[0] == "-a") { 173 helpExitCode, err = p.parse([]string{"help", extraArgs[0]}, commandList) 174 } else { 175 helpExitCode, err = p.parse([]string{"help"}, commandList) 176 } 177 default: 178 if isCommand(extraArgs[0]) { 179 helpExitCode, err = p.parse([]string{"help", extraArgs[0]}, commandList) 180 } else { 181 helpExitCode, err = p.parse(extraArgs[1:], commandList) 182 } 183 } 184 } 185 186 if flagErr.Type == flags.ErrHelp && helpExitCode == 0 { 187 return 0, nil 188 } else { 189 return 1, err 190 } 191 192 case flags.ErrRequired, flags.ErrMarshal: 193 fmt.Fprintf(os.Stderr, "Incorrect Usage: %s\n\n", flagErr.Error()) 194 _, err := p.parse([]string{"help", originalArgs[0]}, commandList) 195 return 1, err 196 197 case flags.ErrUnknownCommand: 198 if containsHelpFlag(originalArgs) { 199 return p.parse([]string{"help", originalArgs[0]}, commandList) 200 } else { 201 return 0, UnknownCommandError{CommandName: originalArgs[0]} 202 } 203 204 case flags.ErrCommandRequired: 205 if common.Commands.VerboseOrVersion { 206 return p.parse([]string{"version"}, commandList) 207 } else { 208 return p.parse([]string{"help"}, commandList) 209 } 210 211 default: 212 fmt.Fprintf(os.Stderr, "Unexpected flag error\ntype: %s\nmessage: %s\n", flagErr.Type, flagErr.Error()) 213 } 214 215 return 0, nil 216 } 217 218 func (p *CommandParser) parse(args []string, commandList interface{}) (int, error) { 219 flagsParser := flags.NewParser(commandList, flags.HelpFlag) 220 flagsParser.CommandHandler = p.executionWrapper 221 extraArgs, err := flagsParser.ParseArgs(args) 222 if err == nil { 223 return 0, nil 224 } else if _, ok := err.(translatableerror.V3V2SwitchError); ok { 225 return 1, err 226 } else if flagErr, ok := err.(*flags.Error); ok { 227 return p.handleFlagErrorAndCommandHelp(flagErr, flagsParser, extraArgs, args, commandList) 228 } else if err == ErrFailed { 229 return 1, nil 230 } else if err == ParseErr { 231 fmt.Println() 232 p.parse([]string{"help", args[0]}, commandList) //nolint: errcheck 233 return 1, err 234 } else if exitError, ok := err.(*ssh.ExitError); ok { 235 return exitError.ExitStatus(), nil 236 } 237 238 fmt.Fprintf(os.Stderr, "Unexpected error: %s\n", err.Error()) 239 return 1, nil 240 } 241 242 func containsHelpFlag(args []string) bool { 243 for _, arg := range args { 244 if arg == "-h" || arg == "--help" || arg == "--h" { 245 return true 246 } 247 } 248 return false 249 } 250 251 func isCommand(s string) bool { 252 _, found := reflect.TypeOf(common.Commands).FieldByNameFunc( 253 func(fieldName string) bool { 254 field, _ := reflect.TypeOf(common.Commands).FieldByName(fieldName) 255 return s == field.Tag.Get("command") || s == field.Tag.Get("alias") 256 }) 257 258 return found 259 } 260 261 func isOption(s string) bool { 262 return strings.HasPrefix(s, "-") 263 }