diff --git a/README.md b/README.md index 66311c46..47730592 100644 --- a/README.md +++ b/README.md @@ -641,7 +641,7 @@ command.SetUsageTemplate(s string) ## PreRun or 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 inherrited by children if they do not declare their own. These function are run in the following order: +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`. These function are run in the following order: - `PersistentPreRun` - `PreRun` @@ -649,7 +649,35 @@ It is possible to run functions before or after the main `Run` function of your - `PostRun` - `PersistentPostRun` -An example of two commands which use all of these features is below. When the subcommand is executed, it will run the root command's `PersistentPreRun` but not the root command's `PersistentPostRun`: +The `Persistent*Run` functions have two modes of operation: + +1. ChainedMode Enabled + - `PersistentPreRun` will run all the defined (non nil) `PersistentPreRun` functions from root to child (both included). + - `PersistentPostRun` will run all the defined (non nil) `PersistPostRun` functions from child to root (both included). +1. ChainedMode Disabled + - `PersistentPreRun` and `PersistentPostRun` will only run the nearest ancestor defined (non nil function) closest to the child. The child is included - meaning, if it defines a Persistent function, only that function is called. + +NOTE: ChainedMode is disabled by default in version 1.0 and will be enabled in version 2.0. In version 3.0 it will cease to exist, and ChainedMode will always be enabled. +The ChainedMode can be Enabled or Disabled once at import time as follows +```go +package main +import "github.com/spf13/cobra" + +func init() { + if err := cobra.EnableChainHooks(); err != nil { + panic(err) + } +} + +// cobra.DisableChainHooks() can be used to disable it +``` +The `EnableChainHooks` or `DisableChainHooks` can be called only once at import time. Multiple calls will return errors and have no change in functionality. + + +An example of two commands which use all of these features is below. +In ChainedMode Disabled: When the subcommand is executed, it will run the root command's `PersistentPreRun` but not the root command's `PersistentPostRun` + +In ChainedMode Enabled: When the subcommand is executed, it will run the root command's `PersistentPreRun`, child command's `PersistentPostRun` and root command's `PersistentPostRun`. ```go package main diff --git a/command.go b/command.go index 9b7a006c..d17bdcfa 100644 --- a/command.go +++ b/command.go @@ -26,6 +26,64 @@ import ( flag "github.com/spf13/pflag" ) +// useChainHooks determines the behaviour of the Persistent*Run functions. +// if true, then PersistentPreRun will run all defined PersistentPreRun +// functions from root to child, and PersistentPostRun will +// run all defined PersistentPostRun functions from child to root. +// if false, it will only run the nearest ancestor of the child that is defined +// i.e.if the child has its own Persistent*Run function, only that is executed. +// TODO cobra v1 by default keeps useChainHooks as false +// TODO in v2, make useChainHooks true +// TODO in v3, remove useChainHooks completely and use the logic +// as if it were true +// Developers may programmatically Enable / Disable this feature only once +// at the root level. +var useChainHooks bool = false + +// To prevent multiple calls to it. +var useChainHooksTouched bool = false + +// EnableChainHooks allows Persistent*Run functions to chain. +// Enable/DisableChainHooks can be called only once at the beginning. +func EnableChainHooks() error { + if useChainHooksTouched { + return fmt.Errorf("Toggle useChainHooks can be done only once !") + } + useChainHooks = true + useChainHooksTouched = true + return nil +} + +// DisableChainHooks makes Persistent*Run functions to call only nearest +// ancestor. +// Enable/DisableChainHooks can be called only once at the beginning. +func DisableChainHooks() error { + if useChainHooksTouched { + return fmt.Errorf("Toggle useChainHooks can be done only once !") + } + useChainHooks = false + useChainHooksTouched = true + return nil +} + +func init() { + useChainHooksTouched = false + // assuming this is cobra v1.0, default behaviour of persistent functions + // is to call the nearest ancestor i.e. not to use chain hooks + DisableChainHooks() + + // TODO in cobra v2.0, the default behaviour of persistent functions + // is to chain i.e. use chain hooks + // EnableChainHooks() + + // Allow the user to be able to modify this once at root level. + useChainHooksTouched = false + + // TODO in cobra v3.0, remove all signs of useChainHooks and + // useChainHooksTouched. Persistent functions should behave as if + // useChainHooks is true. +} + // Command is just that, a command for your application. // eg. 'go run' ... 'run' is the command. Cobra requires // you to define the usage and description as part of your command @@ -501,6 +559,43 @@ func (c *Command) Root() *Command { return findRoot(c) } +// execPersistentPreRunFuncs executes the PreRun functions from root to child. +// useChainHooks determines whether to run all the functions from root to child +// or just the nearest ancestor to the child (included) that has been defined. +// TODO to allow migration for existing users - This useChainHooks has been provided +// Remove logic when useChainHooks is false in cobra v3.0 +func (c *Command) execPersistentPreRunFuncs(cmd *Command, argWoFlags []string) error { + // TODO remove this once useChainHooks is no longer required + if !useChainHooks { + for p := cmd; p != nil; p = p.Parent() { + if p.PersistentPreRunE != nil { + return p.PersistentPreRunE(cmd, argWoFlags) + } else if p.PersistentPreRun != nil { + p.PersistentPreRun(cmd, argWoFlags) + return nil + } + } + return nil + } + + // always run from root to child + if parent := c.Parent(); parent != nil { + if err := parent.execPersistentPreRunFuncs(cmd, argWoFlags); err != nil { + return err + } + } + // run the childs PersistentPreRun function + var err error = nil + switch { + case c.PersistentPreRunE != nil: + err = c.PersistentPreRunE(cmd, argWoFlags) + case c.PersistentPreRun != nil: + c.PersistentPreRun(cmd, argWoFlags) + } + + return err +} + // ArgsLenAtDash will return the length of f.Args at the moment when a -- was // found during arg parsing. This allows your program to know which args were // before the -- and which came after. (Description from @@ -542,17 +637,10 @@ func (c *Command) execute(a []string) (err error) { c.preRun() argWoFlags := c.Flags().Args() - for p := c; p != nil; p = p.Parent() { - if p.PersistentPreRunE != nil { - if err := p.PersistentPreRunE(c, argWoFlags); err != nil { - return err - } - break - } else if p.PersistentPreRun != nil { - p.PersistentPreRun(c, argWoFlags) - break - } + if err := c.execPersistentPreRunFuncs(c, argWoFlags); err != nil { + return err } + if c.PreRunE != nil { if err := c.PreRunE(c, argWoFlags); err != nil { return err @@ -575,16 +663,9 @@ func (c *Command) execute(a []string) (err error) { } else if c.PostRun != nil { c.PostRun(c, argWoFlags) } - for p := c; p != nil; p = p.Parent() { - if p.PersistentPostRunE != nil { - if err := p.PersistentPostRunE(c, argWoFlags); err != nil { - return err - } - break - } else if p.PersistentPostRun != nil { - p.PersistentPostRun(c, argWoFlags) - break - } + + if err := c.execPersistentPostRunFuncs(c, argWoFlags); err != nil { + return err } return nil @@ -596,6 +677,45 @@ func (c *Command) preRun() { } } +// execPersistentPostRunFuncs executes the PostRun functions from child to root. +// useChainHooks determines whether to run all the functions from child to root +// or just the nearest ancestor to the child (included) that has been defined. +// TODO to allow migration for existing users - This useChainHooks has been provided +// Remove logic when useChainHooks is false in cobra v3.0 +func (c *Command) execPersistentPostRunFuncs(cmd *Command, argWoFlags []string) error { + // TODO remove this once useChainHooks is no longer required + if !useChainHooks { + for p := cmd; p != nil; p = p.Parent() { + if p.PersistentPostRunE != nil { + return p.PersistentPostRunE(cmd, argWoFlags) + } else if p.PersistentPostRun != nil { + p.PersistentPostRun(cmd, argWoFlags) + return nil + } + } + return nil + } + + // always run from child to root + var err error = nil + switch { + case c.PersistentPostRunE != nil: + err = c.PersistentPostRunE(cmd, argWoFlags) + case c.PersistentPostRun != nil: + c.PersistentPostRun(cmd, argWoFlags) + } + + if err != nil { + return err + } + + if parent := c.Parent(); parent != nil { + return parent.execPersistentPostRunFuncs(cmd, argWoFlags) + } + + return nil +} + func (c *Command) errorMsgFromParse() string { s := c.flagErrorBuf.String()