command/command.go

204 lines
4.7 KiB
Go

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")
}