mirror of
https://github.com/spf13/cobra
synced 2024-05-19 11:16:42 +00:00
Compare commits
6 commits
77805709cc
...
a21c1ab28a
Author | SHA1 | Date | |
---|---|---|---|
a21c1ab28a | |||
0c72800b8d | |||
c5dacb3ea4 | |||
285460dca6 | |||
4955da7c11 | |||
8e2c9596e1 |
5
.github/workflows/test.yml
vendored
5
.github/workflows/test.yml
vendored
|
@ -43,13 +43,13 @@ jobs:
|
|||
|
||||
- uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: '^1.20'
|
||||
go-version: '^1.21'
|
||||
check-latest: true
|
||||
cache: true
|
||||
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: golangci/golangci-lint-action@v3.6.0
|
||||
- uses: golangci/golangci-lint-action@v3.7.0
|
||||
with:
|
||||
version: latest
|
||||
args: --verbose
|
||||
|
@ -67,6 +67,7 @@ jobs:
|
|||
- 18
|
||||
- 19
|
||||
- 20
|
||||
- 21
|
||||
name: '${{ matrix.platform }} | 1.${{ matrix.go }}.x'
|
||||
runs-on: ${{ matrix.platform }}-latest
|
||||
steps:
|
||||
|
|
37
command.go
37
command.go
|
@ -181,6 +181,9 @@ type Command struct {
|
|||
// versionTemplate is the version template defined by user.
|
||||
versionTemplate string
|
||||
|
||||
// errPrefix is the error message prefix defined by user.
|
||||
errPrefix string
|
||||
|
||||
// inReader is a reader defined by the user that replaces stdin
|
||||
inReader io.Reader
|
||||
// outWriter is a writer defined by the user that replaces stdout
|
||||
|
@ -346,6 +349,11 @@ func (c *Command) SetVersionTemplate(s string) {
|
|||
c.versionTemplate = s
|
||||
}
|
||||
|
||||
// SetErrPrefix sets error message prefix to be used. Application can use it to set custom prefix.
|
||||
func (c *Command) SetErrPrefix(s string) {
|
||||
c.errPrefix = s
|
||||
}
|
||||
|
||||
// SetGlobalNormalizationFunc sets a normalization function to all flag sets and also to child commands.
|
||||
// The user should not have a cyclic dependency on commands.
|
||||
func (c *Command) SetGlobalNormalizationFunc(n func(f *flag.FlagSet, name string) flag.NormalizedName) {
|
||||
|
@ -595,6 +603,18 @@ func (c *Command) VersionTemplate() string {
|
|||
`
|
||||
}
|
||||
|
||||
// ErrPrefix return error message prefix for the command
|
||||
func (c *Command) ErrPrefix() string {
|
||||
if c.errPrefix != "" {
|
||||
return c.errPrefix
|
||||
}
|
||||
|
||||
if c.HasParent() {
|
||||
return c.parent.ErrPrefix()
|
||||
}
|
||||
return "Error:"
|
||||
}
|
||||
|
||||
func hasNoOptDefVal(name string, fs *flag.FlagSet) bool {
|
||||
flag := fs.Lookup(name)
|
||||
if flag == nil {
|
||||
|
@ -752,7 +772,9 @@ func (c *Command) findNext(next string) *Command {
|
|||
}
|
||||
|
||||
if len(matches) == 1 {
|
||||
return matches[0]
|
||||
// Temporarily disable gosec G602, which produces a false positive.
|
||||
// See https://github.com/securego/gosec/issues/1005.
|
||||
return matches[0] // #nosec G602
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -1009,7 +1031,7 @@ func (c *Command) ExecuteC() (cmd *Command, err error) {
|
|||
|
||||
// Regardless of what command execute is called on, run on Root only
|
||||
if c.HasParent() {
|
||||
return c.Root().ExecuteC()
|
||||
return c.Root().ExecuteContextC(c.ctx)
|
||||
}
|
||||
|
||||
// windows hook
|
||||
|
@ -1048,7 +1070,7 @@ func (c *Command) ExecuteC() (cmd *Command, err error) {
|
|||
c = cmd
|
||||
}
|
||||
if !c.SilenceErrors {
|
||||
c.PrintErrln("Error:", err.Error())
|
||||
c.PrintErrln(c.ErrPrefix(), err.Error())
|
||||
c.PrintErrf("Run '%v --help' for usage.\n", c.CommandPath())
|
||||
}
|
||||
return c, err
|
||||
|
@ -1059,11 +1081,8 @@ func (c *Command) ExecuteC() (cmd *Command, err error) {
|
|||
cmd.commandCalledAs.name = cmd.Name()
|
||||
}
|
||||
|
||||
// We have to pass global context to children command
|
||||
// if context is present on the parent command.
|
||||
if cmd.ctx == nil {
|
||||
cmd.ctx = c.ctx
|
||||
}
|
||||
// Pass context of root command to child command.
|
||||
cmd.ctx = c.ctx
|
||||
|
||||
err = cmd.execute(flags)
|
||||
if err != nil {
|
||||
|
@ -1077,7 +1096,7 @@ func (c *Command) ExecuteC() (cmd *Command, err error) {
|
|||
// If root command has SilenceErrors flagged,
|
||||
// all subcommands should respect it
|
||||
if !cmd.SilenceErrors && !c.SilenceErrors {
|
||||
c.PrintErrln("Error:", err.Error())
|
||||
c.PrintErrln(cmd.ErrPrefix(), err.Error())
|
||||
}
|
||||
|
||||
// If root command has SilenceUsage flagged,
|
||||
|
|
123
command_test.go
123
command_test.go
|
@ -232,6 +232,96 @@ func TestExecuteContextC(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
// This tests that the context passed to the root command propagates to its children
|
||||
// not only on the first execution but also subsequent calls.
|
||||
// Calling the same command multiple times is common when testing cobra applications.
|
||||
func TestExecuteContextMultiple(t *testing.T) {
|
||||
var key string
|
||||
|
||||
// Define unique contexts so we can tell them apart below.
|
||||
ctxs := []context.Context{
|
||||
context.WithValue(context.Background(), &key, "1"),
|
||||
context.WithValue(context.Background(), &key, "2"),
|
||||
}
|
||||
|
||||
// Shared reference to the context in the current iteration.
|
||||
var currentCtx context.Context
|
||||
|
||||
ctxRun := func(cmd *Command, args []string) {
|
||||
if cmd.Context() != currentCtx {
|
||||
t.Errorf("Command %q must have context with value %s", cmd.Use, currentCtx.Value(&key))
|
||||
}
|
||||
}
|
||||
|
||||
rootCmd := &Command{Use: "root", Run: ctxRun, PreRun: ctxRun}
|
||||
childCmd := &Command{Use: "child", Run: ctxRun, PreRun: ctxRun}
|
||||
granchildCmd := &Command{Use: "grandchild", Run: ctxRun, PreRun: ctxRun}
|
||||
|
||||
childCmd.AddCommand(granchildCmd)
|
||||
rootCmd.AddCommand(childCmd)
|
||||
|
||||
for i := 0; i < 2; i++ {
|
||||
currentCtx = ctxs[i]
|
||||
|
||||
if _, err := executeCommandWithContext(currentCtx, rootCmd, ""); err != nil {
|
||||
t.Errorf("Root command must not fail: %+v", err)
|
||||
}
|
||||
|
||||
if _, err := executeCommandWithContext(currentCtx, rootCmd, "child"); err != nil {
|
||||
t.Errorf("Subcommand must not fail: %+v", err)
|
||||
}
|
||||
|
||||
if _, err := executeCommandWithContext(currentCtx, rootCmd, "child", "grandchild"); err != nil {
|
||||
t.Errorf("Command child must not fail: %+v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This tests that the context passed to a subcommand propagates to the root.
|
||||
// If the entry point happens to be different from the root command, the
|
||||
// context should still propagate throughout the execution.
|
||||
func TestExecuteContextOnSubcommand(t *testing.T) {
|
||||
var key string
|
||||
|
||||
// Define unique contexts so we can tell them apart below.
|
||||
ctxs := []context.Context{
|
||||
context.WithValue(context.Background(), &key, "1"),
|
||||
context.WithValue(context.Background(), &key, "2"),
|
||||
context.WithValue(context.Background(), &key, "3"),
|
||||
}
|
||||
|
||||
// Shared reference to the context in the current iteration.
|
||||
var currentCtx context.Context
|
||||
|
||||
ctxRun := func(cmd *Command, args []string) {
|
||||
if cmd.Context() != currentCtx {
|
||||
t.Errorf("Command %q must have context with value %s", cmd.Use, currentCtx.Value(&key))
|
||||
}
|
||||
}
|
||||
|
||||
rootCmd := &Command{Use: "root", Run: ctxRun, PreRun: ctxRun}
|
||||
childCmd := &Command{Use: "child", Run: ctxRun, PreRun: ctxRun}
|
||||
granchildCmd := &Command{Use: "grandchild", Run: ctxRun, PreRun: ctxRun}
|
||||
|
||||
childCmd.AddCommand(granchildCmd)
|
||||
rootCmd.AddCommand(childCmd)
|
||||
|
||||
currentCtx = ctxs[0]
|
||||
if _, err := executeCommandWithContext(currentCtx, rootCmd, ""); err != nil {
|
||||
t.Errorf("Root command must not fail: %+v", err)
|
||||
}
|
||||
|
||||
currentCtx = ctxs[1]
|
||||
if _, err := executeCommandWithContext(currentCtx, childCmd, "child"); err != nil {
|
||||
t.Errorf("Subcommand must not fail: %+v", err)
|
||||
}
|
||||
|
||||
currentCtx = ctxs[2]
|
||||
if _, err := executeCommandWithContext(currentCtx, granchildCmd, "child", "grandchild"); err != nil {
|
||||
t.Errorf("Command child must not fail: %+v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExecute_NoContext(t *testing.T) {
|
||||
run := func(cmd *Command, args []string) {
|
||||
if cmd.Context() != context.Background() {
|
||||
|
@ -1099,6 +1189,39 @@ func TestShorthandVersionTemplate(t *testing.T) {
|
|||
checkStringContains(t, output, "customized version: 1.0.0")
|
||||
}
|
||||
|
||||
func TestRootErrPrefixExecutedOnSubcommand(t *testing.T) {
|
||||
rootCmd := &Command{Use: "root", Run: emptyRun}
|
||||
rootCmd.SetErrPrefix("root error prefix:")
|
||||
rootCmd.AddCommand(&Command{Use: "sub", Run: emptyRun})
|
||||
|
||||
output, err := executeCommand(rootCmd, "sub", "--unknown-flag")
|
||||
if err == nil {
|
||||
t.Errorf("Expected error")
|
||||
}
|
||||
|
||||
checkStringContains(t, output, "root error prefix: unknown flag: --unknown-flag")
|
||||
}
|
||||
|
||||
func TestRootAndSubErrPrefix(t *testing.T) {
|
||||
rootCmd := &Command{Use: "root", Run: emptyRun}
|
||||
subCmd := &Command{Use: "sub", Run: emptyRun}
|
||||
rootCmd.AddCommand(subCmd)
|
||||
rootCmd.SetErrPrefix("root error prefix:")
|
||||
subCmd.SetErrPrefix("sub error prefix:")
|
||||
|
||||
if output, err := executeCommand(rootCmd, "--unknown-root-flag"); err == nil {
|
||||
t.Errorf("Expected error")
|
||||
} else {
|
||||
checkStringContains(t, output, "root error prefix: unknown flag: --unknown-root-flag")
|
||||
}
|
||||
|
||||
if output, err := executeCommand(rootCmd, "sub", "--unknown-sub-flag"); err == nil {
|
||||
t.Errorf("Expected error")
|
||||
} else {
|
||||
checkStringContains(t, output, "sub error prefix: unknown flag: --unknown-sub-flag")
|
||||
}
|
||||
}
|
||||
|
||||
func TestVersionFlagExecutedOnSubcommand(t *testing.T) {
|
||||
rootCmd := &Command{Use: "root", Version: "1.0.0"}
|
||||
rootCmd.AddCommand(&Command{Use: "sub", Run: emptyRun})
|
||||
|
|
|
@ -596,6 +596,12 @@ Running an application with the '--version' flag will print the version to stdou
|
|||
the version template. The template can be customized using the
|
||||
`cmd.SetVersionTemplate(s string)` function.
|
||||
|
||||
## Error Message Prefix
|
||||
|
||||
Cobra prints an error message when receiving a non-nil error value.
|
||||
The default error message is `Error: <error contents>`.
|
||||
The Prefix, `Error:` can be customized using the `cmd.SetErrPrefix(s string)` function.
|
||||
|
||||
## PreRun and PostRun Hooks
|
||||
|
||||
It is possible to run functions before or after the main `Run` function of your command. The `PersistentPreRun` and `PreRun` functions will be executed before `Run`. `PersistentPostRun` and `PostRun` will be executed after `Run`. The `Persistent*Run` functions will be inherited by children if they do not declare their own. These functions are run in the following order:
|
||||
|
|
Loading…
Reference in a new issue