Ruby option parser based on Perl’s Getopt::Long.
Option parsing is the act of taking command line arguments and converting them into meaningful structures within the program.
An option parser should support, at least, the following:
- Flag options
-
True
when passed on the command line. For example:ls --all
- options with String arguments
-
The option will accept a string argument. For example:
grepp --ignore .txt
Additionally, arguments to options can be passed with the
=
symbol.grepp --ignore=.txt
The features listed above are enough to create basic programs but an option parser should do better:
- options with Integer arguments
-
Parse an option string argument into an Integer and provide an user error if the string provided is not an integer. For example:
grepp --contex-lines 3
and:
grepp --context-lines string
Error: 'string' is not a valid integer.
- options with Floating point arguments
-
Parse an option string argument into a Floating point value and provide an user error if the string provided is not a valid floating point. For example:
command --approximation 3.5
and:
command --approximation string
Error: 'string' is not a valid floating point value.
The features listed above relieve the programmer from the cumbersome task of converting the option argument into the expected type.
Another feature a better option parser should have is the ability to set a flag to False
.
- Negatable flag options
-
True
when passed on the command line without any modifier andFalse
when the--no-
modifier is prefixed. For example:command --verbose
and:
command --no-verbose
, orcommand --noverbose
That covers the most basic set of features, but still it is not enough to get past a basic program. The following features will allow for a more complete interface.
- options with array arguments
-
This allows the same option to be used multiple times with different arguments. The list of arguments will be saved into an Array structure inside the program. For example:
list-files --exclude .txt --exclude .html --exclude .pdf
- options with Key Value arguments
-
This allows the same option to be used multiple times with arguments of key value type. For example:
rpmbuild --define name=myrpm --define version=123
Both features above should support the basic types listed before: string, integer and floating point.
The features above are useful when you have a variable amount of arguments, but it becomes cumbersome for the user when the number of entries is always the same. The features described below and meant to handle the cases when each option has a known number of multiple entries.
- options with array arguments and multiple entries
-
This allows the user to save typing. For example:
Instead of writting:
color --r 10 --g 20 --b 30 --next-option
orcolor --rgb 10 --rgb 20 --rgb 30 --next-option
The input could be:
color --rgb 10 20 30 --next-option
- options with key value arguments and multiple entries
-
This allows the user to save typing. For example:
Instead of writting:
connection --server hostname=serverIP --server port=123 --client hostname=localhost --client port=456
The input could be:
connection --server hostname=serverIP port=123 --client hostname=localhost port=456
That covers a complete user interface that is flexible enough to accommodate most programs. The following are advanced features:
- stop parsing options when
--
is passed -
Useful when arguments start with dash
-
and you don’t want them interpreted as options. - Allow passing options and non-options in any order
-
Some option parsers force you to put the options before or after the arguments. That is really annoying!
- Allow pass through
-
Have an option to pass through unmatched options. Useful when writing programs with multiple options depending on the main arguments. The initial parser will only capture the help or global options and pass through everything else. Additional parsers are invoked on the remaining arguments based on the initial input.
- Fail on unknown
-
The opposite of the above option. Useful if you want to ensure there are no input mistakes and force the application to stop.
- Require Order
-
Another method to use subcommands. It will stop parsing arguments as soon as it finds the first non option argument. That is, the first argument that doesn’t start with
-
that is not an argument of a previous option.When used in conjunction with Pass Through it should also stop parsing arguments when it finds the first unknown option.
- Warn on unknown
-
Less strict parsing of options. This will warn the user that the option used is not a valid option but it will not stop the rest of the program.
- option aliases
-
Options should be allowed to have different aliases. For example, the same option could be invoked with
--address
or--hostname
. - incremental option
-
Some options can be passed more than one to increment an internal counter. For example:
command --v --v --v
Could increase the verbosity level each time the option is passed.
- Additional types
-
The option parser could provide converters to additional types. The disadvantage of providing non basic types is that the option parser grows in size.
- options with optional arguments
-
If the argument is not passed. The option will set the default value for the option type.
- option flags that call a method internally
-
If all the flag is doing is call a method or function when present, then having a way to call that function directly saves the programmer some time.
Notice how so far only long options (options starting with double dash --
) have been mentioned.
There are 3 main ways to handle short options (options starting with only one dash -
), see the Operation Modes section for details.
The behaviour for long options (options starting with double dash --
) is consistent across operation modes.
The behaviour for short options (options starting with only one dash -
) depends on the operation mode.
The sections below show the different operation modes.
Given argument | Interpretation |
---|---|
--opt |
option: |
--opt=arg |
|
-opt |
option: |
-opt=arg |
Set by defining {mode: "bundling"}
in the options hash.
Given option | Interpretation |
---|---|
--opt |
option: |
--opt=arg |
|
-opt |
option: |
-opt=arg |
Set by defining {mode: "enforce_single_dash"}
or {mode: "single_dash"}
in the options hash.
Given option | Interpretation |
---|---|
--opt |
option: |
--opt=arg |
|
-opt |
|
-opt=arg |
-
Define your command line specification:
require 'ruby-getoptions' options, remaining = GetOptions.parse(ARGV, { "f|flag" => :flag, "string=s" => :string, "int=i" => :int, "float=f" => :float, "procedure" => lambda { puts 'Hello world! :-)' }, "help" => lambda { print_synopsis() }, "man" => lambda { launch_manpage() }, "version" => lambda { puts 'Version is 0.1' }, })
-
Pass cmdline arguments:
$ ./myscript non-option -f --string=mystring -i 7 --float 3.14 --p --version non-option2 -- --nothing
-
It will run the higher order functions that were called on the cmdline:
Hello world! Version is 0.1
-
Internally it will return an array with the arguments that are not options and anything after the
--
identifier, and a Hash with the values of the options that were passed:remaining = ['non-option', 'non-option2', '--nothing'] options = {:flag => true, # Boolean :string => 'mystring', # String :int => 7, # Integer :float => 3.14} # Float
Quick reference of all the option definitions available.
require 'ruby-getoptions'
options, remaining = GetOptions.parse(ARGV, {
"alias|name" => :option_with_aliases,
"flag" => :flag,
"nflag!" => :negatable_flag,
"procedure" => lambda { puts 'Hello world! :-)' },
"string=s" => :required_string,
"int=i" => :required_int,
"float=f" => :required_float,
"o_string:s" => :optional_string,
"o_int:i" => :optional_int,
"o_float:f" => :optional_float,
"a_string=s@" => :string_array,
"a_int=i@" => :int_array,
"a_float=f@" => :float_array,
"ar_string=s@{3,5}" => :string_array_with_repeat,
"ar_int=i@{3,5}" => :int_array_with_repeat,
"ar_float=f@{3,5}" => :float_array_with_repeat,
"h_string=s%" => :string_hash,
"h_int=i%" => :int_hash,
"h_float=f%" => :float_hash,
"hr_string=s%{3,5}" => :string_hash_with_repeat,
"hr_int=i%{3,5}" => :int_hash_with_repeat,
"hr_float=f%{3,5}" => :float_hash_with_repeat
}, {fail_on_unknown: true})
- Name specification
-
Unique list of names to call the option through the command line. Each alias is separated by
|
. - Argument_specification
-
Arg specification Arg spec options Description Flag, Empty argument specification
!
Negatable Flag, Flag that can also be negated. Use with
--no
of--no-
.= type [destype] [repeat]
- type
-
s
,i
,f
- destype
-
@
,%
. - repeat
-
{ [ min ] [ , [ max ] ] }
.
Required argument, argument must be present.
: type [destype]
- type
-
s
,i
,f
- destype
-
@
- repeat
-
{ [ min ] [ , [ max ] ] }
.
Optional argument, argument will default to String
''
, Int0
, or Float0
if not present.NoteHash argument specification is not supported in this mode because there are no reasonable defaults to add.
- Options
-
fail_on_unknown
,pass_through
,mode
,require_order
.
It will take an Array[String]
(normally ARGV
, referred to as arguments) and a Hash[String, Object]
(option definition) and it will return a Hash[Symbol, Object]
(options) of the given arguments and an Array[String]
(remaining) of the remaining arguments.
It will also excecute any procedures (lamba, procedure, method) in the order their corresponding options were passsed on the command line, and only if they were passed (they get executed using the call
method).
GetOptions.parse
will abort
or fail
if the command line options could not be processed successfully (input error), or if they were defined improperly. See Errors and Exceptions for details.
Each Option definition (one key value pair in the Hash) is composed of two elements, the key, referred to as option specification, and the value, referred to as option destination.
require 'ruby-getoptions'
options, remaining = GetOptions.parse(ARGV, {
key => value, # (1)
option_specification => option_destination # (2)
})
-
Each key, value pair is called an Option definition.
-
The key is the Option specification and the value is the Option destination.
Each option specification consists of two parts: the name specification and the argument specification.
The name specification contains the name of the option, optionally followed by a list of alternative names or aliases separated by vertical bar characters |
.
The argument specification contains the type of option and whether or not it takes any arguments and how many.
The option destination can be either a Symbol
or a procedure (only for Flags). If a Symbol
, it will be the Symbol
used to access the resulting value of the command line parameters passed. If a procedure, it will be called if the name of the option was passed in the command line.
For example, for the following option definition:
'entry|input=s' ⇒ :data
The name specification is entry
with an alias of input
, the argument specification is =s
(required string), and the option destination is :data
. If either entry
or input
is passed on the command line, the Symbol
:data
will be set to the String
argument passed with the option.
As another example, for the following option definition:
'version' ⇒ lambda { puts 'Version is 0.1' }
The option name is version
, the option specification is empty (flag), and the option destination is lambda { puts 'Version is 0.1' }
. If version
is passed on the command line, the procedure will be called (using call
).
The full list of option specification's are defined in this section.
Note
|
No option that is not passed as an argument will be touched, meaning they will be nil .
|
- No option specification (Flag)
-
When no option specification is given, the option is considered a flag.
If the option destination is a
Symbol
, its value will be set totrue
if passed. The option won’t be touched if not called,nil
.If the option destination is a procedure (lambda, procedure, method), the procedure will be called if passed (using the
call
method). !
(Negatable Flag)-
The option is considered a flag that can be negated with
no
orno-
. E.g.foo!
can be called with--foo
(set totrue
) as well as--nofoo
and--no-foo
(set tofalse
). The option won’t be touched if not called,nil
.
= type [ desttype ] [ repeat ]
(Required argument)-
The option passed requires an argument. e.g.
--string=argument
or--string argument
. : type [ desttype ]
(Optional argument)-
Like
=
, but designates the argument as optional. If omitted, an empty string will be assigned to string values options, and the value zero to numeric options.
type
s-
s
-
String
, An arbitrary sequence of characters. It is valid for the argument to start with - or — . i
-
Integer
. An optional leading plus or minus sign, followed by a sequence of digits.
f
-
Real number. For example 3.14 , -6.23E24 and so on.
desttype
s-
@
-
Specify that the option is an Array. That means that multiple appearances of the option call will push to the option array. E.g.
'opt=i@' ⇒ :int_array
with--opt 1 --opt 3 --opt 5
will renderoptions[:int_array]
equal to[1, 3, 5]
. %
-
Specify that the option is a Hash. That means that multiple appearances of the option call will add a key=value pair to the Hash. E.g.
'define=s%' ⇒ :defines
with--define name=getoptions --define lang=ruby
will renderoptions[:defines]
equal to{'name' ⇒ 'getoptions', 'lang' ⇒ 'ruby'
.
repeat
-
specifies the number of values this option takes per occurrence on the command line. It has the format
{ [ min ] [ , [ max ] ] }
.min
denotes the minimal number of arguments.max
denotes the maximum number of arguments. It must be at least min.
- Arguments
-
-
Arguments Array
Array[String]
: NormallyARGV
, but any Array of Strings will work. -
Option Definition
Hash[String, Any]
: WhereString
is the option specification andAny
, option destination can beSymbol
, or a procedure (lambda, procedure, method). See Option Definition for details.
-
- Returns
-
-
Options Hash
Map[Symbol,Any]
: whereAny
can be aString
,Integer
,Float
,Array[String]
,Array[Integer]
,Array[Float]
AndHash
ofString
,Integer
andFloat
. -
Remaining Array
Array[String]
:Array
of non-options arguments,pass_through
Options, or any argument after the--
identifier.
-
-
Supports passing
--
to stop parsing arguments (everything after will be left in theremaining
Array[String]
). -
Multiple definitions for the same option separated by
|
. e.g.help|man
. -
Defining what kind of argument you are passing. Currently supports
s
to pass strings,i
to pass integers andf
to pass float values. -
Supports both array and hash modifiers.
-
Argument type checking.
-
Supports calling a given procedure (lambda, procedure, method) if the option name if passed on the command line.
-
Supports command line options with
=
. e.g. You can use--string=mystring
and--string mystring
. -
Supports case sensitive options. For example, you can use
v
to defineverbose
andV
to defineVersion
.
fail_on_unknown
-
if
true
, it will abort when an unknown option is passed on the commandline. Iffalse
it will WARN when an unknown option is passed. Default:false
. pass_through
-
disable warning on unknown option. Defalt:
false
. require_order
-
If
true
, it will place all remaining arguments in the remaining array as soon as it finds the first non option argument. That is, the first argument that doesn’t start with-
that is not an argument of a previous option.When used in conjunction with
pass_through
it should also stop parsing arguments when it finds the first unknown option. mode
-
possible values are "normal", "bundling", "enforce_single_dash". See Operation Modes for details.
-
For incorrect option definitions in the script itself it will
fail
withArgumentError
. -
For user input errors, it will
abort
(SystemExit
) with a description of what the user did wrong.-
"[ERROR] Option 'X' not found!"
-
"[WARNING] Option 'X' not found!"
-
"[ERROR] option 'X' matches multiple names: 'list of matching names'!"
-
"[ERROR] argument for option 'X' is not of type 'Integer'!"
-
"[ERROR] argument for option 'X' is not of type 'Float'!"
-
"[ERROR] missing argument for option 'X'!"
-
"[ERROR] argument for option 'X' must be of type key=value!"
-
-
Get it from rubygems:
gem install 'ruby-getoptions'
-
Then test it in
irb
:2.1.2 :001 > require 'ruby-getoptions' => true 2.1.2 :002 > options, remaining = GetOptions.parse(['world', '-t', 'hello'], {'t=s' => :test}) => [{:test=>"hello"}, ["world"]]
-
Enjoy!
-
Make matching case insensitive.
-
Translations for user facing errors?
-
All other Perl’s Getopt::Long goodies that seem reasonable to add!
The MIT License (MIT)
Copyright (c) 2014-2015 David Gamba
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.