Mike Slinn

Parsing Command Line Arguments with OptionParser

Published 2023-10-03. Last modified 2025-08-29.
Time to read: 12 minutes.

This page is part of the ruby collection.

The Ruby language has many libraries for parsing command-line arguments. This article discusses Option­Parser, which is one of the most popular Ruby libraries for parsing arguments. Option­Parser is popular because it is both powerful and easy to use, and it is part of the Ruby runtime library.

This is the Option­Parser GitHub repository.

Documentation

The detailed Ruby documentation for Option­Parser is here. I am not going to restate the documentation, since many articles have been written about Option­Parser. Instead, this section will just discuss important details that are not usually discussed or are assumed by many writers to be obvious, even though the omitted details might not be obvious to many readers.

Firstly, Option­Parser returns parsed values in a hash.

Denoting Keyword Options

Options can merely be keywords; for example, an option such as -f (--force) might be used to specify that any previous output from a program should be overwritten. Keywords are actually implemented as a key/value pair with value true.

Ruby code fragment
options = {}
OptionParser.new do |parser|
  parser.on '-f', '--force'
end.order!(ARGV, into: options)

If the program containing Option­Parser code is launched with the -f option, the above code fragment would store {force: true} in the options hash.

The name force is used because that is the long-form name of the option.

Name/Value Options

Options with arbitrary values can also be defined, and they are also stored as key/value pairs.

For example, an option named verbose might require a string value such as low, medium or high. In the following minimal example, the default value for verbose is 'low'. The presence of a placeholder, in this example VERBOSE, indicates that the parsed value should be stored in the result hash with a key named after the long option name. The placeholder value is not actually relevant, and its length is also unimportant; the Ruby documentation calls placeholders dummy tokens.

Ruby code fragment
options = { verbose: 'low' }
OptionParser.new do |parser|
  parser.on '-v', '--verbose=VERBOSE'
end.order!(ARGV, into: options)

The following code fragment is functionally identical to the previous even though the value of the placeholder / dummy token is different.

Equivalent Ruby code fragment
options = { verbose: 'low' }
OptionParser.new do |parser|
  parser.on '-v', '--verbose=ABC'
end.order!(ARGV, into: options)

As before, the name verbose is used for the name of the resulting entry in the resulting hash because the long form of the option is --verbose. The value placeholder is shown in yellow. You could also write the above as follows, but there is no reason to do so because specifying the value placeholder twice just represents more typing:

Ruby code fragment
options = { verbose: 'low' }
OptionParser.new do |parser|
  parser.on '-v VERBOSE', '--verbose=VERBOSE'
end.order!(ARGV, into: options)

Positional Parameters Delimit Options

The documentation does not mention a crucial limitation of how Option­Parser parses command lines:

Option parsing stops on the first positional parameter.

A positional parameter is a token that does not begin with a dash and is not an option value.

This means, for example, that the first two options in the following command line are parsed, but the remaining options are not, unless the command line is manipulated:

Shell
$ my_program -o --option=value positional_parameter -p -q --another-option=value2

Subcommands that follow the above syntax are fully discussed in the Option­Parser documentation. Later in this article I discuss why the above syntax is unnecessarily awkward for the user, and in NuGem I discuss an alternative, Nested­Option­Parser, based on a novel usage of Option­Parser that is more user-friendly.

Sod

The F/OSS library called sod enhances Option­Parser. Although sod can be used as an Option­Parser wrapper that provides enhanced capability, you can also just use selected features of sod while continuing to use Option­Parser as usual. That is the approach taken in this article. One sod feature that I especially like is the extra data types that it supports for parsing arguments:

Installation

Option­Parser is part of the standard Ruby runtime library, so once Ruby itself is installed, there are no mandatory additional steps for installation.

Gemfile

If you are building an application, add the following lines to your application’s Gemfile:

Gemfile
gem 'optparse'
gem 'sod' # If you want extra datatypes

And then execute:

Shell
$ bundle

Gem

If you are building a gem, add the following line to your gem’s .gemspec:

your_gem.gemspec
spec.add_dependency 'optparse'
spec.add_dependency 'sod' # If you want extra datatypes

And then execute:

Shell
$ bundle

Usage

My stabilize_video program is an example of how I like to use Option­Parser. Below are portions of three Ruby source files:

  1. option.rb parses the options and generates the help text.
  2. stabilize_video.rb parses the positional parameters.
  3. stablize.rb receives the positional parameters and options.

The following import is required before invoking creating an Option­Parser instance:

Ruby code fragment
require 'optparse'
require 'sod' # If you want to use sod
require 'sod/types/pathname' # If you want to parse Pathnames

Parsing Options

Let’s look at an initial version of the parse_options method, which uses Option­Parser to parse the optional arguments. Later in this article I show a more flexible implementation that allows the parsing code to be testable.

Portion of options.rb
def parse_options
  options = { shake: 5, loglevel: 'warning' }
  OptionParser.new do |parser|
    parser.program_name = File.basename __FILE__
    @parser = parser

    parser.on('-f', '--overwrite', 'Overwrite output file if present')
    parser.on('-l', '--loglevel LOGLEVEL', Integer, "Logging level (#{VERBOSITY.join ', '})")
    parser.on('-s', '--shake SHAKE', Integer, 'Shakiness (1..10)')
    parser.on('-v', '--verbose VERBOSE', 'Verbosity')
    parser.on('-z', '--zoom ZOOM', Integer, 'Zoom percentage')

    parser.on_tail('-h', '--help', 'Show this message') do
      help
    end
  end.order!(into: options)
  help "Invalid verbosity value (#{options[:verbose]}), must be one of one of: #{VERBOSITY.join ', '}." if options[:verbose] && !options[:verbose] in VERBOSITY
  help "Invalid shake value (#{options[:shake]})." if options[:shake].negative? || options[:shake] > 10
  options
end
  1. The default options are set in the highlighted hash. Default values are set for the :shake and :loglevel keys.
  2. When parser.on is passed the name of an option value in UPPER CASE, it creates an entry in the options hash with that name, in lower case. The above code shows the following examples:
    • LOGLEVEL provides a means for the user to specify a value to replace the default value of the loglevel entry in the options hash, which was initialized with the string value 'warning'.
    • SHAKE provides a means for the user to specify a to replacement for the default value of the shake entry in the options hash, which was initialized with the integer value 5.
    • VERBOSE provides a means for the user to specify a string value for a new entry in the options hash with the key verbose.
    • ZOOM provides a means for the user to specify a string value for a new entry in the options hash with the key zoom.
  3. OptionParser.order! has the side effect that option keywords and key/value pairs that match parser.on statements are removed from ARGV.
  4. Ending the end.order! statement with (into: options) causes the parsed option key/value pairs to be added or updated in the hash called options.
  5. The parsed options are returned.

Parsing Positional Parameters

Let’s see how stabilize_video.rb parses positional parameters:

stabilize_video.rb
require 'colorator'
require_relative 'stabilize_video/version'
require_relative 'options'

# Require all Ruby files in 'lib/', except this file
Dir[File.join(__dir__, '*.rb')].each do |file|
  require file unless file.end_with?('/stabilize_video.rb')
end

def main
  options = parse_options
  help 'Video file name must be provided.' if ARGV.empty?
  help "Too many parameters specified.\n#{ARGV}" if ARGV.length > 1
  video_in = ARGV[0]
  video_out = "#{File.dirname video_in}/stabilized_#{File.basename video_in}"
  StablizeVideo.new(video_in, video_out, **options).stabilize
end

main

Here are some notes to help you understand the above code:

  • Usage of colorator or rainbow to output colored strings helps readability but is not required.
  • Because Option­Parser removes each argument from ARGV that it recognizes, when it finishes, all that should be left on the command line are the positional parameters. The main method calls parse_options, which, as we know calls Option­Parser, and then ensure that a mandatory filename parameter is provided.
  • parse_options returns a hash of name/value pairs, which can optionally be passed when doubly dereferenced with two asterisks (**options), often referred to as the double splat operator. This is done in the highlighted code above when creating a new StablizeVideo instance.

    For example, to obtain the value of the overwrite option, which should default to false, write code like this:
    Ruby code
    @overwrite = options.key?(:overwrite) ? options[:overwrite] : false

Warning: AI Misinformation

Warning: AI-generated help from Google provides incorrect information about the double asterisk syntax. It says:

The double asterisks cause each key/value pair in the options hash to be passed as a named argument to the StablizeVideo.initialize method.

The above is incorrect. Here is some code that shows exactly what happens:

deref_fun.rb
def fun(**options)
  puts "fun options: #{options}"
  puts "fun options[:dry_run]: #{options[:dry_run]}"
  @dry_run = options.key?(:dry_run) ? options[:dry_run] : false
  puts "fun dry_run: #{dry_run}" # This line will raise an error because dry_run is not defined
rescue StandardError => e
  puts "Error in fun: #{e.message}"
end

my_options = { dry_run: true }
fun(**my_options)
puts "@dry_run: #{@dry_run}"

Here is the output of the above code:

Shell
$ ruby playground/deref_fun.rb
fun options: {:dry_run=>true}
fun options[:dry_run]: true
Error in fun: undefined local variable or method `dry_run' for main:Object
@dry_run: true 

Passing Options

In the following code, the optional values returned by parse_options are provided to the StablizeVideo.initialize method. Once again, double asterisks are used.

Portion of stabilize.rb
  def initialize(video_in, video_out, **options)
    @options   = options
    @loglevel  = "-loglevel #{options[:loglevel]}"
    @loglevel += ' -stats' unless options[:loglevel] == 'quiet'
    @shakiness = "shakiness=#{options[:shake]}"
    @video_in  = MSUtil.expand_env video_in
    @video_out = MSUtil.expand_env video_out
    unless File.exist?(@video_in)
      printf "Error: file #{@video_in} does not exist.\n"
      exit 2
    end
    unless File.readable? @video_in
      printf "Error: #{@video_in} cannot be read.\n"
      exit 2
    end
    return unless File.exist?(@video_out) && !options.key?(:overwrite)

    printf "Error: #{@video_out} already exists.\n"
    exit 3
  end

Notice in the above code that:

  • The value of the -l / --loglevel option is obtained from options[:loglevel].
  • The value of the -s / --shake option is obtained from options[:shake].
  • If the user specified the -f (--overwrite) option, that is detected by options.key?(:overwrite).

Hand-written Help Text

I find that using the automatically generated help text results in a more complex program for little gain, because there are so many moving parts to keep track of. Explicitly writing the help method is a more maintainable way of showing the user what they need to know.

The help method below generates the help text, which might be preceded by an error message.

Portion of options.rb
def help(msg = nil)
  printf "Error: #{msg}\n\n".yellow unless msg.nil?
  msg = <<~END_HELP
    stabilize: Stabilizes a video using the FFmpeg vidstabdetect and vidstabtransform filters.

    Syntax: stabilize [Options] PATH_TO_VIDEO

    Options:
      -f Overwrite output file if present
      -h Show this help message
      -s Shakiness compensation 1..10 (default 5)
      -v Verbosity; one of: #{VERBOSITY.join ', '}
      -z Zoom percentage (computed if not specified)

    See:
      https://www.ffmpeg.org/ffmpeg-filters.html#vidstabdetect-1
      https://www.ffmpeg.org/ffmpeg-filters.html#toc-vidstabtransform-1
  END_HELP
  printf msg.cyan
  exit 1
end

A Ruby squiggly heredoc is used to store a multiline string as the help text.

Running the Program

Now let's run the above program and view the generated help text:

Shell
$ stabilize -h
stabilize -h
  stabilize: Stabilizes a video using the FFmpeg vidstabdetect and vidstabtransform filters.

  Syntax: stabilize [Options] PATH_TO_VIDEO

  Options:
    -f Overwrite output file if present
    -h Show this help message
    -s Shakiness compensation 1..10 (default 5)
    -v Verbosity; one of: trace, debug, verbose, info, warning, error, fatal, panic, quiet
    -z Zoom percentage (computed if not specified)

  See:
    https://www.ffmpeg.org/ffmpeg-filters.html#vidstabdetect-1
    https://www.ffmpeg.org/ffmpeg-filters.html#toc-vidstabtransform-1 

Testing Argument Parsing

As we have seen, Ruby passes an argument to the Option­Parser block body. As a reminder, the following code shows the block body and the argument, called parser:

Ruby code
OptionParser.new do |parser|
  # OptionParser block body
end

Designing Parsing Code for Testability

The argument provided to the Option­Parser block body, called parser, is an Option­Parser instance. There are two ways to supply a value for ARGV other than the default value that Ruby provides.

  1. The Option­Parser provides methods called parse, parse!, option and option!. All of these methods accept an optional argv parameter.
  2. The first optional argument of each of these methods is the array of arguments to be parsed. The value of this argument defaults to ARGV, which the operating system passes to your Ruby program when it starts.

The following Ruby code defines a method called parse_options that demonstrates a technique for command-line option parsing. Key features of this technique are:

  1. Default values can be established in a modular fashion
  2. Option parsing code is more modular, which makes it easier to understand
  3. Option­Parser functionality is not affected
  4. System-provided values can be overridden for testing
  5. Nothing extra required for full RSpec support

I intend to use this latest iteration of the technique in all my scripts and CLI tools.

Ruby code
require 'optparse'

def parse_options(default_options, argv: ARGV)
  options = default_options
  OptionParser.new do |parser|

    # Remaining parameter parsing code goes here
    # For example:
    parser.on '-v', '--verbose', FalseClass
  end.order! argv, into: options
  options
end

The parse_options method accepts one or two arguments: default_options, which is a hash containing default values for options, and the optional argv argument, which allows you to specify a custom array of arguments to parse instead of the default system ARGV provided by Ruby.

The parse_options method body initializes a new automatic variable called options from the default_options parameter. A new Option­Parser instance is then created called parser. Its scope is only within the Option­Parser body. The odd syntax of this method body actually defines the parsing of the arguments, which stores the results in the options hash. If the parameter list does not specify an option, the default value remains intact; otherwise, the default value is overwritten.

The highlighted line within the Option­Parser body is important: this code tells the parser to use the optional argv array, if provided, as the source of command-line arguments. This technique is useful for testing and for programmatically specifying arguments.

Define the options that your script accepts within the remainder of the parser block. The example recognizes a -v or --verbose flag. After instantiating the Option­Parser instance called parser, the end closes the Option­Parser constructor block, which finalizes the parsing process. The order! method is then called with an option called into:, followed by the name of the variable that will receive the hash resulting from the parsing operation that just completed.

Finally, the parse_options method returns the populated options hash. It is up to the caller to handle any remaining arguments in ARGV.

Testing the Parsing Code With irb

The following example invocation demonstrates how to call parse_options with a set of default options and a custom argument String array, simulating command-line input.

The demonstrated approach shows how to make option parsing flexible and testable. You can easily override the arguments without going through the pain of providing actual command lines to running instances of your code, or getting frustrated with Visual Studio Code’s inability to accept ARGV tokens with embedded spaces.

The following example shows how to use the parse_options method above. You might write code like this for unit tests of CLI user invocation. This example simulates a CLI being called with two positional parameters (param1 and param2), and without an optional parameter (-v / --verbose) being provided.

Example of calling parse_options
irb(main):001* require 'optparse'
irb(main):002* default_options = { irb(main):003* param1: 'value_1', irb(main):004* param2: 'value_2' irb(main):005> } => {:param1=>"value_1", :param2=>"value_2"}
irb(main):006>
my_argv = %w[param1 value_3 param2 value_4] => ["param1", "value_3", "param2", "value_4"]
irb(main):006> parse_options default_options, argv: my_argv {:param1=>"value_1", :param2=>"value_2"}

You can write RSpec unit tests that incorporate similar code. This allows you to test CLI argument parsing easily.

Testing with RSpec

The following code shows an RSpec test that parses an argument as a Ruby Time instance.

Ruby code
require 'optparse'
require 'optparse/time'
require_relative 'spec_helper'

class NestedOptionParserTest
  RSpec.describe OptionParser do
    option_parser = described_class.new do |parser|
      parser.on('-t TIME', '--time=TIME', Time)
      parser.on('-x', '--xray')
    end

    it 'initializes an OptionParser' do
      options = { time: '2020-02-12T00:00:00Z' } # Default value of all options
      option_parser.order! %w[-x -t 2025-07-01T12:00:00Z], into: options
      expect(options).to eq({ xray: true, time: Time.parse('2025-07-01T12:00:00Z') })
    end
  end
end

Advanced Usage

In Ruby, procs are closures that capture variables from their surrounding context. If you are familiar with partial functions, they can be defined as procs so they can be passed around and invoked later. This capability can be useful when defining Option­Parser command-line option parsing.

Ruby procs can be provided to OptionParser, and command-line parsing would occur within them. However, it may not be obvious how to obtain the value of parsed options from a proc, especially if the proc is defined in a different binding context than one containing the accumulator (the options hash).

To provide argument value outside the block, you can:

  1. Close over a variable (simple, but mixes parsing code with business logic)
  2. Pass in a container
  3. Encapsulate the logic in an object

Let’s look at each of these techniques in turn.

Close Over A Variable

Using a proc to define the parsing for an option is comparatively simple to set up. Because only one lexical scope is involved, the accumulator (the options hash) must be defined in the same lexical scope as the parser procs.

Constructing the proc in a method allows the proc to be reused because the lexical scope in force when the proc is constructed includes the method arguments.

With this technique, the proc fills in the provided options hash without any lexical binding trickery. Because the options hash is passed by value, the caller can see the changes to the value that the proc might make.

Ruby code
require 'optparse'

def outdir_proc(options)
  proc do |parser|
    parser.on('-o', '--out_dir=OUT_DIR') do |value|
      options[:out_dir] = value
    end
  end
end

options = {}
parser  = OptionParser.new
outdir_proc(options).call(parser)
parser.parse!(['-o', '/tmp/blah']) # Simulate ARGV
puts options # displays {:out_dir=>"/tmp/blah"}

Subsequent examples demonstrate techniques that work across dissimilar lexical scopes.

Encapsulate in an Object

Here is a version that follows the Rails / Thor style, which consists of a single options hash, reusable across many procs. Each proc consists of one or more parser.on methods that store parsed values into options.

Ruby code
class OutDirOption
  attr_reader :out_dir

  def to_proc
    proc do |parser|
      parser.on('-o', '--out_dir=OUT_DIR') do |value|
        @out_dir = value
      end
    end
  end
end

out_opt = OutDirOption.new
parser  = OptionParser.new
out_opt.to_proc.call(parser)
parser.parse!(ARGV)

puts out_opt.out_dir

Inside the parser.on block, the value of OUT_DIR is given as the block argument (e.g., |out_dir|).

Note:

  1. options is a hash that is passed around.
  2. Each option-defining proc knows nothing about where the hash lives; it just fills in the value for the provided key.
  3. After parsing, user-supplied options are stored in one place.

Multiple Procs

This approach allows for modular and reusable option definitions that can be easily combined in different ways.

A separate proc is defined for each option or group of related options. Each proc adds parsing for one or more options to the parser; parsed values are stored in the options hash.

  1. Default values for options can be set in the options hash before calling the procs.
  2. Procs define how options are parsed and stored in the options hash. Procs are independent and cannot assume that any other proc has been called, so they must not depend on any other option values being present in the options hash.
  3. Procs can be called in any order when accumulating option values from the command line arguments into the options hash.
  4. Idempotency is important: calling a proc multiple times should not cause errors or unexpected behavior.
  5. After all procs have been called, parser.parse! should be called to actually parse the command line arguments and populate the options hash

Here’s a multi-option example using the Rails/Thor-style central options hash where multiple procs contribute their options independently but all populate the same hash. Note that each proc only references values passed to it, and default values are defined separately from the parsing code.

require 'optparse'

# Define default values and accumulate option values into this Hash.
# The procs that follow are invoked such that option values
# accumulate into this variable.
options = {
  dry_run: false,
  out_dir: Dir.home,
  verbose: false,
}

# Proc for handling output directory
out_dir_proc = proc do |parser, opts|
  parser.on('-o', '--out_dir=OUT_DIR', 'Output directory') do |value|
    opts[:out_dir] = value
  end
end

# Proc for handling verbosity
verbose_proc = proc do |parser, opts|
  parser.on('-v', '--[no-]verbose', 'Run verbosely') do |value|
    opts[:verbose] = value
  end
end

# Proc for handling dry-run mode
dry_run_proc = proc do |parser, opts|
  parser.on('-n', '--dry-run', 'Do everything except execute') do |value|
    opts[:dry_run] = value
  end
end

# Build parser and apply all option procs
parser = OptionParser.new
[out_dir_proc, verbose_proc, dry_run_proc].each do |p|
  p.call(parser, options)
end

parser.parse!(['-o', '/tmp/test', '-v', 'very']) # Simulated command line arguments

puts "Options hash: #{options.sort.to_h}"
#   Options hash: {:dry_run=> false, :out_dir=>"/tmp/test", :verbose=>true}

Composing Subcommand Parsers With a Common Option Parser

This approach can be used to compose subcommand option parsers as well. For example, instead of defining a proc per option, you might define a proc for all options of each subcommand. Each subcommand proc would be invoked conditionally based on the first positional argument. The procs would accumulate the option values they parse to the central hash.

Here is an example of how that might look:

require 'optparse'

argv = ['init', '-v', '-t', 'blah']
subcommand = argv.shift
if subcommand.nil?
  puts 'Error: No subcommand provided. Subcommands are: init and deploy'
  exit 1
end
options = { verbose: false } # Default value of options

common_proc = proc do |parser, opts|
  parser.on('-v', '--[no-]verbose', 'Run verbosely') do |value|
    opts[:verbose] = value
  end
end

subcommand_procs = {
  'init'   => proc do |parser, opts|
    parser.on('-t', '--template=TEMPLATE', 'Template to use') do |value|
      opts[:template] = value
    end
  end,
  'deploy' => proc do |parser, opts|
    parser.on('-e', '--env=ENV', 'Environment to deploy to') do |value|
      opts[:env] = value
    end
  end,
}

parser = OptionParser.new
common_proc.call(parser, options)

# Subcommand-specific options
if subcommand_procs.key?(subcommand)
  subcommand_procs[subcommand].call(parser, options)
else
  puts "Unknown subcommand: #{subcommand}"
  exit 1
end

parser.parse(argv) # Example args; replace with ARGV in real use
puts "Subcommand '#{subcommand}' has options #{options.inspect}"

Highline

Highline is a gem that is often found in CLIs that are built with Option­Parser. It provides a way to ask the user questions and get answers, similar in syntax to methods that Thor provides for the same purpose.

The agree method is particularly useful. Both the agree and ask methods have an undocumented feature: putting a space at the end of the question string suppresses the newline between the question and the answer.

The optional character parameter causes the first character that the user types to be immediately processed without requiring them to press Enter.

my_cli.rb
$ irb
irb(main):001> require 'highline'
=> true
irb(main):002* begin
irb(main):003* printf "Work work work"
irb(main):004> end while HighLine.agree "\nAll done! Do you want to do it again? ", character = true
Work work work
All done! Do you want to do it again?
Please enter "yes" or "no".
All done! Do you want to do it again? y
Work work work
All done! Do you want to do it again? y
Work work work
All done! Do you want to do it again? n
=> nil
irb(main):005> 

Below is some code that I wrote for Nugem. The import statement imports the methods of the Highline library directly into the current namespace.

highline_wrappers.rb
require 'highline/import'

module HighlineWrappers
  def yes_no?(prompt = 'More?', default_value: true)
    answer_letter = ''
    suffix = default_value ? '[Y/n]' : '[y/N]'
    default_letter = default_value ? 'y' : 'n'
    until %w[y n].include? answer_letter # rubocop:disable Performance/CollectionLiteralInLoop
      answer_letter = ask("#{prompt} #{suffix} ") do |q|
        q.limit = 1
        q.case = :downcase
      end
      answer_letter = default_letter if answer_letter.empty?
    end
    answer_letter == 'y'
  end

  # Invokes yes_no? with the default answer being 'no'
  def no?(prompt = 'More?')
    yes_no? prompt, default: false
  end

  # Invokes yes_no? with the default answer being 'yes'
  def yes?(prompt = 'More?')
    yes_no? prompt, default: true
  end
end

The above code can be mixed into any class or module. They ask the user a question and return true or false based on the user’s response.

The methods in the code issue a prompt and then read just one character from the user. The user can respond by typing the single character y to answer “yes” or n to answer “no”. If the user presses Space or Enter without typing anything else, the default answer is used.

The following irb session shows how to use the yes? and no? methods.

irb session
irb(main):152> yes? 'asdf'
asdf [Y/n] Enter or Space
=> true
irb(main):153>
yes? 'asdf' asdf [Y/n] n => false
irb(main):154>
no? 'asdf' asdf [y/N] Enter or Space => false
irb(main):155>
no? 'asdf' asdf [y/N] n => false
irb(main):156>
no? 'asdf' asdf [y/N] y => true

Subcommand Options

This section is incomplete, and the version of nugem described here is still under development. I am working on a major new release for nugem at present.

The documentation shows how to set up your code so subcommands can share common options, and also have options specific to certain subcommands. This is how most information on the interwebs says subcommand options should be organized:

Most commonly recommended form of subcommand options
COMMAND [GLOBAL FLAGS] [SUB-COMMAND [SUB-COMMAND FLAGS]]

For example, the nugem CLI has two subcommands: ruby and jekyll.

  • The --loglevel, --executable and --help options are available for all subcommands.
  • The ruby subcommand has no special options.
  • The --tags and --hooks options are only for the jekyll subcommand.

Using the documentated way of implementing subcommand option parsing, the following syntaxes would be allowed:

Allowed syntaxes
nugem --help
nugem ruby
nugem --loglevel info ruby
nugem --executable ruby
nugem --loglevel info --executable ruby
nugem jekyll
nugem jekyll --tags
nugem jekyll --tags --hooks
nugem --loglevel info jekyll --tags --hooks
nugem --loglevel info --executable jekyll --tags --hooks
... and others ...

The most commonly recommended approach requires that common options must precede the subcommand, and subcommand options must follow the subcommand. The following desirable syntaxes would therefore not be allowed:

Forbidden syntaxes
nugem ruby --executable
nugem jekyll --executable
nugem --tags jekyll
nugem --tags jekyll --executable
nugem jekyll --executable --tags
... and others ...

Here is a rough outline of how to implement this type of syntax:

Ruby code
global = OptionParser.new do |opts|
  # on statements for flags common to all commands go here
end

subcommands = {
  'foo' => OptionParser.new do |opts|
    # on statements for the foo command go here
  end
  'baz' => OptionParser.new do |opts|
    # on statements for the baz command go here
  end
}

global.order!
subcommands[ARGV.shift].order!

The last two lines of the above code show how global.order! parses all options in the first element of ARGV until the first token that does not start with a dash and removes them from ARGV.

The next line, subcommands[ARGV.shift].order!, parses and removes all options after the subcommand.

I do not like this syntax.

I prefer to order options before positional parameters and to be able to specify options in any order, anywhere on the command line. However, this makes parsing multiple subcommands difficult.

A more practical approach would be to mandate that the first parameter must be the subcommand name, followed by options and any other positional parameters.

Forbidden syntaxes
nugem ruby --executable
nugem ruby --executable
nugem jekyll --tags
nugem jekyll --tags --executable
nugem jekyll --executable --tags
... and others ...

Option­Parser on statements typically match specific flags or options. However, to match all options, regardless of their name or format, write the on statement as a custom order block:

Separating keywords from options
def separate_keywords_from_options
  @keywords      = ARGV.reject { |x| x.start_with? '-' }
  @argv = ARGV.select { |x| x.start_with? '-' }
end
Common option parsing
def parse_common_options(argv)
  OptionParser.new do |parser|
    parser.default_argv = argv
    parser.on '-e', '--executables', 'Include an executable'
    parser.on '-v', '--verbose', FalseClass
  end.order! into: @options
end
Subcommand option parsing
def parse_subcommand_ruby(argv)
  OptionParser.new do |parser|
    parser.default_argv = argv
    parser.on '-j', '--jump', 'Jump for joy'
    parser.on '-s', '--skip', 'Skip around'
  end.order! into: @options
end
process_keywords
def process_keywords(keywords)
  puts "Keywords are: #{keywords.join ', '}"
end
Ruby code
require 'optparse'

@argv = []
@options = {}
separate_keywords_from_options
parse_common_options @argv
parse_subcommand_ruby @argv
puts "Options: #{@options}"
puts "keywords: #{@keywords}"
process_keywords @keywords
* indicates a required field.

Please select the following to receive Mike Slinn’s newsletter:

You can unsubscribe at any time by clicking the link in the footer of emails.

Mike Slinn uses Mailchimp as his marketing platform. By clicking below to subscribe, you acknowledge that your information will be transferred to Mailchimp for processing. Learn more about Mailchimp’s privacy practices.