Handle command-line switch in Ruby without if...else block - ruby

In a blog post about unconditional programming Michael Feathers shows how limiting if statements can be used as a tool for reducing code complexity.
He uses a specific example to illustrate his point. Now, I've been thinking about other specific examples that could help me learn more about unconditional/ifless/forless programming.
For example, using OptionParser I made a cat clone that will upcase the stream if the --upcase switch is set:
#!/usr/bin/env ruby
require 'optparse'
options = {}
OptionParser.new do |opts|
opts.banner = "Usage: cat [options] [file ...]"
opts.on("-u", "--upcase", "Upcase stream") do
options[:upcase] = true
end
end.parse!
if options[:upcase]
puts ARGF.read.upcase
else
puts ARGF.read
end
How would I handle that switch without an if..else block?
Also interested in links to other illustrative specific examples.

Try this,
#!/usr/bin/env ruby
require 'optparse'
options = { :transform => :itself }
OptionParser.new do |opts|
opts.banner = "Usage: cat [options] [file ...]"
opts.on("-u", "--upcase", "Upcase stream") do
options[:transform] = :upcase
end
# add more options for downcase, reverse, etc ...
end.parse!
puts ARGF.read.send(options[:transform])
This worked quite well, I am actually surprised how well that worked.
What has been changed?
The option is internally renamed to :transform
The internal default value is :itself
The command line switch sets the internal option to :upcase
Call the method with send
Not all if statements can be improved upon like this though. I would guess the idea of unconditional programming is to prefer a combination of meaningful default values, as I did above, and intention revealing functions whenever it seems reasonable but not at all costs.
Here are some examples of intention revealing functions,
max
min
Hash#fetch
Enumerable#detect
Enumerable#select
Enumerable#chunk
Enumerable#drop_while
Enumerable#slice_when
Enumerable#take_while
etc...
Another related practice is forless programming.
If you want to practice unconditional and forless programming best look for examples that process arrays and strings and make use of the many "functional" methods in Ruby's enumerable module.
Here is an example of string justification without for and if,
str = 'This is an example to be aligned to both margins'
words = str.split
width, remainder = (50 - words.map(&:length).inject(:+)).divmod(words.length - 1)
words.take(words.length - 1).each { |each| width.times { each << 32 }}
words.take(words.length - 1).shuffle.take(remainder).each { |each| each << 32 }
p words.join
# => "This is an example to be aligned to both margins"

Eliminating conditions is a tool for reducing complexity, not an end goal. I explained that better in my other answer. In this case the condition must be there because whether or not options[:upcase] is set is part of the logic. But you can at least eliminate the duplication.
Because the else clause is the same as the if clause but just adds a step, you can remove the repetition and make your code linear by unconditionally doing the common bit, and then conditionally doing the extra stuff.
data = ARGF.read;
data.upcase! if options[:upcase];

Related

Detecting a missing sub!, sort!, map!, etc

After returning to Ruby from a long stint coding in another language, I regularly assume that foo.sort, foo.map {...}, foo.sub /bar/, 'zip' will change foo. Of course I meant foo.sort!, etc. But that usually takes 3 or 4 debugging potshots before I notice. Meanwhile, the sort is calculated, but then isn't assigned to anything. Can I make ruby warn about that missing lvalue, like a C compiler warns of a function's ignored return value?
You mean like Perl's somewhat infamous "using map in void context"? I don't know of Ruby having such a thing. Sounds like you need more unit testing to catch mistakes like this before they can worm into your code deeply enough to be considered bugs.
Keep in mind Ruby's a lot more flexible than languages like Perl. For example, the following code might be useful:
def rewrite(list)
list.map do |row|
row += '!'
end
end
Now technically that's a map in a void context, but because it's used as a return value it might be captured elsewhere. It's the responsibility of the caller to make use of it. Flagging the method itself for some sort of warning is a level removed from what most linting type tools can do.
Here's a very basic parser :
#forgetful_methods = %w(sort map sub)
Dir['*.rb'].each do |script|
File.readlines(script).each.with_index(1) do |line, i|
#forgetful_methods.each do |method|
if line =~ /\.#{method}(?!!)/ && $` !~ /(=|\b(puts|print|return)\b|^#)/
puts format('%-25s (%3d) : %s', script, i, line.strip)
end
end
end
end
# =>
# brace_globbing.rb ( 13) : subpatterns.map{|subpattern| explode_extglob(match.pre_match+subpattern+match.post_match)}.flatten
# delegate.rb ( 11) : #targets.map { |t| t.send(m, *args) }
It checks every ruby script in the current directory for sort, map or sub without ! that aren't preceded by =, puts, print or return.
It's just a start, but maybe it could help you find some of the low hanging fruits.
There are many false positives, though.
A more complex version could use abstract syntax trees, for example with Ripper.

Multiple in-place substitutions in one line

What is the most idiomatic way to do multiple in-place substitutions on a string when none of the patterns being replaced are guaranteed to have a match?
For instance, say I have an array of strings and I want to replace "sad" with "happy" and "goodbye" with "hello" in each one:
a = ["I am sad", "goodbye for now"]
# This will work:
a.map! do |s|
s = s.gsub(/sad/,"happy").gsub(/goodbye/,"hello")
end
# So will this:
a.each do |s|
s.gsub!(/sad/,"happy")
s.gsub!(/goodbye/,"hello")
end
# This will fail when s does not match /sad/:
a.each do |s|
s.gsub!(/sad/,"happy").gsub!(/goodbye/,"hello")
end
The first option seems a little bit silly, since logically I'm trying to do an in-place substitution rather than a re-assignment. The second option is okay, but my aesthetic sense tells me that it seems wrong to be required to turn the substitution into two sequential statements, particularly in cases when only one or the other substitution is expected to be successful (which, ironically, is exactly the case that causes the third version, which looks "right" to me, to fail). Also, it's probably wrong to use each destructively as I'm doing here, but if I used map! instead, I'd need to add s as the final line in the block to ensure I don't accidentally make nil entries when substitution fails, which seems almost sillier than the first option.
I'm guessing the reason the (g)sub! methods return nil when no substitution is done is because that makes them convenient for use in logical constructs, which is admittedly a very good reason (especially since the non-destructive versions obviously must return "true" values no matter what).
So...I know this is really very little more than a minor aesthetic quibble, but is there a better way than the two (working) ways I've shown? If not, is there any reason to prefer one over the other (beyond my intuitive aesthetic preference for the second version)?
First you need to create a replacement_hash as below :
replacement_hash = { "sad" => "happy", "goodbye" => "hello"}
a = ["I am sad", "goodbye for now"]
Regexp.union(replacement_hash.keys) # => /sad|goodbye/
a.map { |s| s.gsub(Regexp.union(replacement_hash.keys), replacement_hash) }
# => ["I am happy", "hello for now"]
If in-place replacement needed, do as below :-
a.each { |s| s.gsub!(Regexp.union(replacement_hash.keys), replacement_hash) }

Passing An Array Via Command Line

I have a Ruby script, called foobar.rb, which takes multiple parameters.
I want to (optionally) be able to specify an array of integers on the command line and be able to process them as a single option. I think that my command line would look something like this:
foobar.rb [1,2,3]
On a scale of 1-10 my knowledge of Ruby is probably around a 6. Just enough to know that there's probably an easy way to accomplish this, but not enough to know what it is or even where to look in the docs.
How can I parse this single comma-separated list of integers and end up with an Array in the code? I would prefer an idomatic, 1-liner solution that doesn't require the addition of any external libraries, if such a solution exists.
I would use optparse for it myself, like this:
require 'optparse'
options = {}
OptionParser.new do |opts|
opts.banner = "Usage: #{$0} [options]"
opts.on("-a", "--argument a,b,c", Array, "Array of arguments") { |a| options[:array] = a.map { |v| v.to_i } }
end.parse!
puts options.inspect
=> {:array=>["1", "2", "3", "4"]}
If you're using bash as your terminal, this should work :
integer_array = ARGV[0].scan(/\d/).map(&:to_i) # => Array containing 1,2,3
Tried it with zsh and it crashes, because zsh tries to interpret the [] on the command line though
For zsh, you'd have to use
foobar.rb "[1,2,3]"

Ruby case statement with multiple variables using an Array

I'd like to compare multiple variables for a case statement, and am currently thinking overriding the case equals operator (===) for Array is the best way to do it. Is this the best way?
Here is an example use case:
def deposit_apr deposit,apr
# deposit: can be nil or 2 length Array of [nil or Float, String]
# apr: can be nil or Float
case [deposit,apr]
when [[Float,String],Float]
puts "#{deposit[0]} #{deposit[1]}, #{apr*100.0}% APR"
when [[nil,String],Float]
puts "#{apr*100.0}% APR on deposits greater than 100 #{deposit[1]}"
when [[Float,String],nil]
puts "#{deposit[0]} #{deposit[1]}"
else
puts 'N/A'
end
end
The only problem is the Array case equals operator doesn't apply the case equal to the elements of the Array.
ruby-1.9.2-p0 > deposit_apr([656.00,'rupees'],0.065)
N/A
It will if I override, but am not sure what I'd be breaking if I did:
class Array
def ===(other)
result = true
self.zip(other) {|bp,ap| result &&= bp === ap}
result
end
end
Now, it all works:
ruby-1.9.2-p0 > deposit_apr([656.00,'rupees'],0.065)
656.0 rupees, 6.5% APR
Am I missing something?
I found this question because I was looking to run a case statement on multiple variables, but, going through the following, came to the conclusion that needing to compare multiple variables might suggest that a different approach is needed. (I went back to my own code with this conclusion, and found that even a Hash is helping me write code that is easier to understand.)
Gems today use "no monkey patching" as a selling point. Overriding an operator is probably not the right approach. Monkey patching is great for experimentation, but it's too easy for things to go awry.
Also, there's a lot of type-checking. In a language that is designed for Duck Typing, this clearly indicates the need for a different approach. For example, what happens if I pass in integer values instead of floats? We'd get an 'N/A', even though that's not likely what we're looking for.
You'll notice that the example given in the question is difficult to read. We should be able to find a way to represent this logic more clearly to the reader (and to the writer, when they revisit the code again in a few months and have to puzzle out what's going on).
And finally, since there are multiple numbers with associated logic, it seems like there's at least one value object-type class (Deposit) that wants to be written.
For cleanliness, I'm going to assume that a nil APR can be considered a 0.0% APR.
class Deposit
def initialize(amount, unit='USD', options={})
#amount = amount.to_f # `nil` => 0.0
#unit = unit.to_s # Example assumes unit is always present
#apr = options.fetch(:apr, 0.0).to_f # `apr: nil` => 0.0
end
end
Once we have our Deposit object, we can implement the print logic without needing case statements at all.
class Deposit
# ... lines omitted
def to_s
string = "#{#amount} #{#unit}"
string << ", #{#apr * 100.0}% APR" if #apr > 0.0
string
end
end
d = Deposit.new(656.00, 'rupees', apr: 0.065)
d.to_s
# => "656.0 rupees, 6.5% APR"
e = Deposit.new(100, 'USD', apr: nil)
e.to_s
# => "100.0 USD"
f = Deposit.new(100, 'USD')
f.to_s
# => "100.0 USD"
Conclusion: If you're comparing multiple variables in a case statement, use that as a smell to suggest a deeper design issue. Multiple-variable cases might indicate that there's an object that wants to be created.
If you are worried about breaking something by changing Array behavior, and certainly that's a reasonable worry, then just put your revised operator in a subclass of Array.
it's definitely not the best way. even more - you should not redefine methods of standart classes as core functionality may depend on it - have fun debugging then.
defensive style is nice(with lot of type checks and whatnot) but it usually hurts performance and readability.
if you know that you will not pass anything else than bunch of floats and strings to that method - why do you need all those checks for?
IMO use exception catching and fix the source of problem, don't try to fix the problem somewhere in the middle

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:
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.
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.
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.
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}
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' then ARGV.shift; $quiet = true
when '-l' then ARGV.shift; $logfile = ARGV.shift
when /^-/ then usage("Unknown option: #{ARGV[0].inspect}")
else break
end; }
# Program carries on here.
puts("quiet: #{$quiet} logfile: #{$logfile.inspect} args: #{ARGV.inspect}")
Since nobody appeared to mention it, and the title does refer to cheap command-line parsing, why not just let the Ruby interpreter do the work for you? If you pass the -s switch (in your shebang, for example), you get dirt-simple switches for free, assigned to single-letter global variables. Here's your example using that switch:
#!/usr/bin/env ruby -s
puts "#$0: Quiet=#$q Interactive=#$i, ARGV=#{ARGV.inspect}"
And here's the output when I save that as ./test and chmod it +x:
$ ./test
./test: Quiet= Interactive=, ARGV=[]
$ ./test -q foo
./test: Quiet=true Interactive=, ARGV=["foo"]
$ ./test -q -i foo bar baz
./test: Quiet=true Interactive=true, ARGV=["foo", "bar", "baz"]
$ ./test -q=very foo
./test: Quiet=very Interactive=, ARGV=["foo"]
See ruby -h for details.
That must be as cheap as it gets. It will raise a NameError if you try a switch like -:, so there's some validation there. Of course, you can't have any switches after a non-switch argument, but if you need something fancy, you really should be using at the minimum OptionParser. In fact, the only thing that annoys me about this technique is that you'll get a warning (if you've enabled them) when accessing an unset global variable, but it's still falsey, so it works just fine for throwaway tools and quick scripts.
One caveat pointed out by FelipeC in the comments in "How to do really cheap command-line option parsing in Ruby", is that your shell might not support the 3-token shebang; you may need to replace /usr/bin/env ruby -w with the actual path to your ruby (like /usr/local/bin/ruby -w), or run it from a wrapper script, or something.
I built micro-optparse to fill this obvious need for a short, but easy to use option-parser. It has a syntax similar to Trollop and is 70 lines short. If you don't need validations and can do without empty lines you can cut it down to 45 lines. I think that's exactly what you were looking for.
Short example:
options = Parser.new do |p|
p.version = "fancy script version 1.0"
p.option :verbose, "turn on verbose mode"
p.option :number_of_chairs, "defines how many chairs are in the classroom", :default => 1
p.option :room_number, "select room number", :default => 2, :value_in_set => [1,2,3,4]
end.process!
Calling the script with -h or --help will print
Usage: micro-optparse-example [options]
-v, --[no-]verbose turn on verbose mode
-n, --number-of-chairs 1 defines how many chairs are in the classroom
-r, --room-number 2 select room number
-h, --help Show this message
-V, --version Print version
It checks if input is of same type as the default value, generates short and long accessors, prints descriptive error messages if invalid arguments are given and more.
I compared several option-parser by using each option-parser for the problem I had.
You can use these examples and my summary to make an informative decision.
Feel free to add more implementations to the list. :)
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.
You can try something like:
if( ARGV.include( '-f' ) )
file = ARGV[ARGV.indexof( '-f' ) + 1 )]
ARGV.delete('-f')
ARGV.delete(file)
end
This is what I use for really, really cheap args:
def main
ARGV.each { |a| eval a }
end
main
so if you run programname foo bar it calls foo and then bar. It's handy for throwaway scripts.
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
Inherit from Thor to turn a class into an option mapper
Map additional non-valid identifiers to specific methods. In this case, convert -L to :list
Describe the method immediately below. The first parameter is the usage information, and the second parameter is the description.
Provide any additional options. These will be marshaled from -- and - params. In this case, a --force and a -f option is added.
Here's my favorite quick-and-dirty option parser:
case ARGV.join
when /-h/
puts "help message"
exit
when /-opt1/
puts "running opt1"
end
The options are regular expressions, so "-h" also would match "--help".
Readable, easy to remember, no external library, and minimal code.
Trollop is pretty cheap.
If you want a simple command line parser for key/value commands without the use of gems:
But this only works if you always have key/value pairs.
# example
# script.rb -u username -p mypass
# check if there are even set of params given
if ARGV.count.odd?
puts 'invalid number of arguments'
exit 1
end
# holds key/value pair of cl params {key1 => value1, key2 => valye2, ...}
opts = {}
(ARGV.count/2).times do |i|
k,v = ARGV.shift(2)
opts[k] = v # create k/v pair
end
# set defaults if no params are given
opts['-u'] ||= 'root'
# example use of opts
puts "username:#{opts['-u']} password:#{opts['-p']}"
If you don't' need any checking you could just use:
opts = {}
(ARGV.count/2).times do |i|
k,v = ARGV.shift(2)
opts[k] = v # create k/v pair
end
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 present
This is mean to allow some quick and dirty stuff like...
Validate I've got the right number of positional arguments regardless of the switches passed in ( arghash.argc == 2 )
Access positional arguments by their relative position, regardless of switches appearing before or interspersed with positional arguments ( e.g. arghash[1] always gets the second non-switch argument).
Support value-assigned switches in the command line such as "--max=15" which can be accessed by arghash['--max='] which yields a value of '15' given the example command line.
Test for the presence or absence of a switch in the command line using a very simple notation such as 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)
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. -lfile).
def usage
"usage: #{File.basename($0)}: [-l<logfile>] [-q] file ..."
end
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
Apparently #WilliamMorgan 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.
I'm developing my own option parser gem called Acclaim.
I wrote it because I wanted to create git-style command line interfaces and be able to cleanly separate the functionality of each command into separate classes, but it can also be used without the entire command framework as well:
(options = []) << Acclaim::Option.new(:verbose, '-v', '--verbose')
values = Acclaim::Option::Parser.new(ARGV, options).parse!
puts 'Verbose.' if values.verbose?
No stable release as of yet, but I've already implemented some features like:
custom option parser
flexible parsing of option's arguments that allows for both minimum and optional
support for many option styles
replace, append or raise on multiple instances of the same option
custom option handlers
custom type handlers
predefined handlers for the common standard library classes
There's a lot of emphasis on commands so it might be a little heavy for simple command line parsing but it works well and I've been using it on all of my projects. If you're interested in the command interface aspect then check out the project's GitHub page for more information and examples.
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
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...
I created a very simple yet useful parser: parseopt. It uses Git's internal option parser as inspiration, and also Ruby's OptionParser.
It looks like this:
opts = ParseOpt.new
opts.usage = 'git foo'
opts.on('b', 'bool', 'Boolean') do |v|
$bool = v
end
opts.on('s', 'string', 'String') do |v|
$str = v
end
opts.on('n', 'number', 'Number') do |v|
$num = v.to_i
end
opts.parse

Resources