Using ruby's OptionParser to parse sub-commands
Asked Answered
D

4

30

I'd like to be able to use ruby's OptionParser to parse sub-commands of the form

COMMAND [GLOBAL FLAGS] [SUB-COMMAND [SUB-COMMAND FLAGS]]

like:

git branch -a
gem list foo

I know I could switch to a different option parser library (like Trollop), but I'm interested in learning how to do this from within OptionParser, since I'd like to learn the library better.

Any tips?

Demoralize answered 28/4, 2010 at 20:28 Comment(2)
No tips, aside from a suggestion to remain open to switching directions. In my experience, OptionParser has been frustrating to use for several reasons, one of them being the poor documentation -- hence your question. William Morgan, the author of Trollop, shows no mercy in his criticism (for example, see #898130 and trollop.rubyforge.org). I can't dispute what he says.Unoccupied
@FM: Well, like the author of that question, I'm stuck on a machine where importing libraries is a PITA, so I'm trying to make do with the standard libs - like optparse.Demoralize
D
49

Figured it out. I need to use OptionParser#order!. It will parse all the options from the start of ARGV until it finds a non-option (that isn't an option argument), removing everything it processes from ARGV, and then it will quit.

So I just need to do something like:

global = OptionParser.new do |opts|
  # ...
end
subcommands = { 
  'foo' => OptionParser.new do |opts|
     # ...
   end,
   # ...
   'baz' => OptionParser.new do |opts|
     # ...
   end
 }

 global.order!
 subcommands[ARGV.shift].order!
Demoralize answered 29/4, 2010 at 13:49 Comment(4)
For reference, a more complete example is in this Gist.Allisan
What if foo and baz share a lot of common options? How to avoid repetition?Kansas
Fernando Á : simple, just abstract the common options out to a method. def common_options(&blk) ; OptionParser.new { |opts| opts.on(...) ; ... ; blk.call(opts) } ; end, then call that method with a block for subcommand-specific options later - subcommands = { 'foo' => common_options { |opts| ... }, 'baz' => common_options { |opts| ... }, ... }Demoralize
Could you provide an example in code for this please?Ketose
I
1

It looks like the OptionParser syntax has changed some. I had to use the following so that the arguments array had all of the options not parsed by the opts object.

begin
  opts.order!(arguments)
rescue OptionParser::InvalidOption => io
  # Prepend the invalid option onto the arguments array
  arguments = io.recover(arguments)
rescue => e
  raise "Argument parsing failed: #{e.to_s()}"
end
Insinuation answered 28/4, 2011 at 15:51 Comment(0)
S
1

GLI is the way to go, https://github.com/davetron5000/gli. An excerpt from a tutorial:

#!/usr/bin/env ruby
require 'gli'
require 'hacer'

include GLI::App

program_desc 'A simple todo list'

flag [:t,:tasklist], :default_value => File.join(ENV['HOME'],'.todolist')

pre do |global_options,command,options,args|
  $todo_list = Hacer::Todolist.new(global_options[:tasklist])
end

command :add do |c|
  c.action do |global_options,options,args|
    $todo_list.create(args)
  end
end

command :list do |c|
  c.action do
    $todo_list.list.each do |todo|
      printf("%5d - %s\n",todo.todo_id,todo.text)
    end
  end
end

command :done do |c|
  c.action do |global_options,options,args|
    id = args.shift.to_i
    $todo_list.list.each do |todo|
      $todo_list.complete(todo) if todo.todo_id == id
    end
  end
end

exit run(ARGV)

You can find the tutorial at http://davetron5000.github.io/gli/.

Schwenk answered 14/7, 2013 at 21:25 Comment(0)
T
0

There are also other gems you can look at such as main.

Ticon answered 29/4, 2010 at 14:4 Comment(1)
@rampion: You can look at the samples, for example codeforpeople.com/lib/ruby/main/main-2.8.3/samples/f.rbTicon

© 2022 - 2024 — McMap. All rights reserved.