diff --git a/command.go b/command.go new file mode 100644 index 0000000..c3ac782 --- /dev/null +++ b/command.go @@ -0,0 +1,203 @@ +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") +}