Giter Club home page Giter Club logo

ruby-getoptions's Introduction

ruby-getoptions

Introduction

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 and False when the --no- modifier is prefixed. For example:

command --verbose

and:

command --no-verbose, or command --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 or color --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.

Operation Modes

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.

Normal Mode (default)

Given argument Interpretation

--opt

option: "opt", argument: nil

--opt=arg

option: "opt", argument: "arg" [1]


1. Argument gets type casted depending on option definition.

-opt

option: "opt", argument: nil

-opt=arg

option: "opt", argument: "arg" [2]


2. Argument gets type casted depending on option definition.

Bundling Mode

Set by defining {mode: "bundling"} in the options hash.

Given option Interpretation

--opt

option: "opt", argument: nil

--opt=arg

option: "opt", argument: "arg" [3]


3. Argument gets type casted depending on option definition.

-opt

option: "o", argument: nil
option: "p", argument: nil
option: "t", argument: nil

-opt=arg

option: "o", argument: nil
option: "p", argument: nil
option: "t", argument: "arg" [4]


4. Argument gets type casted depending on option definition.

Enforce Single Dash Mode

Set by defining {mode: "enforce_single_dash"} or {mode: "single_dash"} in the options hash.

Given option Interpretation

--opt

option: "opt", argument: nil

--opt=arg

option: "opt", argument: "arg" [5]


5. Argument gets type casted depending on option definition.

-opt

option: "o", argument: "pt" [6]


6. Argument gets type casted depending on option definition.

-opt=arg

option: "o", argument: "pt=arg" [7]


7. Argument gets type casted depending on option definition.

Quick overview

  1. 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' },
    })
  2. Pass cmdline arguments:

    $ ./myscript non-option -f --string=mystring -i 7 --float 3.14 --p --version non-option2 -- --nothing
  3. It will run the higher order functions that were called on the cmdline:

    Hello world!
    Version is 0.1
  4. 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

Synopsis

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 '', Int 0, or Float 0 if not present.

Note
Hash 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.

Description

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.

Option Definition

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)
})
  1. Each key, value pair is called an Option definition.

  2. 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).

Option Specification

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 to true 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 or no-. E.g. foo! can be called with --foo (set to true) as well as --nofoo and --no-foo (set to false). 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.

Option specification parameters

types
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.

desttypes
@

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 render options[: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 render options[: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.

Input / Output

Arguments
  1. Arguments Array Array[String]: Normally ARGV, but any Array of Strings will work.

  2. Option Definition Hash[String, Any]: Where String is the option specification and Any, option destination can be Symbol, or a procedure (lambda, procedure, method). See Option Definition for details.

Returns
  1. Options Hash Map[Symbol,Any]: where Any can be a String, Integer, Float, Array[String], Array[Integer], Array[Float] And Hash of String, Integer and Float.

  2. Remaining Array Array[String]: Array of non-options arguments, pass_through Options, or any argument after the -- identifier.

Features

  • Supports passing -- to stop parsing arguments (everything after will be left in the remaining 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 and f 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 define verbose and V to define Version.

Options

fail_on_unknown

if true, it will abort when an unknown option is passed on the commandline. If false 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.

Errors and Exceptions

  • For incorrect option definitions in the script itself it will fail with ArgumentError.

  • 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!"

How to install it

  1. Get it from rubygems:

    gem install 'ruby-getoptions'

  2. 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"]]
  3. Enjoy!

Dependencies

Ruby 1.8.7+

Roadmap

  • Make matching case insensitive.

  • Translations for user facing errors?

  • All other Perl’s Getopt::Long goodies that seem reasonable to add!

License

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.

ruby-getoptions's People

Contributors

davidgamba avatar davidpinsky avatar

Stargazers

 avatar  avatar

Watchers

 avatar  avatar  avatar

ruby-getoptions's Issues

Please allow for case sensitive options!

There are many commands which use case sensitive options in a single program. One of the most common is '-V' and '-v', where -V would represent Version, and -v would represent verbosity.

I request either ruby-getoptions.rb simply drop the collapsing of option names to a single case, or at least for an option to GetOptions.parse to accommodate both -V and -v options in one program.

I have found the following definition of fail_on_duplicate_definitions to work quite well. I have commented out two original lines, and replaced one of those with a different message.

def self.fail_on_duplicate_definitions(definition_list)
  # definition_list.map!{ |x| x.downcase }
  unless definition_list.uniq.length == definition_list.length
    duplicate_elements = definition_list.find { |e| definition_list.count(e) > 1 }
    fail ArgumentError,
        "GetOptions option_map found duplicate options: '#{duplicate_elements}'"
        # "GetOptions option_map needs to have unique case insensitive options: '#{duplicate_elements}'"
  end
  true
end

Quote user input before using it in a regex

Using a option like:

{ 'test' => :test,
  'help|?' => lambda { launch_help(); exit 1 }
}

Causes an error saying "[ERROR] option '?' matches multiple names ..." matching every option.

Option with string type arg does not allow string to start with '-'

According to your document, an 's' type argument to an option can start with a '-' or '--'.
This same is stated by the GetOption:Long perl parser. And perl does support this.

The ruby-getoptions checks that argument and if it starts with '-', it treats it the argument to the option as more options, and then errors that the option-with-string option did not have an argument (if it was required).
Essentially, when an option has a (required at least) string, then the arg after that option should not be further processed in any way, but just passed as the value for that options arg.

Given the following program:

#!/usr/bin/env ruby
require 'ruby-getoptions'
VERSION = "0.0.1"
options, remaining = GetOptions.parse(ARGV, {
                                        "o=s" => :o,
                                        "e=s" => :e,
                                        "b"   => :b,
                                        "passthruopts=s" => :passthruopts
                                      }, {fail_on_unknown: true})
puts options.inspect
puts remaining.inspect

Calling this as:
pgrm -passthruopts "-X" -o Log foo
Results in:
[ERROR] missing argument for option 'passthruopts'!

It should be no different than this invocation with a space in front of '-X'
pgrm -passthruopts " -X" -o Log foo
Results in:
{:passthruopts=>" -X", :o=>"Log"}
["foo"]

Note that this will also change the following:
NOW:
unix% pgrm -o O -e E foo
{:o=>"O", :e=>"E"}
["foo"]
unix% pgrm -o -e E foo
[ERROR] missing argument for option 'o'!

BUT AFTER FIX, the second case can no longer detect a missing arg to '-o', so:
unix% pgrm -o -e E foo
{:o=>"-e"}
["E", "foo"]

That is what the perl parser seems to do, and is highly desired for many cases, e.g. where an option to pgrm is used to gather options to pass to another program.
pgrm -grepoptions "-n" fileToProcesWithGrep

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.