package parser import ( "os" "fmt" "errors" "git.arcanium.tech/tristan/go_logging" ) type RunHook func(*Command) type WorkFunc func(*Context) error var commands []*Command type CommandOptions struct { Name string Description string } type Command struct { Name string Description string SubCommands []*Command Flags []*ArgFlag App *App Work WorkFunc preRunHooks []RunHook postRunHooks []RunHook } func (c *Command) getFlag(name string) (*ArgFlag, error) { for _, flag := range c.Flags { if flag.Matches(name) { return flag, nil } } return nil, fmt.Errorf("Command(%s) : ERROR : there is no flag by name %s", c.Name, name) } func (c *Command) Run(args []string) error { if len(c.preRunHooks) > 0 { for _, hook := range c.preRunHooks { hook(c) } } err := c.run(args) if err != nil { return err } if len(c.postRunHooks) > 0 { for _, hook := range c.postRunHooks { hook(c) } } return nil } func (c *Command) isSubCommand(name string) (bool) { for _, subcommand := range c.SubCommands { if subcommand.Name == name { return true } } return false } func (c *Command) getSubCommand(name string) (*Command, error) { for _, subcommand := range c.SubCommands { if subcommand.Name == name { return subcommand, nil } } return nil, fmt.Errorf("ERROR : Command(%s) has no subcommand named %s", c.Name, name) } func (c *Command) determineMatch(arg string) (matchType, error) { if arg == "-h" || arg == "--help" || arg == "help" { return helpMatch, nil } flag_match, err := isFlag(arg) if err != nil { return noMatch, err } if flag_match { _, err := c.getFlag(arg) if err != nil { return unknownFlagMatch, nil } return flagMatch, nil } subcommand := c.isSubCommand(arg) if subcommand == true { return commandMatch, nil } return noMatch, nil } func (c *Command) run (args []string) error { logging.Debug("Command(%s) : Entering Run", c.Name) if c.Work == nil && len(args) == 0 { logging.Debug("Command(%s) : No arguments. Assuming help", c.Name) c.Help() return nil } for i := 0; i < len(args); i++ { arg := args[i] logging.Debug("Command(%s) : Iterating on provided args (i: %d, arg: %s)", c.Name, i, arg) match_type, err := c.determineMatch(arg) if err != nil { return err } switch match_type { case noMatch: return fmt.Errorf("No match found for Arg(%s) for Command(%s)", arg, c.Name) case helpMatch: c.Help() return nil case unknownFlagMatch: return fmt.Errorf("Command(%s) : ERROR : Unconfigured Flag(%s) detected", c.Name, arg) case flagMatch: flag, err := c.getFlag(arg) if err != nil { return err } if flag.requireArg(){ if len(args[i:]) <= 1 { return fmt.Errorf("Command(%s) : ArgFlag(%s) : Required argument not found", c.Name, flag.Name) } arglist := args[i:i+2] i++ // want to call the flag with itself & the argument it wants err = flag.CheckArg(arglist) if err != nil { return err } } else { // Since we want to preserve that they were called at all. (particularly for BoolType) flag.Args = args[i:i+1] } if flag.OnMatch != nil { err := flag.OnMatch() if err != nil { return err } } case commandMatch: remaining_args := args[i+1:] subcommand, err := c.getSubCommand(arg) if err != nil { return err } return subcommand.Run(remaining_args) default: return nil } } if c.Work != nil { flagmap := make(map[string]*ArgFlag) for _, flag := range c.Flags { flagmap[flag.Name] = flag } ctx := Context{ Command: c, Flags: flagmap, App: c.App, Args: args, } return c.Work(&ctx) } return nil } func (c *Command) Help () error { commandHelpTemplate.Execute(os.Stdout, c) return nil } func (c *Command) Check() error { if c.Name == "" { return errors.New("ERROR : Command Name wasn't set") } if c.Description == "" { return fmt.Errorf("Command(%s) : ERROR : Description wasn't set", c.Name) } if len(c.SubCommands) == 0 && c.Work == nil { return fmt.Errorf("Command(%s) : ERROR : Command was configured with neither commands (SubCommands) nor a work function (Work)", c.Name) } if len(c.SubCommands) > 0 && c.Work != nil { return fmt.Errorf("Command(%s) : ERROR : Command was defined with both subcommands & a work function", c.Name) } subcommands := make(map[string]bool) for _, subcommand := range c.SubCommands { err := subcommand.Check() if err != nil { return err } if _, exist := subcommands[subcommand.Name]; exist { return fmt.Errorf("Command(%s) : SubCommand(%s) : SubCommand Name was found as duplicate", c.Name, subcommand.Name) } subcommands[subcommand.Name]=true } for _, flag := range c.Flags { err := flag.Check() if err != nil { return err } } shorts := make(map[string]bool) longs := make(map[string]bool) names := make(map[string]bool) for _, flag := range c.Flags { if _, exist := shorts[flag.Short]; exist { return fmt.Errorf("Command(%s) : ArgFlag(%s) : flag short duplicate found (%s)", c.Name, flag.Name, flag.Short) } shorts[flag.Short] = true if _, exist := longs[flag.Long]; exist { return fmt.Errorf("Command(%s) : ArgFlag(%s) : flag long duplicate found (%s)", c.Name, flag.Name, flag.Long) } longs[flag.Long] = true if _, exist := names[flag.Name]; exist { return fmt.Errorf("Command(%s) : ArgFlag(Long: %s) : flag name duplicate found (%s)", c.Name, flag.Long, flag.Name) } names[flag.Name] = true } return nil }