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 see all sorts of advice about getting rid of the brackets, but as I perform my beginning ruby lessons, I want to see if I'm creating a 2-D array correctly.
Right now I'm limited to
puts my_2D_array[0]
puts my_2D_array[1] #etc
but I want to see
[ [6,6,3] , [7,4,7] , [4,7,4] ]
instead of what I'm getting, which is
663747474
What's the trick? And I'm definitely dealing with an array - not a string...
p array will give you output you've asked for:
[[6,6,3] , [7,4,7] , [4,7,4]]
Want each row on a separate line? Then use:
array.each {|e| p e}
to get:
[6,6,3]
[7,4,7]
[4,7,4]
You could add a method to the Array class:
class Array
def ppa # 'pretty-print array'
self.each {|e| p e} # or just 'each {|e| p e}'
end
end
that wold allow you to write
array.ppa
and get the same three-line output. (You could use puts and inspect instead of p.) Think you might use this often? Then put this code in a file called, say, 'array_print.rb' and add 'require array_print' to the beginning of your '.rb' code file. Each time you run your program, the statements in array_print.rb will be executed, making the Array method ppr available to you.
Let's not stop there! Suppose you also wanted nicely-formatted output for three-dimensional arrays, hashes, hashes of arrays, and so on. You could elaborate on the approach I've described above, but why reinvent the wheel? There are several excellent Ruby gems available that take care of all of this for you. One popular one is "awesome print". After having installed this gem, all you need do is add require 'awesome_print' in your code file. You can then use its ap method to format your output. (See RubyGems for instructions on how to install gems. It's easy).
To get a taste of what awesome print does, suppose that instead of the array above you wanted to display this hash:
hash = {"cat"=>["mice", "birds"], "dog"=>["master",["kids", "moms"]]}
By executing ap hash, you'd get this:
{
"cat" => [
[0] "mice",
[1] "birds"
],
"dog" => [
[0] "master",
[1] [
[0] "kids",
[1] "moms"
]
]
}
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