12876: Util to accept subcommand after initial args.
authorTom Clegg <tclegg@veritasgenetics.com>
Sat, 23 Dec 2017 06:14:11 +0000 (01:14 -0500)
committerTom Clegg <tclegg@veritasgenetics.com>
Tue, 2 Jan 2018 14:44:27 +0000 (09:44 -0500)
Arvados-DCO-1.1-Signed-off-by: Tom Clegg <tclegg@veritasgenetics.com>

build/run-tests.sh
lib/cmd/cmd.go
lib/cmd/cmd_test.go [new file with mode: 0644]

index 5114cef3f3a7bbcfa35e5e5304495603440549b6..46418be63a77763b0320075d6327c47a8cb768a2 100755 (executable)
@@ -72,6 +72,7 @@ apps/workbench_profile
 cmd/arvados-client
 doc
 lib/cli
+lib/cmd
 lib/crunchstat
 services/api
 services/arv-git-httpd
@@ -834,6 +835,7 @@ declare -a gostuff
 gostuff=(
     cmd/arvados-client
     lib/cli
+    lib/cmd
     lib/crunchstat
     sdk/go/arvados
     sdk/go/arvadosclient
index 03b751a8a0a24620863cf9c79c2ff29651f914da..a3d5ae876785bf6a47784e6c86bf5100187f19a8 100644 (file)
@@ -7,6 +7,7 @@
 package cmd
 
 import (
+       "flag"
        "fmt"
        "io"
 )
@@ -42,3 +43,36 @@ func Multi(m map[string]RunFunc) RunFunc {
                }
        }
 }
+
+// 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:
+//
+//     // 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.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)
+       }
+}
diff --git a/lib/cmd/cmd_test.go b/lib/cmd/cmd_test.go
new file mode 100644 (file)
index 0000000..fc20bef
--- /dev/null
@@ -0,0 +1,50 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: Apache-2.0
+
+package cmd
+
+import (
+       "bytes"
+       "fmt"
+       "io"
+       "strings"
+       "testing"
+
+       check "gopkg.in/check.v1"
+)
+
+// Gocheck boilerplate
+func Test(t *testing.T) {
+       check.TestingT(t)
+}
+
+var _ = check.Suite(&CmdSuite{})
+
+type CmdSuite struct{}
+
+var testCmd = Multi(map[string]RunFunc{
+       "echo": func(prog string, args []string, stdin io.Reader, stdout io.Writer, stderr io.Writer) int {
+               fmt.Fprintln(stdout, strings.Join(args, " "))
+               return 0
+       },
+})
+
+func (s *CmdSuite) TestHello(c *check.C) {
+       stdout := bytes.NewBuffer(nil)
+       stderr := bytes.NewBuffer(nil)
+       exited := testCmd("prog", []string{"echo", "hello", "world"}, bytes.NewReader(nil), stdout, stderr)
+       c.Check(exited, check.Equals, 0)
+       c.Check(stdout.String(), check.Equals, "hello world\n")
+       c.Check(stderr.String(), check.Equals, "")
+}
+
+func (s *CmdSuite) TestWithLateSubcommand(c *check.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, "")
+}