tags:

views:

2765

answers:

8

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

  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.

+4  A: 

You can try something like:

if( ARGV.include( '-f' ) )
  file = ARGV[ARGV.indexof( '-f' ) + 1 )]
  ARGV.delete('-f')
  ARGV.delete(file)
end
NixNinja
+1 but good luck dealing with incorrect syntax
krusty.ar
Not exactly as clean and small as I'd been hoping for....
Curt Sampson
+6  A: 

I totally understand why you want to avoid optparse - it can get too much. But there are a few far "lighter" solutions (compared to OptParse) that come as libraries but are simple enough to make a single gem installation worthwhile.

For example, check out this OptiFlag example. Just a few lines for the processing. A slightly truncated example tailored to your case:

require 'optiflag'

module Whatever extend OptiFlagSet
  flag "f"
  and_process!
end 

ARGV.flags.f # => .. whatever ..

There are tons of customized examples too. I recall using another that was even easier, but it has escaped me for now but I will come back and add a comment here if I find it.

Peter Cooper
+12  A: 

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}
rampion
That's pretty awesome all right.
Curt Sampson
+1  A: 

Have you considered Thor by wycats? I think it's a lot cleaner than optparse. If you already have a script written, it might be some more work to format it or refactor it for thor, but it does make handling options very simple.

Here's the example snippet from the README:

class MyApp < Thor                                                # [1]
  map "-L" => :list                                               # [2]

  desc "install APP_NAME", "install one of the available apps"    # [3]
  method_options :force => :boolean, :alias => :optional          # [4]
  def install(name)
    user_alias = options[:alias]
    if options.force?
      # do something
    end
    # ... other code ...
  end

  desc "list [SEARCH]", "list all of the available apps, limited by SEARCH"
  def list(search = "")
    # list everything
  end
end

Thor automatically maps commands as such:

app install myname --force

That gets converted to:

MyApp.new.install("myname")
# with {'force' => true} as options hash
  1. Inherit from Thor to turn a class into an option mapper
  2. Map additional non-valid identifiers to specific methods. In this case, convert -L to :list
  3. Describe the method immediately below. The first parameter is the usage information, and the second parameter is the description.
  4. Provide any additional options. These will be marshaled from -- and - params. In this case, a --force and a -f option is added.
Jack Chu
I like the command-mapping thing, since a single binary with a bunch of subcommands is something I do often. Still, though you've gone a ways from 'light'. Could you find an even simpler way to express that same functionality? What if you didn't need to print `--help` output? What if "head myprogram.rb" was the help output?
Curt Sampson
+1  A: 

Trollop is pretty cheap.

Noah
That would be, <http://trollop.rubyforge.org/>. I rather like it, I think, though I really wasn't looking for a library.
Curt Sampson
True, it is a library. However, at < 800 LOC, it's a pretty negligible one, at that.http://gitorious.org/trollop/mainline/blobs/master/lib/trollop.rb
Noah
I was kinda thinking that maybe 30-50 lines would be good, if I were going so far as to use a "library." But then again, I guess once you've got as far as a separate file full of code, API design is more important than line count. Still, I'm not sure I would want to include it in a one-off script that I just want to plop into the bin directory on a random system.
Curt Sampson
+27  A: 

As the author of Trollop, I cannot BELIEVE the shit 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 half-assed "framework" just to parse the goddamn commandline?

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.

BTW, +1 for having written Trollop (which had already been mentioned here), but feel free to tone down the first paragraph a bit.
Curt Sampson
He has a right to complain in this case I'm afraid. When you look at the alternatives: [[1](http://rubyforge.org/docman/view.php/632/233/posted-docs.index.html)] [[2](http://ruby-doc.org/core/classes/OptionParser.html)] [[3](http://ruby-doc.org/core/classes/GetoptLong.html)], for what is basically just processing a simple string array (no really, let that sink in), you can't help but wonder WHY? What do you gain from all that bloat? This is not C, where strings are, "problematic". Of course to each his own. :)
srcspider
+3  A: 

Here's the standard technique I usually use:

#!/usr/bin/env ruby

def usage(s)
    $stderr.puts(s)
    $stderr.puts("Usage: #{File.basename($0)}: [-l <logfile] [-q] file ...")
    exit(2)
end

$quiet   = false
$logfile = nil

loop { case ARGV[0]
    when '-q':  ARGV.shift; $quiet = true
    when '-l':  ARGV.shift; $logfile = ARGV.shift
    when /^-/:  usage("Unknown option: #{ARGV[0].inspect}")
    else break
end; }

# Program carries on here.
puts("quiet: #{$quiet} logfile: #{$logfile.inspect} args: #{ARGV.inspect}")
Curt Sampson
A: 

Apparently Mr. Morgan and I think alike. I just released last night to Github what I now see is a similar library to Trollop (Named how?) after having done a search for OptionParser on Github, see Switches

There are a few differences, but the philosophy is the same. One obvious difference is that Switches is dependent on OptionParser.

thoran