1 // Copyright (C) The Arvados Authors. All rights reserved.
3 // SPDX-License-Identifier: Apache-2.0
5 // package cmd helps define reusable functions that can be exposed as
6 // [subcommands of] command line programs.
21 type Handler interface {
22 RunCommand(prog string, args []string, stdin io.Reader, stdout, stderr io.Writer) int
25 type HandlerFunc func(prog string, args []string, stdin io.Reader, stdout, stderr io.Writer) int
27 func (f HandlerFunc) RunCommand(prog string, args []string, stdin io.Reader, stdout, stderr io.Writer) int {
28 return f(prog, args, stdin, stdout, stderr)
33 func (v Version) RunCommand(prog string, args []string, stdin io.Reader, stdout, stderr io.Writer) int {
34 prog = regexp.MustCompile(` -*version$`).ReplaceAllLiteralString(prog, "")
35 fmt.Fprintf(stdout, "%s %s (%s)\n", prog, v, runtime.Version())
39 // Multi is a Handler that looks up its first argument in a map, and
40 // invokes the resulting Handler with the remaining args.
44 // os.Exit(Multi(map[string]Handler{
45 // "foobar": HandlerFunc(func(prog string, args []string) int {
46 // fmt.Println(args[0])
49 // })("/usr/bin/multi", []string{"foobar", "baz"}))
51 // ...prints "baz" and exits 2.
52 type Multi map[string]Handler
54 func (m Multi) RunCommand(prog string, args []string, stdin io.Reader, stdout, stderr io.Writer) int {
56 fmt.Fprintf(stderr, "usage: %s command [args]\n", prog)
60 _, basename := filepath.Split(prog)
61 if strings.HasPrefix(basename, "arvados-") {
62 basename = basename[8:]
63 } else if strings.HasPrefix(basename, "crunch-") {
64 basename = basename[7:]
66 if cmd, ok := m[basename]; ok {
67 return cmd.RunCommand(prog, args, stdin, stdout, stderr)
68 } else if cmd, ok = m[args[0]]; ok {
69 return cmd.RunCommand(prog+" "+args[0], args[1:], stdin, stdout, stderr)
71 fmt.Fprintf(stderr, "%s: unrecognized command %q\n", prog, args[0])
77 func (m Multi) Usage(stderr io.Writer) {
78 fmt.Fprintf(stderr, "\nAvailable commands:\n")
79 m.listSubcommands(stderr, "")
82 func (m Multi) listSubcommands(out io.Writer, prefix string) {
83 var subcommands []string
85 if strings.HasPrefix(sc, "-") {
86 // Some subcommands have alternate versions
87 // like "--version" for compatibility. Don't
88 // clutter the subcommand summary with those.
91 subcommands = append(subcommands, sc)
93 sort.Strings(subcommands)
94 for _, sc := range subcommands {
95 switch cmd := m[sc].(type) {
97 cmd.listSubcommands(out, prefix+sc+" ")
99 fmt.Fprintf(out, " %s%s\n", prefix, sc)
104 type FlagSet interface {
105 Init(string, flag.ErrorHandling)
108 Parse([]string) error
113 // SubcommandToFront silently parses args using flagset, and returns a
114 // copy of args with the first non-flag argument moved to the
115 // front. If parsing fails or consumes all of args, args is returned
118 // SubcommandToFront invokes methods on flagset that have side
119 // effects, including Parse. In typical usage, flagset will not used
120 // for anything else after being passed to SubcommandToFront.
121 func SubcommandToFront(args []string, flagset FlagSet) []string {
122 flagset.Init("", flag.ContinueOnError)
123 flagset.SetOutput(ioutil.Discard)
124 if err := flagset.Parse(args); err != nil || flagset.NArg() == 0 {
125 // No subcommand found.
128 // Move subcommand to the front.
129 flagargs := len(args) - flagset.NArg()
130 newargs := make([]string, len(args))
131 newargs[0] = args[flagargs]
132 copy(newargs[1:flagargs+1], args[:flagargs])
133 copy(newargs[flagargs+1:], args[flagargs+1:])