Shellopts
ShellOpts
is a command line processing library. It supports short
and long options and subcommands, and has built-in help and error messages
Usage
ShellOpts use a string to specify legal options and documentation. The following program accepts the options --alpha and --beta with an argument. -a and -b are option aliases:
require 'shellopts'
SPEC = "-a,alpha -b,beta=VAL -- ARG1 ARG2"
opts, args = ShellOpts.process(SPEC, ARGV)
alpha = opts.alpha? # True if -a or --alpha are present
beta = opts.beta # The value of the -b or --beta option
ShellOpts also allow multi-line definitions with comments that are used as part of help messages
require 'shellopts'
SPEC = %(
-a,alpha @ Brief comment for -a and --alpha options
-b,beta=VAL
@ Alternative style of brief comment
-- ARG1 ARG2
)
opts, args = ShellOpts.process(SPEC, ARGV)
ShellOpts::ShellOpts.brief
prints
Usage
main --alpha --beta=VAL ARG1 ARG2
Options
-a, --alpha Brief comment for -a and --alpha options
-b, --beta=VAL Alternative style of brief comment
There is also a ShellOpts.help
method that prints a more detailed
documentation, and a ShellOpts.usage
method that prints a compact usage
string
If there is an error in the command line options, the program will exit with status 1 and print an error message and usage on standard error. If there is an error in the specification, a message to the developer with the origin of the error is printed on standard error
Processing
ShellOpts.process
creates a ShellOpts::ShellOpts
object and use it to
compile the specification and process the command line. It returns a tuple of a
Program object and an array of the remaining arguments
The Program object has accessor methods for each defined option and sub-command to check presence and return an optional argument. Given the options "--alpha --beta=ARG" then the following accessor methods are available:
# Returns true if the option is present and false otherwise
opts.alpha?()
opts.beta?()
# Returns the argument of the beta option or nil if missing
opts.beta()
Given the commands "cmd1! cmd2!" the following methods are available:
# Returns the sub-command object or nil if not present
opts.cmd1!
opts.cmd2!
opts.subcommand! # Returns the sub-command object or nil if not present
opts.subcommand # Returns the sub-command's identifier (eg. :cmd1!)
It is used like this
case opts.subcommand
when :cmd1
# opts.cmd1 is defined here
when :cmd2
# opts.cmd2 is defined here
end
end
Sub-commands have options and even sub-sub-commands of their own. They can be nested to any depth (which is not recommended, btw.)
The module methods ::usage
, ::brief
, and ::help
prints documentation with
increasing detail. ::usage
lists the options and commands without any comments,
::brief
includes source text that starts with a '@', and ::help
the full
documentation in a man-page like format. Example
SPEC = "-h --help"
opts, args = ShellOpts.process(SPEC, ARGV)
if opts.h?
ShellOpts.brief
exit
elsif opts.help?
ShellOpts.help
exit
end
The module methods ::error
and ::failure
are used to report errors in a common
format and then terminate the program with status 1. ::error
is used to report
errors that the user can correct and prints a usage description as a reminder.
::failure
is used to report errors within the program so the usage descriptionn
is not printed:
SPEC = "--var=VAR"
opts, args = ShellOpts.process(SPEC, ARGV)
# --var is a mandatory 'option'
opts.var? or error "Missing --var 'option'"
# later during processing
condition or failure "Memory overflow"
Specification
The specification is possibly multi-line string, typically named SPEC
, that
is a mix of option or command definitions and related documentation.
Indentation is used to nest the elements and '@' is used to tag an option or
command with a brief description
The specifiction is parsed line-by-line: Lines matching and initial '-', or '--' are considered option definitions and lines starting with a word immediately followed by an exclamation mark is a command definition (like 'cmd! ...'). Text following a '@' (except in paragraphs) is a brief comment, the rest is paragraphs
-a,alpha @ Brief comment for -a and --alpha options
Longer description of the option that is used by `::help`
cmd!
@ Alternative style of brief comment
Longer description of the command
Text starting with '--' follow by a blank character is a free-text description of the command-line arguments. It is not parsed but used in documentation and error messages:
SPEC = "-a cmd! -- ARG1 ARG2"
Options
The general syntax for options is
<prefix><optionlist>[=argspec][,][?]
(TODO: Restructure: prefix,options,argument-spec,argument-spec-modifier,etc.)
The option list is a comma-separated list of option names. It is prefixed with a '-' if the option list starts with a short option name and '--' if the option list starts with a long name. '-' and '--' can be replaced with '+' or '++' to indicate that the option can be repeated
-a,alpha @ '-a' and '--alpha'
++beta @ '--beta', can be repeated
--gamma=ARG? @ '--gamma', takes an optional argument
--delta=ARG, @ '--delta', takes a mandatory comma-separated list of arguments
--epsilon=ARG,? @ '--delta', takes an optional list
An option argument has a name and a type. The type can be specified as '#' (integer), '$' (float), ',' (list) or as a comma-separated list of allowed values (enum). The name should be in capital letters. Some names are keywords with a special meaning:
Keyword | Type |
---|---|
FILE | A file if present or in an existing directory if not |
DIR | A directory if present or in an existing directory if not |
PATH | FILE or DIR |
EFILE | An existing file |
EDIR | An existing directory |
EPATH | EFILE or EDIR |
NFILE | A new file |
NDIR | A new directory |
NPATH | NFILE or NDIR |
By default the option name is inferred from the type but it can be specified explicitly by separating it from the type with a ':'. Examples:
-a=# @ -a takes an integer argument
-b=MONEY:$ @ -b takes a float argument. Is shown as '-b=MONEY' in messages
-c=red,blue,green @ -c takes one of the listed words
-d=FILE @ Fails if file exists and is not a file
-d=EDIR @ Fails if directory doesn't exist or is not a directory
-d=INPUT:EFILE @ Expects an existing file. Shown as '-d=INPUT' in messages
Commands
Commands are specified as lines starting with the name of the command
immediately followed by a '!' like cmd!
. Commands can have options and even
subcommands of their own, in the multi-line format they're indented under the
command line like this
-a @ Program level option
cmd!
-b @ Command level option
subcmd!
-c @ Sub-command level option
In single-line format, subcommands are specified by prefixing the supercommand's name:
-a cmd! -b cmd.subcmd! -c
Example
The standard rm(1) command could be specified like this:
require 'shellopts'
# Define options
SPEC = %(
-f,force @ ignore nonexisten files and arguments, never prompt
-i @ prompt before every removal
-I
@ prompt once
prompt once before removing more than three files, or when removing
recursively; less intrusive than -i, while still giving protection
against most mistakes
--interactive=WHEN:never,once,always
@ prompt according to WHEN
prompt according to WHEN: never, once (-I), or always (-i); without WHEN, prompt always
--one-file-system
@ stay on fuile system
when removing a hierarchy recursively, skip any directory that is on a file system different from
that of the corresponding command line argument
--no-preserve-root @ do not treat '/' specially
--preserve-root @ do not remove '/' (default)
-r,R,recursive @ remove directories and their contents recursively
-d,dir @ remove empty directories
-v,verbose @ explain what is being done
--help @ display this help and exit
--version @ output version information and exit
-- FILE...
)
See also
Installation
To install in your gem repository:
$ gem install shellopts
To add it as a dependency for an executable add this line to your application's
Gemfile
. Use exact version match as ShellOpts is still in development:
gem 'shellopts', 'x.y.z'
If you're developing a library, you should add the dependency to the *.gemfile
instead:
spec.add_dependency 'shellopts', 'x.y.z'
Development
After checking out the repo, run bin/setup
to install dependencies. Then, run
rake spec
to run the tests. You can also run bin/console
for an interactive
prompt that will allow you to experiment.
To install this gem onto your local machine, run bundle exec rake install
. To
release a new version, update the version number in version.rb
, and then run
bundle exec rake release
, which will create a git tag for the version, push
git commits and tags, and push the .gem
file to
rubygems.org.
Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/shellopts.