13497: Use basename($0) as subcommand, if it is one.
[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 helps define reusable functions that can be exposed as
6 // [subcommands of] command line programs.
7 package cmd
8
9 import (
10         "flag"
11         "fmt"
12         "io"
13         "io/ioutil"
14         "path/filepath"
15         "sort"
16         "strings"
17 )
18
19 type Handler interface {
20         RunCommand(prog string, args []string, stdin io.Reader, stdout, stderr io.Writer) int
21 }
22
23 type HandlerFunc func(prog string, args []string, stdin io.Reader, stdout, stderr io.Writer) int
24
25 func (f HandlerFunc) RunCommand(prog string, args []string, stdin io.Reader, stdout, stderr io.Writer) int {
26         return f(prog, args, stdin, stdout, stderr)
27 }
28
29 // Multi is a Handler that looks up its first argument in a map, and
30 // invokes the resulting Handler with the remaining args.
31 //
32 // Example:
33 //
34 //     os.Exit(Multi(map[string]Handler{
35 //             "foobar": HandlerFunc(func(prog string, args []string) int {
36 //                     fmt.Println(args[0])
37 //                     return 2
38 //             }),
39 //     })("/usr/bin/multi", []string{"foobar", "baz"}))
40 //
41 // ...prints "baz" and exits 2.
42 type Multi map[string]Handler
43
44 func (m Multi) RunCommand(prog string, args []string, stdin io.Reader, stdout, stderr io.Writer) int {
45         if len(args) < 1 {
46                 fmt.Fprintf(stderr, "usage: %s command [args]\n", prog)
47                 m.Usage(stderr)
48                 return 2
49         }
50         _, basename := filepath.Split(prog)
51         if cmd, ok := m[basename]; ok {
52                 return cmd.RunCommand(prog, args, stdin, stdout, stderr)
53         } else if cmd, ok = m[args[0]]; ok {
54                 return cmd.RunCommand(prog+" "+args[0], args[1:], stdin, stdout, stderr)
55         } else {
56                 fmt.Fprintf(stderr, "%s: unrecognized command %q\n", prog, args[0])
57                 m.Usage(stderr)
58                 return 2
59         }
60 }
61
62 func (m Multi) Usage(stderr io.Writer) {
63         fmt.Fprintf(stderr, "\nAvailable commands:\n")
64         m.listSubcommands(stderr, "")
65 }
66
67 func (m Multi) listSubcommands(out io.Writer, prefix string) {
68         var subcommands []string
69         for sc := range m {
70                 if strings.HasPrefix(sc, "-") {
71                         // Some subcommands have alternate versions
72                         // like "--version" for compatibility. Don't
73                         // clutter the subcommand summary with those.
74                         continue
75                 }
76                 subcommands = append(subcommands, sc)
77         }
78         sort.Strings(subcommands)
79         for _, sc := range subcommands {
80                 switch cmd := m[sc].(type) {
81                 case Multi:
82                         cmd.listSubcommands(out, prefix+sc+" ")
83                 default:
84                         fmt.Fprintf(out, "    %s%s\n", prefix, sc)
85                 }
86         }
87 }
88
89 type FlagSet interface {
90         Init(string, flag.ErrorHandling)
91         Args() []string
92         NArg() int
93         Parse([]string) error
94         SetOutput(io.Writer)
95         PrintDefaults()
96 }
97
98 // SubcommandToFront silently parses args using flagset, and returns a
99 // copy of args with the first non-flag argument moved to the
100 // front. If parsing fails or consumes all of args, args is returned
101 // unchanged.
102 //
103 // SubcommandToFront invokes methods on flagset that have side
104 // effects, including Parse. In typical usage, flagset will not used
105 // for anything else after being passed to SubcommandToFront.
106 func SubcommandToFront(args []string, flagset FlagSet) []string {
107         flagset.Init("", flag.ContinueOnError)
108         flagset.SetOutput(ioutil.Discard)
109         if err := flagset.Parse(args); err != nil || flagset.NArg() == 0 {
110                 // No subcommand found.
111                 return args
112         }
113         // Move subcommand to the front.
114         flagargs := len(args) - flagset.NArg()
115         newargs := make([]string, len(args))
116         newargs[0] = args[flagargs]
117         copy(newargs[1:flagargs+1], args[:flagargs])
118         copy(newargs[flagargs+1:], args[flagargs+1:])
119         return newargs
120 }