package main
import (
+ "flag"
"fmt"
"io"
"os"
"git.curoverse.com/arvados.git/lib/cli"
"git.curoverse.com/arvados.git/lib/cmd"
+ "rsc.io/getopt"
)
var version = "dev"
-var Run = cmd.WithLateSubcommand(cmd.Multi(map[string]cmd.RunFunc{
+var Run = cmd.Multi(map[string]cmd.RunFunc{
"get": cli.Get,
"-e": cmdVersion,
"version": cmdVersion,
"-version": cmdVersion,
"--version": cmdVersion,
-}), []string{"f", "format"}, []string{"n", "dry-run", "v", "verbose", "s", "short"})
+})
func cmdVersion(prog string, args []string, _ io.Reader, stdout, _ io.Writer) int {
prog = regexp.MustCompile(` -*version$`).ReplaceAllLiteralString(prog, "")
return 0
}
+func fixLegacyArgs(args []string) []string {
+ flags := getopt.NewFlagSet("", flag.ContinueOnError)
+ flags.Bool("dry-run", false, "dry run")
+ flags.Alias("n", "dry-run")
+ flags.String("format", "json", "output format")
+ flags.Alias("f", "format")
+ flags.Bool("short", false, "short")
+ flags.Alias("s", "short")
+ flags.Bool("verbose", false, "verbose")
+ flags.Alias("v", "verbose")
+ return cmd.SubcommandToFront(args, flags)
+}
+
func main() {
- os.Exit(Run(os.Args[0], os.Args[1:], os.Stdin, os.Stdout, os.Stderr))
+ os.Exit(Run(os.Args[0], fixLegacyArgs(os.Args[1:]), os.Stdin, os.Stdout, os.Stderr))
}
"git.curoverse.com/arvados.git/sdk/go/arvados"
"github.com/ghodss/yaml"
+ "rsc.io/getopt"
)
func Get(prog string, args []string, stdin io.Reader, stdout, stderr io.Writer) int {
}
}()
- flags := flag.NewFlagSet(prog, flag.ContinueOnError)
+ flags := getopt.NewFlagSet(prog, flag.ContinueOnError)
flags.SetOutput(stderr)
format := flags.String("format", "json", "output format (json, yaml, or uuid)")
- flags.StringVar(format, "f", "json", "output format (json, yaml, or uuid)")
+ flags.Alias("f", "format")
short := flags.Bool("short", false, "equivalent to --format=uuid")
- flags.BoolVar(short, "s", false, "equivalent to --format=uuid")
+ flags.Alias("s", "short")
flags.Bool("dry-run", false, "dry run (ignored, for compatibility)")
- flags.Bool("n", false, "dry run (ignored, for compatibility)")
+ flags.Alias("n", "dry-run")
flags.Bool("verbose", false, "verbose (ignored, for compatibility)")
- flags.Bool("v", false, "verbose (ignored, for compatibility)")
+ flags.Alias("v", "verbose")
err = flags.Parse(args)
if err != nil {
return 2
}
}
-// WithLateSubcommand wraps a RunFunc by skipping over some known
-// flags to find a subcommand, and moving that subcommand to the front
-// of the args before calling the wrapped RunFunc. For example:
+type FlagSet interface {
+ Init(string, flag.ErrorHandling)
+ NArg() int
+ Parse([]string) error
+ SetOutput(io.Writer)
+}
+
+// SubcommandToFront silently parses args using flagset, and returns a
+// copy of args with the first non-flag argument moved to the
+// front. If parsing fails or consumes all of args, args is returned
+// unchanged.
//
-// // Translate [ --format foo subcommand bar]
-// // to [subcommand --format foo bar]
-// WithLateSubcommand(fn, []string{"format"}, nil)
-func WithLateSubcommand(run RunFunc, argFlags, boolFlags []string) RunFunc {
- return func(prog string, args []string, stdin io.Reader, stdout, stderr io.Writer) int {
- flags := flag.NewFlagSet("prog", flag.ContinueOnError)
- for _, arg := range argFlags {
- flags.String(arg, "", "")
- }
- for _, arg := range boolFlags {
- flags.Bool(arg, false, "")
- }
- // Ignore errors. We can't report a useful error
- // message anyway.
- flags.SetOutput(ioutil.Discard)
- flags.Usage = func() {}
- flags.Parse(args)
- if flags.NArg() > 0 {
- // Move the first arg after the recognized
- // flags up to the front.
- flagargs := len(args) - flags.NArg()
- newargs := make([]string, len(args))
- newargs[0] = args[flagargs]
- copy(newargs[1:flagargs+1], args[:flagargs])
- copy(newargs[flagargs+1:], args[flagargs+1:])
- args = newargs
- }
- return run(prog, args, stdin, stdout, stderr)
+// SubcommandToFront invokes methods on flagset that have side
+// effects, including Parse. In typical usage, flagset will not used
+// for anything else after being passed to SubcommandToFront.
+func SubcommandToFront(args []string, flagset FlagSet) []string {
+ flagset.Init("", flag.ContinueOnError)
+ flagset.SetOutput(ioutil.Discard)
+ if err := flagset.Parse(args); err != nil || flagset.NArg() == 0 {
+ // No subcommand found.
+ return args
}
+ // Move subcommand to the front.
+ flagargs := len(args) - flagset.NArg()
+ newargs := make([]string, len(args))
+ newargs[0] = args[flagargs]
+ copy(newargs[1:flagargs+1], args[:flagargs])
+ copy(newargs[flagargs+1:], args[flagargs+1:])
+ return newargs
}
import (
"bytes"
+ "flag"
"fmt"
"io"
"strings"
c.Check(stderr.String(), check.Matches, `(?ms)^unrecognized command "nosuchcommand"\n.*echo.*\n`)
}
-func (s *CmdSuite) TestWithLateSubcommand(c *check.C) {
+func (s *CmdSuite) TestSubcommandToFront(c *check.C) {
defer cmdtest.LeakCheck(c)()
- stdout := bytes.NewBuffer(nil)
- stderr := bytes.NewBuffer(nil)
- run := WithLateSubcommand(testCmd, []string{"format", "f"}, []string{"n"})
- exited := run("prog", []string{"--format=yaml", "-n", "-format", "beep", "echo", "hi"}, bytes.NewReader(nil), stdout, stderr)
- c.Check(exited, check.Equals, 0)
- c.Check(stdout.String(), check.Equals, "--format=yaml -n -format beep hi\n")
- c.Check(stderr.String(), check.Equals, "")
+ flags := flag.NewFlagSet("", flag.ContinueOnError)
+ flags.String("format", "json", "")
+ flags.Bool("n", false, "")
+ args := SubcommandToFront([]string{"--format=yaml", "-n", "-format", "beep", "echo", "hi"}, flags)
+ c.Check(args, check.DeepEquals, []string{"echo", "--format=yaml", "-n", "-format", "beep", "hi"})
}