Merge "lib/cmdline: adding support for binary based subcommands"
diff --git a/buildinfo/.api b/buildinfo/.api
deleted file mode 100644
index e69de29..0000000
--- a/buildinfo/.api
+++ /dev/null
diff --git a/cmdline/.api b/cmdline/.api
index a0a8b60..583cc7e 100644
--- a/cmdline/.api
+++ b/cmdline/.api
@@ -14,6 +14,7 @@
pkg cmdline, type Command struct, Children []*Command
pkg cmdline, type Command struct, Flags flag.FlagSet
pkg cmdline, type Command struct, Long string
+pkg cmdline, type Command struct, LookPath bool
pkg cmdline, type Command struct, Name string
pkg cmdline, type Command struct, Runner Runner
pkg cmdline, type Command struct, Short string
diff --git a/cmdline/cmdline.go b/cmdline/cmdline.go
index 7b1ddbc..549ac5b 100644
--- a/cmdline/cmdline.go
+++ b/cmdline/cmdline.go
@@ -41,9 +41,11 @@
"io"
"io/ioutil"
"os"
+ "os/exec"
"strings"
- _ "v.io/x/lib/metadata" // for the -v23.metadata flag
+ "v.io/x/lib/envvar"
+ _ "v.io/x/lib/metadata" // for the -metadata flag
)
// Command represents a single command in a command-line program. A program
@@ -57,6 +59,7 @@
Flags flag.FlagSet // Flags for the command.
ArgsName string // Name of the args, shown in usage line.
ArgsLong string // Long description of the args, shown in help.
+ LookPath bool // Check for subcommands in PATH.
// Children of the command.
Children []*Command
@@ -157,7 +160,7 @@
path := []*Command{root}
env.Usage = makeHelpRunner(path, env).usageFunc
cleanTree(root)
- if err := checkTreeInvariants(path); err != nil {
+ if err := checkTreeInvariants(path, env); err != nil {
return nil, nil, err
}
runner, args, err := root.parse(nil, env, args)
@@ -204,8 +207,8 @@
})
}
-func checkTreeInvariants(path []*Command) error {
- cmd, cmdPath := path[len(path)-1], pathName(path)
+func checkTreeInvariants(path []*Command, env *Env) error {
+ cmd, cmdPath := path[len(path)-1], pathName(env.prefix(), path)
// Check that the root name is non-empty.
if cmdPath == "" {
return fmt.Errorf(`CODE INVARIANT BROKEN; FIX YOUR CODE
@@ -255,24 +258,27 @@
}
// Check recursively for all children
for _, child := range cmd.Children {
- if err := checkTreeInvariants(append(path, child)); err != nil {
+ if err := checkTreeInvariants(append(path, child), env); err != nil {
return err
}
}
return nil
}
-func pathName(path []*Command) string {
+func pathName(prefix string, path []*Command) string {
name := path[0].Name
for _, cmd := range path[1:] {
name += " " + cmd.Name
}
+ if prefix != "" {
+ return prefix + " " + name
+ }
return name
}
func (cmd *Command) parse(path []*Command, env *Env, args []string) (Runner, []string, error) {
path = append(path, cmd)
- cmdPath := pathName(path)
+ cmdPath := pathName(env.prefix(), path)
runHelp := makeHelpRunner(path, env)
env.Usage = runHelp.usageFunc
// Parse flags and retrieve the args remaining after the parse.
@@ -304,7 +310,14 @@
return runHelp.newCommand().parse(path, env, subArgs)
}
}
- // No matching children, check various error cases.
+ if cmd.LookPath {
+ // Look for a matching executable in PATH.
+ subCmd := cmd.Name + "-" + subName
+ if lookPath(subCmd, env.pathDirs()) {
+ return binaryRunner{subCmd, cmdPath}, subArgs, nil
+ }
+ }
+ // No matching subcommands, check various error cases.
switch {
case cmd.Runner == nil:
return nil, nil, env.UsageErrorf("%s: unknown command %q", cmdPath, subName)
@@ -406,3 +419,58 @@
}
return 1
}
+
+type binaryRunner struct {
+ subCmd string
+ cmdPath string
+}
+
+func (b binaryRunner) Run(env *Env, args []string) error {
+ cmd := exec.Command(b.subCmd, args...)
+ cmd.Stdin = env.Stdin
+ cmd.Stdout = env.Stdout
+ cmd.Stderr = env.Stderr
+ cmd.Env = envvar.MapToSlice(env.Vars)
+ cmd.Env = append(cmd.Env, "CMDLINE_PREFIX="+b.cmdPath)
+ return cmd.Run()
+}
+
+// lookPath returns a boolean that indicates whether executable <name>
+// can be found in any of the given directories.
+func lookPath(name string, dirs []string) bool {
+ for _, dir := range dirs {
+ fileInfos, err := ioutil.ReadDir(dir)
+ if err != nil {
+ continue
+ }
+ for _, fileInfo := range fileInfos {
+ if m := fileInfo.Mode(); !m.IsRegular() || (m&os.FileMode(0111)) == 0 {
+ continue
+ }
+ if fileInfo.Name() == name {
+ return true
+ }
+ }
+ }
+ return false
+}
+
+// lookPathAll returns a list of all executables found in the given
+// directories whose name starts with "<name>-".
+func lookPathAll(name string, dirs []string) (result []string) {
+ for _, dir := range dirs {
+ fileInfos, err := ioutil.ReadDir(dir)
+ if err != nil {
+ continue
+ }
+ for _, fileInfo := range fileInfos {
+ if m := fileInfo.Mode(); !m.IsRegular() || (m&os.FileMode(0111)) == 0 {
+ continue
+ }
+ if strings.HasPrefix(fileInfo.Name(), name+"-") {
+ result = append(result, fileInfo.Name())
+ }
+ }
+ }
+ return
+}
diff --git a/cmdline/cmdline_test.go b/cmdline/cmdline_test.go
index 0f2af2e..d2c56ae 100644
--- a/cmdline/cmdline_test.go
+++ b/cmdline/cmdline_test.go
@@ -9,6 +9,10 @@
"errors"
"flag"
"fmt"
+ "io/ioutil"
+ "os"
+ "os/exec"
+ "path/filepath"
"regexp"
"strings"
"testing"
@@ -2432,3 +2436,262 @@
t.Errorf("rstring got %v want %v", got, want)
}
}
+
+func TestBinarySubcommand(t *testing.T) {
+ // Create a temporary directory for the binary subcommands.
+ tmpDir, err := ioutil.TempDir("", "cmdline-test")
+ if err != nil {
+ t.Fatalf("%v", err)
+ }
+ defer os.RemoveAll(tmpDir)
+
+ // Add the temporary directory to PATH.
+ oldPath := os.Getenv("PATH")
+ defer os.Setenv("PATH", oldPath)
+ tokens := strings.Split(oldPath, string(os.PathListSeparator))
+ tokens = append([]string{tmpDir}, tokens...)
+ os.Setenv("PATH", strings.Join(tokens, string(os.PathListSeparator)))
+
+ // Build the binary subcommands.
+ for _, subCmd := range []string{"flat", "foreign", "nested"} {
+ cmd := exec.Command("go", "build", "-o", filepath.Join(tmpDir, "unlikely-"+subCmd), filepath.Join(".", "testdata", subCmd+".go"))
+ if out, err := cmd.CombinedOutput(); err != nil {
+ t.Fatalf("%v, %v", string(out), err)
+ }
+ }
+
+ // Create a command that uses these.
+ cmd := &Command{
+ Name: "unlikely",
+ Short: "Short description of command unlikely",
+ Long: "Long description of command unlikely.",
+ LookPath: true,
+ Children: []*Command{
+ &Command{
+ Runner: RunnerFunc(runHello),
+ Name: "foo",
+ Short: "Short description of command foo",
+ Long: "Long description of command foo.",
+ },
+ },
+ }
+
+ var tests = []testCase{
+ {
+ Args: []string{"-help"},
+ Vars: map[string]string{
+ "PATH": strings.Join(tokens, string(os.PathListSeparator)),
+ },
+ Stdout: `Long description of command unlikely.
+
+Usage:
+ unlikely <command>
+
+The unlikely commands are:
+ foo Short description of command foo
+ help Display help for commands or topics
+ flat Short description of command flat
+ foreign No description available
+ nested Short description of command nested
+Run "unlikely help [command]" for command usage.
+
+The global flags are:
+ -global1=
+ global test flag 1
+ -global2=0
+ global test flag 2
+`,
+ },
+ {
+ Args: []string{"help"},
+ Vars: map[string]string{
+ "PATH": strings.Join(tokens, string(os.PathListSeparator)),
+ },
+ Stdout: `Long description of command unlikely.
+
+Usage:
+ unlikely <command>
+
+The unlikely commands are:
+ foo Short description of command foo
+ help Display help for commands or topics
+ flat Short description of command flat
+ foreign No description available
+ nested Short description of command nested
+Run "unlikely help [command]" for command usage.
+
+The global flags are:
+ -global1=
+ global test flag 1
+ -global2=0
+ global test flag 2
+`,
+ },
+ {
+ Args: []string{"help", "..."},
+ Vars: map[string]string{
+ "PATH": strings.Join(tokens, string(os.PathListSeparator)),
+ },
+ Stdout: `Long description of command unlikely.
+
+Usage:
+ unlikely <command>
+
+The unlikely commands are:
+ foo Short description of command foo
+ help Display help for commands or topics
+ flat Short description of command flat
+ foreign No description available
+ nested Short description of command nested
+Run "unlikely help [command]" for command usage.
+
+The global flags are:
+ -global1=
+ global test flag 1
+ -global2=0
+ global test flag 2
+================================================================================
+Unlikely foo - Short description of command foo
+
+Long description of command foo.
+
+Usage:
+ unlikely foo
+================================================================================
+Unlikely flat - Short description of command flat
+
+Long description of command flat.
+
+Usage:
+ unlikely flat
+================================================================================
+Unlikely foreign - No description available
+================================================================================
+Unlikely nested - Short description of command nested
+
+Long description of command nested.
+
+Usage:
+ unlikely nested <command>
+
+The unlikely nested commands are:
+ child Short description of command child
+================================================================================
+Unlikely nested child - Short description of command child
+
+Long description of command child.
+
+Usage:
+ unlikely nested child
+================================================================================
+Unlikely help - Display help for commands or topics
+
+Help with no args displays the usage of the parent command.
+
+Help with args displays the usage of the specified sub-command or help topic.
+
+"help ..." recursively displays help for all commands and topics.
+
+Usage:
+ unlikely help [flags] [command/topic ...]
+
+[command/topic ...] optionally identifies a specific sub-command or help topic.
+
+The unlikely help flags are:
+ -style=compact
+ The formatting style for help output:
+ compact - Good for compact cmdline output.
+ full - Good for cmdline output, shows all global flags.
+ godoc - Good for godoc processing.
+ Override the default by setting the CMDLINE_STYLE environment variable.
+ -width=80
+ Format output to this target width in runes, or unlimited if width < 0.
+ Defaults to the terminal width if available. Override the default by setting
+ the CMDLINE_WIDTH environment variable.
+`,
+ },
+ {
+ Args: []string{"help", "-style=godoc", "..."},
+ Vars: map[string]string{
+ "PATH": strings.Join(tokens, string(os.PathListSeparator)),
+ },
+ Stdout: `Long description of command unlikely.
+
+Usage:
+ unlikely <command>
+
+The unlikely commands are:
+ foo Short description of command foo
+ help Display help for commands or topics
+ flat Short description of command flat
+ foreign No description available
+ nested Short description of command nested
+
+The global flags are:
+ -global1=
+ global test flag 1
+ -global2=0
+ global test flag 2
+
+Unlikely foo - Short description of command foo
+
+Long description of command foo.
+
+Usage:
+ unlikely foo
+
+Unlikely flat - Short description of command flat
+
+Long description of command flat.
+
+Usage:
+ unlikely flat
+
+Unlikely foreign - No description available
+
+Unlikely nested - Short description of command nested
+
+Long description of command nested.
+
+Usage:
+ unlikely nested <command>
+
+The unlikely nested commands are:
+ child Short description of command child
+
+Unlikely nested child - Short description of command child
+
+Long description of command child.
+
+Usage:
+ unlikely nested child
+
+Unlikely help - Display help for commands or topics
+
+Help with no args displays the usage of the parent command.
+
+Help with args displays the usage of the specified sub-command or help topic.
+
+"help ..." recursively displays help for all commands and topics.
+
+Usage:
+ unlikely help [flags] [command/topic ...]
+
+[command/topic ...] optionally identifies a specific sub-command or help topic.
+
+The unlikely help flags are:
+ -style=compact
+ The formatting style for help output:
+ compact - Good for compact cmdline output.
+ full - Good for cmdline output, shows all global flags.
+ godoc - Good for godoc processing.
+ Override the default by setting the CMDLINE_STYLE environment variable.
+ -width=<terminal width>
+ Format output to this target width in runes, or unlimited if width < 0.
+ Defaults to the terminal width if available. Override the default by setting
+ the CMDLINE_WIDTH environment variable.
+`,
+ },
+ }
+ runTestCases(t, cmd, tests)
+}
diff --git a/cmdline/env.go b/cmdline/env.go
index 77ff558..624fbff 100644
--- a/cmdline/env.go
+++ b/cmdline/env.go
@@ -9,6 +9,7 @@
"io"
"os"
"strconv"
+ "strings"
"v.io/x/lib/envvar"
"v.io/x/lib/textutil"
@@ -45,6 +46,21 @@
return usageErrorf(e.Stderr, e.Usage, format, args...)
}
+// Clone creates a deep copy of Env.
+func (e *Env) clone() *Env {
+ env := &Env{
+ Stdin: e.Stdin,
+ Stdout: e.Stdout,
+ Stderr: e.Stderr,
+ Vars: map[string]string{},
+ Usage: e.Usage,
+ }
+ for key, value := range e.Vars {
+ env.Vars[key] = value
+ }
+ return env
+}
+
func usageErrorf(w io.Writer, usage func(io.Writer), format string, args ...interface{}) error {
fmt.Fprint(w, "ERROR: ")
fmt.Fprintf(w, format, args...)
@@ -76,6 +92,18 @@
return style
}
+func (e *Env) prefix() string {
+ return e.Vars["CMDLINE_PREFIX"]
+}
+
+func (e *Env) pathDirs() []string {
+ return strings.Split(e.Vars["PATH"], string(os.PathListSeparator))
+}
+
+func (e *Env) firstCall() bool {
+ return e.Vars["CMDLINE_FIRST_CALL"] == ""
+}
+
// style describes the formatting style for usage descriptions.
type style int
@@ -83,6 +111,7 @@
styleCompact style = iota // Default style, good for compact cmdline output.
styleFull // Similar to compact but shows global flags.
styleGoDoc // Style good for godoc processing.
+ styleShort // Style good for displaying help of binary subcommands.
)
func (s *style) String() string {
@@ -93,6 +122,8 @@
return "full"
case styleGoDoc:
return "godoc"
+ case styleShort:
+ return "short"
default:
panic(fmt.Errorf("unhandled style %d", *s))
}
@@ -107,6 +138,8 @@
*s = styleFull
case "godoc":
*s = styleGoDoc
+ case "short":
+ *s = styleShort
default:
return fmt.Errorf("unknown style %q", value)
}
diff --git a/cmdline/help.go b/cmdline/help.go
index 178c02b..4eafa16 100644
--- a/cmdline/help.go
+++ b/cmdline/help.go
@@ -18,6 +18,8 @@
"v.io/x/lib/textutil"
)
+const missingDescription = "No description available"
+
// helpRunner is a Runner that implements the "help" functionality. Help is
// requested for the last command in rootPath, which must not be empty.
type helpRunner struct {
@@ -26,12 +28,17 @@
}
func makeHelpRunner(path []*Command, env *Env) helpRunner {
- return helpRunner{path, &helpConfig{env.style(), env.width()}}
+ return helpRunner{path, &helpConfig{
+ env: env,
+ style: env.style(),
+ width: env.width(),
+ }}
}
// helpConfig holds configuration data for help. The style and width may be
// overriden by flags if the command returned by newCommand is parsed.
type helpConfig struct {
+ env *Env
style style
width int
}
@@ -39,15 +46,14 @@
// Run implements the Runner interface method.
func (h helpRunner) Run(env *Env, args []string) error {
w := textutil.NewUTF8LineWriter(env.Stdout, h.width)
- err := runHelp(w, env.Stderr, args, h.rootPath, h.helpConfig)
- w.Flush()
- return err
+ defer w.Flush()
+ return runHelp(w, env.Stderr, args, h.rootPath, h.helpConfig)
}
// usageFunc is used as the implementation of the Env.Usage function.
func (h helpRunner) usageFunc(writer io.Writer) {
w := textutil.NewUTF8LineWriter(writer, h.width)
- usage(w, h.rootPath, h.helpConfig, true)
+ usage(w, h.rootPath, h.helpConfig, h.env.firstCall())
w.Flush()
}
@@ -93,11 +99,11 @@
// runHelp implements the run-time behavior of the help command.
func runHelp(w *textutil.LineWriter, stderr io.Writer, args []string, path []*Command, config *helpConfig) error {
if len(args) == 0 {
- usage(w, path, config, true)
+ usage(w, path, config, config.env.firstCall())
return nil
}
if args[0] == "..." {
- usageAll(w, path, config, true)
+ usageAll(w, path, config, config.env.firstCall())
return nil
}
// Look for matching children.
@@ -111,6 +117,19 @@
help := helpRunner{path, config}.newCommand()
return runHelp(w, stderr, subArgs, append(path, help), config)
}
+ if cmd.LookPath {
+ // Look for a matching executable in PATH.
+ subCmd := cmd.Name + "-" + subName
+ if lookPath(subCmd, config.env.pathDirs()) {
+ runner := binaryRunner{subCmd, pathName(config.env.prefix(), path)}
+ env := config.env.clone()
+ env.Vars["CMDLINE_STYLE"] = config.style.String()
+ if len(subArgs) == 0 {
+ return runner.Run(env, []string{"-help"})
+ }
+ return runner.Run(env, append([]string{helpName}, subArgs...))
+ }
+ }
// Look for matching topic.
for _, topic := range cmd.Topics {
if topic.Name == subName {
@@ -119,7 +138,7 @@
}
}
fn := helpRunner{path, config}.usageFunc
- return usageErrorf(stderr, fn, "%s: unknown command or topic %q", pathName(path), subName)
+ return usageErrorf(stderr, fn, "%s: unknown command or topic %q", pathName(config.env.prefix(), path), subName)
}
func godocHeader(path, short string) string {
@@ -190,18 +209,49 @@
// usageAll prints usage recursively via DFS from the path onward.
func usageAll(w *textutil.LineWriter, path []*Command, config *helpConfig, firstCall bool) {
- cmd, cmdPath := path[len(path)-1], pathName(path)
- if !firstCall {
- lineBreak(w, config.style)
- w.ForceVerbatim(true)
- fmt.Fprintln(w, godocHeader(cmdPath, cmd.Short))
- w.ForceVerbatim(false)
- fmt.Fprintln(w)
- }
+ cmd, cmdPath := path[len(path)-1], pathName(config.env.prefix(), path)
usage(w, path, config, firstCall)
for _, child := range cmd.Children {
usageAll(w, append(path, child), config, false)
}
+ if cmd.LookPath {
+ subCmds := lookPathAll(cmd.Name, config.env.pathDirs())
+ for _, subCmd := range subCmds {
+ runner := binaryRunner{subCmd, cmdPath}
+ var buffer bytes.Buffer
+ env := config.env.clone()
+ env.Stdout = &buffer
+ env.Stderr = &buffer
+ env.Vars["CMDLINE_FIRST_CALL"] = "1"
+ env.Vars["CMDLINE_STYLE"] = config.style.String()
+ if err := runner.Run(env, []string{helpName, "..."}); err == nil {
+ // The binary subcommand supports "help".
+ if config.style == styleGoDoc {
+ // The textutil package will discard any leading empty lines
+ // produced by the child process output, so we need to
+ // output it here.
+ fmt.Fprintln(w)
+ }
+ fmt.Fprint(w, buffer.String())
+ continue
+ }
+ buffer.Reset()
+ if err := runner.Run(env, []string{"-help"}); err == nil {
+ // The binary subcommand supports "-help".
+ if config.style == styleGoDoc {
+ // The textutil package will discard any leading empty lines
+ // produced by the child process output, so we need to
+ // output it here.
+ fmt.Fprintln(w)
+ }
+ fmt.Fprint(w, buffer.String())
+ continue
+ }
+ // The binary subcommand does not support "help" or "-help".
+ lineBreak(w, config.style)
+ fmt.Fprintln(w, godocHeader(cmdPath+" "+strings.TrimPrefix(subCmd, cmd.Name+"-"), missingDescription))
+ }
+ }
if firstCall && needsHelpChild(cmd) {
help := helpRunner{path, config}.newCommand()
usageAll(w, append(path, help), config, false)
@@ -220,12 +270,27 @@
// is set to false when printing usage for multiple commands, and is used to
// avoid printing redundant information (e.g. help command, global flags).
func usage(w *textutil.LineWriter, path []*Command, config *helpConfig, firstCall bool) {
- cmd, cmdPath := path[len(path)-1], pathName(path)
+ cmd, cmdPath := path[len(path)-1], pathName(config.env.prefix(), path)
+ if config.style == styleShort {
+ fmt.Fprintln(w, cmd.Short)
+ return
+ }
children := cmd.Children
if firstCall && needsHelpChild(cmd) {
help := helpRunner{path, config}.newCommand()
children = append(children, help)
}
+ var subCmds []string
+ if cmd.LookPath {
+ subCmds = lookPathAll(cmd.Name, config.env.pathDirs())
+ }
+ if !firstCall {
+ lineBreak(w, config.style)
+ w.ForceVerbatim(true)
+ fmt.Fprintln(w, godocHeader(cmdPath, cmd.Short))
+ w.ForceVerbatim(false)
+ fmt.Fprintln(w)
+ }
fmt.Fprintln(w, cmd.Long)
fmt.Fprintln(w)
// Usage line.
@@ -241,26 +306,60 @@
fmt.Fprintln(w, cmdPathF)
}
}
- if len(children) > 0 {
+ hasSubcommands := len(subCmds) > 0 || len(children) > 0
+ if hasSubcommands {
fmt.Fprintln(w, cmdPathF, "<command>")
}
- // Commands.
+ // Compute the name width.
const minNameWidth = 11
- if len(children) > 0 {
+ nameWidth := minNameWidth
+ for _, child := range children {
+ if len(child.Name) > nameWidth {
+ nameWidth = len(child.Name)
+ }
+ }
+ for _, subCmd := range subCmds {
+ length := len(strings.TrimPrefix(subCmd, cmd.Name+"-"))
+ if length > nameWidth {
+ nameWidth = length
+ }
+ }
+ // Command header.
+ if hasSubcommands {
fmt.Fprintln(w)
fmt.Fprintln(w, "The", cmdPath, "commands are:")
- nameWidth := minNameWidth
- for _, child := range children {
- if len(child.Name) > nameWidth {
- nameWidth = len(child.Name)
- }
- }
// Print as a table with aligned columns Name and Short.
w.SetIndents(spaces(3), spaces(3+nameWidth+1))
+ }
+ // Built-in subcommands.
+ if len(children) > 0 {
for _, child := range children {
fmt.Fprintf(w, "%-[1]*[2]s %[3]s", nameWidth, child.Name, child.Short)
w.Flush()
}
+ }
+ // Binary subcommands.
+ if len(subCmds) > 0 {
+ for _, subCmd := range subCmds {
+ runner := binaryRunner{subCmd, cmdPath}
+ var buffer bytes.Buffer
+ env := config.env.clone()
+ env.Stdout = &buffer
+ env.Stderr = &buffer
+ env.Vars["CMDLINE_STYLE"] = "short"
+ if err := runner.Run(env, []string{"-help"}); err == nil {
+ // The binary subcommand supports "-help".
+ fmt.Fprintf(w, "%-[1]*[2]s %[3]s", nameWidth, strings.TrimPrefix(subCmd, cmd.Name+"-"), buffer.String())
+ w.Flush()
+ continue
+ }
+ // The binary subcommand does not support "-help".
+ fmt.Fprintf(w, "%-[1]*[2]s %[3]s", nameWidth, strings.TrimPrefix(subCmd, cmd.Name+"-"), missingDescription)
+ w.Flush()
+ }
+ }
+ // Command footer.
+ if hasSubcommands {
w.SetIndents()
if firstCall && config.style != styleGoDoc {
fmt.Fprintf(w, "Run \"%s help [command]\" for command usage.\n", cmdPath)
@@ -296,7 +395,7 @@
}
func flagsUsage(w *textutil.LineWriter, path []*Command, config *helpConfig, firstCall bool) {
- cmd, cmdPath := path[len(path)-1], pathName(path)
+ cmd, cmdPath := path[len(path)-1], pathName(config.env.prefix(), path)
// Flags.
if countFlags(&cmd.Flags, nil, true) > 0 {
fmt.Fprintln(w)
@@ -333,7 +432,7 @@
fullhelp := fmt.Sprintf(`Run "%s help -style=full" to show all global flags.`, cmdPath)
if len(cmd.Children) == 0 {
if len(path) > 1 {
- parentPath := pathName(path[:len(path)-1])
+ parentPath := pathName(config.env.prefix(), path[:len(path)-1])
fullhelp = fmt.Sprintf(`Run "%s help -style=full %s" to show all global flags.`, parentPath, cmd.Name)
} else {
fullhelp = fmt.Sprintf(`Run "CMDLINE_STYLE=full %s -help" to show all global flags.`, cmdPath)
diff --git a/cmdline/testdata/flat.go b/cmdline/testdata/flat.go
new file mode 100644
index 0000000..aa06085
--- /dev/null
+++ b/cmdline/testdata/flat.go
@@ -0,0 +1,25 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+import (
+ ".."
+)
+
+// cmdFlat represents the flat command.
+var cmdFlat = &cmdline.Command{
+ Runner: cmdline.RunnerFunc(runFlat),
+ Name: "flat",
+ Short: "Short description of command flat",
+ Long: "Long description of command flat.",
+}
+
+func runFlat(env *cmdline.Env, _ []string) error {
+ return nil
+}
+
+func main() {
+ cmdline.Main(cmdFlat)
+}
diff --git a/cmdline/testdata/foreign.go b/cmdline/testdata/foreign.go
new file mode 100644
index 0000000..45d71fb
--- /dev/null
+++ b/cmdline/testdata/foreign.go
@@ -0,0 +1,13 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+import (
+ "os"
+)
+
+func main() {
+ os.Exit(1)
+}
diff --git a/cmdline/testdata/nested.go b/cmdline/testdata/nested.go
new file mode 100644
index 0000000..cf9826c
--- /dev/null
+++ b/cmdline/testdata/nested.go
@@ -0,0 +1,34 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+import (
+ ".."
+)
+
+// cmdNested represents the nested command.
+var cmdNested = &cmdline.Command{
+ Name: "nested",
+ Short: "Short description of command nested",
+ Long: "Long description of command nested.",
+ LookPath: true,
+ Children: []*cmdline.Command{cmdChild},
+}
+
+// cmdChild represents the child command.
+var cmdChild = &cmdline.Command{
+ Runner: cmdline.RunnerFunc(runChild),
+ Name: "child",
+ Short: "Short description of command child",
+ Long: "Long description of command child.",
+}
+
+func runChild(env *cmdline.Env, _ []string) error {
+ return nil
+}
+
+func main() {
+ cmdline.Main(cmdNested)
+}
diff --git a/metadata/metadata.go b/metadata/metadata.go
index 2510b42..167af58 100644
--- a/metadata/metadata.go
+++ b/metadata/metadata.go
@@ -38,8 +38,8 @@
// The built-in metadata comes pre-populated with the Go architecture, operating
// system and version.
//
-// This package registers a flag -v23.metadata via an init function. Setting
-// -v23.metadata on the command-line causes the program to dump metadata in the
+// This package registers a flag -metadata via an init function. Setting
+// -metadata on the command-line causes the program to dump metadata in the
// XML format and exit.
package metadata
@@ -308,7 +308,7 @@
BuiltIn.Insert("go.OS", runtime.GOOS)
BuiltIn.Insert("go.Version", runtime.Version())
- flag.Var(metadataFlag{}, "v23.metadata", "Displays metadata for the program and exits.")
+ flag.Var(metadataFlag{}, "metadata", "Displays metadata for the program and exits.")
}
// metadataFlag implements a flag that dumps the default metadata and exits the
@@ -316,7 +316,7 @@
type metadataFlag struct{}
func (metadataFlag) IsBoolFlag() bool { return true }
-func (metadataFlag) String() string { return "<just specify -v23.metadata to activate>" }
+func (metadataFlag) String() string { return "<just specify -metadata to activate>" }
func (metadataFlag) Set(string) error {
fmt.Println(BuiltIn.String())
os.Exit(0)
diff --git a/metadata/metadata_test.go b/metadata/metadata_test.go
index 5dbc47a..f9e2763 100644
--- a/metadata/metadata_test.go
+++ b/metadata/metadata_test.go
@@ -234,12 +234,12 @@
}
// TestInitAndFlag builds a test binary with some metadata, and invokes the
-// -v23.metadata flag to make sure it dumps the expected metadata.
+// -metadata flag to make sure it dumps the expected metadata.
func TestInitAndFlag(t *testing.T) {
// Run the test binary.
const id, value = "zzzTestID", "abcdefg"
x := FromMap(map[string]string{id: value})
- cmdRun := exec.Command("go", "run", "-ldflags="+LDFlag(x), "./testdata/testbin.go", "-v23.metadata")
+ cmdRun := exec.Command("go", "run", "-ldflags="+LDFlag(x), "./testdata/testbin.go", "-metadata")
outXML, err := cmdRun.CombinedOutput()
if err != nil {
t.Errorf("%v failed: %v\n%v", cmdRun.Args, err, outXML)