-
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.
You may think that flags and switches are scoped to, and associated with, subcommands. In a certain sense they are, but their implementation was not done this way. In reality, all flags and switches of commands and subcommands are "owned" by the top level command. Issue 96 exists to correct this, which would eliminate the complexity I'll now describe.
Suppose we have this code:
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
end
# ...
end
When we get help for list
, we'll see only one option available, --long
. When we get help for list tasks
, we won't see the --long
option but we will see the --late
option. This is how you would expect things to work.
Suppose, however, that we want to give both switches a short-form option of -l
:
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 generate an exception with a message like "l has already been specified as a switch". This is the implementation bleeding through. Until 96 is addressed, this code is essentially not possible.
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 to design your user interface to avoid this issue.
In general, simple apps that have light nesting will not encounter this issue.
If you'd like to help fix it, please read the thread in the issue, and then examine gli_option_parser.rb as a start.