14745: Improves azure driver exported var comment
[arvados.git] / lib / cmd / cmd.go
index f33354a24bcc60a90e65dd1ddff9a78d694ac147..9292ef7e5ff5b3afb6012833299d9f89a7ea346c 100644 (file)
@@ -2,8 +2,8 @@
 //
 // SPDX-License-Identifier: Apache-2.0
 
-// package cmd defines a RunFunc type, representing a process that can
-// be invoked from a command line.
+// package cmd helps define reusable functions that can be exposed as
+// [subcommands of] command line programs.
 package cmd
 
 import (
@@ -11,45 +11,72 @@ import (
        "fmt"
        "io"
        "io/ioutil"
+       "path/filepath"
+       "regexp"
+       "runtime"
        "sort"
        "strings"
 )
 
-// A RunFunc runs a command with the given args, and returns an exit
-// code.
-type RunFunc func(prog string, args []string, stdin io.Reader, stdout, stderr io.Writer) int
+type Handler interface {
+       RunCommand(prog string, args []string, stdin io.Reader, stdout, stderr io.Writer) int
+}
+
+type HandlerFunc func(prog string, args []string, stdin io.Reader, stdout, stderr io.Writer) int
+
+func (f HandlerFunc) RunCommand(prog string, args []string, stdin io.Reader, stdout, stderr io.Writer) int {
+       return f(prog, args, stdin, stdout, stderr)
+}
 
-// Multi returns a RunFunc that looks up its first argument in m, and
-// invokes the resulting RunFunc with the remaining args.
+type Version string
+
+func (v Version) RunCommand(prog string, args []string, stdin io.Reader, stdout, stderr io.Writer) int {
+       prog = regexp.MustCompile(` -*version$`).ReplaceAllLiteralString(prog, "")
+       fmt.Fprintf(stdout, "%s %s (%s)\n", prog, v, runtime.Version())
+       return 0
+}
+
+// Multi is a Handler that looks up its first argument in a map (after
+// stripping any "arvados-" or "crunch-" prefix), and invokes the
+// resulting Handler with the remaining args.
 //
 // Example:
 //
-//     os.Exit(Multi(map[string]RunFunc{
-//             "foobar": func(prog string, args []string) int {
+//     os.Exit(Multi(map[string]Handler{
+//             "foobar": HandlerFunc(func(prog string, args []string) int {
 //                     fmt.Println(args[0])
 //                     return 2
-//             },
+//             }),
 //     })("/usr/bin/multi", []string{"foobar", "baz"}))
 //
 // ...prints "baz" and exits 2.
-func Multi(m map[string]RunFunc) RunFunc {
-       return func(prog string, args []string, stdin io.Reader, stdout, stderr io.Writer) int {
-               if len(args) < 1 {
-                       fmt.Fprintf(stderr, "usage: %s command [args]\n", prog)
-                       multiUsage(stderr, m)
-                       return 2
-               }
-               if cmd, ok := m[args[0]]; !ok {
-                       fmt.Fprintf(stderr, "unrecognized command %q\n", args[0])
-                       multiUsage(stderr, m)
-                       return 2
-               } else {
-                       return cmd(prog+" "+args[0], args[1:], stdin, stdout, stderr)
-               }
+type Multi map[string]Handler
+
+func (m Multi) RunCommand(prog string, args []string, stdin io.Reader, stdout, stderr io.Writer) int {
+       _, basename := filepath.Split(prog)
+       basename = strings.TrimPrefix(basename, "arvados-")
+       basename = strings.TrimPrefix(basename, "crunch-")
+       if cmd, ok := m[basename]; ok {
+               return cmd.RunCommand(prog, args, stdin, stdout, stderr)
+       } else if len(args) < 1 {
+               fmt.Fprintf(stderr, "usage: %s command [args]\n", prog)
+               m.Usage(stderr)
+               return 2
+       } else if cmd, ok = m[args[0]]; ok {
+               return cmd.RunCommand(prog+" "+args[0], args[1:], stdin, stdout, stderr)
+       } else {
+               fmt.Fprintf(stderr, "%s: unrecognized command %q\n", prog, args[0])
+               m.Usage(stderr)
+               return 2
        }
 }
 
-func multiUsage(stderr io.Writer, m map[string]RunFunc) {
+func (m Multi) Usage(stderr io.Writer) {
+       fmt.Fprintf(stderr, "\nAvailable commands:\n")
+       m.listSubcommands(stderr, "")
+}
+
+func (m Multi) listSubcommands(out io.Writer, prefix string) {
        var subcommands []string
        for sc := range m {
                if strings.HasPrefix(sc, "-") {
@@ -61,17 +88,23 @@ func multiUsage(stderr io.Writer, m map[string]RunFunc) {
                subcommands = append(subcommands, sc)
        }
        sort.Strings(subcommands)
-       fmt.Fprintf(stderr, "\nAvailable commands:\n")
        for _, sc := range subcommands {
-               fmt.Fprintf(stderr, "    %s\n", sc)
+               switch cmd := m[sc].(type) {
+               case Multi:
+                       cmd.listSubcommands(out, prefix+sc+" ")
+               default:
+                       fmt.Fprintf(out, "    %s%s\n", prefix, sc)
+               }
        }
 }
 
 type FlagSet interface {
        Init(string, flag.ErrorHandling)
+       Args() []string
        NArg() int
        Parse([]string) error
        SetOutput(io.Writer)
+       PrintDefaults()
 }
 
 // SubcommandToFront silently parses args using flagset, and returns a