-
Notifications
You must be signed in to change notification settings - Fork 102
Subcommands
GLI2 added subcommand support. This allows you to have deeply nested commands beyond the initial one, e.g. to implement something like git remote add
.
If you are upgrading from GLI 2.5.x or earlier, you will likely want to add this to your app
subcommand_option_handling :normal
This is needed for backwards compatibility and should be added automatically by GLI when bootstrapping apps with 2.6 or greater.
See https://github.com/davetron5000/gli/issues/96#issuecomment-27238249 for more info
Adding this creates some complexity and nuances to how your app behaves and how you declare your UI. The design was intended to make obvious patterns simple, but have some flexibility for more complex UIs.
Before GLI2, a call to command
took a block, and inside this block you could only declare flags, switches, and the action block:
desc "adds a remote repo to your local config"
command :remote_add do |c|
c.flag :flag
c.switch :verbose
c.action do |global_options,options,args|
# ... logic here
end
end
The most basic way to add subcommands in a GLI2-powered app is to use command
inside an existing command
block.
desc "manage remotes"
command :remote do |c|
c.desc "adds a remote repo to your local config"
c.command :add do |add|
add.action do |global_options,options,args|
# ... logic here
end
end
end
Note that the desc
for :remote
is documentation for the namespace created by :remote
, so we've used a more generic description.
You can nest this as many times deep as you like:
desc "manage remotes"
command :remote do |c|
c.command :repo do |repo|
repo.desc "adds a remote repo to your local config"
repo.command :add do |add|
add.action do |global_options,options,args|
# ... logic here
end
end
end
end
If you are just using subcommands as namespaces, and have few or no command line options, this is all you need to know.
The code/UI can get tricky in the following situations:
- You want to choose a default subcommand to execute when the user omits in on the command-line (e.g what should
git remote
do?) - You want to use commands and non-leaf subcommands as actions in their own right, not just as namespaces.
- You have a lot of options between subcommands that share the same name
By default, omitting a subcommand will show the help for the last command entered on the command line, e.g. executing git remote
would show all the possible subcommands of remote
, with their help text.
You can, however, designate one subcommand as the default to execute when the subcommand is omitted. This is what happens when you execute git stash
- the default command, save
, is executed.
To designate the default command in GLI2, use default_command
(being sure to call it after you've declared the command you are designating)
desc "manipulate the stash"
command :stash do |c|
c.desc "list items in the stash"
c.command :list do
# ...
end
c.desc "save current changes to stash"
c.command :save do
# ...
end
c.desc "apply topmost stash to current wd"
c.command :apply do
# ...
end
c.default_command :save
end
Now, if you do git stash
, the save
command will be executed. If you were to get help, via git stash help
, you'd see that save
is listed as the default.
Suppose, however, you want an actual action to be run when the subcommand is omitted.
Coming back to git remote
, when you just execute that, it lists the remote repos. There is no subcommand for that. To do this in GLI2, we simply give the command an action block as normal, however we must use the default_desc
command to describe this particular action because, as you recall, the desc
describes the namespace. Let's see an example:
desc "manage remote repos"
command :remote do |c|
c.desc "add a remote repo"
c.command :add do |add|
# ...
end
# ...
c.default_desc "show a list of existing remotes"
c.action do |global_options,options,args|
# ...
end
end
We can see how this works by getting help via git help remote
NAME
remote - manage remote repos
SYNOPSIS
git [global options] remote [command options]
git [global options] remote [command options] add
COMMANDS
<default> - show a list of existing remotes
add - add a remote repo
We can see that our top-level desc
is used to describe the namespace remote, but that our default_desc
is used to describe the default action when the subcommand is omitted. We also see that GLI generated an example command line where the subcommand is omitted.
Both this, and the default command are necessary complexities of handling subcommands. With regard to options (flags and switches), there is some additional unnecessary complexity.
Since GLI 2.6 flags and switches are scoped to, and associated with, subcommands. Suppose we have this code:
desc "List things"
command :list do |c|
c.desc "show long form output"
c.switch [:l,:long]
c.command :tasks do |tasks|
tasks.desc "Show only tasks that are late"
tasks.switch [:l,:late]
end
end
This would define a separate sets of options for parent list
and sub list tasks
commands. Options are allowed to have same names (in either short or long version).
This creates a slight issue as to how to access the value of each option. In this example, how can the action
for list tasks
access the value for :l
, given that options[:l]
will return the value for the :late
switch?
You can navigate a hierarchy of options by using the parent command's options.
In order to access options defined in parent command you can use GLI::Command::PARENT
key:
desc "List things"
command :list do |c|
c.desc "show long form output"
c.switch :long
c.command :tasks do |tasks|
tasks.desc "Show only tasks that are late"
tasks.switch :late
tasks.action do |global_options, options, args|
if options[GLI::Command::PARENT][:long]
# display tasks using long form output
end
end
end
end
You can follows this all the way to the top, if you have deeply nested commands, e.g. options[GLI::Command::PARENT][GLI::Command::PARENT][:foo]
In older GLI versions it wasn't possible to have option with a same name on parent and subcommand level. As a workaround, you could do this:
desc "List things"
command :list do |c|
c.desc "show long form output"
c.switch :long
c.switch :l
c.command :tasks do |tasks|
tasks.desc "Show only tasks that are late"
tasks.switch :late
end
end
When the user executes todo list tasks -l
, options[:l]
will return true (however options[:late]
would still be false).
I realize this is less than ideal, so my suggestion would be update GLI to latest version.