// SubCommands provides support for git subcommands style command handling. func SubCommands(name, version string, commands map[string]func([]string, string), commandsUsage map[string]string, additional ...string) { var commandNames, helpCommands []string var complete bool var mainUsage string callCommand := func(command string, args []string, complete bool) { var findexe bool if command[0] == '-' { args[0] = name } else { args[0] = fmt.Sprintf("%s %s", name, command) findexe = true } if handler, ok := commands[command]; ok { handler(args, commandsUsage[command]) } else if findexe { exe := fmt.Sprintf("%s-%s", strings.Replace(name, " ", "-", -1), command) exePath, err := exec.LookPath(exe) if err != nil { exit("ERROR: Couldn't find '%s'\n", exe) } args[0] = exe process, err := os.StartProcess(exePath, args, &os.ProcAttr{ Dir: ".", Env: os.Environ(), Files: []*os.File{nil, os.Stdout, os.Stderr}, }) if err != nil { exit(fmt.Sprintf("ERROR: %s: %s\n", exe, err)) } _, err = process.Wait() if err != nil { exit(fmt.Sprintf("ERROR: %s: %s\n", exe, err)) } } else { exit(fmt.Sprintf("%s: error: no such option: %s\n", name, command)) } os.Exit(0) } if _, ok := commands["help"]; !ok { commands["help"] = func(args []string, usage string) { opts := New(mainUsage) opts.ParseHelp = false opts.Completer = ListCompleter(helpCommands...) helpArgs := opts.Parse(args) if len(helpArgs) == 0 { fmt.Print(mainUsage) return } if len(helpArgs) != 1 { exit("ERROR: Unknown command '%s'\n", strings.Join(helpArgs, " ")) } command := helpArgs[0] if command == "help" { fmt.Print(mainUsage) } else { if !complete { argLen := len(os.Args) os.Args[argLen-2], os.Args[argLen-1] = os.Args[argLen-1], "--help" } callCommand(command, []string{name, "--help"}, false) } } commands["-h"] = commands["help"] commands["--help"] = commands["help"] } if len(version) != 0 { if _, ok := commands["version"]; !ok { commands["version"] = func(args []string, usage string) { opts := New(fmt.Sprintf("Usage: %s version\n\n %s\n", name, usage)) opts.Parse(args) fmt.Printf("%s\n", version) return } commands["-v"] = commands["version"] commands["--version"] = commands["version"] } } commandNames = make([]string, len(commands)) helpCommands = make([]string, len(commands)) i, j := 0, 0 for name, _ := range commands { if !strings.HasPrefix(name, "-") { commandNames[i] = name i += 1 if name != "help" { helpCommands[j] = name j += 1 } } } usageKeys := structure.SortedKeys(commandsUsage) padding := 10 for _, key := range usageKeys { if len(key) > padding { padding = len(key) } } var suffix string additionalItems := len(additional) if additionalItems == 0 { suffix = "" } else if additionalItems == 1 { mainUsage = additional[0] + "\n" suffix = "" } else { mainUsage = additional[0] + "\n" suffix = "\n" + additional[1] } mainUsage += fmt.Sprintf("Usage: %s <command> [options]\n\nCommands:\n\n", name) usageLine := fmt.Sprintf(" %%-%ds %%s\n", padding) for _, key := range usageKeys { mainUsage += fmt.Sprintf(usageLine, key, commandsUsage[key]) } mainUsage += suffix mainUsage += fmt.Sprintf( "\nSee `%s help <command>` for more info on a specific command.\n", name) complete, words, compWord, prefix := getCompletionData() baseLength := len(strings.Split(name, " ")) args := os.Args if complete && len(args) == 1 { if compWord == baseLength { completions := make([]string, 0) for _, cmd := range commandNames { if strings.HasPrefix(cmd, prefix) { completions = append(completions, cmd) } } fmt.Print(strings.Join(completions, " ")) os.Exit(1) } else { command := words[baseLength] args = []string{name} callCommand(command, args, true) } } args = args[baseLength:] if len(args) == 0 { fmt.Print(mainUsage) os.Exit(0) } command := args[0] args[0] = name callCommand(command, args, false) }
// Commands provides support for git subcommands style command handling. func Commands(name string, version interface{}, commands map[string]func([]string, string), commandsUsage map[string]string, additional ...string) { var commandNames, helpCommands []string var complete bool var mainUsage string callCommand := func(command string, args []string, complete bool) { var findexe bool if command[0] == '-' { args[0] = name } else { args[0] = fmt.Sprintf("%s %s", name, command) findexe = true } if handler, ok := commands[command]; ok { handler(args, commandsUsage[command]) } else if findexe { exe := fmt.Sprintf("%s-%s", strings.Replace(name, " ", "-", -1), command) exePath, err := exec.LookPath(exe) if err != nil { exit("%s: '%s' is not a valid command: see '%s help'", name, command, name) } args[0] = exe process, err := os.StartProcess(exePath, args, &os.ProcAttr{ Dir: ".", Env: os.Environ(), Files: []*os.File{nil, os.Stdout, os.Stderr}, }) if err != nil { exit("%s: %s", exe, err) } _, err = process.Wait() if err != nil { exit("%s: %s", exe, err) } } else { exit(fmt.Sprintf("%s: no such option: %s", name, command)) } process.Exit(0) } if _, ok := commands["help"]; !ok { commands["help"] = func(args []string, usage string) { opts := New(mainUsage) opts.ParseHelp = false opts.Completer = ListCompleter(helpCommands) helpArgs := opts.Parse(args) if len(helpArgs) == 0 { fmt.Print(mainUsage) process.Exit(1) } if len(helpArgs) != 1 { exit("%s: invalid help option: '%s'", name, strings.Join(helpArgs, " ")) } command := helpArgs[0] if command == "help" { exit("%s: invalid help command on help", name) } else { if !complete { argLen := len(os.Args) os.Args[argLen-2], os.Args[argLen-1] = os.Args[argLen-1], "--help" } callCommand(command, []string{name, "--help"}, false) } process.Exit(1) } commands["-h"] = commands["help"] commands["--help"] = commands["help"] } var setVersion bool var versionFunc func() string if versionString, found := version.(string); found { if len(versionString) != 0 { setVersion = true versionFunc = func() string { return versionString } } } else if versionFunc, found = version.(func() string); found { setVersion = true } if _, ok := commands["version"]; !ok && setVersion { commands["version"] = func(args []string, usage string) { if usage == "" { usage = fmt.Sprintf(" Show the %s version information.", name) } opts := New(fmt.Sprintf("Usage: %s version\n\n%s\n", name, usage)) opts.HideHelpOpt = true opts.Parse(args) fmt.Printf("%s\n", versionFunc()) return } commands["-v"] = commands["version"] commands["--version"] = commands["version"] } commandNames = make([]string, len(commands)) helpCommands = make([]string, len(commands)) i, j := 0, 0 for name, _ := range commands { if !strings.HasPrefix(name, "-") { commandNames[i] = name i += 1 if name != "help" { helpCommands[j] = name j += 1 } } } usageKeys := structure.SortedKeys(commandsUsage) padding := 10 for _, key := range usageKeys { if len(key) > padding { padding = len(key) } } var prefix string var suffix string lenExtra := len(additional) if lenExtra >= 1 { prefix = additional[0] + "\n\n" } if lenExtra >= 2 { suffix = additional[1] + "\n" } if lenExtra >= 3 { mainUsage = additional[2] + "\n" } mainUsage += fmt.Sprintf("Usage: %s COMMAND [OPTIONS]\n\n%sCommands:\n\n", name, prefix) usageLine := fmt.Sprintf(" %%-%ds %%s\n", padding) for _, key := range usageKeys { mainUsage += fmt.Sprintf(usageLine, key, commandsUsage[key]) } mainUsage += fmt.Sprintf( ` Run "%s help <command>" for more info on a specific command. %s`, name, suffix) complete, words, compWord, prefix := getCompletionData() baseLength := len(strings.Split(name, " ")) args := os.Args if complete && len(args) == 1 { if compWord == baseLength { completions := make([]string, 0) for _, cmd := range commandNames { if strings.HasPrefix(cmd, prefix) { completions = append(completions, cmd) } } fmt.Print(strings.Join(completions, " ")) process.Exit(1) } else { command := words[baseLength] args = []string{name} callCommand(command, args, true) } } args = args[baseLength:] if len(args) == 0 { fmt.Print(mainUsage) process.Exit(1) } command := args[0] args[0] = name callCommand(command, args, false) }