Documentation
¶
Index ¶
- Variables
- func AbsolutePath(in string) string
- func AskConfirmation(label string, args ...any) (answeredYes bool, wasAnswered bool)
- func ConfigureViperForCommand(root *cobra.Command, envPrefix string)
- func CopyFile(inPath, outPath string)
- func Dedent(input string) string
- func Description(value string) description
- func DirectoryExists(path string) bool
- func Ensure(condition bool, message string, args ...any)
- func Example(value string) example
- func ExamplePrefixed(prefix string, examples string) example
- func Execute(f func(cmd *cobra.Command, args []string) error) execute
- func Exit(code int)
- func ExitHandler(id string, onExit func(code int))
- func FileExists(path string) bool
- func FlagDescription(in string, args ...interface{}) string
- func FlagDescriptionOneLine(in string, args ...interface{}) string
- func GetDisplayVersion(version string) string
- func MaybePrompt[T any](label string, transformer PromptTransformer[T], opts ...PromptOption) (T, error)
- func MaybePromptConfirm(label string, opts ...PromptOption) (answer bool, wasAnswered bool, err error)
- func MaybePromptSelect[T any](label string, items []string, transformer PromptTransformer[T], ...) (T, error)
- func NoError(err error, message string, args ...any)
- func Prompt[T any](label string, transformer PromptTransformer[T], opts ...PromptOption) T
- func PromptConfirm(label string, opts ...PromptOption) (answer bool, wasAnswered bool)
- func PromptRaw(label string, opts ...PromptOption) (answer string, err error)
- func PromptSelect[T any](label string, items []string, transformer PromptTransformer[T], ...) T
- func Quit(message string, args ...any)
- func ReadFile(name string) string
- func Root(usage, short string, opts ...CommandOption) *cobra.Command
- func Run(usage, short string, opts ...CommandOption)
- func SetLogger(newZlog *zap.Logger, newTracer logging.Tracer)
- func SetupSignalHandler(unreadyPeriodDelay time.Duration, logger *zap.Logger) (receiveOutgoingSignals <-chan os.Signal, ...)
- func ToCobraCmd(cliCmd CommandOption) *cobra.Command
- func UserHomeDirectory() string
- func WorkingDirectory() string
- func WriteFile(name string, content string, args ...any)
- type AfterAllHook
- type Application
- func (a *Application) BlockUntilTerminated(logger *zap.Logger, gracefulShutdownDelay time.Duration) error
- func (a *Application) Context() context.Context
- func (a *Application) IsReady() bool
- func (a *Application) Supervise(child Shutter)
- func (a *Application) SuperviseAndStart(child Shutter)
- func (a *Application) SuperviseAndStartUsing(child Shutter, runner any)
- func (a *Application) WaitForTermination(logger *zap.Logger, unreadyPeriodDelay, gracefulShutdownDelay time.Duration) error
- type Args
- type BeforeAllHook
- type CommandOption
- func Command(execute func(cmd *cobra.Command, args []string) error, usage, short string, ...) CommandOption
- func ConfigureVersion(version string) CommandOption
- func ConfigureViper(envPrefix string) CommandOption
- func Group(usage, short string, opts ...CommandOption) CommandOption
- func OnCommandError(onError func(err error)) CommandOption
- func OnCommandErrorLogAndExit(logger *zap.Logger) CommandOption
- type CommandOptionFunc
- type Flags
- type OnCommandErrorHandler
- type PersistentFlags
- type PromptOption
- type PromptSelectOption
- type PromptTransformer
- type Runnable
- type RunnableContext
- type RunnableContextError
- type RunnableError
- type Shutter
Constants ¶
This section is empty.
Variables ¶
var ( PromptTypeOptionalString = func(x string) (*string, error) { if x == "" { return nil, nil } return &x, nil } PromptTypeString = func(x string) (string, error) { return x, nil } PromptTypeInt = strconv.Atoi PromptTypeInt64 = func(x string) (int64, error) { return strconv.ParseInt(x, 0, 64) } PromptTypeUint64 = func(x string) (uint64, error) { return strconv.ParseUint(x, 0, 64) } PromptTypeYesNo = func(in string) (bool, error) { in = strings.ToLower(in); return in == "y" || in == "yes", nil } )
Various prompt transformer declared like you are using standard type and generics make the rest
To be used like:
PromptT("Input your age", opts, PromptTypeUint64)
var ( PrompValidateString = func(x string) error { return nil } PrompValidateInt = func(x string) error { _, err := strconv.Atoi(x); return err } PrompValidateInt64 = func(x string) error { _, err := strconv.ParseInt(x, 0, 64); return err } PrompValidateUint64 = func(x string) error { _, err := strconv.ParseUint(x, 0, 64); return err } PrompValidateYesNo = func(in string) error { if !confirmPromptRegex.MatchString(in) { return errors.New("answer with y/yes/Yes or n/no/No") } return nil } )
Various prompt validation declared like you are using standard validation.
To be used like:
cli.PromptT("Input your age", cli.PromptTypeUint64, cli.WithPromptValidate("invalid age", cli.PrompValidateUint64))
var ErrorStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("1"))
var HeaderStyle = lipgloss.NewStyle().Bold(true)
var OnAssertionFailure func(message string)
OnAssertionFailure is a global variable that can be overriden to control how the program should print/process when cli module assertion fail.
The message can be "" in which case it should not be printed/logged.
If your handler does not exit by itself, a call to `cli.Exit(1)` is performed after the handler has executed.
If you exit yourself, you should use `cli.Exit(code)` so that exit handlers are called if any present.
var PurpleStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("12"))
var QuestionTextStyle = HeaderStyle
var ReboundFlagAnnotation = "github.com/streamingfast/cli#rebound-key"
var WarningStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("11"))
Functions ¶
func AbsolutePath ¶
func AskConfirmation ¶
AskConfirmation prompts the user for a yes/no confirmation.
If 'label' is a multiple line string, it will be split into separate lines and each line will be rendered with the appropriate style but once answered, only the final line changes, the previous written lines are not affected.
This is why that rendering wise, favoring single line for 'label' will offer a better user experience.
func ConfigureViperForCommand ¶
ConfigureViperForCommand sets env prefix to 'prefix', automatic env to check in env for any flags coming from anywhere (flag, config, default, etc.) as well as scoping flags to the command it's defined in for global acces.
func Description ¶
func Description(value string) description
func DirectoryExists ¶
func Ensure ¶
Ensure checks the condition and if it is false, it will call `cli.Quit` with the message effectively exiting the program with code 1.
func ExamplePrefixed ¶
func Exit ¶
func Exit(code int)
Exit executes the registered exit handlers and then call `os.Exit(code)`. This can be used to implement a trap behavior. Of course, you must ensure that `os.Exit` are all done through `cli.Exit()` otherwise we will not be invoked.
**Caveats** Panics are not recovered by those exit handlers, right now you need to implement your own trapping and then exit through `cli.Exit`.
This library use `cli.Exit(code)` throughout so quitting due to `cli.NoError` or `cli.Ensure` will correctly call the exit handlers.
func ExitHandler ¶
ExitHandler registers or unregisters an exit handler. If the `onExit` received is nil, unregister the handler with given `id`. Otherwise, register or update an existing one.
No collision is checked, so an id overrides any previously existing id. This library is meant to be used on final CLI product, you should have low number of exit handler, pick your id and keep them short.
func FileExists ¶
func FlagDescription ¶
FlagDescription accepts a multi line indented description and transform it into a multi line flag description.
func FlagDescriptionOneLine ¶
FlagDescriptionOneLine accepts a multi line indented description and transform it into a single line flag description. This method is used to make it easier to define long flag messages.
func GetDisplayVersion ¶
GetDisplayVersion is a helper function that can be used to get the same version string as the one configured by ConfigureVersion in other places of your application, in logging for example.
This string is the same that a user will see when doing `<app> --version` on the CLI.
It panics if it cannot retrieve the build info debug.ReadBuildInfo, which should not happen in normal conditions as the build info is always available in Go binaries built with module support, which is the default since Go 1.18.
func MaybePrompt ¶
func MaybePrompt[T any](label string, transformer PromptTransformer[T], opts ...PromptOption) (T, error)
MaybePrompt is just like Prompt but may return an error
func MaybePromptConfirm ¶
func MaybePromptConfirm(label string, opts ...PromptOption) (answer bool, wasAnswered bool, err error)
MaybePromptConfirm is just like PromptConfirm but returns an error instead of panicking.
func MaybePromptSelect ¶
func MaybePromptSelect[T any](label string, items []string, transformer PromptTransformer[T], opts ...PromptSelectOption) (T, error)
func NoError ¶
NoError checks if the error is nil, and if it is not, it will call `cli.Quit` with the message effectively exiting the program with code 1.
func Prompt ¶
func Prompt[T any](label string, transformer PromptTransformer[T], opts ...PromptOption) T
Prompt will ask the following "label" question to the user and will transform the received `string` into the generic type T via the `transformer` function. There is a few predefined transformer for common types like `PromptTypeString`, `PromptTypeInt`, `PromptTypeInt64`, `PromptTypeUint64`, `PromptTypeYesNo`. Using the right transformer will automatically infers the right T genric type.
userID := cli.Prompt("Please enter the user ID to issue the token to", cli.PromptTypeString)
In this case, the `userID` variable will be of type `string`.
If 'label' is a multiple line string, it will be split into separate lines and each line will be rendered with the appropriate style but once answered, only the final line changes, the previous written lines are not affected.
This is why that rendering wise, favoring single line for 'label' will offer a better user experience.
func PromptConfirm ¶
func PromptConfirm(label string, opts ...PromptOption) (answer bool, wasAnswered bool)
PromptConfirm is just like Prompt but enforce `IsConfirm` and returns a boolean which is either `true` for yes answer or `false` for a no answer.
func PromptSelect ¶
func PromptSelect[T any](label string, items []string, transformer PromptTransformer[T], opts ...PromptSelectOption) T
func Quit ¶
Quit prints the message and exits the program with code 1. If OnAssertionFailure is set, it will be called with the message instead of printing it to stdout.
If you want to exit with a different code, use `cli.Exit(code)` instead.
func Run ¶
func Run(usage, short string, opts ...CommandOption)
func SetupSignalHandler ¶
func SetupSignalHandler(unreadyPeriodDelay time.Duration, logger *zap.Logger) (receiveOutgoingSignals <-chan os.Signal, hasBeenSignaled, waitedFullDelay *atomic.Bool)
SetupSignalHandler registers a signal handler for SIGINT and SIGTERM and returns a channel to receive determines if the service has been signaled to shutdown gracefully as well as 2 atomic booleans.
The first one `hasBeenSignaled` determines if the service has been signaled already, this could be true before the channel is notified if `unreadyPeriodDelay > 0`. The usefulness of this is to change the readiness of your app based on the value in `hasBeenSignaled`. This way, your app is unready as soon as it has been signaled to shutdown which helps a lot in Kubernetes and other orchestration systems. See the `unreadyPeriodDelay` parameter details below for more information.
The second one `waitedFullDelay` determines if the service has waited the full delay period before notifying the channel. This will be `true` unless `unreadyPeriodDelay > 0` and that `Ctrl-C` was pressed 4 times or more which forces a kill `os.Exit(1)`!
The parameter `unreadyPeriodDelay` influences when you will be notified of the signal through the channel. If you set it to 0, you will be notified immediately. If you set it to 5 seconds, you will be notified of the signal after 5 seconds.
The usefulness of this is to give your app some time to become unready before it is killed. This is important for some orchestration systems like Kubernetes where you can use this delay to mark your app as unready by checking the value of `hasBeenSignaled` while your app continues to work properly. This will give Kubernetes `unreadyPeriodDelay` time to stop sending traffic and removing from the active list of endpoints.
This should usually be used for HTTP/gRPC servers, for other types of apps, you can set this to 0 to avoid this needlessly waiting period.
func ToCobraCmd ¶
func ToCobraCmd(cliCmd CommandOption) *cobra.Command
ToCobraCmd converts a CommandOption to a cobra.Command suitable to be added to another "parent" command using the `AddCommand` method.
func UserHomeDirectory ¶
func UserHomeDirectory() string
func WorkingDirectory ¶
func WorkingDirectory() string
Types ¶
type AfterAllHook ¶
func (AfterAllHook) Apply ¶
func (f AfterAllHook) Apply(cmd *cobra.Command)
type Application ¶
type Application struct {
// contains filtered or unexported fields
}
Application is a simple object that can be used to manage the lifecycle of an application. You create the application with `NewApplication` and then you can use it to supervise and start child processes by using `SuperviseAndStart`.
You then wait for the application to terminate by calling `WaitForTermination`. This call is blocking and register a signal handler to be notified of SIGINT and SIGTERM.
func NewApplication ¶
func NewApplication(ctx context.Context) *Application
func (*Application) BlockUntilTerminated ¶
func (a *Application) BlockUntilTerminated(logger *zap.Logger, gracefulShutdownDelay time.Duration) error
BlockUntilTerminated waits for the application to terminate. This is a blocking call that will wait for the application to be terminated. After receiving the initial terminating signal, if gracefulShutdownDelay is not 0, the terminated signal must happen within the gracefulShutdownDelay otherwise the call will unblock.
func (*Application) Context ¶
func (a *Application) Context() context.Context
func (*Application) IsReady ¶
func (a *Application) IsReady() bool
IsReady returns true if the application is ready to be used. When the Ctrl-C signal is received, the app is immediately marked as not ready and the `WaitForTermination` call will wait for the unreadyPeriodDelay to expire before terminating the application.
func (*Application) Supervise ¶
func (a *Application) Supervise(child Shutter)
Supervise the received child shutter, mainly, this ensures that on child's termination, the application is also terminated with the error that caused the child to terminate.
If the application shuts down before the child, the child is also terminated but gracefully (it does **not** receive the error that caused the application to terminate).
The child termination is always performed before the application fully complete, unless the gracecul shutdown delay has expired.
func (*Application) SuperviseAndStart ¶
func (a *Application) SuperviseAndStart(child Shutter)
SuperviseAndStart calls [Supervise] and then starts the child in a goroutine. The received child must implement one of Runnable, RunnableContext, RunnableError or RunnableContextError to be able to be started correctly.
The child is started in a goroutine and tied to the application lifecycle because we also called [Supervise]. Later the call to `WaitForTermination` will wait for the application to terminate which will also terminates and wait for all child.
People can use either [SuperviseAndStart] or [SuperviseAndStartUsing] and passing `child.Run` explicitly depending on their preference, see [SuperviseAndStartUsing] for details.
func (*Application) SuperviseAndStartUsing ¶
func (a *Application) SuperviseAndStartUsing(child Shutter, runner any)
SuperviseAndStartUsing calls [Supervise] and then starts the child in a goroutine using the provided runner function. The runner function must be one of the following types:
func() func(ctx context.Context) func() error func(ctx context.Context) error
This can be used to make it more explicit what is the runner function that is started, making the `Run` method apparent at call site. Compare:
a.SuperviseAndStart(child)
with
a.SuperviseAndStartUsing(child, child.Run)
In the second case, you can "<key>-click" to go to the `Run` method of the child directly, while in the first case, you have to search, the linking is not as direct.
People can use either [SuperviseAndStart] or [SuperviseAndStartUsing] depending on their preference.
func (*Application) WaitForTermination ¶
func (a *Application) WaitForTermination(logger *zap.Logger, unreadyPeriodDelay, gracefulShutdownDelay time.Duration) error
WaitForTermination waits for the application to terminate. This first setup the signal handler and then wait for either the signal handler to be notified or the application to be terminating.
On application terminating, all child registered with [Supervise] are also terminated. We then wait for all child to gracefully terminate. If the graceful shutdown delay is reached, we force the termination of the application right now.
Doing Ctrl-C 4 times or more will lead to a force quit of the whole process by calling `os.Exit(1)`, this is performed by the signal handler code and is does **not** respect the graceful shutdown delay in this case.
type Args ¶
type Args cobra.PositionalArgs
func ExactValidArgs ¶
ExactValidArgs returns an error if there are not exactly N positional args OR there are any positional args that are not in the `ValidArgs` field of `Command`
func MaximumNArgs ¶
MaximumNArgs returns an error if there are more than N args.
func MinimumNArgs ¶
MinimumNArgs returns an error if there is not at least N args.
func OnlyValidArgs ¶
func OnlyValidArgs() Args
OnlyValidArgs returns an error if any args are not in the list of ValidArgs.
type BeforeAllHook ¶
func (BeforeAllHook) Apply ¶
func (f BeforeAllHook) Apply(cmd *cobra.Command)
type CommandOption ¶
func Command ¶
func Command(execute func(cmd *cobra.Command, args []string) error, usage, short string, opts ...CommandOption) CommandOption
func ConfigureVersion ¶
func ConfigureVersion(version string) CommandOption
ConfigureVersion is an option that configures the `cobra.Command#Version` field automatically based fetching commit revision and date build from Golang available built-in build info.
The version formatted output can take different forms depending on the state of 'vcs.revision' availability, 'vcs.time' availability and received 'version':
if vcs.revision == "" && vcs.time == "" return "{version}"
if vcs.revision != "" && vcs.time == "" return "{version} (Commit {vcs.revision[0:7]})"
if vcs.revision == "" && vcs.time == "" return "{version} (Commit Date {vcs.date})"
if vcs.revision != "" && vcs.time != "" return "{version} (Commit {vcs.revision[0:7]}, Commit Date {vcs.date})"
func ConfigureViper ¶
func ConfigureViper(envPrefix string) CommandOption
ConfigureViper installs an AfterAllHook on the cobra.Command that rebind all your flags into viper with a new layout.
Persistent flags on a root command can be accessed with `global-<flag>` Persistent flags on a sub-command can be accessed with `<cmd1>-<cmd2>-global-<flag>` where `<cmd1>-<cmd2>` is the command fully qualified path (see below for more details). Standard flags on a command can be accessed with `<cmd1>-<cmd2>-<flag>` where `<cmd1>-<cmd2>` is the command fully qualified path (see below for more details).
For the following config:
Root("acme", "CLI sample application",
PersistentFlags(func(flags *pflag.FlagSet) { flags.String("auth", "", "Auth token") }),
Group("tools", "Tools for developers",
PersistentFlags(func(flags *pflag.FlagSet) { flags.Bool("dev", false, "Dev mode") }),
Command(toolsReadE, "read",
"Read command",
Flags(func(flags *pflag.FlagSet) { flags.Bool("skip-errors", false, "Skip read errors") }),
),
Command(toolsWriteE, "write",
"Write command",
Flags(func(flags *pflag.FlagSet) { flags.Bool("skip-errors", false, "Skip write errors") })),
),
)
Which renders the follow CLI hierarchy of commands:
acme (--auth <auth>) tools (--dev) read (--skip-errors) write (--skip-errors)
You can access the flags using viper sub-commands through this hierarchy:
viper.GetString("global-auth") // acme --auth ""
viper.GetBool("tools-global-dev") // acme tools --dev
viper.GetString("tools-read-skip-errors") // acme tools read --skip-errors
viper.GetString("tools-write-skip-errors") // acme tools write --skip-errors
And also with environment variables:
viper.GetString("global-auth") // {PREFIX}_GLOBAL_AUTH=<auth> acme
viper.GetBool("tools-global-dev") // {PREFIX}_TOOLS_GLOBAL_DEV=<dev> acme acme tools
viper.GetString("tools-read-skip-errors") // {PREFIX}_TOOLS_READ_SKIP_ERRORS=<skip> acme tools read
viper.GetString("tools-write-skip-errors") // {PREFIX}_TOOLS_WRITE_SKIP_ERRORS=<skip> acme tools write
Priority is: Flag definition via CLI overrides everyone else (--<flag>) Environment overrides values provided by config file or defaults ({PREFIX}_{ENV_KEY}) Config file (if configure separately) overrides defaults values (if configure separately) (--<flag>) Defaults values defined on the flag definition directly
func Group ¶
func Group(usage, short string, opts ...CommandOption) CommandOption
func OnCommandError ¶
func OnCommandError(onError func(err error)) CommandOption
OnCommandError intercepts error returned when running your `cobra.Command.RunE` handler and enable you to do something with it, like logging it. Once your handler has finish running, the process will exit with code 1.
You are free to exit yourself in your own handler, for example if some error should still exit with code 0.
func OnCommandErrorLogAndExit ¶
func OnCommandErrorLogAndExit(logger *zap.Logger) CommandOption
OnCommandErrorLogAndExit logs the error to the logger, sync the logger and exit with 1. It also intercepts assertion error produced by this library through `cli.Ensure` and `cli.NoError`.
type CommandOptionFunc ¶
func (CommandOptionFunc) Apply ¶
func (f CommandOptionFunc) Apply(cmd *cobra.Command)
type OnCommandErrorHandler ¶
type OnCommandErrorHandler func(err error)
type PersistentFlags ¶
func (PersistentFlags) Apply ¶
func (f PersistentFlags) Apply(cmd *cobra.Command)
type PromptOption ¶
type PromptOption interface {
Apply(opts *promptOptions)
}
func WithPromptConfirm ¶
func WithPromptConfirm() PromptOption
func WithPromptDefaultValue ¶
func WithPromptDefaultValue(in string) PromptOption
func WithPromptTemplates ¶
func WithPromptTemplates(templates *promptui.PromptTemplates) PromptOption
func WithPromptValidate ¶
func WithPromptValidate(label string, fn promptui.ValidateFunc) PromptOption
type PromptSelectOption ¶
type PromptSelectOption interface {
Apply(opts *promptSelectOptions)
}
func WithPromptSelectTemplates ¶
func WithPromptSelectTemplates(templates *promptui.SelectTemplates) PromptSelectOption
type PromptTransformer ¶
type Runnable ¶
type Runnable interface {
Run()
}
Runnable contracts is to be blocking and to return only when the task is done.
type RunnableContext ¶
RunnableContext contracts is to be blocking and to return only when the task is done. The context must be **used** only for the bootstrap period of task, long running task should be tied to a `*shutter.Shutter` instance.
type RunnableContextError ¶
RunnableContext contracts is to be blocking and to return only when the task is done. The context must be **used** only for the bootstrap period of task, long running task should be tied to a `*shutter.Shutter` instance. We assume the error happens while bootstrapping the task.
type RunnableError ¶
type RunnableError interface {
Run() error
}
RunnableError contracts is to be blocking and to return only when the task is done. We assume the error happens while bootstrapping the task.