12876: Fix up error/usage messages.
[arvados.git] / lib / cmd / cmd.go
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: Apache-2.0
4
5 // package cmd defines a RunFunc type, representing a process that can
6 // be invoked from a command line.
7 package cmd
8
9 import (
10         "flag"
11         "fmt"
12         "io"
13         "io/ioutil"
14         "sort"
15         "strings"
16 )
17
18 // A RunFunc runs a command with the given args, and returns an exit
19 // code.
20 type RunFunc func(prog string, args []string, stdin io.Reader, stdout, stderr io.Writer) int
21
22 // Multi returns a RunFunc that looks up its first argument in m, and
23 // invokes the resulting RunFunc with the remaining args.
24 //
25 // Example:
26 //
27 //     os.Exit(Multi(map[string]RunFunc{
28 //             "foobar": func(prog string, args []string) int {
29 //                     fmt.Println(args[0])
30 //                     return 2
31 //             },
32 //     })("/usr/bin/multi", []string{"foobar", "baz"}))
33 //
34 // ...prints "baz" and exits 2.
35 func Multi(m map[string]RunFunc) RunFunc {
36         return func(prog string, args []string, stdin io.Reader, stdout, stderr io.Writer) int {
37                 if len(args) < 1 {
38                         fmt.Fprintf(stderr, "usage: %s command [args]\n", prog)
39                         multiUsage(stderr, m)
40                         return 2
41                 }
42                 if cmd, ok := m[args[0]]; !ok {
43                         fmt.Fprintf(stderr, "unrecognized command %q\n", args[0])
44                         multiUsage(stderr, m)
45                         return 2
46                 } else {
47                         return cmd(prog+" "+args[0], args[1:], stdin, stdout, stderr)
48                 }
49         }
50 }
51
52 func multiUsage(stderr io.Writer, m map[string]RunFunc) {
53         var subcommands []string
54         for sc := range m {
55                 if strings.HasPrefix(sc, "-") {
56                         // Some subcommands have alternate versions
57                         // like "--version" for compatibility. Don't
58                         // clutter the subcommand summary with those.
59                         continue
60                 }
61                 subcommands = append(subcommands, sc)
62         }
63         sort.Strings(subcommands)
64         fmt.Fprintf(stderr, "\nAvailable commands:\n")
65         for _, sc := range subcommands {
66                 fmt.Fprintf(stderr, "    %s\n", sc)
67         }
68 }
69
70 // WithLateSubcommand wraps a RunFunc by skipping over some known
71 // flags to find a subcommand, and moving that subcommand to the front
72 // of the args before calling the wrapped RunFunc. For example:
73 //
74 //      // Translate [           --format foo subcommand bar]
75 //      //        to [subcommand --format foo            bar]
76 //      WithLateSubcommand(fn, []string{"format"}, nil)
77 func WithLateSubcommand(run RunFunc, argFlags, boolFlags []string) RunFunc {
78         return func(prog string, args []string, stdin io.Reader, stdout, stderr io.Writer) int {
79                 flags := flag.NewFlagSet("prog", flag.ContinueOnError)
80                 for _, arg := range argFlags {
81                         flags.String(arg, "", "")
82                 }
83                 for _, arg := range boolFlags {
84                         flags.Bool(arg, false, "")
85                 }
86                 // Ignore errors. We can't report a useful error
87                 // message anyway.
88                 flags.SetOutput(ioutil.Discard)
89                 flags.Usage = func() {}
90                 flags.Parse(args)
91                 if flags.NArg() > 0 {
92                         // Move the first arg after the recognized
93                         // flags up to the front.
94                         flagargs := len(args) - flags.NArg()
95                         newargs := make([]string, len(args))
96                         newargs[0] = args[flagargs]
97                         copy(newargs[1:flagargs+1], args[:flagargs])
98                         copy(newargs[flagargs+1:], args[flagargs+1:])
99                         args = newargs
100                 }
101                 return run(prog, args, stdin, stdout, stderr)
102         }
103 }