veyron/lib/cmdline: Add ErrExitCode to support exit codes.

Previously cmdline users had to call os.Exit directly within
their Run function in order to have their program exit with a
specific error code.  After this change, they can return an error
of type ErrExitCode to specify an explicit exit code.

ErrUsage has been updated to simply correspond to ErrExitCode(1).

Command.Errorf() is renamed to Command.UsageErrorf(), to make it
more obvious that it will print out the usage.

Change-Id: Ie8948edf8cd1d3cac87efefcf5b43459691998a0
diff --git a/lib/cmdline/cmdline.go b/lib/cmdline/cmdline.go
index d6705e6..dcc5aee 100644
--- a/lib/cmdline/cmdline.go
+++ b/lib/cmdline/cmdline.go
@@ -15,7 +15,6 @@
 package cmdline
 
 import (
-	"errors"
 	"flag"
 	"fmt"
 	"io"
@@ -23,9 +22,17 @@
 	"strings"
 )
 
+// ErrExitCode may be returned by the Run function of a Command to cause the
+// program to exit with a specific error code.
+type ErrExitCode int
+
+func (x ErrExitCode) Error() string {
+	return fmt.Sprintf("exit code %d", x)
+}
+
 // ErrUsage is returned to indicate an error in command usage; e.g. unknown
-// flags, subcommands or args.
-var ErrUsage = errors.New("usage error")
+// flags, subcommands or args.  It corresponds to exit code 1.
+const ErrUsage = ErrExitCode(1)
 
 // Command represents a single command in a command-line program.  A program
 // with subcommands is represented as a root Command with children representing
@@ -46,7 +53,8 @@
 
 	// Run is a function that runs cmd with args.  If both Children and Run are
 	// specified, Run will only be called if none of the children match.  It is an
-	// error if neither is specified.
+	// error if neither is specified.  The special ErrExitCode error may be
+	// returned to indicate the command should exit with a specific exit code.
 	Run func(cmd *Command, args []string) error
 
 	// parent holds the parent of this Command, or nil if this is the root.
@@ -107,8 +115,10 @@
 	return cmd.stderr
 }
 
-// Errorf should be called to signal an invalid usage of the command.
-func (cmd *Command) Errorf(format string, v ...interface{}) error {
+// UsageErrorf prints the error message represented by the printf-style format
+// string and args, followed by the usage description of cmd.  Returns ErrUsage
+// to make it easy to use from within the cmd.Run function.
+func (cmd *Command) UsageErrorf(format string, v ...interface{}) error {
 	fmt.Fprint(cmd.stderr, "ERROR: ")
 	fmt.Fprintf(cmd.stderr, format, v...)
 	fmt.Fprint(cmd.stderr, "\n\n")
@@ -236,7 +246,7 @@
 			return runHelp(child, subArgs, style)
 		}
 	}
-	return cmd.Errorf("%s: unknown command %q", cmd.Name, subName)
+	return cmd.UsageErrorf("%s: unknown command %q", cmd.Name, subName)
 }
 
 // recursiveHelp prints help recursively via DFS from this cmd onward.
@@ -328,20 +338,20 @@
 	if cmd.Run != nil {
 		if cmd.ArgsName == "" && len(args) > 0 {
 			if len(cmd.Children) > 0 {
-				return cmd.Errorf("%s: unknown command %q", cmd.Name, args[0])
+				return cmd.UsageErrorf("%s: unknown command %q", cmd.Name, args[0])
 			} else {
-				return cmd.Errorf("%s doesn't take any arguments", cmd.Name)
+				return cmd.UsageErrorf("%s doesn't take any arguments", cmd.Name)
 			}
 		}
 		return cmd.Run(cmd, args)
 	}
 	switch {
 	case len(cmd.Children) == 0:
-		return cmd.Errorf("%s: neither Children nor Run is specified", cmd.Name)
+		return cmd.UsageErrorf("%s: neither Children nor Run is specified", cmd.Name)
 	case len(args) > 0:
-		return cmd.Errorf("%s: unknown command %q", cmd.Name, args[0])
+		return cmd.UsageErrorf("%s: unknown command %q", cmd.Name, args[0])
 	default:
-		return cmd.Errorf("%s: no command specified", cmd.Name)
+		return cmd.UsageErrorf("%s: no command specified", cmd.Name)
 	}
 }
 
@@ -352,8 +362,8 @@
 func (cmd *Command) Main() {
 	cmd.Init(nil, os.Stdout, os.Stderr)
 	if err := cmd.Execute(os.Args[1:]); err != nil {
-		if err == ErrUsage {
-			os.Exit(1)
+		if code, ok := err.(ErrExitCode); ok {
+			os.Exit(int(code))
 		} else {
 			fmt.Fprintf(os.Stderr, "ERROR: %v\n", err)
 			os.Exit(2)
diff --git a/lib/cmdline/cmdline_test.go b/lib/cmdline/cmdline_test.go
index 6fdf3c5..e9c471b 100644
--- a/lib/cmdline/cmdline_test.go
+++ b/lib/cmdline/cmdline_test.go
@@ -25,7 +25,7 @@
 		if args[0] == "error" {
 			return errEcho
 		} else if args[0] == "bad_arg" {
-			return cmd.Errorf("Invalid argument %v", args[0])
+			return cmd.UsageErrorf("Invalid argument %v", args[0])
 		}
 	}
 	if flagExtra {
diff --git a/tools/application/impl/impl.go b/tools/application/impl/impl.go
index fccb840..8af0a9f 100644
--- a/tools/application/impl/impl.go
+++ b/tools/application/impl/impl.go
@@ -64,7 +64,7 @@
 
 func runMatch(cmd *cmdline.Command, args []string) error {
 	if expected, got := 2, len(args); expected != got {
-		return cmd.Errorf("match: incorrect number of arguments, expected %d, got %d", expected, got)
+		return cmd.UsageErrorf("match: incorrect number of arguments, expected %d, got %d", expected, got)
 	}
 	name, profiles := args[0], args[1]
 	app, err := repository.BindApplication(name)
@@ -95,7 +95,7 @@
 
 func runPut(cmd *cmdline.Command, args []string) error {
 	if expected, got := 3, len(args); expected != got {
-		return cmd.Errorf("put: incorrect number of arguments, expected %d, got %d", expected, got)
+		return cmd.UsageErrorf("put: incorrect number of arguments, expected %d, got %d", expected, got)
 	}
 	name, profiles, envelope := args[0], args[1], args[2]
 	app, err := repository.BindApplication(name)
@@ -128,7 +128,7 @@
 
 func runRemove(cmd *cmdline.Command, args []string) error {
 	if expected, got := 2, len(args); expected != got {
-		return cmd.Errorf("remove: incorrect number of arguments, expected %d, got %d", expected, got)
+		return cmd.UsageErrorf("remove: incorrect number of arguments, expected %d, got %d", expected, got)
 	}
 	name, profile := args[0], args[1]
 	app, err := repository.BindApplication(name)
@@ -157,7 +157,7 @@
 
 func runEdit(cmd *cmdline.Command, args []string) error {
 	if expected, got := 2, len(args); expected != got {
-		return cmd.Errorf("edit: incorrect number of arguments, expected %d, got %d", expected, got)
+		return cmd.UsageErrorf("edit: incorrect number of arguments, expected %d, got %d", expected, got)
 	}
 	name, profile := args[0], args[1]
 	app, err := repository.BindApplication(name)
diff --git a/tools/binary/impl/impl.go b/tools/binary/impl/impl.go
index f690e54..b5a1cec 100644
--- a/tools/binary/impl/impl.go
+++ b/tools/binary/impl/impl.go
@@ -18,7 +18,7 @@
 
 func runDelete(cmd *cmdline.Command, args []string) error {
 	if expected, got := 1, len(args); expected != got {
-		return cmd.Errorf("delete: incorrect number of arguments, expected %d, got %d", expected, got)
+		return cmd.UsageErrorf("delete: incorrect number of arguments, expected %d, got %d", expected, got)
 	}
 	von := args[0]
 	if err := binary.Delete(von); err != nil {
@@ -45,7 +45,7 @@
 
 func runDownload(cmd *cmdline.Command, args []string) error {
 	if expected, got := 2, len(args); expected != got {
-		return cmd.Errorf("download: incorrect number of arguments, expected %d, got %d", expected, got)
+		return cmd.UsageErrorf("download: incorrect number of arguments, expected %d, got %d", expected, got)
 	}
 	von, filename := args[0], args[1]
 	if err := binary.DownloadToFile(von, filename); err != nil {
@@ -72,7 +72,7 @@
 
 func runUpload(cmd *cmdline.Command, args []string) error {
 	if expected, got := 2, len(args); expected != got {
-		return cmd.Errorf("upload: incorrect number of arguments, expected %d, got %d", expected, got)
+		return cmd.UsageErrorf("upload: incorrect number of arguments, expected %d, got %d", expected, got)
 	}
 	von, filename := args[0], args[1]
 	if err := binary.UploadFromFile(von, filename); err != nil {
diff --git a/tools/mounttable/impl/impl.go b/tools/mounttable/impl/impl.go
index 9d9a96d..5d67326 100644
--- a/tools/mounttable/impl/impl.go
+++ b/tools/mounttable/impl/impl.go
@@ -38,7 +38,7 @@
 
 func runGlob(cmd *cmdline.Command, args []string) error {
 	if expected, got := 2, len(args); expected != got {
-		return cmd.Errorf("glob: incorrect number of arguments, expected %d, got %d", expected, got)
+		return cmd.UsageErrorf("glob: incorrect number of arguments, expected %d, got %d", expected, got)
 	}
 	ctx, cancel := rt.R().NewContext().WithTimeout(time.Minute)
 	defer cancel()
@@ -87,7 +87,7 @@
 
 func runMount(cmd *cmdline.Command, args []string) error {
 	if expected, got := 3, len(args); expected != got {
-		return cmd.Errorf("mount: incorrect number of arguments, expected %d, got %d", expected, got)
+		return cmd.UsageErrorf("mount: incorrect number of arguments, expected %d, got %d", expected, got)
 	}
 	ctx, cancel := rt.R().NewContext().WithTimeout(time.Minute)
 	defer cancel()
@@ -122,7 +122,7 @@
 
 func runUnmount(cmd *cmdline.Command, args []string) error {
 	if expected, got := 2, len(args); expected != got {
-		return cmd.Errorf("unmount: incorrect number of arguments, expected %d, got %d", expected, got)
+		return cmd.UsageErrorf("unmount: incorrect number of arguments, expected %d, got %d", expected, got)
 	}
 	ctx, cancel := rt.R().NewContext().WithTimeout(time.Minute)
 	defer cancel()
@@ -152,7 +152,7 @@
 
 func runResolveStep(cmd *cmdline.Command, args []string) error {
 	if expected, got := 1, len(args); expected != got {
-		return cmd.Errorf("mount: incorrect number of arguments, expected %d, got %d", expected, got)
+		return cmd.UsageErrorf("mount: incorrect number of arguments, expected %d, got %d", expected, got)
 	}
 	ctx, cancel := rt.R().NewContext().WithTimeout(time.Minute)
 	defer cancel()
diff --git a/tools/namespace/impl/impl.go b/tools/namespace/impl/impl.go
index 2c87e49..d8fe9a9 100644
--- a/tools/namespace/impl/impl.go
+++ b/tools/namespace/impl/impl.go
@@ -24,7 +24,7 @@
 
 func runGlob(cmd *cmdline.Command, args []string) error {
 	if expected, got := 1, len(args); expected != got {
-		return cmd.Errorf("glob: incorrect number of arguments, expected %d, got %d", expected, got)
+		return cmd.UsageErrorf("glob: incorrect number of arguments, expected %d, got %d", expected, got)
 	}
 	pattern := args[0]
 	ns := rt.R().Namespace()
@@ -61,7 +61,7 @@
 
 func runMount(cmd *cmdline.Command, args []string) error {
 	if expected, got := 3, len(args); expected != got {
-		return cmd.Errorf("mount: incorrect number of arguments, expected %d, got %d", expected, got)
+		return cmd.UsageErrorf("mount: incorrect number of arguments, expected %d, got %d", expected, got)
 	}
 	name := args[0]
 	server := args[1]
@@ -96,7 +96,7 @@
 
 func runUnmount(cmd *cmdline.Command, args []string) error {
 	if expected, got := 2, len(args); expected != got {
-		return cmd.Errorf("unmount: incorrect number of arguments, expected %d, got %d", expected, got)
+		return cmd.UsageErrorf("unmount: incorrect number of arguments, expected %d, got %d", expected, got)
 	}
 	name := args[0]
 	server := args[1]
@@ -122,7 +122,7 @@
 
 func runResolve(cmd *cmdline.Command, args []string) error {
 	if expected, got := 1, len(args); expected != got {
-		return cmd.Errorf("resolve: incorrect number of arguments, expected %d, got %d", expected, got)
+		return cmd.UsageErrorf("resolve: incorrect number of arguments, expected %d, got %d", expected, got)
 	}
 	name := args[0]
 	ns := rt.R().Namespace()
@@ -150,7 +150,7 @@
 
 func runResolveToMT(cmd *cmdline.Command, args []string) error {
 	if expected, got := 1, len(args); expected != got {
-		return cmd.Errorf("resolvetomt: incorrect number of arguments, expected %d, got %d", expected, got)
+		return cmd.UsageErrorf("resolvetomt: incorrect number of arguments, expected %d, got %d", expected, got)
 	}
 	name := args[0]
 	ns := rt.R().Namespace()
@@ -178,7 +178,7 @@
 
 func runUnresolve(cmd *cmdline.Command, args []string) error {
 	if expected, got := 1, len(args); expected != got {
-		return cmd.Errorf("unresolve: incorrect number of arguments, expected %d, got %d", expected, got)
+		return cmd.UsageErrorf("unresolve: incorrect number of arguments, expected %d, got %d", expected, got)
 	}
 	name := args[0]
 	ns := rt.R().Namespace()
diff --git a/tools/profile/impl/impl.go b/tools/profile/impl/impl.go
index 1a69555..4413896 100644
--- a/tools/profile/impl/impl.go
+++ b/tools/profile/impl/impl.go
@@ -23,7 +23,7 @@
 
 func runLabel(cmd *cmdline.Command, args []string) error {
 	if expected, got := 1, len(args); expected != got {
-		return cmd.Errorf("label: incorrect number of arguments, expected %d, got %d", expected, got)
+		return cmd.UsageErrorf("label: incorrect number of arguments, expected %d, got %d", expected, got)
 	}
 	name := args[0]
 	p, err := repository.BindProfile(name)
@@ -51,7 +51,7 @@
 
 func runDescription(cmd *cmdline.Command, args []string) error {
 	if expected, got := 1, len(args); expected != got {
-		return cmd.Errorf("description: incorrect number of arguments, expected %d, got %d", expected, got)
+		return cmd.UsageErrorf("description: incorrect number of arguments, expected %d, got %d", expected, got)
 	}
 	name := args[0]
 	p, err := repository.BindProfile(name)
@@ -79,7 +79,7 @@
 
 func runSpecification(cmd *cmdline.Command, args []string) error {
 	if expected, got := 1, len(args); expected != got {
-		return cmd.Errorf("spec: incorrect number of arguments, expected %d, got %d", expected, got)
+		return cmd.UsageErrorf("spec: incorrect number of arguments, expected %d, got %d", expected, got)
 	}
 	name := args[0]
 	p, err := repository.BindProfile(name)
@@ -107,7 +107,7 @@
 
 func runPut(cmd *cmdline.Command, args []string) error {
 	if expected, got := 1, len(args); expected != got {
-		return cmd.Errorf("put: incorrect number of arguments, expected %d, got %d", expected, got)
+		return cmd.UsageErrorf("put: incorrect number of arguments, expected %d, got %d", expected, got)
 	}
 	name := args[0]
 	p, err := repository.BindProfile(name)
@@ -144,7 +144,7 @@
 
 func runRemove(cmd *cmdline.Command, args []string) error {
 	if expected, got := 1, len(args); expected != got {
-		return cmd.Errorf("remove: incorrect number of arguments, expected %d, got %d", expected, got)
+		return cmd.UsageErrorf("remove: incorrect number of arguments, expected %d, got %d", expected, got)
 	}
 	name := args[0]
 	p, err := repository.BindProfile(name)
diff --git a/tools/vrpc/impl/impl.go b/tools/vrpc/impl/impl.go
index 3851bac..05483e9 100644
--- a/tools/vrpc/impl/impl.go
+++ b/tools/vrpc/impl/impl.go
@@ -50,7 +50,7 @@
 
 func runDescribe(cmd *cmdline.Command, args []string) error {
 	if len(args) != 1 {
-		return cmd.Errorf("describe: incorrect number of arguments, expected 1, got %d", len(args))
+		return cmd.UsageErrorf("describe: incorrect number of arguments, expected 1, got %d", len(args))
 	}
 
 	runtime := rt.R()
@@ -89,7 +89,7 @@
 
 func runInvoke(cmd *cmdline.Command, args []string) error {
 	if len(args) < 2 {
-		return cmd.Errorf("invoke: incorrect number of arguments, expected at least 2, got %d", len(args))
+		return cmd.UsageErrorf("invoke: incorrect number of arguments, expected at least 2, got %d", len(args))
 	}
 	server, method, args := args[0], args[1], args[2:]
 
@@ -117,7 +117,7 @@
 	}
 
 	if len(args) != len(methodSignature.InArgs) {
-		return cmd.Errorf("invoke: incorrect number of arguments, expected %d, got %d", len(methodSignature.InArgs), len(args))
+		return cmd.UsageErrorf("invoke: incorrect number of arguments, expected %d, got %d", len(methodSignature.InArgs), len(args))
 	}
 
 	// Register all user-defined types you would like to use.
diff --git a/tools/vrpc/impl/impl_test.go b/tools/vrpc/impl/impl_test.go
index 0dd2811..2d44a19 100644
--- a/tools/vrpc/impl/impl_test.go
+++ b/tools/vrpc/impl/impl_test.go
@@ -275,8 +275,8 @@
 	}
 
 	testErrors := [][]string{
-		[]string{"EchoBool", "usage error"},
-		[]string{"DoesNotExit", "invoke: method DoesNotExit not found"},
+		[]string{"EchoBool", "exit code 1"},
+		[]string{"DoesNotExist", "invoke: method DoesNotExist not found"},
 	}
 	for _, test := range testErrors {
 		testError(t, cmd, append([]string{"invoke", name, test[0]}, test[2:]...), test[1])