cmd/cue: use stderr and non-zero exit for errors
Exit code is determined from returning an error or if
anything was printed to stderr.
Closes #101
Closes #100
Change-Id: I980798586d8a23d17a32c52d62da49376e7165ef
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/3102
Reviewed-by: Marcel van Lohuizen <mpvl@google.com>
diff --git a/cmd/cue/cmd/add.go b/cmd/cue/cmd/add.go
index a4b2b3e..4eba6e2 100644
--- a/cmd/cue/cmd/add.go
+++ b/cmd/cue/cmd/add.go
@@ -195,7 +195,7 @@
func restoreOriginals(cmd *Command, originals []originalFile) {
for _, fo := range originals {
if err := fo.restore(); err != nil {
- fmt.Fprintln(cmd.OutOrStderr(), "Error restoring file: ", err)
+ fmt.Fprintln(cmd.Stderr(), "Error restoring file: ", err)
}
}
}
diff --git a/cmd/cue/cmd/cmd.go b/cmd/cue/cmd/cmd.go
index 9e5fb0c..0f928ab 100644
--- a/cmd/cue/cmd/cmd.go
+++ b/cmd/cue/cmd/cmd.go
@@ -211,12 +211,13 @@
`,
RunE: mkRunE(c, func(cmd *Command, args []string) error {
+ w := cmd.Stderr()
if len(args) == 0 {
- fmt.Println("cmd must be run as one of its subcommands")
+ fmt.Fprintln(w, "cmd must be run as one of its subcommands")
} else {
- fmt.Printf("cmd must be run as one of its subcommands: unknown subcommand %q\n", args[0])
+ fmt.Fprintf(w, "cmd must be run as one of its subcommands: unknown subcommand %q\n", args[0])
}
- fmt.Println("Run 'cue help cmd' for known subcommands.")
+ fmt.Fprintln(w, "Run 'cue help cmd' for known subcommands.")
os.Exit(1) // TODO: get rid of this
return nil
}),
diff --git a/cmd/cue/cmd/cmd_test.go b/cmd/cue/cmd/cmd_test.go
index d09c41f..d69e005 100644
--- a/cmd/cue/cmd/cmd_test.go
+++ b/cmd/cue/cmd/cmd_test.go
@@ -41,7 +41,7 @@
c := newRootCmd()
run := func(cmd *cobra.Command, args []string) error {
stdout = cmd.OutOrStdout()
- stderr = cmd.OutOrStderr()
+ stderr = c.Stderr()
tools, _ := buildTools(c, args)
cmd, err := addCustom(c, c.root, "command", name, tools)
diff --git a/cmd/cue/cmd/common.go b/cmd/cue/cmd/common.go
index 2bf9f37..0678480 100644
--- a/cmd/cue/cmd/common.go
+++ b/cmd/cue/cmd/common.go
@@ -74,7 +74,7 @@
})
b := w.Bytes()
- _, _ = cmd.OutOrStderr().Write(b)
+ _, _ = cmd.Stderr().Write(b)
if fatal {
exit()
}
@@ -89,7 +89,6 @@
}
func loadFromArgs(cmd *Command, args []string) []*build.Instance {
- log.SetOutput(cmd.OutOrStderr())
binst := load.Instances(args, nil)
if len(binst) == 0 {
return nil
diff --git a/cmd/cue/cmd/common_test.go b/cmd/cue/cmd/common_test.go
index 58333d9..8348106 100644
--- a/cmd/cue/cmd/common_test.go
+++ b/cmd/cue/cmd/common_test.go
@@ -51,7 +51,6 @@
func runCommand(t *testing.T, cmd *cobra.Command, name string, args ...string) {
t.Helper()
- log.SetFlags(0)
const dir = "./testdata"
diff --git a/cmd/cue/cmd/get.go b/cmd/cue/cmd/get.go
index 499e62c..e562f78 100644
--- a/cmd/cue/cmd/get.go
+++ b/cmd/cue/cmd/get.go
@@ -36,12 +36,13 @@
per language and are documented in the respective subcommands.
`,
RunE: mkRunE(c, func(cmd *Command, args []string) error {
+ stderr := cmd.Stderr()
if len(args) == 0 {
- fmt.Println("get must be run as one of its subcommands")
+ fmt.Fprintln(stderr, "get must be run as one of its subcommands")
} else {
- fmt.Printf("get must be run as one of its subcommands: unknown subcommand %q\n", args[0])
+ fmt.Fprintf(stderr, "get must be run as one of its subcommands: unknown subcommand %q\n", args[0])
}
- fmt.Println("Run 'cue help get' for known subcommands.")
+ fmt.Fprintln(stderr, "Run 'cue help get' for known subcommands.")
os.Exit(1) // TODO: get rid of this
return nil
}),
diff --git a/cmd/cue/cmd/get_go.go b/cmd/cue/cmd/get_go.go
index 411cf71..bedc4e8 100644
--- a/cmd/cue/cmd/get_go.go
+++ b/cmd/cue/cmd/get_go.go
@@ -337,7 +337,7 @@
e := extractor{
cmd: cmd,
- stderr: cmd.OutOrStderr(),
+ stderr: cmd.Stderr(),
pkgs: pkgs,
orig: map[types.Type]*ast.StructType{},
}
@@ -474,8 +474,9 @@
b, err := format.Source(w.Bytes())
if err != nil {
_ = ioutil.WriteFile(filepath.Join(dir, file), w.Bytes(), 0644)
- fmt.Println(w.String())
- fmt.Println(dir, file)
+ stderr := e.cmd.Stderr()
+ fmt.Fprintln(stderr, w.String())
+ fmt.Fprintln(stderr, dir, file)
return err
}
err = ioutil.WriteFile(filepath.Join(dir, file), b, 0644)
@@ -894,7 +895,7 @@
case *types.Map:
if b, ok := x.Key().Underlying().(*types.Basic); !ok || b.Kind() != types.String {
- log.Panicf("unsupported map key type %T", x.Key())
+ panic(fmt.Sprintf("unsupported map key type %T", x.Key()))
}
fmt.Fprintf(e.w, "{ <_>: ")
e.printType(x.Elem())
diff --git a/cmd/cue/cmd/get_go_test.go b/cmd/cue/cmd/get_go_test.go
index 509be39..56092e5 100644
--- a/cmd/cue/cmd/get_go_test.go
+++ b/cmd/cue/cmd/get_go_test.go
@@ -42,7 +42,7 @@
cmd.SetArgs([]string{"./testdata/code/go/..."})
err = cmd.Execute()
if err != nil {
- log.Fatal(err)
+ t.Fatal(err)
}
// Packages will generate differently in modules versus GOPATH. Search
diff --git a/cmd/cue/cmd/import.go b/cmd/cue/cmd/import.go
index 612f64d..adb58c2 100644
--- a/cmd/cue/cmd/import.go
+++ b/cmd/cue/cmd/import.go
@@ -251,8 +251,6 @@
}
func runImport(cmd *Command, args []string) error {
- log.SetOutput(cmd.OutOrStderr())
-
var group errgroup.Group
pkgFlag := flagPackage.String(cmd)
@@ -391,7 +389,10 @@
case os.IsNotExist(err):
case err == nil:
if !flagForce.Bool(cmd) {
- log.Printf("skipping file %q: already exists", cueFile)
+ // TODO: mimic old behavior: write to stderr, but do not exit
+ // with error code. Consider what is best to do here.
+ stderr := cmd.Command.OutOrStderr()
+ fmt.Fprintf(stderr, "skipping file %q: already exists\n", cueFile)
return nil
}
default:
diff --git a/cmd/cue/cmd/root.go b/cmd/cue/cmd/root.go
index 433ffe4..71c9760 100644
--- a/cmd/cue/cmd/root.go
+++ b/cmd/cue/cmd/root.go
@@ -18,7 +18,6 @@
"context"
"fmt"
"io"
- logger "log"
"os"
"strings"
@@ -39,8 +38,6 @@
// TODO: documentation of concepts
// tasks the key element for cmd, serve, and fix
-var log = logger.New(os.Stderr, "", logger.Lshortfile)
-
type runFunction func(cmd *Command, args []string) error
func mkRunE(c *Command, f runFunction) func(*cobra.Command, []string) error {
@@ -125,8 +122,37 @@
// Subcommands
cmd *cobra.Command
+
+ hasErr bool
}
+type errWriter Command
+
+func (w *errWriter) Write(b []byte) (int, error) {
+ c := (*Command)(w)
+ c.hasErr = true
+ return c.Command.OutOrStderr().Write(b)
+}
+
+// Hint: search for uses of OutOrStderr other than the one here to see
+// which output does not trigger a non-zero exit code. os.Stderr may never
+// be used directly.
+
+// Stderr returns a writer that should be used for error messages.
+func (c *Command) Stderr() io.Writer {
+ return (*errWriter)(c)
+}
+
+// TODO: add something similar for Stdout. The output model of Cobra isn't
+// entirely clear, and such a change seems non-trivial.
+
+// Consider overriding these methods from Cobra using OutOrStdErr.
+// We don't use them currently, but may be safer to block. Having them
+// will encourage their usage, and the naming is inconsistent with other CUE APIs.
+// PrintErrf(format string, args ...interface{})
+// PrintErrln(args ...interface{})
+// PrintErr(args ...interface{})
+
func (c *Command) SetOutput(w io.Writer) {
c.root.SetOutput(w)
}
@@ -136,8 +162,10 @@
stdin = r
}
+// ErrPrintedError indicates error messages have been printed to stderr.
+var ErrPrintedError = errors.New("terminating because of errors")
+
func (c *Command) Run(ctx context.Context) (err error) {
- log.SetFlags(0)
// Three categories of commands:
// - normal
// - user defined
@@ -145,7 +173,13 @@
// For the latter two, we need to use the default loading.
defer recoverError(&err)
- return c.root.Execute()
+ if err := c.root.Execute(); err != nil {
+ return err
+ }
+ if c.hasErr {
+ return ErrPrintedError
+ }
+ return nil
}
func recoverError(err *error) {
@@ -199,8 +233,9 @@
}
_, err = addCustom(cmd, rootCmd, commandSection, args[0], tools)
if err != nil {
- fmt.Printf("command %s %q is not defined\n", commandSection, args[0])
- fmt.Println("Run 'cue help' to show available commands.")
+ stderr := cmd.Stderr()
+ fmt.Fprintf(stderr, "command %s %q is not defined\n", commandSection, args[0])
+ fmt.Println(stderr, "Run 'cue help' to show available commands.")
os.Exit(1)
}
return cmd, nil
diff --git a/cmd/cue/cmd/vet.go b/cmd/cue/cmd/vet.go
index 6e5e49a..06c184e 100644
--- a/cmd/cue/cmd/vet.go
+++ b/cmd/cue/cmd/vet.go
@@ -119,7 +119,7 @@
cue.Optional(true),
cue.Hidden(true),
}
- w := cmd.OutOrStderr()
+ w := cmd.Stderr()
err := inst.Value().Validate(append(opt, cue.Concrete(concrete))...)
if err != nil && !hasFlag {
err = inst.Value().Validate(append(opt, cue.Concrete(false))...)
diff --git a/cmd/cue/main.go b/cmd/cue/main.go
index d319567..77c34bb 100644
--- a/cmd/cue/main.go
+++ b/cmd/cue/main.go
@@ -25,7 +25,9 @@
func main() {
err := cmd.Main(context.Background(), os.Args[1:])
if err != nil {
- fmt.Println(err)
+ if err != cmd.ErrPrintedError {
+ fmt.Println(err)
+ }
os.Exit(1)
}
}