Related
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];
I am having some issues searching for what options have been passed in the ARGV. I have,
if ARGV.include? '-v' == true
puts "Do junk"
else
puts "Nope"
end
This seems like a shockingly simple thing but I always get "Nope" either when I place "-v" or when I do not. Am I missing something simple here?
I don know if it's your problem, but you don't need the == true.
if ARGV.include? '-v'
puts "Do junk"
else
puts "Nope"
end
The include? method returns true or false.
Why are you searching ARGV for options? Use the standard library's OptionParser class, which is made for this.
require 'optparse'
require 'pp'
options = {}
OptionParser.new { |opt|
opt.on('-v', '--verbose', 'Be verbose') { |o| options[:verbose] = o }
}.parse!
pp options
Save that, and run it with something like: ruby test.rb -v or ruby test.rb --verbose and you'll see:
{:verbose=>true}
Run it with ruby test.rb -h or ruby test.rb --help and you'll see:
Usage: test [options]
-v, --verbose Be verbose
OptionParser has all sorts of nice (and intelligent) tricks for setting booleans, required parameters, coercing values so you can get multiple values for a parameter returned as an array, etc. And, notice that it created the help for me, including using -h or --help for the flag.
Look at the examples in the documentation for additional ideas of what it can do.
This is a precedence problem. Write as'
if ARGV.include?('-v') == true
puts "Do junk"
else
puts "Nope"
end
In your case if ARGV.include? '-v' == true has been interpreted as if ARGV.include?
('-v' == true). Now '-v' == true returns false, and false is not included in your ARGV array, so if evaluates as false, and else part is getting executed.
Better to write as
if ARGV.include? '-v'
puts "Do junk"
else
puts "Nope"
end
Because ARGV is an array, and Array#include?(ob) returns true, if the ob found inside the array.
All of the other answers so far are right in that it's a precedence problem.
Here are a couple helpful notes, the first of which has already been touched on:
You don't need == true, since the include? method already returns true or false.
For parsing command-line arguments, you might find it more intuitive to use a library like Trollop. Using Trollop, your example could be done like this:
require 'trollop'
opts = Trollop::options do
opt :verbose, "Run verbosely" # (-v is created automatically from this)
end
if opts[:v]
puts "yes"
else
puts "no"
end
(EDIT: See also the Tin Man's answer re: optparse)
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]"
Is there any way to kick off OptionParser several times in one Ruby program, each with different sets of options?
For example:
$ myscript.rb --subsys1opt a --subsys2opt b
Here, myscript.rb would use subsys1 and subsys2, delegating their options handling logic to them, possibly in a sequence where 'a' is processed first, followed by 'b' in separate OptionParser object; each time picking options only relevant for that context.
A final phase could check that there is nothing unknown left after each part processed theirs.
The use cases are:
In a loosely coupled front-end program, where various components have different arguments, I don't want 'main' to know about everything, just to delegate sets of arguments/options to each part.
Embedding some larger system like RSpec into my application, and I'd to simply pass a command-line through their options without my wrapper knowing those.
I'd be OK with some delimiter option as well, like -- or --vmargs in some Java apps.
There are lots of real world examples for similar things in the Unix world (startx/X, git plumbing and porcelain), where one layer handles some options but propagates the rest to the lower layer.
Out of the box, this doesn't seem to work. Each OptionParse.parse! call will do exhaustive processing, failing on anything it doesn't know about.
I guess I'd happy to skip unknown options.
Any hints, perhaps alternative approaches are welcome.
I needed a solution that wouldn't throw OptionParser::InvalidOption ever, and couldn't find an elegant solution among the current answers. This monkey patch is based on one of the other answers but cleans it up and makes it work more like the current order! semantics. But see below for an unsolved issue inherent to multiple-pass option parsing.
class OptionParser
# Like order!, but leave any unrecognized --switches alone
def order_recognized!(args)
extra_opts = []
begin
order!(args) { |a| extra_opts << a }
rescue OptionParser::InvalidOption => e
extra_opts << e.args[0]
retry
end
args[0, 0] = extra_opts
end
end
Works just like order! except instead of throwing InvalidOption, it leaves the unrecognized switch in ARGV.
RSpec tests:
describe OptionParser do
before(:each) do
#parser = OptionParser.new do |opts|
opts.on('--foo=BAR', OptionParser::DecimalInteger) { |f| #found << f }
end
#found = []
end
describe 'order_recognized!' do
it 'finds good switches using equals (--foo=3)' do
argv = %w(one two --foo=3 three)
#parser.order_recognized!(argv)
expect(#found).to eq([3])
expect(argv).to eq(%w(one two three))
end
it 'leaves unknown switches alone' do
argv = %w(one --bar=2 two three)
#parser.order_recognized!(argv)
expect(#found).to eq([])
expect(argv).to eq(%w(one --bar=2 two three))
end
it 'leaves unknown single-dash switches alone' do
argv = %w(one -bar=2 two three)
#parser.order_recognized!(argv)
expect(#found).to eq([])
expect(argv).to eq(%w(one -bar=2 two three))
end
it 'finds good switches using space (--foo 3)' do
argv = %w(one --bar=2 two --foo 3 three)
#parser.order_recognized!(argv)
expect(#found).to eq([3])
expect(argv).to eq(%w(one --bar=2 two three))
end
it 'finds repeated args' do
argv = %w(one --foo=1 two --foo=3 three)
#parser.order_recognized!(argv)
expect(#found).to eq([1, 3])
expect(argv).to eq(%w(one two three))
end
it 'maintains repeated non-switches' do
argv = %w(one --foo=1 one --foo=3 three)
#parser.order_recognized!(argv)
expect(#found).to eq([1, 3])
expect(argv).to eq(%w(one one three))
end
it 'maintains repeated unrecognized switches' do
argv = %w(one --bar=1 one --bar=3 three)
#parser.order_recognized!(argv)
expect(#found).to eq([])
expect(argv).to eq(%w(one --bar=1 one --bar=3 three))
end
it 'still raises InvalidArgument' do
argv = %w(one --foo=bar)
expect { #parser.order_recognized!(argv) }.to raise_error(OptionParser::InvalidArgument)
end
it 'still raises MissingArgument' do
argv = %w(one --foo)
expect { #parser.order_recognized!(argv) }.to raise_error(OptionParser::MissingArgument)
end
end
end
Problem: normally OptionParser allows abbreviated options, provided there are enough characters to uniquely identify the intended option. Parsing options in multiple stages breaks this, because OptionParser doesn't see all the possible arguments in the first pass. For example:
describe OptionParser do
context 'one parser with similar prefixed options' do
before(:each) do
#parser1 = OptionParser.new do |opts|
opts.on('--foobar=BAR', OptionParser::DecimalInteger) { |f| #found_foobar << f }
opts.on('--foo=BAR', OptionParser::DecimalInteger) { |f| #found_foo << f }
end
#found_foobar = []
#found_foo = []
end
it 'distinguishes similar prefixed switches' do
argv = %w(--foo=3 --foobar=4)
#parser1.order_recognized!(argv)
expect(#found_foobar).to eq([4])
expect(#found_foo).to eq([3])
end
end
context 'two parsers in separate passes' do
before(:each) do
#parser1 = OptionParser.new do |opts|
opts.on('--foobar=BAR', OptionParser::DecimalInteger) { |f| #found_foobar << f }
end
#parser2 = OptionParser.new do |opts|
opts.on('--foo=BAR', OptionParser::DecimalInteger) { |f| #found_foo << f }
end
#found_foobar = []
#found_foo = []
end
it 'confuses similar prefixed switches' do
# This is not generally desirable behavior
argv = %w(--foo=3 --foobar=4)
#parser1.order_recognized!(argv)
#parser2.order_recognized!(argv)
expect(#found_foobar).to eq([3, 4])
expect(#found_foo).to eq([])
end
end
end
Assuming the order in which the parsers will run is well defined, you can just store the extra options in a temporary global variable and run OptionParser#parse! on each set of options.
The easiest way to do this is to use a delimiter like you alluded to. Suppose the second set of arguments is separated from the first by the delimiter --. Then this will do what you want:
opts = OptionParser.new do |opts|
# set up one OptionParser here
end
both_args = $*.join(" ").split(" -- ")
$extra_args = both_args[1].split(/\s+/)
opts.parse!(both_args[0].split(/\s+/))
Then, in the second code/context, you could do:
other_opts = OptionParser.new do |opts|
# set up the other OptionParser here
end
other_opts.parse!($extra_args)
Alternatively, and this is probably the "more proper" way to do this, you could simply use OptionParser#parse, without the exclamation point, which won't remove the command-line switches from the $* array, and make sure that there aren't options defined the same in both sets. I would advise against modifying the $* array by hand, since it makes your code harder to understand if you are only looking at the second part, but you could do that. You would have to ignore invalid options in this case:
begin
opts.parse
rescue OptionParser::InvalidOption
puts "Warning: Invalid option"
end
The second method doesn't actually work, as was pointed out in a comment. However, if you have to modify the $* array anyway, you can do this instead:
tmp = Array.new
while($*.size > 0)
begin
opts.parse!
rescue OptionParser::InvalidOption => e
tmp.push(e.to_s.sub(/invalid option:\s+/,''))
end
end
tmp.each { |a| $*.push(a) }
It's more than a little bit hack-y, but it should do what you want.
I've got the same problem, and I found the following solution:
options = ARGV.dup
remaining = []
while !options.empty?
begin
head = options.shift
remaining.concat(parser.parse([head]))
rescue OptionParser::InvalidOption
remaining << head
retry
end
end
For posterity, you can do this with the order! method:
option_parser.order!(args) do |unrecognized_option|
args.unshift(unrecognized_option)
end
At this point, args has been modified - all known options were consumed and handled by option_parser - and can be passed to a different option parser:
some_other_option_parser.order!(args) do |unrecognized_option|
args.unshift(unrecognized_option)
end
Obviously, this solution is order-dependent, but what you are trying to do is somewhat complex and unusual.
One thing that might be a good compromise is to just use -- on the command line to stop processing. Doing that would leave args with whatever followed --, be that more options or just regular arguments.
I also needed the same... it took me a while but a relatively simple way has worked fine in the end.
options = {
:input_file => 'input.txt', # default input file
}
opts = OptionParser.new do |opt|
opt.on('-i', '--input FILE', String,
'Input file name',
'Default is %s' % options[:input_file] ) do |input_file|
options[:input_file] = input_file
end
opt.on_tail('-h', '--help', 'Show this message') do
puts opt
exit
end
end
extra_opts = Array.new
orig_args = ARGV.dup
begin
opts.parse!(ARGV)
rescue OptionParser::InvalidOption => e
extra_opts << e.args
retry
end
args = orig_args & ( ARGV | extra_opts.flatten )
"args" will contain all command line arguments without the ones already parsed into the "options" hash. I'm passing this "args" to an external program to be called from this ruby script.
Another solution which relies on parse! having a side effect on the argument list even if an error is thrown.
Let's define a method which tries to scan some argument list using a user defined parser and calls itself recursively when an InvalidOption error is thrown, saving the invalid option for later with eventual parameters:
def parse_known_to(parser, initial_args=ARGV.dup)
other_args = [] # this contains the unknown options
rec_parse = Proc.new { |arg_list| # in_method defined proc
begin
parser.parse! arg_list # try to parse the arg list
rescue OptionParser::InvalidOption => e
other_args += e.args # save the unknown arg
while arg_list[0] && arg_list[0][0] != "-" # certainly not perfect but
other_args << arg_list.shift # quick hack to save any parameters
end
rec_parse.call arg_list # call itself recursively
end
}
rec_parse.call initial_args # start the rec call
other_args # return the invalid arguments
end
my_parser = OptionParser.new do
...
end
other_options = parse_known_to my_parser
I ran into a similar problem when I was writing a script that wrapped a ruby gem, which needed its own options with arguments passed to it.
I came up with the following solution in which it supports options with arguments for the wrapped tool. It works by parsing it through the first optparser, and separates what it can't use into a seperate array (which can be re-parsed again with another optparse).
optparse = OptionParser.new do |opts|
# OptionParser settings here
end
arguments = ARGV.dup
secondary_arguments = []
first_run = true
errors = false
while errors || first_run
errors = false
first_run = false
begin
optparse.order!(arguments) do |unrecognized_option|
secondary_arguments.push(unrecognized_option)
end
rescue OptionParser::InvalidOption => e
errors = true
e.args.each { |arg| secondary_arguments.push(arg) }
arguments.delete(e.args)
end
end
primary_arguments = ARGV.dup
secondary_arguments.each do |cuke_arg|
primary_arguments.delete(cuke_arg)
end
puts "Primary Args: #{primary_arguments}"
puts "Secondary Args: #{secondary_args}"
optparse.parse(primary_arguments)
# Can parse the second list here, if needed
# optparse_2.parse(secondary_args)
Probably not the greatest or most efficient way of doing it, but it worked for me.
I've just moved from Python. Python's ArgumentParser has great method parse_known_args(). But it still doesn't accept second argument, such as:
$ your-app -x 0 -x 1
First -x 0 is your app's argument. Second -x 1 can belong to the target app that you need to forward to. ArgumentParser will raise error in this case.
Now come back to Ruby, you can use #order. Fortunately it accepts unlimited duplicate arguments. For example you need -a and -b. Your target app needs another -a and a mandatory argument some (note that there is no prefix -/--). Normally #parse will ignore mandatory arguments. But with #order, you will get the rest -- great. Note that you have to pass your own app's arguments first, then the target app's arguments.
$ your-app -a 0 -b 1 -a 2 some
And the code should be:
require 'optparse'
require 'ostruct'
# Build default arguments
options = OpenStruct.new
options.a = -1
options.b = -1
# Now parse arguments
target_app_argv = OptionParser.new do |opts|
# Handle your own arguments here
# ...
end.order
puts ' > Options = %s' % [options]
puts ' > Target app argv = %s' % [target_app_argv]
Tada :-)
My attempt:
def first_parse
left = []
begin
#options.order!(ARGV) do |opt|
left << opt
end
rescue OptionParser::InvalidOption => e
e.recover(args)
left << args.shift
retry
end
left
end
In my case, I want to scan the options and pick up any predefined options that may set debugging levels, output files, etc. Then I am going to load custom processors which may add to the options. After all the custom processors have been loaded, I call #options.parse!(left) to process the left over options. Note that --help is built in to the options so if you want to not recognize help the first time, you need to do ' OptionParser::Officious.delete('help')' before you create the OptParser and then add in your own help option
Parse options up until the first unknown option ... the block might be called multiple times, so make sure that is safe ...
options = {
:input_file => 'input.txt', # default input file
}
opts = OptionParser.new do |opt|
opt.on('-i', '--input FILE', String,
'Input file name',
'Default is %s' % options[:input_file] ) do |input_file|
options[:input_file] = input_file
end
opt.on_tail('-h', '--help', 'Show this message') do
puts opt
exit
end
end
original = ARGV.dup
leftover = []
loop do
begin
opts.parse(original)
rescue OptionParser::InvalidOption
leftover.unshift(original.pop)
else
break
end
end
puts "GOT #{leftover} -- #{original}"
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