Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Emit non-zero exit code upon non-runnable subcommand #922

Merged
merged 6 commits into from Aug 1, 2019
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 3 additions & 2 deletions .gitignore
Expand Up @@ -32,7 +32,8 @@ Session.vim
tags

*.exe

cobra
cobra.test

.idea/*
.idea/
*.iml
1 change: 1 addition & 0 deletions args.go
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"
)

// PositionalArgs defines positional arguments callback
bruceadowns marked this conversation as resolved.
Show resolved Hide resolved
type PositionalArgs func(cmd *Command, args []string) error

// Legacy arg validation has the following behaviour:
Expand Down
4 changes: 2 additions & 2 deletions cobra.go
Expand Up @@ -52,7 +52,7 @@ var EnableCommandSorting = true
// if the CLI is started from explorer.exe.
// To disable the mousetrap, just set this variable to blank string ("").
// Works only on Microsoft Windows.
var MousetrapHelpText string = `This is a command line tool.
var MousetrapHelpText = `This is a command line tool.

You need to open cmd.exe and run it from there.
`
Expand All @@ -61,7 +61,7 @@ You need to open cmd.exe and run it from there.
// if the CLI is started from explorer.exe. Set to 0 to wait for the return key to be pressed.
// To disable the mousetrap, just set MousetrapHelpText to blank string ("").
// Works only on Microsoft Windows.
var MousetrapDisplayDuration time.Duration = 5 * time.Second
var MousetrapDisplayDuration = 5 * time.Second

// AddTemplateFunc adds a template function that's available to Usage and Help
// template generation.
Expand Down
5 changes: 3 additions & 2 deletions cobra/cmd/init.go
Expand Up @@ -15,10 +15,11 @@ package cmd

import (
"fmt"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"os"
"path"

"github.com/spf13/cobra"
"github.com/spf13/viper"
)

var (
Expand Down
7 changes: 5 additions & 2 deletions cobra/cmd/project.go
Expand Up @@ -2,9 +2,10 @@ package cmd

import (
"fmt"
"github.com/spf13/cobra/cobra/tpl"
"os"
"text/template"

"github.com/spf13/cobra/cobra/tpl"
)

// Project contains name, license and paths to projects.
Expand All @@ -18,14 +19,15 @@ type Project struct {
AppName string
}

// Command structure
bruceadowns marked this conversation as resolved.
Show resolved Hide resolved
type Command struct {
CmdName string
CmdParent string
*Project
}

// Create project receiver
bruceadowns marked this conversation as resolved.
Show resolved Hide resolved
func (p *Project) Create() error {

// check if AbsolutePath exists
if _, err := os.Stat(p.AbsolutePath); os.IsNotExist(err) {
// create directory
Expand Down Expand Up @@ -80,6 +82,7 @@ func (p *Project) createLicenseFile() error {
return licenseTemplate.Execute(licenseFile, data)
}

// Create command receiver
bruceadowns marked this conversation as resolved.
Show resolved Hide resolved
func (c *Command) Create() error {
cmdFile, err := os.Create(fmt.Sprintf("%s/cmd/%s.go", c.AbsolutePath, c.CmdName))
if err != nil {
Expand Down
4 changes: 2 additions & 2 deletions cobra/cmd/root.go
Expand Up @@ -36,8 +36,8 @@ to quickly create a Cobra application.`,
)

// Execute executes the root command.
func Execute() {
rootCmd.Execute()
func Execute() error {
return rootCmd.Execute()
}

func init() {
Expand Down
14 changes: 12 additions & 2 deletions cobra/main.go
Expand Up @@ -13,8 +13,18 @@

package main

import "github.com/spf13/cobra/cobra/cmd"
import (
"os"

"github.com/spf13/cobra/cobra/cmd"
)

func main() {
cmd.Execute()
if err := runMain(); err != nil {
os.Exit(1)
}
}

func runMain() error {
bruceadowns marked this conversation as resolved.
Show resolved Hide resolved
return cmd.Execute()
}
3 changes: 3 additions & 0 deletions cobra/tpl/main.go
@@ -1,5 +1,6 @@
package tpl

// MainTemplate defines main template string
bruceadowns marked this conversation as resolved.
Show resolved Hide resolved
func MainTemplate() []byte {
return []byte(`/*
{{ .Copyright }}
Expand All @@ -15,6 +16,7 @@ func main() {
`)
}

// RootTemplate defines root template string
bruceadowns marked this conversation as resolved.
Show resolved Hide resolved
func RootTemplate() []byte {
return []byte(`/*
{{ .Copyright }}
Expand Down Expand Up @@ -108,6 +110,7 @@ func initConfig() {
`)
}

// AddCommandTemplate defines add command template string
bruceadowns marked this conversation as resolved.
Show resolved Hide resolved
func AddCommandTemplate() []byte {
return []byte(`/*
{{ .Project.Copyright }}
Expand Down
21 changes: 17 additions & 4 deletions command.go
Expand Up @@ -17,6 +17,7 @@ package cobra

import (
"bytes"
"errors"
"fmt"
"io"
"os"
Expand All @@ -27,6 +28,9 @@ import (
flag "github.com/spf13/pflag"
)

// ErrSubCommandRequired defines subcommand error
bruceadowns marked this conversation as resolved.
Show resolved Hide resolved
var ErrSubCommandRequired = errors.New("subcommand is required")

// FParseErrWhitelist configures Flag parse errors to be ignored
type FParseErrWhitelist flag.ParseErrorsWhitelist

Expand Down Expand Up @@ -228,7 +232,7 @@ func (c *Command) SetErr(newErr io.Writer) {
c.errWriter = newErr
}

// SetOut sets the source for input data
// SetIn sets the source for input data
// If newIn is nil, os.Stdin is used.
func (c *Command) SetIn(newIn io.Reader) {
c.inReader = newIn
Expand Down Expand Up @@ -297,7 +301,7 @@ func (c *Command) ErrOrStderr() io.Writer {
return c.getErr(os.Stderr)
}

// ErrOrStderr returns output to stderr
// InOrStdin returns output to stderr
func (c *Command) InOrStdin() io.Reader {
return c.getIn(os.Stdin)
}
Expand Down Expand Up @@ -369,7 +373,7 @@ func (c *Command) HelpFunc() func(*Command, []string) {
}
return func(c *Command, a []string) {
c.mergePersistentFlags()
err := tmpl(c.OutOrStdout(), c.HelpTemplate(), c)
err := tmpl(c.OutOrStderr(), c.HelpTemplate(), c)
if err != nil {
c.Println(err)
}
Expand Down Expand Up @@ -786,7 +790,7 @@ func (c *Command) execute(a []string) (err error) {
}

if !c.Runnable() {
return flag.ErrHelp
return ErrSubCommandRequired
}

c.preRun()
Expand Down Expand Up @@ -920,6 +924,14 @@ func (c *Command) ExecuteC() (cmd *Command, err error) {
return cmd, nil
}

// If command wasn't runnable, show full help, but do return the error.
// This will result in apps by default returning a non-success exit code, but also gives them the option to
// handle specially.
if err == ErrSubCommandRequired {
cmd.HelpFunc()(cmd, args)
return cmd, err
}

// If root command has SilentErrors flagged,
// all subcommands should respect it
if !cmd.SilenceErrors && !c.SilenceErrors {
Expand All @@ -935,6 +947,7 @@ func (c *Command) ExecuteC() (cmd *Command, err error) {
return cmd, err
}

// ValidateArgs validates arguments
bruceadowns marked this conversation as resolved.
Show resolved Hide resolved
func (c *Command) ValidateArgs(args []string) error {
if c.Args == nil {
return nil
Expand Down
4 changes: 2 additions & 2 deletions command_test.go
Expand Up @@ -836,8 +836,8 @@ func TestHelpExecutedOnNonRunnableChild(t *testing.T) {
rootCmd.AddCommand(childCmd)

output, err := executeCommand(rootCmd, "child")
if err != nil {
t.Errorf("Unexpected error: %v", err)
if err != ErrSubCommandRequired {
t.Errorf("Expected error")
}

checkStringContains(t, output, childCmd.Long)
Expand Down