1

I'm trying to create a command line program with sub-commands using OptionParser. I'm following "ruby's OptionParser to get subcommands".

The problem is that it does not allow for a use case like this:

ruby main.rb --version 
#=> main.rb in `<main>': undefined method `order!' for nil:NilClass (NoMethodError)

But it does allow for this:

ruby main.rb foo --options
ruby main.rb --options foo
ruby main.rb --options foo --options

How would I be properly handle command line arguments, in the case that no subcommand is given.

My example code is:

global = OptionParser.new do |opts|
  opts.banner = "Usage: opt.rb [options] [subcommand [options]]"
  opts.on("-v", "--version", "Print the version") do |v|
    options[:version] = v
  end
  opts.separator ""
  opts.separator subtext
end
wurde
  • 2,487
  • 2
  • 20
  • 39
user2840647
  • 1,236
  • 1
  • 13
  • 32
  • 3
    Can you add your actual code? The example you linked to does allow for your case of `ruby main.rb --version` so without seeing your code, we cannot debug the problem. – jkeuhlen Jul 20 '15 at 18:10
  • @jkeuhlen the code is exactly the same as the URL I posted, except "--verbose" is replaced with "--version", the point being that the code as is, will throw a `nil:NilClass (NoMethodError)` if it does not follow `program [[options] [subcommand] [options]]` format – user2840647 Jul 20 '15 at 18:33

2 Answers2

1

Maybe this'll help:

require 'optparse'

VERSION = '1.0.0'

options = {}
OptionParser.new do |opt|
  opt.on('-f', '--foo', 'Foo it') { |o| options[:foo] = o }

  opt.on_tail('-v', '--version') do
    puts VERSION
    exit
  end

end.parse!

puts options

Saving it as "test.rb" and running it with ruby test.rb returns:

{}

Running it with ruby test.rb -f or --foo returns:

{:foo=>true}

Running it with ruby test.rb -v or --version returns:

1.0.0

For more fun, running ruby test.rb -h or --help returns:

Usage: test [options]
    -f, --foo                        Foo it

even though I didn't define -h or --help.

If I wanted the -v and --version flags to appear in the list then I'd change them from a on_tail method to a normal on method:

require 'optparse'

VERSION = '1.0.0'

options = {}
OptionParser.new do |opt|
  opt.on('-f', '--foo', 'Foo it') { |o| options[:foo] = o }

  opt.on('-v', '--version', 'Returns the version') do
    puts VERSION
    exit
  end

end.parse!

puts options

which would return:

Usage: test [options]
    -f, --foo                        Foo it
    -v, --version                    Returns the version

I can add:

puts ARGV

to the end of the script and see that OptionParser is correctly handling flags and parameters:

>ruby test.rb bar --foo
{:foo=>true}
bar

>ruby test.rb --foo bar
{:foo=>true}
bar

See "Pass variables to Ruby script via command line" for more information.


There is no way your example code will handle your sample inputs using --options. No handler for --options is defined. Nor is subtext. Your code returns:

undefined local variable or method `subtext' for main:Object (NameError)

Stripping the block to:

global = OptionParser.new do |opts|
  opts.on("-v", "--version", "Print the version") do |v|
    options[:version] = v
  end
end

and running again returns:

invalid option: --options (OptionParser::InvalidOption)

So, again, your example doesn't match the results you say you're getting.

Community
  • 1
  • 1
the Tin Man
  • 158,662
  • 42
  • 215
  • 303
1

The lines with the error:

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

If global.order! uses all of ARGV, then command is nil. So... check for that.

global.order!
command = ARGV.shift
unless command
  STDERR.puts "ERROR: no subcommand"
  STDERR.puts global # prints usage
  exit(-1)
end
subcommands[command].order!
rampion
  • 87,131
  • 49
  • 199
  • 315