diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..72e38f0 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,19 @@ +{ + // 使用 IntelliSense 了解相关属性。 + // 悬停以查看现有属性的描述。 + // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Launch Package", + "type": "go", + "request": "launch", + "mode": "auto", + "program": "${fileDirname}", + "args":[ + "-a", + "ssd" + ] + } + ] +} \ No newline at end of file diff --git a/command.go b/command.go index c3ac782..54a8471 100644 --- a/command.go +++ b/command.go @@ -2,9 +2,12 @@ package command import ( "errors" + "fmt" + "os" "reflect" "strconv" "strings" + "text/template" ) type Option struct { @@ -15,17 +18,48 @@ type Option struct { OType string DefaultValue interface{} } + +var UsageTemplate, _ = template.New("Usage").Parse(`Usage: + {{if .ParentCommand }} {{.ParentCommand.Name}} {{end}} {{.Name}} [OPTIONS] [ARGS] +{{ if .Description }} +Description: + {{.Description}} +{{ end }} +{{ if .Args }} +Options: + {{range .Args}} + -{{.ShortName}}|--{{.LongName}} {{.OType}} {{.Description}} default:{{.DefaultValue}} + {{end}} +{{ end }} +{{ if .SubCommands }} + Sub Commands: + {{range .SubCommands}} + {{.Name}} {{.Description}} + {{end}} +{{end}} +`) + type Command struct { Name string Description string Args []*Option SubCommands []*Command - parentCommand *Command + ParentCommand *Command subcommand string globalOptions map[string]interface{} subcommandOptions map[string]interface{} + remainArgs []string } +func (c *Command) GetGlobalOptions() map[string]interface{} { + return c.globalOptions +} +func (c *Command) GetSubCommandOptions() map[string]interface{} { + return c.subcommandOptions +} +func (c *Command) GetSubCommand() string { + return c.subcommand +} func (c *Command) AddArg(name string, shortName string, description string, defaultValue interface{}) { oType := reflect.TypeOf(defaultValue).String() c.Args = append(c.Args, &Option{ @@ -39,19 +73,19 @@ func (c *Command) AddArg(name string, shortName string, description string, defa } func (c *Command) AddSubCommand(name string, description string) *Command { - if c.parentCommand != nil { + if c.ParentCommand != nil { panic("Sub commands can only be added to top level commands") } command := &Command{ Name: name, Description: description, - parentCommand: c, + ParentCommand: c, } c.SubCommands = append(c.SubCommands, command) return command } func (c *Command) Usage() { - panic("not implemented") + UsageTemplate.Execute(os.Stdout, c) } func (c *Command) GetOption(name string) *Option { if len(name) > 1 { @@ -68,17 +102,91 @@ func (c *Command) GetOption(name string) *Option { } return nil } - -func (c *Command) parse(args []string) { +func (c *Command) parseError(errMsg string) { + fmt.Println(errMsg) + os.Exit(1) +} +func (c *Command) showHelpWithOption(args []string) { + if len(args) > 0 && (args[0] == "-h" || args[0] == "--help") { + c.Usage() + os.Exit(0) + } +} +func (c *Command) showSubCommandHelp(args []string) { + if len(args) == 2 && args[0] == "help" { + cmd := c.findSubcommand(args[1]) + if cmd == nil { + c.parseError("Unknown subcommand " + args[1]) + } + cmd.Usage() + os.Exit(0) + } +} +func (c *Command) Parse(args []string) ([]string, error) { + c.showHelpWithOption(args) + c.showSubCommandHelp(args) if len(args) == 0 { - if len(c.Args) != 0 || len(c.SubCommands) != 0 || c.parentCommand == nil { + if len(c.Args) != 0 || len(c.SubCommands) != 0 || c.ParentCommand == nil { c.Usage() } - if c.parentCommand != nil { - c.parentCommand.subcommand = c.Name + if c.ParentCommand != nil { + c.ParentCommand.subcommand = c.Name } } + vargs := args + for { + if len(vargs) == 0 { + break + } + var parsed bool + var err error + vargs, parsed, err = c.longOption(vargs) + if err != nil { + c.parseError(err.Error()) + } + if parsed { + continue + } + vargs, parsed, err = c.shortOption(vargs) + if err != nil { + c.parseError(err.Error()) + } + if parsed { + continue + } + if len(c.SubCommands) > 0 { + cmd := c.findSubcommand(args[0]) + if cmd != nil { + c.subcommand = cmd.Name + vargs = vargs[1:] + vargs, err = cmd.Parse(vargs) + if err != nil { + c.parseError(err.Error()) + } + } else { + c.parseError("Unknown subcommand " + args[0]) + } + } else { + if c.ParentCommand != nil { + c.ParentCommand.remainArgs = append(c.ParentCommand.remainArgs, vargs[0]) + + } else { + c.remainArgs = append(c.remainArgs, vargs[0]) + } + vargs = vargs[1:] + + } + } + return vargs, nil +} +func (c *Command) findSubcommand(cmd string) *Command { + for _, subCmd := range c.SubCommands { + if subCmd.Name == cmd { + return subCmd + } + } + return nil } func (c *Command) longOption(args []string) ([]string, bool, error) { @@ -98,8 +206,8 @@ func (c *Command) longOption(args []string) ([]string, bool, error) { return args, false, err } } - if c.parentCommand != nil { - return c.parentCommand.longOption(args) + if c.ParentCommand != nil { + return c.ParentCommand.longOption(args) } return args, false, errors.New("Unknown option " + args[0]) } @@ -129,10 +237,10 @@ func (c *Command) shortOption(args []string) ([]string, bool, error) { } } else { - if c.parentCommand != nil { - opt = c.parentCommand.GetOption(string(s)) + if c.ParentCommand != nil { + opt = c.ParentCommand.GetOption(string(s)) if opt != nil { - rargs, _, err := c.parentCommand.getOptValue(args[i:], opt, i != last) + rargs, _, err := c.ParentCommand.getOptValue(args[i:], opt, i != last) if err == nil { return rargs, true, nil } else { @@ -150,8 +258,8 @@ func (c *Command) shortOption(args []string) ([]string, bool, error) { 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 c.ParentCommand != nil { + paramMap = c.ParentCommand.subcommandOptions } if isFixBool { if opt.OType != "bool" { @@ -196,8 +304,18 @@ func (c *Command) getOptValue(args []string, opt *Option, isFixBool bool) ([]str return args[2:], true, nil } case "default": - return args, nil, errors.New("Unsupported type") + return args, nil, errors.New("unsupported type") } } - return args, nil, errors.New("Unsupported type") + return args, nil, errors.New("unsupported type") +} + +func NewCommand(name string, desc string) *Command { + return &Command{ + Name: name, + Description: desc, + globalOptions: map[string]interface{}{}, + subcommandOptions: map[string]interface{}{}, + remainArgs: []string{}, + } } diff --git a/testcmd/main.go b/testcmd/main.go new file mode 100644 index 0000000..6fc95da --- /dev/null +++ b/testcmd/main.go @@ -0,0 +1,17 @@ +package main + +import ( + "fmt" + "os" + + "git.pyer.club/kingecg/command" +) + +func main() { + var cmd = command.NewCommand("test", "test command") + cmd.AddArg("arg1", "a", "arg1 description", "default value") + cmd.AddArg("arg2", "b", "arg2 description", "default value") + cmd.AddSubCommand("sub1", "sub1 description") + cmd.Parse(os.Args[1:]) + fmt.Println(cmd.GetGlobalOptions()) +}