You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The CLI should panic with HALT! due to running the requested App.Before callback
Additional context
In my usage I rely heavily on the App.Before callback to setup required dependencies, fill in missing flag information, and validate state and configurations. This works well when the entrypoint is an existing or non existing flag/command. However the App.Before seems to be bypassed when printing help.
In my usage if the user were to try and print help while using the program in an invalid context, my Before callback cannot catch this to inform the user.
@dearchap thanks for the quick reply. I have other initialization logic that is meant to present to the user even under --help scenarios. Here is one contrived example where I would like to set a default for a flag that is not set, that is dynamic based on where the user is in the filesystem:
package main
import (
"fmt""log""os""github.com/urfave/cli/v2"
)
funcmain() {
app:=&cli.App{
Name: "boom",
Usage: "make an explosive entrance",
Before: before,
Action: func(*cli.Context) error {
fmt.Println("boom! I say!")
returnnil
},
Flags: []cli.Flag{
&cli.PathFlag{
Name: "project-dir",
Usage: "The path to the project",
},
},
}
iferr:=app.Run(os.Args); err!=nil {
log.Fatal(err)
}
}
funcbefore(c*cli.Context) error {
if!c.IsSet("project-dir") {
cwd, _:=os.Getwd()
c.Set("project-dir", cwd)
// other behaviors such as settings the default text of the flag so the// user knows what 'project-dir' is currently being operated on.
}
returnnil
}
My example works with any combination of valid or invalid commands / flags, but fails for the one case of calling --help where the user still may want to have specific defaults set and contextual information provided to them.
I did experiment with calling App.Setup prior to calling a function to do my checks/setup, but that call does not seem to do flag parsing so flag information is not filled in yet. I have an odd case where I'm hoping cli can do the heavy lifting of parsing flags for me, but I can interrupt prior to actually running any commands to do some additional setup and fiddling with the flag values and settings.
EDIT: I want to add that my cli project dynamically generates new commands and flags for the user based on a project configuration within a directory. Since --help is not injectable in the way i'm describing, i'm unable to load a settings file to know what custom user-defined commands and flags are available to show to the user when issuing --help
@rdlaitila In this case you should actually define the flag as
cwd, _ := os.Getcwd()
app := &App {
....
Flags: []cli.Flag{
&cli.PathFlag{
Name: "project-dir",
Usage: "The path to the project",
Value: cwd,
},
},
This way you know that the default for project-dir will be cwd if nothing is set. If you have other flags which you want to fill in dynamically depending on contents on project-dir you should default them similarly. Of course that would sort of preclude creating flags dynamically. I'm sorry but at this point there are no other hooks available to do what you want.
@dearchap thanks for the follow up. Per your original suggestion I ended up moving almost all of my dynamic initialization logic outside of the App's setup/bootstrapping hooks. This ensured that any dynamic commands and flags that are defined by a user supplied config file are present in the App's declarative structure prior to calling into App.Run (by parsing the config file and generating new flag/command structs).
The downside to this is my initialization logic becomes a imperative shell around urfave/cli's declarative behavior for which I was hoping to avoid. I really enjoy the declarative nature of urfave/cli and was hoping I could have the right pre/before/bootstrap/setup hooks on the app, flag, or command levels to dynamically generate and mutate the structure of the App and sub components prior to the app actually running, even in the --help scenario I detailed earlier.
Again the only big hiccup I had was in order for my project to parse a config file with user defined flags/commands, I need to locate said config file prior to parsing. So I need to manually parse a few flags and environment variables prior to flag/command generation ie: --project-file to know what to load. So a bit of a chicken and egg problem where I need cli structure to parse --project-file and at the same time generate new cli structures prior to the full CLI being executed.
Below is what I could imagine as a way to hook into the CLI bootstrap/setup lifecycle to eagerly get values before the full CLI is parsed ready for execution. Here we introduce a Build callback that allows injecting actions into the flag/command graph while its being parsed:
package main
varvalues=struct{
ProjectDir*stringProjectFile*stringProject*Project
}{}
varapp=&cli.App{
Name: "A Dynamic CLI App",
Flags: []cli.Flag{
&cli.PathFlag{
Name: "project-dir",
Env: ["PROJECT_DIR"],
Usage: "Path to the project directory. defaults to current directory"Destination: values.ProjectDir,
Build: func(c*cli.FlagBuildCtx) error {
// set value if the flag was not found or parsed via flag or env varif!c.Parsed() {
cwd, _:=os.Cwd()
c.SetValue(cwd)
}
returnnil
}
},
&cli.PathFlag{
Name: "project-file",
Env: ["PROJECT_FILE"],
Usage: "Path to the project file. defaults to {project-dir}/config.yaml"Destination: values.ProjectFile,
Build: func(c*cli.FlagBuildCtx) error {
// set value if the flag was not found or pasred via flag or env varif!c.Parsed() {
path:=strings.Join([]string{values.ProjectDir, "config.yaml"}, "/")
c.SetValue(path)
} else {
c.SetValue(c.ParsedValue)
}
project, _:=parseConfigFile(values.ProjectFile)
values.Project=projectreturnnil
}
}
}
Commands: []*cli.Command{
&cli.Command{
Name: "init",
Usage: "Initializes a new project",
Action: func(c*cli.Context) error {
...
}
},
},
Build: func(c*cli.AppBuildCtx) error {
c.AfterFlagBuilt("project-file", func(cf*cli.FlagBuildCtx) error {
for_, cmd:=rangevalues.Project.Commands {
c.AddCommand(&cli.Command{
Name: cmd.Name,
Usage: cmd.usage,
Build: buildProjectCommand,
})
}
})
}
}
funcparseConfigFile(pathstring) error {
...
}
funcbuildProjectCommand(c*cli.CommandBuildCtx) error {
...
}
Each component type has a specific build context called in whatever order urfave/cli handles the go structs:
cli.AppBuildCtx
cli.FlagBuildCtx
cli.CommandBuildCtx
Each build context can have lifecycle or other component specific actions:
Just a rough take on the idea to hook into urfave/cli's bootstraping logic and move any imperative bits into urfave/cli's declarative structure. This all runs pre command invocation (even in --help scenarios).
No expectations here just what I think would solve my advanced use case. Thanks again for the discussion!
This is a really important and nice feature. I also need to have "app.Before" called before "--help" to prepare some dynamic contents for the help output.
Update: with HelpFlag=nil, I think this feature is not a must. Because developer can totally disable the bultin help action, and use their own code to handle helps.
A new case to use beforeFun() before --help flag is to set up a language for that help-information.
And also it is really strange, that help as a function calls befireFuns(), but help as a flag - is not, imho.
UPDATE:
Version: github.com/urfave/cli/v3 v3.3.8
Also, if i add new BoolFlag with Name = "help", it adds without problems. And it is shown in help-message, but you can't call it, cause standard --help is in prior. And you can do it for every command, not only for app. And copied flag several types.
Activity
dearchap commentedon Apr 11, 2023
@rdlaitila This is by design. Can you provide an example of when you need a Before callback to be triggered when "--help" is invoked. ?
rdlaitila commentedon Apr 11, 2023
@dearchap thanks for the quick reply. I have other initialization logic that is meant to present to the user even under
--helpscenarios. Here is one contrived example where I would like to set a default for a flag that is not set, that is dynamic based on where the user is in the filesystem:My example works with any combination of valid or invalid commands / flags, but fails for the one case of calling
--helpwhere the user still may want to have specific defaults set and contextual information provided to them.I did experiment with calling
App.Setupprior to calling a function to do my checks/setup, but that call does not seem to do flag parsing so flag information is not filled in yet. I have an odd case where I'm hoping cli can do the heavy lifting of parsing flags for me, but I can interrupt prior to actually running any commands to do some additional setup and fiddling with the flag values and settings.EDIT: I want to add that my cli project dynamically generates new commands and flags for the user based on a project configuration within a directory. Since
--helpis not injectable in the way i'm describing, i'm unable to load a settings file to know what custom user-defined commands and flags are available to show to the user when issuing--helpdearchap commentedon Apr 12, 2023
@rdlaitila In this case you should actually define the flag as
This way you know that the default for project-dir will be cwd if nothing is set. If you have other flags which you want to fill in dynamically depending on contents on project-dir you should default them similarly. Of course that would sort of preclude creating flags dynamically. I'm sorry but at this point there are no other hooks available to do what you want.
dearchap commentedon Jun 19, 2023
@rdlaitila I am revisiting this in the hope of adding more callback hooks. What kind of hooks would you like to see ?
rdlaitila commentedon Jun 19, 2023
@dearchap thanks for the follow up. Per your original suggestion I ended up moving almost all of my dynamic initialization logic outside of the
App's setup/bootstrapping hooks. This ensured that any dynamic commands and flags that are defined by a user supplied config file are present in the App's declarative structure prior to calling intoApp.Run(by parsing the config file and generating new flag/command structs).The downside to this is my initialization logic becomes a imperative shell around
urfave/cli's declarative behavior for which I was hoping to avoid. I really enjoy the declarative nature ofurfave/cliand was hoping I could have the right pre/before/bootstrap/setup hooks on the app, flag, or command levels to dynamically generate and mutate the structure of theAppand sub components prior to the app actually running, even in the--helpscenario I detailed earlier.Again the only big hiccup I had was in order for my project to parse a config file with user defined flags/commands, I need to locate said config file prior to parsing. So I need to manually parse a few flags and environment variables prior to flag/command generation ie:
--project-fileto know what to load. So a bit of a chicken and egg problem where I need cli structure to parse--project-fileand at the same time generate new cli structures prior to the full CLI being executed.Below is what I could imagine as a way to hook into the CLI bootstrap/setup lifecycle to eagerly get values before the full CLI is parsed ready for execution. Here we introduce a
Buildcallback that allows injecting actions into the flag/command graph while its being parsed:Each component type has a specific build context called in whatever order
urfave/clihandles the go structs:cli.AppBuildCtxcli.FlagBuildCtxcli.CommandBuildCtxEach build context can have lifecycle or other component specific actions:
AppBuildCtx.AfterFlagBuilt(flag string, func)AppBuildCtx.AfterCommandBuilt(command string, func)AppBuildCtx.AddFlag(cli.Flag)AppBuildCtx.AddCommand(*cli.Command)CommandBuildCtx.AfterFlagBuild(flag string, func)CommandBuildCtx.AfterSubCommandBuilt(command string, func)CommandBuildCtx.AddFlag(cli.Flag)CommandBuildCtx.AddSubCommand(*cli.Command)Just a rough take on the idea to hook into
urfave/cli's bootstraping logic and move any imperative bits intourfave/cli's declarative structure. This all runs pre command invocation (even in--helpscenarios).No expectations here just what I think would solve my advanced use case. Thanks again for the discussion!
wxiaoguang commentedon Jun 28, 2023
This is a really important and nice feature. I also need to have "app.Before" called before "--help" to prepare some dynamic contents for the help output.
Thank you very much.
wxiaoguang commentedon Jul 23, 2023
Update: with HelpFlag=nil, I think this feature is not a must. Because developer can totally disable the bultin help action, and use their own code to handle helps.
Olimpian commentedon Jul 29, 2025
A new case to use beforeFun() before --help flag is to set up a language for that help-information.
And also it is really strange, that help as a function calls befireFuns(), but help as a flag - is not, imho.
UPDATE:
Version: github.com/urfave/cli/v3 v3.3.8
Also, if i add new BoolFlag with Name = "help", it adds without problems. And it is shown in help-message, but you can't call it, cause standard --help is in prior. And you can do it for every command, not only for app. And copied flag several types.
get if you call
app command1 --helpyou will see:
And you can create similarly named flags of any type or name.
I think it's a bug...