cobra is lenient where a script needs it strict. A command that only holds
subcommands prints its help and succeeds for a word that names none of
them, so a script with a misspelt command goes on to its next step.
cobratree.Strict fixes this once the tree is built.
Procedure#
- Build the tree. Add the last command.
- Call
Stricton the root. Give itcobratree.Movedfor the commands that you renamed, andcobratree.Topicsfor the help topics that are no command. - Set
cobratree.FlagErroras theFlagErrorFuncof the root. - In a command, refuse a value with
cobratree.InvalidValueorcobratree.Usagef. - Where the cause of a failure is known, give its exit status with
cobratree.Coded. - In
main, map the error to the exit status:cobratree.IsUsagefirst, commonly 2, thencobratree.Status.
root := &cobra.Command{Use: "prog", SilenceErrors: true, SilenceUsage: true}
get := &cobra.Command{Use: "get", Short: "Show things"}
get.AddCommand(&cobra.Command{
Use: "cluster [NAME]",
Args: cobra.MaximumNArgs(1),
RunE: func(*cobra.Command, []string) error { return nil },
})
root.AddCommand(get)
cobratree.Strict(root, cobratree.Moved(map[string]string{"status": "get cluster"}))
for _, args := range [][]string{
{"gte"},
{"status"},
{"help", "get", "clustr"},
{"get", "cluster", "a", "b"},
{"get", "cluster", "--bogus"},
} {
root.SetArgs(args)
err := root.Execute()
code := 1
if cobratree.IsUsage(err) {
code = 2
}
fmt.Printf("%d %v\n", code, err)
}2 unknown command "gte" for "prog" (did you mean "get"?)
2 unknown command "status" for "prog" (it is now "get cluster")
2 unknown help topic "get clustr": unknown command "clustr" for "prog get" (did you mean "cluster"?)
2 accepts at most 1 arg(s), received 2 (usage: prog get cluster [NAME])
2 unknown flag: --boguscobratree/example_test.go. CI runs it and checks its output.What the user sees:
prog gte unknown command "gte" for "prog" (did you mean "get"?)
prog status unknown command "status" for "prog" (it is now "get cluster")
prog power on requires at least 1 arg(s), only received 0 (usage: prog power on NODES)
The texts of these errors are an interface (decisions 24 and 29).
The version#
cobratree.ReadBuildInfo returns what the toolchain recorded:
the module version, the commit and its time, and if the checkout was
modified. cobratree.BuildInfo.Resolve puts in place what a
release build set with -ldflags.
var version, commit string // set with -ldflags "-X main.version=v1.2.3 -X main.commit=…"
bi := cobratree.ReadBuildInfo().Resolve(version, commit)
line := "prog " + cmp.Or(bi.Version, "dev")
if rev := bi.Short(7); rev != "" {
line += " (" + rev + ")"
}
fmt.Println(line)cobratree/example_test.go. CI compiles it.Test the tree#
cobratreetest.Run runs a command line in a test
and keeps the result apart from the diagnostics.
cobratreetest.CheckCommandLines checks that each
command line of the manual names a command and flags that exist.
_ = func(t *testing.T) {
stdout, stderr, err := cobratreetest.Run(t, newTree(), "get", "cluster")
if err != nil || stdout != "NAME NODES\n" || stderr != "prog: no realm given, using default\n" {
t.Errorf("prog get cluster: %q, %q, %v", stdout, stderr, err)
}
}cobratree/cobratreetest/example_test.go. CI compiles it.