Really Cheap Command-Line Option Parsing in Ruby

EDIT: Please, please, please read the two requirements listed at the bottom of this post before replying. People keep posting their new gems and libraries and whatnot, which clearly don't meet the requirements.

Sometimes I want to very cheaply hack some command line options into a simple script. A fun way to do it, without dealing with getopts or parsing or anything like that, is:

...
$quiet       = ARGV.delete('-d')
$interactive = ARGV.delete('-i')
...
# Deal with ARGV as usual here, maybe using ARGF or whatever.

It's not quite the normal Unix options syntax, because it will accept options non-option command line parameters, as in "myprog -i foo bar -q", but I can live with that. (Some people, such as the Subversion developers, prefer this. Sometimes I do too.)

An option that's just present or absent can't be implemented much more simply than the above. (One assignment, one function call, one side effect.) Is there an equally simple way to deal with options that take a parameter, such as "-f filename"?

EDIT:

One point I didn't make earlier on, because it hadn't become clear to me until the author of Trollop mentioned that the library fit "in one [800-line] file," is that I'm looking not only for clean syntax, but for a technique that has the following characteristics:

  1. The entirety of the code can be included in the script file (without overwhelming the actual script itself, which may be only a couple of dozen lines), so that one can drop a single file in a bin dir on any system with a standard Ruby 1.8.[5-7] installation and use it. If you can't write a Ruby script that has no require statements and where the code to parse a couple of options is under a dozen lines or so, you fail this requirement.

  2. The code is small and simple enough that one can remember enough of it to directly type in code that will do the trick, rather than cutting and pasting from somewhere else. Think of the situation where you're on the console of a firewalled sever with no Internet access, and you want to toss together a quick script for a client to use. I don't know about you, but (besides failing the requirement above) memorizing even the 45 lines of simplified micro-optparse is not something I care to do.


Solution 1:

As the author of Trollop, I cannot BELIEVE the stuff that people think is reasonable in an option parser. Seriously. It boggles the mind.

Why should I have to make a module that extends some other module to parse options? Why should I have to subclass anything? Why should I have to subscribe to some "framework" just to parse the command line?

Here's the Trollop version of the above:

opts = Trollop::options do
  opt :quiet, "Use minimal output", :short => 'q'
  opt :interactive, "Be interactive"
  opt :filename, "File to process", :type => String
end

And that's it. opts is now a hash with keys :quiet, :interactive, and :filename. You can do whatever you want with it. And you get a beautiful help page, formatted to fit your screen width, automatic short argument names, type checking... everything you need.

It's one file, so you can drop it in your lib/ directory if you don't want a formal dependency. It has a minimal DSL that is easy to pick up.

LOC per option people. It matters.

Solution 2:

I share your distaste for require 'getopts', mainly due to the awesomeness that is OptionParser:

% cat temp.rb                                                            
require 'optparse'
OptionParser.new do |o|
  o.on('-d') { |b| $quiet = b }
  o.on('-i') { |b| $interactive = b }
  o.on('-f FILENAME') { |filename| $filename = filename }
  o.on('-h') { puts o; exit }
  o.parse!
end
p :quiet => $quiet, :interactive => $interactive, :filename => $filename
% ruby temp.rb                                                           
{:interactive=>nil, :filename=>nil, :quiet=>nil}
% ruby temp.rb -h                                                        
Usage: temp [options]
    -d
    -i
    -f FILENAME
    -h
% ruby temp.rb -d                                                        
{:interactive=>nil, :filename=>nil, :quiet=>true}
% ruby temp.rb -i                                                        
{:interactive=>true, :filename=>nil, :quiet=>nil}
% ruby temp.rb -di                                                       
{:interactive=>true, :filename=>nil, :quiet=>true}
% ruby temp.rb -dif apelad                                               
{:interactive=>true, :filename=>"apelad", :quiet=>true}
% ruby temp.rb -f apelad -i                                              
{:interactive=>true, :filename=>"apelad", :quiet=>nil}