-
Notifications
You must be signed in to change notification settings - Fork 6
Binding Commands
You should now have a configured shell readline, created menus, and prompts setup for each. We can now come to the core of the application: commands.
- Each menu embeds a
*cobra.Command
type (not nil by default), to which users can bind any number of commands with any desired structure, behavior and specifications. - At each command line execution, the active menu will execute the command line with a normal
call to the command's
Execute()
function, thus triggering the normal cobra execution workflow. - Thus, you can also leverage the reeflective/flags library to generate command trees, mix them with traditionally declared cobra commands, and use all of them in your console application.
- The cobra model is that of a traditional CLI library: it assumes one command execution per application lifetime. Therefore, we need to take care of resetting commands to a blank state after each execution.
Each console menu offers the following function and type to bind commands to it:
// Commands is a function yielding a root cobra.Commmand.
type Commands func() *cobra.Command
// SetCommands binds the yielder function to the menu.
func (m *Menu) SetCommands(cmds Commands)
Note: Since these commands are to be reset/regenerated after each command execution,
you should not use init() {}
functions to initialize and set them up.
The following shows how to declare and bind a command tree to our menu:
func myMenuCommands() *cobra.Command {
// Root -----------------------------------------------------------
// Our root command does not need any name. Even if it had one,
// it will be ignored when the menu is being passed a command-line.
root := &cobra.Command{}
// We however set any wished behavior to it.
// Set any behavior we want
root.Long = `An introduction help string before the commands list`
root.SetHelpCommand(&customHelpCommand)
// Set Pre/Post runners
root.PreRunE = myMenuPreRunFunc
root.PostRunE = myMenuPostRunFunc
// Commands --------------------------------------------------------
versioncmd := &cobra.command{
use: "version",
short: "print the version number of hugo",
long: `all software has versions. this is hugo's`,
run: func(cmd *cobra.command, args []string) {
fmt.println("hugo static site generator v0.9 -- head")
},
// flags, positional args functions, pre/post runners, etc.
root.AddCommand(versionCmd)
}
We then bind the root to the menu with this simple call:
menu.SetCommands(myMenuCommands)
If you are using only reeflective/flags to generate your cobra commands, this simple snippet is sufficient to bind them to the console, and everything will be taken care of out of the box (completions and reset). In this case, your application is ready to run. The example application uses such a configuration.
func flagsCommands() *cobra.Command {
// Our root command structure encapsulates
// the entire command tree for our application.
rootData := &commands.Root{}
// Options can be used for several purposes:
// influence the flags naming conventions, register
// other scan handlers for specialized work, etc...
var opts []flags.OptFunc
// One example of specialized handler is the validator,
// which checks for struct tags specifying validations:
// when found, this handler wraps the generated flag into
// a special value which will validate the user input.
opts = append(opts, flags.Validator(validator.New()))
// Run the scan: this generates the entire command tree
// into a cobra root command (and its subcommands).
// By default, the name of the command is os.Args[0].
rootCmd := genflags.Generate(rootData, opts...)
// Since we now dispose of a cobra command, we can further
// set it up to our liking: modify/set fields and options, etc.
// There is virtually no restriction to the modifications one
// can do on them, except that their RunE() is already bound.
rootCmd.SilenceUsage = true
rootCmd.Short = shortUsage
rootCmd.Long = shortUsage + "\n" + commands.LongUsage
// We might also have longer help strings contained in our
// various commands' packages, which we also bind now.
commands.AddCommandsLongHelp(rootCmd)
// The completion generator is another example of specialized
// scan handler: it will generate completers if it finds tags
// specifying what to complete, or completer implementations
// by the positional arguments / command flags' types themselves.
completions.Generate(rootCmd, rootData, nil)
return rootCmd
}
The following section provides precisions and advices in the case where you would happen to use a command tree that has been build with interspersed cobra commands, some of them being traditionally declared like in Section 2, and others being generated out of structs like in Section 3.
There are some cases when a subset of the available commands for a given menu should not be available,
(they might be specific to some context that is not met, like a given OS, etc.). The command.Hidden
attribute of cobra.Commands
, however, will not prevent a hidden command from being run.
Therefore, the following two methods allow users to deactivate/reactivate commands based on one or more given filter words:
func (c *Console) HideCommands(filters ...string)
func (c *Console) ShowCommands(filters ...string)
A command that should be filtered for a given filter
word should thus be annotated like this:
var myCmd &cobra.Command{Annotations: make(map[string]string{})}
// Multiple filters can be specified if comma-separated.
myCmd.Annotations[console.CommandFilterKey] = "filter1,filter2"
Then, generally when switching to another menu in the application, and since the command tree changes, you would probably do this:
console.SwitchMenu("client")
offline := getApplicationNetworkStatus()
// Note that you might want to specify both calls, since you might have
// filtered the commands in a previous menu switch, and that this one might
// now have network, so you need to unhide the commands.
if offline {
console.HideCommands("networked-commands")
} else {
console.ShowCommands("networked-commands")
}
The commands that are filtered will also be automatically marked Hidden
via their cobra field:
they wont appear in the menu's help/usage strings, and not proposed as completions.