cmd/cue: improve error messages for unknown commands
Change-Id: I97d1436d0205d9ebe768344803d17ada6b7f5dfa
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/2500
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/cmd/cue/cmd/cmd.go b/cmd/cue/cmd/cmd.go
index 001af58..ad466c5 100644
--- a/cmd/cue/cmd/cmd.go
+++ b/cmd/cue/cmd/cmd.go
@@ -16,6 +16,7 @@
import (
"fmt"
+ "os"
"github.com/spf13/cobra"
)
@@ -210,7 +211,13 @@
`,
RunE: func(cmd *cobra.Command, args []string) error {
- fmt.Println("cmd run but shouldn't")
+ if len(args) == 0 {
+ fmt.Println("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.Println("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 0ad23ff..c24e638 100644
--- a/cmd/cue/cmd/cmd_test.go
+++ b/cmd/cue/cmd/cmd_test.go
@@ -43,7 +43,7 @@
stdout = cmd.OutOrStdout()
stderr = cmd.OutOrStderr()
- tools := buildTools(rootCmd, args)
+ tools, _ := buildTools(rootCmd, args)
cmd, err := addCustom(rootCmd, "command", name, tools)
if err != nil {
return err
diff --git a/cmd/cue/cmd/common.go b/cmd/cue/cmd/common.go
index a41c350..992164d 100644
--- a/cmd/cue/cmd/common.go
+++ b/cmd/cue/cmd/common.go
@@ -111,10 +111,10 @@
return instances
}
-func buildTools(cmd *cobra.Command, args []string) *cue.Instance {
+func buildTools(cmd *cobra.Command, args []string) (*cue.Instance, error) {
binst := loadFromArgs(cmd, args)
if len(binst) == 0 {
- return nil
+ return nil, nil
}
included := map[string]bool{}
@@ -130,6 +130,5 @@
}
inst := cue.Merge(buildInstances(cmd, binst)...).Build(ti)
- exitIfErr(cmd, inst, inst.Err, true)
- return inst
+ return inst, inst.Err
}
diff --git a/cmd/cue/cmd/get.go b/cmd/cue/cmd/get.go
index a0603ef..6cf6049 100644
--- a/cmd/cue/cmd/get.go
+++ b/cmd/cue/cmd/get.go
@@ -16,6 +16,7 @@
import (
"fmt"
+ "os"
"github.com/spf13/cobra"
)
@@ -35,7 +36,13 @@
per language and are documented in the respective subcommands.
`,
RunE: func(cmd *cobra.Command, args []string) error {
- fmt.Println("get must be run as one of its subcommands")
+ if len(args) == 0 {
+ fmt.Println("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.Println("Run 'cue help get' for known subcommands.")
+ os.Exit(1) // TODO: get rid of this
return nil
},
}
diff --git a/cmd/cue/cmd/root.go b/cmd/cue/cmd/root.go
index 7dbf6e7..671da30 100644
--- a/cmd/cue/cmd/root.go
+++ b/cmd/cue/cmd/root.go
@@ -16,11 +16,14 @@
import (
"context"
- "errors"
+ "fmt"
"io"
logger "log"
"os"
+ "strings"
+ "cuelang.org/go/cue/errors"
+ "cuelang.org/go/cue/token"
"github.com/spf13/cobra"
)
@@ -147,50 +150,93 @@
cmd = newRootCmd()
rootCmd := cmd.root
rootCmd.SetArgs(args)
- if len(args) >= 1 && args[0] != "help" {
- // TODO: for now we only allow one instance. Eventually, we can allow
- // more if they all belong to the same package and we merge them
- // before computing commands.
- if cmd, _, err := rootCmd.Find(args); err != nil || cmd == nil {
- tools := buildTools(rootCmd, args[1:])
- addCustom(rootCmd, commandSection, args[0], tools)
- }
+ if len(args) == 0 {
+ return cmd, nil
+ }
- type subSpec struct {
- name string
- cmd *cobra.Command
- }
- sub := map[string]subSpec{
- "cmd": {commandSection, cmd.cmd},
- // "serve": {"server", nil},
- // "fix": {"fix", nil},
- }
- if sub, ok := sub[args[0]]; ok && len(args) >= 2 {
- args = args[1:]
- if len(args) == 0 {
- tools := buildTools(rootCmd, args)
- // list available commands
- commands := tools.Lookup(sub.name)
- i, err := commands.Fields()
- if err != nil {
- return nil, err
- }
- for i.Next() {
- _, _ = addCustom(sub.cmd, sub.name, i.Label(), tools)
- }
- return cmd, nil
- }
- tools := buildTools(rootCmd, args[1:])
- _, err := addCustom(sub.cmd, sub.name, args[0], tools)
- if err != nil {
- log.Printf("%s %q is not defined", sub.name, args[0])
- exit()
- }
- }
+ var sub = map[string]*subSpec{
+ "cmd": {commandSection, cmd.cmd},
+ // "serve": {"server", nil},
+ // "fix": {"fix", nil},
+ }
+
+ if args[0] == "help" {
+ return cmd, addSubcommands(cmd, sub, args[1:])
+ }
+
+ if _, ok := sub[args[0]]; ok {
+ return cmd, addSubcommands(cmd, sub, args)
+ }
+
+ if c, _, err := rootCmd.Find(args); err == nil && c != nil {
+ return cmd, nil
+ }
+
+ if !isCommandName(args[0]) {
+ return cmd, nil // Forces unknown command message from Cobra.
+ }
+
+ tools, err := buildTools(rootCmd, args[1:])
+ if err != nil {
+ return cmd, err
+ }
+ _, err = addCustom(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.")
+ os.Exit(1)
}
return cmd, nil
}
+type subSpec struct {
+ name string
+ cmd *cobra.Command
+}
+
+func addSubcommands(cmd *Command, sub map[string]*subSpec, args []string) error {
+ if len(args) == 0 {
+ return nil
+ }
+
+ if _, ok := sub[args[0]]; ok {
+ args = args[1:]
+ }
+
+ if len(args) > 0 {
+ if !isCommandName(args[0]) {
+ return nil // Forces unknown command message from Cobra.
+ }
+ args = args[1:]
+ }
+
+ tools, err := buildTools(cmd.root, args)
+ if err != nil {
+ return err
+ }
+
+ // TODO: for now we only allow one instance. Eventually, we can allow
+ // more if they all belong to the same package and we merge them
+ // before computing commands.
+ for _, spec := range sub {
+ commands := tools.Lookup(spec.name)
+ i, err := commands.Fields()
+ if err != nil {
+ return errors.Newf(token.NoPos, "could not create command definitions: %v", err)
+ }
+ for i.Next() {
+ _, _ = addCustom(spec.cmd, spec.name, i.Label(), tools)
+ }
+ }
+ return nil
+}
+
+func isCommandName(s string) bool {
+ return !strings.Contains(s, `/\`) &&
+ !strings.HasPrefix(s, ".") &&
+ !strings.HasSuffix(s, ".cue")
+}
+
type panicError struct {
Err error
}