From d7b3bc3f3638cb348a4074f40f4a86650e01c53f Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Sun, 27 Nov 2022 21:51:19 -0500 Subject: [PATCH] Starting to collapse App into Command Connected to #1586 Connected to #1600 --- app.go | 49 ++++++-- app_test.go | 25 ++-- command.go | 159 ++++++++++++++++++++++++- command_test.go | 2 +- godoc-current.txt | 174 ++++++++-------------------- help_test.go | 4 + internal/build/build.go | 8 +- internal/example-cli/example-cli.go | 4 +- 8 files changed, 268 insertions(+), 157 deletions(-) diff --git a/app.go b/app.go index 51b2672a38..982d7f8fad 100644 --- a/app.go +++ b/app.go @@ -2,13 +2,9 @@ package cli import ( "context" - "flag" "fmt" - "io" "os" "path/filepath" - "sort" - "strings" ) const suggestDidYouMeanTemplate = "Did you mean %q?" @@ -27,6 +23,9 @@ var ( SuggestDidYouMeanTemplate string = suggestDidYouMeanTemplate ) +type App = Command + +/* // App is the main structure of a cli application. It is recommended that // an app be created with the cli.NewApp() function type App struct { @@ -122,6 +121,7 @@ type App struct { rootCommand *Command } +*/ type SuggestFlagFunc func(flags []Flag, provided string, hideHelp bool) string @@ -142,6 +142,7 @@ func NewApp() *App { } } +/* // Setup runs initialization code to ensure all data structures are ready for // `Run` or inspection prior to `Run`. It is internally called by `Run`, but // will return early if setup has already happened. @@ -252,7 +253,9 @@ func (a *App) Setup() { disableSliceFlagSeparator = a.DisableSliceFlagSeparator } +*/ +/* func (a *App) newRootCommand() *Command { return &Command{ Name: a.Name, @@ -278,18 +281,23 @@ func (a *App) newRootCommand() *Command { isRoot: true, } } +*/ +/* func (a *App) newFlagSet() (*flag.FlagSet, error) { return flagSet(a.Name, a.Flags) } +*/ +/* func (a *App) useShortOptionHandling() bool { return a.UseShortOptionHandling } +*/ // Run is the entry point to the cli app. Parses the arguments slice and routes // to the proper flag/args combination -func (a *App) Run(arguments []string) (err error) { +func (a *App) Run(arguments []string) error { return a.RunContext(context.Background(), arguments) } @@ -297,7 +305,8 @@ func (a *App) Run(arguments []string) (err error) { // passed to its commands and sub-commands. Through this, you can // propagate timeouts and cancellation requests func (a *App) RunContext(ctx context.Context, arguments []string) (err error) { - a.Setup() + a.isRoot = true + a.setupDefaults() // handle the completion flag separately from the flagset since // completion could be attempted after a flag, but before its value was put @@ -310,19 +319,26 @@ func (a *App) RunContext(ctx context.Context, arguments []string) (err error) { cCtx := NewContext(a, nil, &Context{Context: ctx}) cCtx.shellComplete = shellComplete - a.rootCommand = a.newRootCommand() - cCtx.Command = a.rootCommand + /* + a.rootCommand = a.newRootCommand() + cCtx.Command = a.rootCommand + + return a.rootCommand.Run(cCtx, arguments...) + */ - return a.rootCommand.Run(cCtx, arguments...) + return a.run(cCtx, arguments...) } +/* // This is a stub function to keep public API unchanged from old code // // Deprecated: use App.Run or App.RunContext func (a *App) RunAsSubcommand(ctx *Context) (err error) { return a.RunContext(ctx.Context, ctx.Args().Slice()) } +*/ +/* func (a *App) suggestFlagFromError(err error, command string) (string, error) { flag, parseErr := flagFromError(err) if parseErr != nil { @@ -347,7 +363,9 @@ func (a *App) suggestFlagFromError(err error, command string) (string, error) { return fmt.Sprintf(SuggestDidYouMeanTemplate+"\n\n", suggestion), nil } +*/ +/* // RunAndExitOnError calls .Run() and exits non-zero if an error was returned // // Deprecated: instead you should return an error that fulfills cli.ExitCoder @@ -359,7 +377,9 @@ func (a *App) RunAndExitOnError() { OsExiter(1) } } +*/ +/* // Command returns the named command on App. Returns nil if the command does not exist func (a *App) Command(name string) *Command { for _, c := range a.Commands { @@ -370,7 +390,9 @@ func (a *App) Command(name string) *Command { return nil } +*/ +/* // VisibleCategories returns a slice of categories and commands that are // Hidden=false func (a *App) VisibleCategories() []CommandCategory { @@ -387,7 +409,9 @@ func (a *App) VisibleCategories() []CommandCategory { } return ret } +*/ +/* // VisibleCommands returns a slice of the Commands with Hidden=false func (a *App) VisibleCommands() []*Command { var ret []*Command @@ -398,7 +422,9 @@ func (a *App) VisibleCommands() []*Command { } return ret } +*/ +/* // VisibleFlagCategories returns a slice containing all the categories with the flags they contain func (a *App) VisibleFlagCategories() []VisibleFlagCategory { if a.flagCategories == nil { @@ -406,17 +432,22 @@ func (a *App) VisibleFlagCategories() []VisibleFlagCategory { } return a.flagCategories.VisibleCategories() } +*/ +/* // VisibleFlags returns a slice of the Flags with Hidden=false func (a *App) VisibleFlags() []Flag { return visibleFlags(a.Flags) } +*/ +/* func (a *App) appendFlag(fl Flag) { if !hasFlag(a.Flags, fl) { a.Flags = append(a.Flags, fl) } } +*/ func (a *App) appendCommand(c *Command) { if !hasCommand(a.Commands, c) { diff --git a/app_test.go b/app_test.go index 330d5ca03c..c34789198a 100644 --- a/app_test.go +++ b/app_test.go @@ -703,13 +703,13 @@ func TestApp_FlagsFromExtPackage(t *testing.T) { func TestApp_Setup_defaultsReader(t *testing.T) { app := &App{} - app.Setup() + app.setupDefaults() expect(t, app.Reader, os.Stdin) } func TestApp_Setup_defaultsWriter(t *testing.T) { app := &App{} - app.Setup() + app.setupDefaults() expect(t, app.Writer, os.Stdout) } @@ -847,7 +847,9 @@ func TestApp_VisibleCommands(t *testing.T) { }, } - app.Setup() + cCtx := NewContext(app, nil, nil) + app.setup(cCtx) + expected := []*Command{ app.Commands[0], app.Commands[2], // help @@ -1157,7 +1159,7 @@ func TestApp_ParseSliceFlagsWithMissingValue(t *testing.T) { func TestApp_DefaultStdin(t *testing.T) { app := &App{} - app.Setup() + app.setupDefaults() if app.Reader != os.Stdin { t.Error("Default input reader not set.") @@ -1166,7 +1168,7 @@ func TestApp_DefaultStdin(t *testing.T) { func TestApp_DefaultStdout(t *testing.T) { app := &App{} - app.Setup() + app.setupDefaults() if app.Writer != os.Stdout { t.Error("Default output writer not set.") @@ -2232,7 +2234,8 @@ func TestApp_VisibleCategories(t *testing.T) { }, } - app.Setup() + cCtx := NewContext(app, nil, nil) + app.setup(cCtx) expect(t, expected, app.VisibleCategories()) app = &App{ @@ -2268,7 +2271,7 @@ func TestApp_VisibleCategories(t *testing.T) { }, } - app.Setup() + app.setup(cCtx) expect(t, expected, app.VisibleCategories()) app = &App{ @@ -2296,7 +2299,7 @@ func TestApp_VisibleCategories(t *testing.T) { }, } - app.Setup() + app.setup(cCtx) expect(t, []CommandCategory{}, app.VisibleCategories()) } @@ -2313,7 +2316,7 @@ func TestApp_VisibleFlagCategories(t *testing.T) { }, }, } - app.Setup() + app.setupDefaults() vfc := app.VisibleFlagCategories() if len(vfc) != 1 { t.Fatalf("unexpected visible flag categories %+v", vfc) @@ -2691,7 +2694,7 @@ func newTestApp() *App { func TestSetupInitializesBothWriters(t *testing.T) { a := &App{} - a.Setup() + a.setupDefaults() if a.ErrWriter != os.Stderr { t.Errorf("expected a.ErrWriter to be os.Stderr") @@ -2708,7 +2711,7 @@ func TestSetupInitializesOnlyNilWriters(t *testing.T) { ErrWriter: wr, } - a.Setup() + a.setupDefaults() if a.ErrWriter != wr { t.Errorf("expected a.ErrWriter to be a *bytes.Buffer instance") diff --git a/command.go b/command.go index bf9ce01b83..f7b61932c6 100644 --- a/command.go +++ b/command.go @@ -3,6 +3,9 @@ package cli import ( "flag" "fmt" + "io" + "os" + "path/filepath" "reflect" "sort" "strings" @@ -22,6 +25,11 @@ type Command struct { Description string // A short description of the arguments of this command ArgsUsage string + // Version of the program + Version string + // DefaultCommand is the (optional) name of a command + // to run if no command names are passed as CLI arguments. + DefaultCommand string // The category the command is part of Category string // The function to call when checking for bash command completions @@ -34,13 +42,23 @@ type Command struct { After AfterFunc // The function to call when this command is invoked Action ActionFunc + // Execute this function if the proper command cannot be found + CommandNotFound CommandNotFoundFunc // Execute this function if a usage error occurs. OnUsageError OnUsageErrorFunc + // Execute this function when an invalid flag is accessed from the context + InvalidFlagAccessHandler InvalidFlagAccessFunc + // List of all authors who contributed + Authors []*Author + // Copyright of the binary if any + Copyright string // List of child commands Commands []*Command // List of flags to parse Flags []Flag flagCategories FlagCategories + // Boolean to enable bash completion commands + EnableBashCompletion bool // Treat all flags as normal arguments if true SkipFlagParsing bool // Boolean to hide built-in help command and help flag @@ -48,12 +66,16 @@ type Command struct { // Boolean to hide built-in help command but keep help flag // Ignored if HideHelp is true. HideHelpCommand bool + // Boolean to hide built-in version flag and the VERSION section of help + HideVersion bool // Boolean to hide this command from help or completion Hidden bool // Boolean to enable short-option handling so user can combine several // single-character bool arguments into one // i.e. foobar -o -v -> foobar -ov UseShortOptionHandling bool + // Enable suggestions for commands and flags + Suggest bool // Full name of command for help, defaults to full command name, including parent commands. HelpName string @@ -69,6 +91,34 @@ type Command struct { // if this is a root "special" command isRoot bool + + didSetupDefaults bool + + // Reader reader to write input to (useful for tests) + Reader io.Reader + // Writer writer to write output to + Writer io.Writer + // ErrWriter writes error output + ErrWriter io.Writer + // ExitErrHandler processes any error encountered while running an App before + // it is returned to the caller. If no function is provided, HandleExitCoder + // is used as the default behavior. + ExitErrHandler ExitErrHandlerFunc + // Other custom info + Metadata map[string]any + // Carries a function which returns app specific info. + ExtraInfo func() map[string]string + // CustomAppHelpTemplate the text template for app help topic. + // cli.go uses text/template to render templates. You can + // render custom help text by setting this variable. + CustomAppHelpTemplate string + // SliceFlagSeparator is used to customize the separator for SliceFlag, the default is "," + SliceFlagSeparator string + // DisableSliceFlagSeparator is used to disable SliceFlagSeparator, the default is false + DisableSliceFlagSeparator bool + // Allows global flags set by libraries which use flag.XXXVar(...) directly + // to be parsed through this library + AllowExtFlags bool } type Commands []*Command @@ -106,7 +156,71 @@ func (cmd *Command) Command(name string) *Command { return nil } +func (c *Command) setupDefaults() { + if c.didSetupDefaults { + return + } + + c.didSetupDefaults = true + + if c.Name == "" { + c.Name = filepath.Base(os.Args[0]) + } + + if c.HelpName == "" { + c.HelpName = c.Name + } + + if c.Usage == "" { + c.Usage = "A new cli application" + } + + if c.Version == "" { + c.HideVersion = true + } + + if c.BashComplete == nil { + c.BashComplete = DefaultAppComplete + } + + if c.Action == nil { + c.Action = helpCommand.Action + } + + if c.Reader == nil { + c.Reader = os.Stdin + } + + if c.Writer == nil { + c.Writer = os.Stdout + } + + if c.ErrWriter == nil { + c.ErrWriter = os.Stderr + } + + if c.Metadata == nil { + c.Metadata = make(map[string]any) + } +} + func (c *Command) setup(ctx *Context) { + c.setupDefaults() + + if c.AllowExtFlags { + // add global flags added by other packages + flag.VisitAll(func(f *flag.Flag) { + // skip test flags + if !strings.HasPrefix(f.Name, ignoreFlagPrefix) { + c.Flags = append(c.Flags, &extFlag{f}) + } + }) + } + + if c.isRoot { + return + } + if c.Command(helpCommand.Name) == nil && !c.HideHelp { if !c.HideHelpCommand { helpCommand.HelpName = fmt.Sprintf("%s %s", c.HelpName, helpName) @@ -137,14 +251,47 @@ func (c *Command) setup(ctx *Context) { newCmds = append(newCmds, scmd) } c.Commands = newCmds -} -func (c *Command) Run(cCtx *Context, arguments ...string) (err error) { + if c.Command(helpCommand.Name) == nil && !c.HideHelp { + if !c.HideHelpCommand { + helpCommand.HelpName = fmt.Sprintf("%s %s", c.HelpName, helpName) + c.appendCommand(helpCommand) + } - if !c.isRoot { - c.setup(cCtx) + if HelpFlag != nil { + c.appendFlag(HelpFlag) + } } + if !c.HideVersion { + c.appendFlag(VersionFlag) + } + + c.categories = newCommandCategories() + for _, command := range c.Commands { + c.categories.AddCommand(command.Category, command) + } + sort.Sort(c.categories.(*commandCategories)) + + c.flagCategories = newFlagCategories() + for _, fl := range c.Flags { + if cf, ok := fl.(CategorizableFlag); ok { + if cf.GetCategory() != "" { + c.flagCategories.AddFlag(cf.GetCategory(), cf) + } + } + } + + if len(c.SliceFlagSeparator) != 0 { + defaultSliceFlagSeparator = c.SliceFlagSeparator + } + + disableSliceFlagSeparator = c.DisableSliceFlagSeparator +} + +func (c *Command) run(cCtx *Context, arguments ...string) (err error) { + c.setup(cCtx) + a := args(arguments) set, err := c.parseFlags(&a, cCtx) cCtx.flagSet = set @@ -249,7 +396,7 @@ func (c *Command) Run(cCtx *Context, arguments ...string) (err error) { if isFlagName || (hasDefault && (defaultHasSubcommands && isDefaultSubcommand)) { argsWithDefault := cCtx.App.argsWithDefaultCommand(args) if !reflect.DeepEqual(args, argsWithDefault) { - cmd = cCtx.App.rootCommand.Command(argsWithDefault.First()) + cmd = cCtx.App.Command(argsWithDefault.First()) } } } @@ -262,7 +409,7 @@ func (c *Command) Run(cCtx *Context, arguments ...string) (err error) { if cmd != nil { newcCtx := NewContext(cCtx.App, nil, cCtx) newcCtx.Command = cmd - return cmd.Run(newcCtx, cCtx.Args().Slice()...) + return cmd.run(newcCtx, cCtx.Args().Slice()...) } if c.Action == nil { diff --git a/command_test.go b/command_test.go index 011ec85d90..f9a8d03d2e 100644 --- a/command_test.go +++ b/command_test.go @@ -43,7 +43,7 @@ func TestCommandFlagParsing(t *testing.T) { isRoot: true, } - err := command.Run(cCtx, c.testArgs...) + err := command.run(cCtx, c.testArgs...) expect(t, err, c.expectedErr) // expect(t, cCtx.Args().Slice(), c.testArgs) diff --git a/godoc-current.txt b/godoc-current.txt index 2a3dbb2e5b..719f45bf28 100644 --- a/godoc-current.txt +++ b/godoc-current.txt @@ -250,131 +250,21 @@ type AfterFunc func(*Context) error AfterFunc is an action to execute after any subcommands are run, but after the subcommand has finished it is run even if Action() panics -type App struct { - // The name of the program. Defaults to path.Base(os.Args[0]) - Name string - // Full name of command for help, defaults to Name - HelpName string - // Description of the program. - Usage string - // Text to override the USAGE section of help - UsageText string - // Description of the program argument format. - ArgsUsage string - // Version of the program - Version string - // Description of the program - Description string - // DefaultCommand is the (optional) name of a command - // to run if no command names are passed as CLI arguments. - DefaultCommand string - // List of commands to execute - Commands []*Command - // List of flags to parse - Flags []Flag - // Boolean to enable bash completion commands - EnableBashCompletion bool - // Boolean to hide built-in help command and help flag - HideHelp bool - // Boolean to hide built-in help command but keep help flag. - // Ignored if HideHelp is true. - HideHelpCommand bool - // Boolean to hide built-in version flag and the VERSION section of help - HideVersion bool - - // An action to execute when the shell completion flag is set - BashComplete BashCompleteFunc - // An action to execute before any subcommands are run, but after the context is ready - // If a non-nil error is returned, no subcommands are run - Before BeforeFunc - // An action to execute after any subcommands are run, but after the subcommand has finished - // It is run even if Action() panics - After AfterFunc - // The action to execute when no subcommands are specified - Action ActionFunc - // Execute this function if the proper command cannot be found - CommandNotFound CommandNotFoundFunc - // Execute this function if a usage error occurs - OnUsageError OnUsageErrorFunc - // Execute this function when an invalid flag is accessed from the context - InvalidFlagAccessHandler InvalidFlagAccessFunc - // List of all authors who contributed - Authors []*Author - // Copyright of the binary if any - Copyright string - // Reader reader to write input to (useful for tests) - Reader io.Reader - // Writer writer to write output to - Writer io.Writer - // ErrWriter writes error output - ErrWriter io.Writer - // ExitErrHandler processes any error encountered while running an App before - // it is returned to the caller. If no function is provided, HandleExitCoder - // is used as the default behavior. - ExitErrHandler ExitErrHandlerFunc - // Other custom info - Metadata map[string]interface{} - // Carries a function which returns app specific info. - ExtraInfo func() map[string]string - // CustomAppHelpTemplate the text template for app help topic. - // cli.go uses text/template to render templates. You can - // render custom help text by setting this variable. - CustomAppHelpTemplate string - // SliceFlagSeparator is used to customize the separator for SliceFlag, the default is "," - SliceFlagSeparator string - // DisableSliceFlagSeparator is used to disable SliceFlagSeparator, the default is false - DisableSliceFlagSeparator bool - // Boolean to enable short-option handling so user can combine several - // single-character bool arguments into one - // i.e. foobar -o -v -> foobar -ov - UseShortOptionHandling bool - // Enable suggestions for commands and flags - Suggest bool - // Allows global flags set by libraries which use flag.XXXVar(...) directly - // to be parsed through this library - AllowExtFlags bool - // Treat all flags as normal arguments if true - SkipFlagParsing bool - - // Has unexported fields. -} - App is the main structure of a cli application. It is recommended that an - app be created with the cli.NewApp() function +type App = Command func NewApp() *App NewApp creates a new cli Application with some reasonable defaults for Name, Usage, Version and Action. -func (a *App) Command(name string) *Command - Command returns the named command on App. Returns nil if the command does - not exist - -func (a *App) Run(arguments []string) (err error) +func (a *App) Run(arguments []string) error Run is the entry point to the cli app. Parses the arguments slice and routes to the proper flag/args combination -func (a *App) RunAndExitOnError() - RunAndExitOnError calls .Run() and exits non-zero if an error was returned - - Deprecated: instead you should return an error that fulfills cli.ExitCoder - to cli.App.Run. This will cause the application to exit with the given error - code in the cli.ExitCoder - -func (a *App) RunAsSubcommand(ctx *Context) (err error) - This is a stub function to keep public API unchanged from old code - - Deprecated: use App.Run or App.RunContext - func (a *App) RunContext(ctx context.Context, arguments []string) (err error) RunContext is like Run except it takes a Context that will be passed to its commands and sub-commands. Through this, you can propagate timeouts and cancellation requests -func (a *App) Setup() - Setup runs initialization code to ensure all data structures are ready - for `Run` or inspection prior to `Run`. It is internally called by `Run`, - but will return early if setup has already happened. - func (a *App) ToFishCompletion() (string, error) ToFishCompletion creates a fish completion string for the `*App` The function errors if either parsing or writing of the string fails. @@ -391,20 +281,6 @@ func (a *App) ToMarkdown() (string, error) ToMarkdown creates a markdown string for the `*App` The function errors if either parsing or writing of the string fails. -func (a *App) VisibleCategories() []CommandCategory - VisibleCategories returns a slice of categories and commands that are - Hidden=false - -func (a *App) VisibleCommands() []*Command - VisibleCommands returns a slice of the Commands with Hidden=false - -func (a *App) VisibleFlagCategories() []VisibleFlagCategory - VisibleFlagCategories returns a slice containing all the categories with the - flags they contain - -func (a *App) VisibleFlags() []Flag - VisibleFlags returns a slice of the Flags with Hidden=false - type Args interface { // Get returns the nth argument, or else a blank string Get(n int) string @@ -468,6 +344,11 @@ type Command struct { Description string // A short description of the arguments of this command ArgsUsage string + // Version of the program + Version string + // DefaultCommand is the (optional) name of a command + // to run if no command names are passed as CLI arguments. + DefaultCommand string // The category the command is part of Category string // The function to call when checking for bash command completions @@ -480,13 +361,23 @@ type Command struct { After AfterFunc // The function to call when this command is invoked Action ActionFunc + // Execute this function if the proper command cannot be found + CommandNotFound CommandNotFoundFunc // Execute this function if a usage error occurs. OnUsageError OnUsageErrorFunc + // Execute this function when an invalid flag is accessed from the context + InvalidFlagAccessHandler InvalidFlagAccessFunc + // List of all authors who contributed + Authors []*Author + // Copyright of the binary if any + Copyright string // List of child commands Commands []*Command // List of flags to parse Flags []Flag + // Boolean to enable bash completion commands + EnableBashCompletion bool // Treat all flags as normal arguments if true SkipFlagParsing bool // Boolean to hide built-in help command and help flag @@ -494,12 +385,16 @@ type Command struct { // Boolean to hide built-in help command but keep help flag // Ignored if HideHelp is true. HideHelpCommand bool + // Boolean to hide built-in version flag and the VERSION section of help + HideVersion bool // Boolean to hide this command from help or completion Hidden bool // Boolean to enable short-option handling so user can combine several // single-character bool arguments into one // i.e. foobar -o -v -> foobar -ov UseShortOptionHandling bool + // Enable suggestions for commands and flags + Suggest bool // Full name of command for help, defaults to full command name, including parent commands. HelpName string @@ -509,6 +404,31 @@ type Command struct { // render custom help text by setting this variable. CustomHelpTemplate string + // Reader reader to write input to (useful for tests) + Reader io.Reader + // Writer writer to write output to + Writer io.Writer + // ErrWriter writes error output + ErrWriter io.Writer + // ExitErrHandler processes any error encountered while running an App before + // it is returned to the caller. If no function is provided, HandleExitCoder + // is used as the default behavior. + ExitErrHandler ExitErrHandlerFunc + // Other custom info + Metadata map[string]any + // Carries a function which returns app specific info. + ExtraInfo func() map[string]string + // CustomAppHelpTemplate the text template for app help topic. + // cli.go uses text/template to render templates. You can + // render custom help text by setting this variable. + CustomAppHelpTemplate string + // SliceFlagSeparator is used to customize the separator for SliceFlag, the default is "," + SliceFlagSeparator string + // DisableSliceFlagSeparator is used to disable SliceFlagSeparator, the default is false + DisableSliceFlagSeparator bool + // Allows global flags set by libraries which use flag.XXXVar(...) directly + // to be parsed through this library + AllowExtFlags bool // Has unexported fields. } Command is a subcommand for a cli.App. @@ -525,8 +445,6 @@ func (c *Command) HasName(name string) bool func (c *Command) Names() []string Names returns the names including short names and aliases. -func (c *Command) Run(cCtx *Context, arguments ...string) (err error) - func (c *Command) VisibleCategories() []CommandCategory VisibleCategories returns a slice of categories and commands that are Hidden=false diff --git a/help_test.go b/help_test.go index d7299dfa79..5956113c82 100644 --- a/help_test.go +++ b/help_test.go @@ -1196,6 +1196,7 @@ func newContextFromStringSlice(ss []string) *Context { return &Context{flagSet: set} } +/* func TestHideHelpCommand_RunAsSubcommand(t *testing.T) { app := &App{ HideHelpCommand: true, @@ -1220,7 +1221,9 @@ func TestHideHelpCommand_RunAsSubcommand(t *testing.T) { t.Errorf("Run returned unexpected error: %v", err) } } +*/ +/* func TestHideHelpCommand_RunAsSubcommand_False(t *testing.T) { app := &App{ HideHelpCommand: false, @@ -1242,6 +1245,7 @@ func TestHideHelpCommand_RunAsSubcommand_False(t *testing.T) { t.Errorf("Run returned unexpected error: %v", err) } } +*/ func TestHideHelpCommand_WithSubcommands(t *testing.T) { app := &App{ diff --git a/internal/build/build.go b/internal/build/build.go index d9c16affa2..a8b99bfb3d 100644 --- a/internal/build/build.go +++ b/internal/build/build.go @@ -5,6 +5,7 @@ package main import ( "bufio" "bytes" + "context" "errors" "fmt" "io" @@ -14,9 +15,11 @@ import ( "net/url" "os" "os/exec" + "os/signal" "path/filepath" "runtime" "strings" + "syscall" "github.com/urfave/cli/v3" ) @@ -51,6 +54,9 @@ func main() { log.Fatal(err) } + ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) + defer cancel() + app := &cli.App{ Name: "builder", Usage: "Do a thing for urfave/cli! (maybe build?)", @@ -153,7 +159,7 @@ func main() { }, } - if err := app.Run(os.Args); err != nil { + if err := app.RunContext(ctx, os.Args); err != nil { log.Fatal(err) } } diff --git a/internal/example-cli/example-cli.go b/internal/example-cli/example-cli.go index 3e6e4dae4d..6b09da5f7d 100644 --- a/internal/example-cli/example-cli.go +++ b/internal/example-cli/example-cli.go @@ -3,9 +3,11 @@ package main import ( + "context" + "github.com/urfave/cli/v3" ) func main() { - (&cli.App{}).Run([]string{""}) + (&cli.App{}).RunContext(context.Background(), []string{""}) }