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.


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

cmd = ARGV.shift

cmd_opts = case cmd
           when "posts"
             Trollop::options do
               opt :by_time
           when "ideas"
             Trollop::options do
               opt :limit, "Limit the number of ideas", default: 5
             Trollop::die "Unknown subcommand #{cmd.inspect}"

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


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")

  puts ideas

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's 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 options:
          :limit => 3,
           :help => false,
    :limit_given => true

Remaining arguments:

And now it works just fine.


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.