package command import ( "errors" "reflect" "strconv" "strings" ) type Option struct { Name string LongName string ShortName string Description string OType string DefaultValue interface{} } type Command struct { Name string Description string Args []*Option SubCommands []*Command parentCommand *Command subcommand string globalOptions map[string]interface{} subcommandOptions map[string]interface{} } func (c *Command) AddArg(name string, shortName string, description string, defaultValue interface{}) { oType := reflect.TypeOf(defaultValue).String() c.Args = append(c.Args, &Option{ Name: name, LongName: ToKebabCase(name), ShortName: shortName, Description: description, OType: oType, DefaultValue: defaultValue, }) } func (c *Command) AddSubCommand(name string, description string) *Command { if c.parentCommand != nil { panic("Sub commands can only be added to top level commands") } command := &Command{ Name: name, Description: description, parentCommand: c, } c.SubCommands = append(c.SubCommands, command) return command } func (c *Command) Usage() { panic("not implemented") } func (c *Command) GetOption(name string) *Option { if len(name) > 1 { for _, arg := range c.Args { if arg.LongName == name { return arg } } } for _, arg := range c.Args { if arg.ShortName == name { return arg } } return nil } func (c *Command) parse(args []string) { if len(args) == 0 { if len(c.Args) != 0 || len(c.SubCommands) != 0 || c.parentCommand == nil { c.Usage() } if c.parentCommand != nil { c.parentCommand.subcommand = c.Name } } } func (c *Command) longOption(args []string) ([]string, bool, error) { if !strings.HasPrefix(args[0], "--") { return args, false, nil } optName := strings.TrimPrefix(args[0], "--") if len(optName) < 2 { return args, false, errors.New("Invalid option name") } opt := c.GetOption(optName) if opt != nil { rargs, _, err := c.getOptValue(args, opt, false) if err == nil { return rargs, true, nil } else { return args, false, err } } if c.parentCommand != nil { return c.parentCommand.longOption(args) } return args, false, errors.New("Unknown option " + args[0]) } func (c *Command) shortOption(args []string) ([]string, bool, error) { if !strings.HasPrefix(args[0], "-") { return args, false, nil } optName := strings.TrimPrefix(args[0], "-") last := len(optName) - 1 for i, s := range optName { opt := c.GetOption(string(s)) if opt != nil { if i != last { _, _, err := c.getOptValue(args[i:], opt, true) if err == nil { return args, true, nil } else { continue } } else { rargs, _, err := c.getOptValue(args[i:], opt, false) if err == nil { return rargs, true, nil } else { return args, false, err } } } else { if c.parentCommand != nil { opt = c.parentCommand.GetOption(string(s)) if opt != nil { rargs, _, err := c.parentCommand.getOptValue(args[i:], opt, i != last) if err == nil { return rargs, true, nil } else { return args, false, err } } } else { return args, false, errors.New("Unknown option " + args[0]) } } } return args, false, errors.New("Unknown option " + args[0]) } func (c *Command) getOptValue(args []string, opt *Option, isFixBool bool) ([]string, interface{}, error) { paramMap := c.globalOptions if c.parentCommand != nil { paramMap = c.parentCommand.subcommandOptions } if isFixBool { if opt.OType != "bool" { return args, nil, errors.New("Invalid type for boolean option") } paramMap[opt.Name] = true return args, true, nil } if opt.OType == "bool" { nextStr := strings.ToLower(args[1]) if nextStr == "f" || nextStr == "false" { paramMap[opt.Name] = false return args[2:], true, nil } else if nextStr == "t" || nextStr == "true" { paramMap[opt.Name] = true return args[2:], true, nil } else { paramMap[opt.Name] = true return args[1:], true, nil } } else { valStr := args[1] switch opt.OType { case "int": val, err := strconv.Atoi(valStr) if err != nil { return args, nil, errors.New("Invalid value for option " + opt.Name) } else { paramMap[opt.Name] = val return args[2:], true, nil } case "string": paramMap[opt.Name] = valStr return args[2:], true, nil case "float64": val, err := strconv.ParseFloat(valStr, 64) if err != nil { return args, nil, errors.New("Invalid value for option " + opt.Name) } else { paramMap[opt.Name] = val return args[2:], true, nil } case "default": return args, nil, errors.New("Unsupported type") } } return args, nil, errors.New("Unsupported type") }