EDIT: Please, please, please read the two requirements listed at the bottom of this post before replying. People keep posting their new gems and li
Trollop is pretty cheap.
https://github.com/soveran/clap
other_args = Clap.run ARGV,
"-s" => lambda { |s| switch = s },
"-o" => lambda { other = true }
46LOC (at 1.0.0), no dependency on external option parser. Gets the job done. Probably not as full featured as others, but it's 46LOC.
If you check the code you can pretty easily duplicate the underlying technique -- assign lambdas and use the arity to ensure the proper number of args follow the flag if you really don't want an external library.
Simple. Cheap.
EDIT: the underlying concept boiled down as I suppose you might copy/paste it into a script to make a reasonable command line parser. It's definitely not something I would commit to memory, but using the lambda arity as a cheap parser is a novel idea:
flag = false
option = nil
opts = {
"--flag" => ->() { flag = true },
"--option" => ->(v) { option = v }
}
argv = ARGV
args = []
while argv.any?
item = argv.shift
flag = opts[item]
if flag
raise ArgumentError if argv.size < arity
flag.call(*argv.shift(arity))
else
args << item
end
end
# ...do stuff...
Suppose a command has at most one action and arbitrary number of options like this:
cmd.rb
cmd.rb action
cmd.rb action -a -b ...
cmd.rb action -ab ...
The parsing without validation may be like this:
ACTION = ARGV.shift
OPTIONS = ARGV.join.tr('-', '')
if ACTION == '***'
...
if OPTIONS.include? '*'
...
end
...
end
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.
This is very similar to the accepted answer, but using ARGV.delete_if
which is what I use in my simple parser. The only real difference is that options with arguments must be together (e.g. -l=file
).
def usage
"usage: #{File.basename($0)}: [-l=<logfile>] [-q] file ..."
end
$quiet = false
$logfile = nil
ARGV.delete_if do |cur|
next false if cur[0] != '-'
case cur
when '-q'
$quiet = true
when /^-l=(.+)$/
$logfile = $1
else
$stderr.puts "Unknown option: #{cur}"
$stderr.puts usage
exit 1
end
end
puts "quiet: #{$quiet} logfile: #{$logfile.inspect} args: #{ARGV.inspect}"
Here's the code snippet I use at the top of most of my scripts:
arghash = Hash.new.tap { |h| # Parse ARGV into a hash
i = -1
ARGV.map{ |s| /(-[a-zA-Z_-])?([^=]+)?(=)?(.+)?/m.match(s).to_a }
.each{ |(_,a,b,c,d)| h[ a ? "#{a}#{b}#{c}" : (i+=1) ] =
(a ? (c ? "#{d}" : true) : "#{b}#{c}#{d}")
}
[[:argc,Proc.new {|| h.count{|(k,_)| !k.is_a?(String)}}],
[:switches, Proc.new {|| h.keys.select{|k| k[0] == '-' }}]
].each{|(n,p)| h.define_singleton_method(n,&p) }
}
I also hate to require additional files in my quick-and-dirty scripts. My solution is very nearly what you're asking for. I paste a 10 line snippet of code at the top of any of my scripts that parses the command line and sticks positional args and switches into a Hash object (usually assigned to a object that I've named arghash in the examples below).
Here's an example command line you might want to parse...
./myexampleprog.rb -s -x=15 --longswitch arg1 --longswitch2=val1 arg2
Which would become a Hash like this.
{
'-s' => true,
'-x=' => '15',
'--longswitch' => true,
'--longswitch2=' => 'val1',
0 => 'arg1',
1 => 'arg2'
}
In addition to that, two convenience methods are added to the Hash:
argc()
will return the count of non-switch arguments.switches()
will return an array containing the keys for switches that are presentThis is mean to allow some quick and dirty stuff like...
arghash.argc == 2
)arghash[1]
always gets the second non-switch argument).arghash['--max=']
which yields a value of '15' given the example command line.arghash['-s']
which evaluates to true if it's present and nil if its absent.Test for the presence of a switch or alternatives of switches using set operations like
puts USAGETEXT if !(%w(-h --help) & arghash.switches()).empty?
Identify use of invalid switches using set operations such as
puts "Invalid switch found!" if !(arghash.switches - %w(-valid1 -valid2)).empty?
Specify default values for missing arguments using a simple Hash.merge()
such as the below example that fills in a value for -max= if one was not set and adds a 4th positional argument if one was not passed.
with_defaults = {'-max=' => 20, 3 => 'default.txt'}.merge(arghash)