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