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  }