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
2
  • 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. Commented Jul 20, 2015 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 Commented Jul 20, 2015 at 18:33

2 Answers 2

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!
Sign up to request clarification or add additional context in comments.

Comments

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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.