On our last post we learned to create a basic CLI application using Trollop. We learned how to parse command line options with different types and how to ask for help. Today I want to explore some more advanced options.
Sub-commands
A sub-command is a way of branching our code to perform different actions depending on a command string.
A great example for this is the git
CLI. Calling git status
will perform something completely different than calling git branch
or git clone
. This is not just a matter of refining our options, but performing entirely different tasks.
In order to put this in action, we'll create a script that wil explore my blogging framework settings.
We'll create a sub-command tha will list all the posts files (by reading the posts directory), with an option to sort them by name or by creation date (which'll be a boolean for simplicity).
And a second one for exploring my post ideas, that I save on a file called IDEAS.org, with an option to limit the number of retrieved ideas.
Finally, we'll add a global option --dry-run
to just print the passed options and exit.
require "trollop"
require "awesome_print"
SUB_COMMANDS = %w(posts ideas)
global_opts = Trollop::options do
banner "My writing framework information:"
opt :dry_run, "Just show me what I passed in"
stop_on SUB_COMMANDS
end
cmd = ARGV.shift
cmd_opts = case cmd
when "posts"
Trollop::options do
opt :by_time
end
when "ideas"
Trollop::options do
opt :limit, "Limit the number of ideas", default: 5
end
else
Trollop::die "Unknown subcommand #{cmd.inspect}"
end
if global_opts[:dry_run]
puts "Global options:"
ap global_opts
puts "Subcommand:"
ap cmd
puts "Subcommand options:"
ap cmd_opts
puts "Remaining arguments:"
ap ARGV
exit
end
case cmd
when "posts"
puts "Blog posts: "
command = "ls ./posts/"
command << " -t" if cmd_opts[:by_time]
system command
when "ideas"
puts "Post ideas"
ideas = File.open("./IDEAS.org")
.each_line
.take(cmd_opts[:limit])
puts ideas
end
Now we can run our script to fetch our post list sorted by name (default) or by time:
$ wf.rb posts
Blog posts:
ls /home/fedex/Dropbox/wordpress_rake/posts/
case_vs_hash.org erb-to-haml.org ruby_scratch_buffer.org trollop.org writing_framework_fixup.org
dig.org hyper.org secret_santa_algorithm.org trollop_advanced.org writing_framework.org
duplicate-line.org moving_lines_in_emacs.org trollop WIP_a_jim_weirich_day.org writing_framework_putting_it_all_together.org
$ wf.rb posts --by-time
Blog posts:
ls /home/fedex/Dropbox/wordpress_rake/posts/ -t
trollop_advanced.org erb-to-haml.org secret_santa_algorithm.org hyper.org writing_framework_fixup.org
trollop dig.org case_vs_hash.org ruby_scratch_buffer.org writing_framework_putting_it_all_together.org
trollop.org moving_lines_in_emacs.org duplicate-line.org WIP_a_jim_weirich_day.org writing_framework.org
We can list our top 5 post ideas (default), or maybe just the top 3:
$ wf.rb ideas
Post ideas
* Habitica
* yield_self vs tap
* My default Ruby packages
* Org capture
** what is org capture and how to configure it
$ wf.rb ideas --limit 3
Post ideas
* Habitica
* yield_self vs tap
* My default Ruby packages
Or try a dry run:
$ wf.rb ideas --limit 3 --dry-run
Error: unknown argument '--dry-run'.
Try --help for help.
Oops, thah didn'go well. The problem is that the global options need to be passed before any sub-command or else trollop will try to parse them as sub-command options.
$ wf.rb --dry-run ideas --limit 3
Global options:
{
:dry_run => true,
:help => false,
:dry_run_given => true
}
Subcommand:
"ideas"
Subcommand options:
{
:limit => 3,
:help => false,
:limit_given => true
}
Remaining arguments:
[]
And now it works just fine.
Conclusion
I find trollop to be my preferred tool for building simple CLIs. I find it to be very powerful and simple to use. I hope you try it out for your next CLI.
Saluti.