Skip to content

florianpilz/micro-optparse

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

84 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

µ-optparse

Why another command line parser?

There are lots of command line parser out there, for example trollop is a great alternative. However, it is 800 lines long. In addition, trollop sucks at validating the input. So µ-optparse is for you if you are looking for

  • a small command line parser to copy and paste it into your scripts to avoid injecting gem-dependencies
  • a command line parser with powerful validations
  • an easily usable, understandable and extendable command line parser
  • a wrapper around optparse, which intelligently fills out most informations on its own

What is µ-optparse?

µ-optparse is a small wrapper around optparse, weighing less than 80 lines of code. optparse (or OptionParser) on the other hand is a command line parser, which ships with ruby. After you defined available options, it automatically creates a help page and is able to parse ARGV accordingly. However, optparse requires you to repeat yourself quite often, which leads to many lines of code, just to configure the available options. µ-optparse removes this obstacle by extracting the information contained in few lines of configuration. In addition, µ-optparse extends optparse by some powerful validations, which where inspired by OptiFlag.

Talk in code!

require 'rubygems' # necessary for ruby v1.8.*
require 'micro-optparse'

options = Parser.new do |p|
   p.banner = "This is a fancy script, for usage see below"
   p.version = "fancy script 0.0 alpha"
   p.option :severity, "set severity", :default => 4, :value_in_set => [4,5,6,7,8]
   p.option :verbose, "enable verbose output"
   p.option :mutation, "set mutation", :default => "MightyMutation", :value_matches => /Mutation/
   p.option :plus_selection, "use plus-selection if set", :default => true
   p.option :selection, "selection used", :default => "BestSelection", :short => "l"
   p.option :chance, "set mutation chance", :default => 0.8, :value_satisfies => lambda {|x| x >= 0.0 && x <= 1.0}
end.process!

What this piece of code does is the following:

  • it creates a help message and help options, with the banner above the options
  • it creates a version option, which displays the given text
  • it creates a long accessor for each option, according to the symbol - for example "--verbose" for :verbose
  • it creates a short accessor, which is the first character of the long accessor (automatically resolves duplication)
  • it converts under_score options in Ruby to hypenated-options for the CLI, e.g. :plus_selection becomes --plus-selection
  • it checks if the class of the input and the default value match
  • it creates a switch if no default value exist or the default value is true or false
  • when value_in_set is given, it validates if the input value is in the given array
  • when value_matches is given, it validates if the input value matches the regular expression
  • when value_satisfies is given, it validates if the lambda or Proc evaluates to true when fed with the input value
  • it stores all parsed arguments and default values in the options hash, i.e. to access the value of :mutation in your script, write options[:mutation]

The automatically generated help message looks like this:

This is a fancy script, for usage see below
    -s, --severity 4                 set severity
    -v, --[no-]verbose               enable verbose output
    -m, --mutation MightyMutation    set mutation
    -p, --[no-]plus-selection        use plus-selection if set
    -l, --selection BestSelection    selection used
    -c, --chance 0.8                 set mutation chance
    -h, --help                       Show this message
    -V, --version                    Print version

To show some example calls and results, I will use a simplified version of the Parser above:

require 'rubygems' # necessary for ruby v1.8.*
require 'micro-optparse'

options = Parser.new do |p|
   p.option :severity, "set severity", :default => 4, :value_in_set => [4,5,6,7,8]
   p.option :mutation, "set mutation", :default => "MightyMutation", :value_matches => /Mutation/
   p.option :plus_selection, "use plus-selection if set", :default => true, :optional => true
end.process!
  • ruby myprogram.rb --help will yield a help message formatted like the one above
  • ruby myprogram.rb will fill the variable options with the hash {:severity => 4, :mutation => "MightyMutation"} due to the given default values (:plus_selection does not appear in the result, due to the :optional => true setting, but can still be filled by the user)
  • ruby myprogram.rb -s 2 --mutation WeakMutation -p true will fill the variable options with the hash {:severity => 2, :mutation => "WeakMutation", :plus_selection => true}, since the given values overwrite default values

It doesn't stop at the command line!

µ-optparse can parse any array which is formatted like ARGV, e.g. ["--severity", "4"] is the same as typing "--severity 4" behind your program call. You can even process several arrays with the same parser (see example below). In addition, you don't need to specify all options at once, i.e. you can pass the parser around and add more options until you call the process!-method.

require 'rubygems' # necessary for ruby v1.8.*
require 'micro-optparse'

parser = Parser.new
parser.option :eat_snickers, "How many?", :default => 0
options1 = parser.process!(["--eat-snickers", "2"])
options2 = parser.process!(["--eat-snickers", "1"])

Where do I get µ-optparse?

You can either go and install the gem via gem install micro-optparse or grab it from this repository and paste it into your script. If you choose the latter option, you may delete the validate-method to spare another 15 lines of code.

If you want to contribute, you can fork this repository, make your changes and send me a pull request. However, improvements must be one of the following:

  • use fewer lines of code, without sacrificing readablity or functionality
  • enhance readablity or functionality, without increasing the lines of code

Frequently Asked Questions

All my argument values are either true or false - what's wrong?

You must define default values, if the option should accept an argument. Every option without a default value (or with true or false as default) is treated as a switch: true if given and false / default otherwise.

Is it possible to define mandatory / required arguments, which must be provided?

No it's not. Every option that has no default argument is a switch and if an option has a default argument, well there is a default to fall back to. However, what you can do is using µ-optparse to parse all options and switches (which are then removed from the ARGV array) and use everything that remains in ARGV as the mandatory arguments. Of course you have to raise an error yourself if no argument is left.

Consider the following example to implement mandatory arguments yourself:

require 'rubygems' # necessary for ruby v1.8.*
require 'micro-optparse'

options = Parser.new do |p|
  p.option :meal, "Choose Meal", :default => "CucumberSalad"
end.parse!

raise ArgumentError, "No files given!" unless ARGV.size > 0
file = ARGV.shift

If this short file is saved as script.rb, a call could look like the following: ruby script.rb --meal=BrainSandwich file1.txt file2.txt.

Are long arguments with spaces and other special characters allowed?

Yes, just define an option which takes a String as an argument, i.e. pass a string as the default value for that option. Now everything between quotes will be parsed as the value for that argument, e.g. ruby testscript.rb --selection 'I want the best selection you have!!! And if possible, add Donuts.' Note that double quotes may cause trouble, for example I get an error if I use an exclamation mark in double quotes, but no error in single quotes.

Is it possible to define arguments which accept lists / arrays / multiple files / ... ?

Yes, just define an option which takes an Array as an argument, i.e. pass an array as the default value for that option. The input will be split by comma. If the arguments contain spaces, wrap the whole thing in single quotes or double quotes.

For example if you want to accept multiple file names with whitespaces in them:

require 'rubygems' # necessary for ruby v1.8.*
require 'micro-optparse'

options = Parser.new do |p|
  p.option :filenames, "Files which will be processed", :default => []
end.process!

p options[:filenames]

ruby testscript.rb --filenames 'todo.txt,my great adventures.txt' yields ["todo.txt", "my great adventures.txt"].

Is it possible define an option without a default value?

Yes and No. The literal answer to this question is "No", since a default value must be given for all arguments, except switches which default to false. However, what you are probably trying to achieve is to define an option which does only show up in the resulting hash, if the argument was given by the user. This is actually possible! Just add :optional => true to the line where you define your option and you are ready to go! The default value is still used for type checking.

require 'rubygems' # necessary for ruby v1.8.*
require 'micro-optparse'

options = Parser.new do |p|
  p.option :file, "File to process (optional)", :default => "String", :optional => true
end.process!

puts options

Example usage of this parser:

  • ruby myprogram.rb will yield {}
  • ruby myprogram.rb --file testfile.txt will yield {:file => "testfile.txt"}

Can I disable the short accessors?

Yes you can. To disable them globally, i.e. for all arguments of the parser, just create the Parser with the default setting :no_short => true. To disable the short accessor of just a few arguments, add :no_short => true as a setting to these arguments.

For example to disable short accessors for all arguments:

require 'rubygems' # necessary for ruby v1.8.*
require 'micro-optparse'

options = Parser.new(:no_short => true) do |p|
  p.option :foo, "Foo"
end.process!

puts options

To disable short accessor of just a few arguments:

require 'rubygems' # necessary for ruby v1.8.*
require 'micro-optparse'

options = Parser.new do |p|
  p.option :foo, "Foo without short", :no_short => true
  p.option :bar, "Bar with short"
end.process!

puts options