CLI
Command Line Interface gem allows you to quickly specify command argument parser that will automatically generate usage, handle stdin, switches, options and arguments with default values and value casting.
Installing
gem install cli
Examples
HTTPClient example
The following example shows basic usage of the CLI gem.
It will use HTTPClient to connect to server that address and port can be specified with --server
and --port
switches.
It expects at least one argument specifying the URL (that needs to start with /
) and optional set of POST arguments.
require 'rubygems'
require 'cli'
require 'httpclient'
settings = CLI.new do
option :server, :description => 'server address', :default => 'www.google.com'
option :port, :description => 'server port', :cast => Integer, :default => 80
argument :url, :description => 'URL to GET or POST to if arguments are given'
arguments :post_arguments, :required => false
end.parse! do |settings|
fail "invalid URL '#{settings.url}', URL has to start with '/'" unless settings.url =~ /^\//
end
c = HTTPClient.new
begin
if settings.post_arguments.empty?
puts c.get_async("http://#{settings.server}:#{settings.port}#{settings.url}").pop.content.read
else
puts c.post_async("http://#{settings.server}:#{settings.port}#{settings.url}", settings.post_arguments.join("\n")).pop.content.read
end
rescue SocketError, Errno::ECONNREFUSED => e
puts "Failed to connect: #{e}"
end
Example usage with default server:
examples/httpclient /index.html
The output will contain Google website.
Using different server:
examples/httpclient --server ibm.com /index.html
When run without arguments:
examples/httpclient
The following message will be printed and the program will exit (status 42):
Error: mandatory argument 'url' not given
Usage: httpclient [switches|options] [--] url [post-arguments*]
Switches:
--help (-h) - display this help message
Options:
--server [www.google.com] - server address
--port [80] - server port
Arguments:
url - URL to GET or POST to if arguments are given
post-arguments* (optional)
When used with command that does not start with /
:
examples/httpclient test
It will print the following message and exit:
Error: invalid URL 'test', URL has to start with '/'
Usage: httpclient [switches|options] [--] url [post-arguments*]
Switches:
--help (-h) - display this help message
Options:
--server [www.google.com] - server address
--port [80] - server port
Arguments:
url - URL to GET or POST to if arguments are given
post-arguments* (optional)
Sinatra server example
require 'rubygems'
require 'cli'
require 'ip'
settings = CLI.new do
description 'Example CLI usage for Sinatra server application'
version "1.0.0"
switch :no_bind, :description => "Do not bind to TCP socket - useful with -s fastcgi option"
switch :no_logging, :description => "Disable logging"
switch :debug, :description => "Enable debugging"
switch :no_optimization, :description => "Disable size hinting and related optimization (loading, prescaling)"
option :bind, :short => :b, :default => '127.0.0.1', :cast => IP, :description => "HTTP server bind address - use 0.0.0.0 to bind to all interfaces"
option :port, :short => :p, :default => 3100, :cast => Integer, :description => "HTTP server TCP port"
option :server, :short => :s, :default => 'mongrel', :description => "Rack server handler like thin, mongrel, webrick, fastcgi etc."
option :limit_memory, :default => 128*1024**2, :cast => Integer, :description => "Image cache heap memory size limit in bytes"
option :limit_map, :default => 256*1024**2, :cast => Integer, :description => "Image cache memory mapped file size limit in bytes - used when heap memory limit is used up"
option :limit_disk, :default => 0, :cast => Integer, :description => "Image cache temporary file size limit in bytes - used when memory mapped file limit is used up"
end.parse!
# use to set sinatra settings
require 'sinatra/base'
sinatra = Sinatra.new
sinatra.set :environment, 'production'
sinatra.set :server, settings.server
sinatra.set :lock, true
sinatra.set :boundary, "thumbnail image data"
sinatra.set :logging, (not settings.no_logging)
sinatra.set :debug, settings.debug
sinatra.set :optimization, (not settings.no_optimization)
sinatra.set :limit_memory, settings.limit_memory
sinatra.set :limit_map, settings.limit_map
sinatra.set :limit_disk, settings.limit_disk
# set up your application
sinatra.run!
To see help message use --help
or -h
anywhere on the command line:
examples/sinatra --help
Example help message:
Usage: sinatra [switches|options]
Example CLI usage for Sinatra server application
Switches:
--no-bind - Do not bind to TCP socket - useful with -s fastcgi option
--no-logging - Disable logging
--debug - Enable debugging
--no-optimization - Disable size hinting and related optimization (loading, prescaling)
--help (-h) - display this help message
--version - display version string
Options:
--bind (-b) [127.0.0.1] - HTTP server bind address - use 0.0.0.0 to bind to all interfaces
--port (-p) [3100] - HTTP server TCP port
--server (-s) [mongrel] - Rack server handler like thin, mongrel, webrick, fastcgi etc.
--limit-memory [134217728] - Image cache heap memory size limit in bytes
--limit-map [268435456] - Image cache memory mapped file size limit in bytes - used when heap memory limit is used up
--limit-disk [0] - Image cache temporary file size limit in bytes - used when memory mapped file limit is used up
To see version string use --version
examples/sinatra --version
Example version output:
sinatra version "0.0.4"
Statistic data processor example
require 'rubygems'
require 'cli'
require 'pathname'
require 'yaml'
settings = CLI.new do
description 'Generate blog posts in given Jekyll directory from input statistics'
stdin :log_data, :cast => YAML, :description => 'statistic data in YAML format'
option :location, :short => :l, :description => 'location name (ex. Dublin, Singapore, California)'
option :csv_dir, :short => :c, :cast => Pathname, :default => 'csv', :description => 'directory name where CSV file will be stored (relative to jekyll-dir)'
argument :jekyll_dir, :cast => Pathname, :default => '/var/lib/vhs/jekyll', :description => 'directory where site source is located'
end.parse! do |settings|
fail 'jekyll-dir is not a directory' unless settings.jekyll_dir.directory?
fail '--csv-dir is not a directory (relative to jekyll-dir)' unless (settings.jekyll_dir + settings.csv_dir).directory?
end
p settings
# do your stuff
Example help message:
Usage: processor [switches|options] [--] [jekyll-dir] < log-data
Generate blog posts in given Jekyll directory from input statistics
Input:
log-data - statistic data in YAML format
Switches:
--help (-h) - display this help message
Options:
--location (-l) - location name (ex. Dublin, Singapore, California)
--csv-dir (-c) [csv] - directory name where CSV file will be stored (relative to jekyll-dir)
Arguments:
jekyll-dir [/var/lib/vhs/jekyll] - directory where site source is located
With this example usage (assuming /var/lib/vhs/jekyll/csv dir exist):
examples/processor --location Singapore <<EOF
:parser:
:successes: 41
:failures: 0
EOF
The settings
variable will contain:
#<CLI::Values stdin={:parser=>{:successes=>41, :failures=>0}}, jekyll_dir=#<Pathname:/var/lib/vhs/jekyll>, csv_dir=#<Pathname:csv>, help=nil, location="Singapore">
Output if jekyll-dir does not exist:
Error: jekyll-dir is not a directory
Usage: processor [switches|options] [--] [jekyll-dir] < log-data
Generate blog posts in given Jekyll directory from input statistics
Input:
log-data - statistic data in YAML format
Switches:
--help (-h) - display this help message
Options:
--location (-l) - location name (ex. Dublin, Singapore, Califorina)
--csv-dir (-c) [csv] - directory name where CSV file will be storred (relative to jekyll-dir)
Arguments:
jekyll-dir [/var/lib/vhs/jekyll] - directory where site source is located
Ls like utility
arguments
specifier can be used to match multiple arguments.
The arguments
specifier matched value will always be an array of casted elements.
Default and mandatory arguments will have priority on matching values (see specs for examples).
options
specifier can be used to allow specifing same option multiple times.
The options
specifier matched value will always be an array of casted elements or empty if option not specified.
require 'rubygems'
require 'cli'
require 'pathname'
settings = CLI.new do
description 'Lists content of directories'
switch :long, :short => :l, :description => 'use long listing'
options :exclude, :short => :e, :description => 'exclude files from listing'
arguments :directories, :cast => Pathname, :default => '.', :description => 'directories to list content of'
end.parse!
settings.directories.each do |dir|
next unless dir.directory?
dir.each_entry do |e|
next if e.to_s == '.' or e.to_s == '..'
e = dir + e
next if settings.exclude.include? e.to_s
if settings.long
puts "#{e.stat.uid}:#{e.stat.gid} #{e}"
else
puts e
end
end
end
Example help message:
Usage: ls [switches|options] [--] [directories*]
Lists content of directories
Switches:
--long (-l) - use long listing
--help (-h) - display this help message
Options:
--exclude* (-e) - exclude files from listing
Arguments:
directories* [.] - directories to list content of
Example usage:
examples/ls
Prints:
.document
.git
.gitignore
.README.md.swp
.rspec
cli.gemspec
examples
features
...
Excluding .git and .gitignore:
examples/ls -e .git -e .gitignore
Prints:
.document
.README.md.swp
.rspec
cli.gemspec
examples
features
...
With directory list:
examples/ls *
Prints:
examples/.ls.swp
examples/ls
examples/processor
examples/sinatra
features/cli.feature
features/step_definitions
features/support
lib/cli
lib/cli.rb
pkg/cli-0.0.1.gem
pkg/cli-0.0.2.gem
...
Long printout:
examples/ls -l *
Prints:
501:20 examples/.ls.swp
501:20 examples/ls
501:20 examples/processor
501:20 examples/sinatra
501:20 features/cli.feature
501:20 features/step_definitions
501:20 features/support
501:20 lib/cli
501:20 lib/cli.rb
501:20 pkg/cli-0.0.1.gem
501:20 pkg/cli-0.0.2.gem
...
Usage
CLI.new
takes a block where you specify parser behavior. The returned object is a parser that has #parse
and #parse!
methods.
#parse
method
It will take argument array (defaults to ARGV), standard input IO (defaults to STDIO) and standard error IO (defaults to STDERR).
The method will parse argument array and cast standard input IO according to parser specification and return OpenStruct kind of object with resulting values.
The returned object will have help
attribute set if --help
or -h
switch was found in argument array or version
attribute if --version
argument was found.
In other case all the attributes will be set to appropriate values depending on argument array and parser specification.
In case of parsing error CLI::ParsingError
kind of exception will be raised.
#parse!
method
This is higher level version of #parse
method that will exit the program and print out usage if there was parsing error. Also it will display usage on --help
or -h
switch and version string on --version
switch found in argument array.
In other case it will return OpenStruct object from #parse
method.
Additionally this method can be called with a block that will get the OpenStruct like object before returning it. This block should contain additional value verifications and if it raises RuntimeError (via fail
method for instance) the error message will be displayed followed by usage and the program will exit.
Parser behavior specifiers
description 'string'
The string will be displayed in usage output as your program short description.
version 'string'
The string representing program version that will be displayed when --version
switch is used
switch :name [, options hash]
When switch is specified in the command argument list the object returned by #parse
or #parse!
will contain argument of same name set to true
. Otherwise the argument value will be nil
.
:name should be a symbol that will map to long switch (--name
) where underscore (_
) will be replaced with minus (-
). Name has to be unique.
Option hash can contain following pairs:
-
:short => :symbol - where :symbol is a single letter symbol that will represent short switch name (
-n
). Short name has to be unique. - :description => 'string' - switch description string that will be displayed in the usage output
option :name [, options hash]
Same as switch but additionally it has to be followed by a value on the command argument list.
The value after casting (if used) will be available from the #parse
or #parse!
returned object as argument of the same name.
In addition to switch, option hash can have following pairs:
-
:default => value - use default value of value if the option was not specified on the command argument list. The value will firstly be casted to string (with
#to_s
) and then it will be casted if casting is specified. - :default_label => label - display label in usage rather than default value - useful to describe default value if default value is generated if no value is provided
-
:cast => cast specifier - cast the provided value (or default) with given cast specifier.
The specifier can be a class constant - the value will be provided to
#new
method of the class and resulting object used as option value. When provided constant does not respond to#new
(i.e. it is a module) the#load
method will be tried instead. If provided specifier is a Proc (orlambda {}
) the Proc will be called with the value and resulting value will be used. OtherwiseCLI::ParsingError::CastError
will be raised. Special cast specifiedInteger
orFloat
can also be used - the value will be strictly casted to integer or float type. -
:required => true - if used and no default value is specified the
#parse
method will fail withCLI::ParsingError::MissingOptionValueError
if the option was not specified in the command argument list. If#parse!
method was used the program will exit with appropriate message.
options :name [, options hash]
Same as option but can be specified multiple times in the command argument list.
The resulting #parse
or #parse!
returned object will contain an argument with the same name that will always be an array.
The array may be empty if the option was not used and required option was not set, otherwise it will contain casted values in order of specification in the command argument list.
argument :name [,options hash]
After the parser encounters command line argument that is not a switch or option or it is literally --
string it will start matching arguments.
Each argument will be matched to argument specifications in order and their value after optional casting will be available as #parse
or #parse!
returned object argument with the same name.
Options hash can contain the same pairs as option expect of :short => :symbol.
If defaults are used the parser will keep using default values until it has enough command line arguments available to fill all mandatory arguments.
Arguments are required by default, use :required => false option pair to use nil
value if argument is not specified on the command line argument list.
arguments :name [,options hash]
Same as argument but will match one or more arguments and provide them in array of casted values. If argument is not required and not specified in command argument list then its value will be an empty array.
When used with argument specifiers that use default values the parser will try to assign at least one value to this specifier, but not more values so that all mandatory (that have no default and are required) arguments will be assigned.
stdin :name, [options hash]
Used once to specify that stdin should be handled.
When used the #parse
or #parse!
returned object will have stdin
argument that by default will contain stdin IO object.
As with switch specifier the :description => 'string' can be used. Also :cast => cast specifier option pair can be used but the value will be an IO object and not string.
Contributing to CLI
- Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
- Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
- Fork the project
- Start a feature/bugfix branch
- Commit and push until you are happy with your contribution
- Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
- Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
Copyright
Copyright (c) 2011-2022 Jakub Pastuszek. See LICENSE.txt for further details.