Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/go.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: 1.18
go-version: 1.22

- name: Build
run: go build -v ./...
Expand Down
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
*.coverprofile
node_modules/
*.test
*.test
.idea/
.vscode/
138 changes: 89 additions & 49 deletions app.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,7 @@ import (

var (
changeLogURL = "https://github.com/urfave/cli/blob/master/CHANGELOG.md"
appActionDeprecationURL = fmt.Sprintf("%s#deprecated-cli-app-action-signature", changeLogURL)
runAndExitOnErrorDeprecationURL = fmt.Sprintf("%s#deprecated-cli-app-runandexitonerror", changeLogURL)

contactSysadmin = "This is an error in the application. Please contact the distributor of this application if this is not you."

errInvalidActionType = NewExitError("ERROR invalid Action type. "+
fmt.Sprintf("Must be `func(*Context`)` or `func(*Context) error). %s", contactSysadmin)+
fmt.Sprintf("See %s", appActionDeprecationURL), 2)
)

// App is the main structure of a cli application. It is recommended that
Expand Down Expand Up @@ -63,9 +56,27 @@ type App struct {
After AfterFunc

// The action to execute when no subcommands are specified
// Expects a `cli.ActionFunc` but will accept the *deprecated* signature of `func(*cli.Context) {}`
// *Note*: support for the deprecated `Action` signature will be removed in a future version
Action interface{}
Action ActionFunc

// DefaultBefore executes before the app, commands, and subcommands that do
// not specify a BeforeFunc of their own
DefaultBefore BeforeFunc
// DefaultAction is the action executed by the app, commands, and subcommands
// that do not specify an action of their own
DefaultAction ActionFunc
// DefaultAfter executes after the app, commands, and subcommands that do not
// specify an AfterFunc of their own
DefaultAfter AfterFunc
// DefaultOnUsageError is executed on a usage error by the app, commands, and
// subcommands that do not specify an OnUsageError of their own
DefaultOnUsageError OnUsageErrorFunc
// GlobalHideHelp hides the help flag for the app, all commands, and all subcommands
GlobalHideHelp bool
// GlobalHideHelpCommand hides the help command for the app, all commands, and all subcommands
GlobalHideHelpCommand bool
// GlobalFlags are flags that can be used by the app, any command, or any
// subcommand, unless command.NoGlobalFlags is true
GlobalFlags []Flag

// Execute this function if the proper command cannot be found
CommandNotFound CommandNotFoundFunc
Expand Down Expand Up @@ -149,11 +160,16 @@ func (a *App) Setup() {
}
a.Commands = newCmds

// make the app-wide GlobalFlags usable by the app itself
for _, fl := range a.GlobalFlags {
a.appendFlag(fl)
}

if a.Command(helpCommand.Name) == nil {
if !a.HideHelpCommand {
if !a.hideHelpCommand() {
a.Commands = append(a.Commands, helpCommand)
}
if !a.HideHelp && (HelpFlag != BoolFlag{}) {
if !a.hideHelp() && (HelpFlag != BoolFlag{}) {
a.appendFlag(HelpFlag)
}
}
Expand Down Expand Up @@ -211,16 +227,16 @@ func (a *App) Run(arguments []string) (err error) {
}

if err != nil {
if a.OnUsageError != nil {
err := a.OnUsageError(context, err, false)
if onUsageError := a.resolveOnUsageError(); onUsageError != nil {
err := onUsageError(context, err, false)
HandleExitCoder(err)
return err
}
fmt.Fprintf(a.Writer, "%s %s\n\n", "Incorrect Usage.", err.Error())
Comment thread
coderabbitai[bot] marked this conversation as resolved.
return err
}

if !a.HideHelp && checkHelp(context) {
if !a.hideHelp() && checkHelp(context) {
ShowAppHelp(context)
return nil
}
Expand All @@ -230,9 +246,9 @@ func (a *App) Run(arguments []string) (err error) {
return nil
}

if a.After != nil {
if after := a.resolveAfter(); after != nil {
defer func() {
if afterErr := a.After(context); afterErr != nil {
if afterErr := after(context); afterErr != nil {
if err != nil {
err = NewMultiError(err, afterErr)
} else {
Expand All @@ -242,8 +258,8 @@ func (a *App) Run(arguments []string) (err error) {
}()
}

if a.Before != nil {
beforeErr := a.Before(context)
if before := a.resolveBefore(); before != nil {
beforeErr := before(context)
if beforeErr != nil {
fmt.Fprintf(a.Writer, "%v\n\n", beforeErr)
HandleExitCoder(beforeErr)
Expand All @@ -261,12 +277,8 @@ func (a *App) Run(arguments []string) (err error) {
}
}

if a.Action == nil {
a.Action = helpCommand.Action
}

// Run default Action
err = HandleAction(a.Action, context)
err = a.resolveAction()(context)

HandleExitCoder(err)
return err
Expand All @@ -290,10 +302,10 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) {
// append help to commands
if len(a.Commands) > 0 {
if a.Command(helpCommand.Name) == nil {
if !a.HideHelpCommand {
if !a.hideHelpCommand() {
a.Commands = append(a.Commands, helpCommand)
}
if !a.HideHelp && (HelpFlag != BoolFlag{}) {
if !a.hideHelp() && (HelpFlag != BoolFlag{}) {
a.appendFlag(HelpFlag)
}
}
Expand Down Expand Up @@ -330,8 +342,8 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) {
}

if err != nil {
if a.OnUsageError != nil {
err = a.OnUsageError(context, err, true)
if onUsageError := a.resolveOnUsageError(); onUsageError != nil {
err = onUsageError(context, err, true)
HandleExitCoder(err)
return err
}
Expand All @@ -349,9 +361,9 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) {
}
}

if a.After != nil {
if after := a.resolveAfter(); after != nil {
defer func() {
afterErr := a.After(context)
afterErr := after(context)
if afterErr != nil {
HandleExitCoder(err)
if err != nil {
Expand All @@ -363,8 +375,8 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) {
}()
}

if a.Before != nil {
beforeErr := a.Before(context)
if before := a.resolveBefore(); before != nil {
beforeErr := before(context)
if beforeErr != nil {
HandleExitCoder(beforeErr)
err = beforeErr
Expand All @@ -382,7 +394,7 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) {
}

// Run default Action
err = HandleAction(a.Action, context)
err = a.resolveAction()(context)

HandleExitCoder(err)
return err
Expand Down Expand Up @@ -464,6 +476,50 @@ func (a *App) appendFlag(flag Flag) {
}
}

// hideHelp reports whether the built-in help flag should be hidden for this
// app, honoring both the app-specific HideHelp and the app-wide GlobalHideHelp.
func (a *App) hideHelp() bool {
return a.GlobalHideHelp || a.HideHelp
}

// hideHelpCommand reports whether the built-in help command should be hidden
// for this app, honoring both the app-specific HideHelpCommand and the
// app-wide GlobalHideHelpCommand.
func (a *App) hideHelpCommand() bool {
return a.GlobalHideHelpCommand || a.HideHelpCommand
}

func (a *App) resolveBefore() BeforeFunc {
if a.Before != nil {
return a.Before
}
return a.DefaultBefore
}

func (a *App) resolveAfter() AfterFunc {
if a.After != nil {
return a.After
}
return a.DefaultAfter
}

func (a *App) resolveAction() ActionFunc {
if a.Action != nil {
return a.Action
}
if a.DefaultAction != nil {
return a.DefaultAction
}
return helpCommand.Action
}

func (a *App) resolveOnUsageError() OnUsageErrorFunc {
if a.OnUsageError != nil {
return a.OnUsageError
}
return a.DefaultOnUsageError
}

// Author represents someone who has contributed to a cli project.
type Author struct {
Name string // The Authors name
Expand All @@ -479,19 +535,3 @@ func (a Author) String() string {

return fmt.Sprintf("%v%v", a.Name, e)
}

// HandleAction attempts to figure out which Action signature was used. If
// it's an ActionFunc or a func with the legacy signature for Action, the func
// is run!
func HandleAction(action interface{}, context *Context) (err error) {
if a, ok := action.(ActionFunc); ok {
return a(context)
} else if a, ok := action.(func(*Context) error); ok {
return a(context)
} else if a, ok := action.(func(*Context)); ok { // deprecated function signature
a(context)
return nil
} else {
return errInvalidActionType
}
}
104 changes: 1 addition & 103 deletions app_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1579,90 +1579,6 @@ func TestCustomHelpVersionFlags(t *testing.T) {
}
}

func TestHandleAction_WithNonFuncAction(t *testing.T) {
app := NewApp()
app.Action = 42
fs, err := flagSet(app.Name, app.Flags)
if err != nil {
t.Errorf("error creating FlagSet: %s", err)
}
err = HandleAction(app.Action, NewContext(app, fs, nil))

if err == nil {
t.Fatalf("expected to receive error from Run, got none")
}

exitErr, ok := err.(*ExitError)

if !ok {
t.Fatalf("expected to receive a *ExitError")
}

if !strings.HasPrefix(exitErr.Error(), "ERROR invalid Action type.") {
t.Fatalf("expected an unknown Action error, but got: %v", exitErr.Error())
}

if exitErr.ExitCode() != 2 {
t.Fatalf("expected error exit code to be 2, but got: %v", exitErr.ExitCode())
}
}

func TestHandleAction_WithInvalidFuncSignature(t *testing.T) {
app := NewApp()
app.Action = func() string { return "" }
fs, err := flagSet(app.Name, app.Flags)
if err != nil {
t.Errorf("error creating FlagSet: %s", err)
}
err = HandleAction(app.Action, NewContext(app, fs, nil))

if err == nil {
t.Fatalf("expected to receive error from Run, got none")
}

exitErr, ok := err.(*ExitError)

if !ok {
t.Fatalf("expected to receive a *ExitError")
}

if !strings.HasPrefix(exitErr.Error(), "ERROR invalid Action type") {
t.Fatalf("expected an unknown Action error, but got: %v", exitErr.Error())
}

if exitErr.ExitCode() != 2 {
t.Fatalf("expected error exit code to be 2, but got: %v", exitErr.ExitCode())
}
}

func TestHandleAction_WithInvalidFuncReturnSignature(t *testing.T) {
app := NewApp()
app.Action = func(_ *Context) (int, error) { return 0, nil }
fs, err := flagSet(app.Name, app.Flags)
if err != nil {
t.Errorf("error creating FlagSet: %s", err)
}
err = HandleAction(app.Action, NewContext(app, fs, nil))

if err == nil {
t.Fatalf("expected to receive error from Run, got none")
}

exitErr, ok := err.(*ExitError)

if !ok {
t.Fatalf("expected to receive a *ExitError")
}

if !strings.HasPrefix(exitErr.Error(), "ERROR invalid Action type") {
t.Fatalf("expected an invalid Action signature error, but got: %v", exitErr.Error())
}

if exitErr.ExitCode() != 2 {
t.Fatalf("expected error exit code to be 2, but got: %v", exitErr.ExitCode())
}
}

func TestHandleAction_WithUnknownPanic(t *testing.T) {
defer func() { refute(t, recover(), nil) }()

Expand All @@ -1677,7 +1593,7 @@ func TestHandleAction_WithUnknownPanic(t *testing.T) {
if err != nil {
t.Errorf("error creating FlagSet: %s", err)
}
HandleAction(app.Action, NewContext(app, fs, nil))
app.Action(NewContext(app, fs, nil))
}

func TestShellCompletionForIncompleteFlags(t *testing.T) {
Expand Down Expand Up @@ -1723,21 +1639,3 @@ func TestShellCompletionForIncompleteFlags(t *testing.T) {
t.Errorf("app should not return an error: %s", err)
}
}

func TestHandleActionActuallyWorksWithActions(t *testing.T) {
var f ActionFunc
called := false
f = func(c *Context) error {
called = true
return nil
}

err := HandleAction(f, nil)
if err != nil {
t.Errorf("Should not have errored: %v", err)
}

if !called {
t.Errorf("Function was not called")
}
}
Loading
Loading