A
Ara.T.Howard
NAME
main.rb
SYNOPSIS
a class factory and dsl for generating real main programs real quick
URI
http://rubyforge.org/projects/codeforpeople/
http://codeforpeople.com/lib/ruby/
INSTALL
$sudo gem install main
DESCRIPTION
main.rb is a library which simplifies and unifies the details of creating
command line programs. for instance, this program
require 'main'
Main {
argument 'foo'
option 'bar'
def run
p params['foo']
p params['bar']
exit_success!
end
}
sets up a program which requires one argument, 'bar', and which may accept one
command line switch, '--foo' in addition to the single option which is always
accepted and handled appropriately: '--help', '-h'.
for simple programs this is a real time saver but it's for more complex
applications where main.rb's unification of parameter parsing, class
configuration dsl, and auto-generation of usage messages can really streamline
command line application development. for example the following 'a.rb'
program:
require 'main'
Main {
argument('foo'){
cast :int
}
keyword('bar'){
arity 2
cast :float
defaults 0.0, 1.0
}
option('foobar'){
argument ptional
description 'the foobar option is very handy'
}
environment('BARFOO'){
cast :list_of_bool
synopsis 'export barfoo=value'
}
def run
p params['foo'].value
p params['bar'].values
p params['foobar'].value
p params['BARFOO'].value
end
}
when run with a command line of
BARFOO=true,false,false ruby a.rb 42 bar=40 bar=2 --foobar=a
will produce
42
[40.0, 2.0]
"a"
[true, false, false]
while a command line of
ruby a.rb --help
will produce
NAME
a.rb
SYNOPSIS
a.rb foo [bar=bar] [options]+
PARAMETERS
* foo [ 1 -> int(foo) ]
* bar=bar [ 2 ~> float(bar=0.0,1.0) ]
* --foobar=[foobar] [ 1 ~> foobar ]
the foobar option is very handy
* --help, -h
* export barfoo=value
and this shows how all of argument, keyword, option, and environment parsing
can be declartively dealt with in a unified fashion - the dsl for all
parameter types is the same - and how auto synopsis and usage generation saves
keystrokes. the parameter synopsis is compact and can be read as
* foo [ 1 -> int(foo) ]
'one argument will get processed via int(argument_name)'
1 : one argument
-> : will get processed (the argument is required)
int(foo) : the cast is int, the arg name is foo
* bar=bar [ 2 ~> float(bar=0.0,1.0) ]
'two keyword arguments might be processed via float(bar=0.0,1.0)'
2 : two arguments
~> : might be processed (the argument is optional)
float(bar=0.0,1.0) : the cast will be float, the default values are
0.0 and 1.0
* --foobar=[foobar] [ 1 ~> foobar ]
'one option with optional argument may be given directly'
* --help, -h
no synopsis, simple switch takes no args and is not required
* export barfoo=value
a user defined synopsis
SAMPLES
<========< samples/a.rb >========>
~ > cat samples/a.rb
require 'main'
ARGV.replace %w( 42 ) if ARGV.empty?
Main {
argument('foo'){
required # this is the default
cast :int # value cast to Fixnum
validate{|foo| foo == 42} # raises error in failure case
description 'the foo param' # shown in --help
}
def run
p params['foo'].given?
p params['foo'].value
end
}
~ > ruby samples/a.rb
true
42
~ > ruby samples/a.rb --help
NAME
a.rb
SYNOPSIS
a.rb foo [options]+
PARAMETERS
* foo [ 1 -> int(foo) ]
the foo param
* --help, -h
<========< samples/b.rb >========>
~ > cat samples/b.rb
require 'main'
ARGV.replace %w( 40 1 1 ) if ARGV.empty?
Main {
argument('foo'){
arity 3 # foo will given three times
cast :int # value cast to Fixnum
validate{|foo| [40,1].include? foo} # raises error in failure case
description 'the foo param' # shown in --help
}
def run
p params['foo'].given?
p params['foo'].values
end
}
~ > ruby samples/b.rb
true
[40, 1, 1]
~ > ruby samples/b.rb --help
NAME
b.rb
SYNOPSIS
b.rb foo [options]+
PARAMETERS
* foo [ 3 -> int(foo) ]
the foo param
* --help, -h
<========< samples/c.rb >========>
~ > cat samples/c.rb
require 'main'
ARGV.replace %w( foo=40 foo=2 bar=false ) if ARGV.empty?
Main {
keyword('foo'){
required # by default keywords are not required
arity 2
cast :float
}
keyword('bar'){
cast :bool
}
def run
p params['foo'].given?
p params['foo'].values
p params['bar'].given?
p params['bar'].value
end
}
~ > ruby samples/c.rb
true
[40.0, 2.0]
true
false
~ > ruby samples/c.rb --help
NAME
c.rb
SYNOPSIS
c.rb foo=foo [bar=bar] [options]+
PARAMETERS
* foo=foo [ 2 -> float(foo) ]
* bar=bar [ 1 ~> bool(bar) ]
* --help, -h
<========< samples/d.rb >========>
~ > cat samples/d.rb
require 'main'
ARGV.replace %w( --foo=40 -f2 ) if ARGV.empty?
Main {
option('foo', 'f'){
required # by default options are not required, we could use 'foo=foo'
# above as a shortcut
argument_required
arity 2
cast :float
}
option('bar=[bar]', 'b'){ # note shortcut syntax for optional args
# argument_optional # we could also use this method
cast :bool
default false
}
def run
p params['foo'].given?
p params['foo'].values
p params['bar'].given?
p params['bar'].value
end
}
~ > ruby samples/d.rb
true
[40.0, 2.0]
nil
false
~ > ruby samples/d.rb --help
NAME
d.rb
SYNOPSIS
d.rb --foo=foo [options]+
PARAMETERS
* --foo=foo, -f [ 2 -> float(foo) ]
* --bar=[bar], -b [ 1 ~> bool(bar=false) ]
* --help, -h
DOCS
test/main.rb
find lib|xargs -n1 vi -R
API section below
HISTORY
0.0.2
- removed dependancy on attributes/arrayfields. main now has zero gem
dependancies.
- added support for io redirection. redirection of stdin, stdout, and
stderr can be done to any io like object or object that can be
inerpreted as a pathname (object.to_s)
- main objects can now easily be created and run on demand, which makes
testing a breeze
def test_unit_goodness!
main =
Main.new{
stdout StringIO.new
stderr '/dev/null'
def run
puts 42
end
}
main.run
main.stdout.rewind
assert main.stdout.read == "42\n"
end
- added API section to readme and called it 'docs'
- wrote a bunch more tests. there are now 42 of them.
0.0.1
initial version. this version extracts much of the functionality of alib's
(gen install alib) Alib.script main program generator and also some of jim's
freeze's excellent CommandLine::Aplication into what i hope is a simpler and
more unified interface
API
Main {
###########################################################################
# CLASS LEVEL API #
###########################################################################
#
# the name of the program, auto-set and used in usage
#
program 'foo.rb'
#
# a short description of program functionality, auto-set and used in usage
#
synopsis "foo.rb arg [options]+"
#
# long description of program functionality, used in usage iff set
#
description <<-hdoc
this text will automatically be indented to the right level.
it should describe how the program works in detail
hdoc
#
# used in usage iff set
#
author '(e-mail address removed)'
#
# used in usage
#
version '0.0.42'
#
# stdin/out/err can be anthing which responds to read/write or a string
# which will be opened as in the appropriate mode
#
stdin '/dev/null'
stdout '/dev/null'
stderr open('/dev/null', 'w')
#
# the logger should be a Logger object, something 'write'-able, or a string
# which will be used to open the logger. the logger_level specifies the
# initalize verbosity setting, the default is Logger::INFO
#
logger(( program + '.log' ))
logger_level Logger:EBUG
#
# you can configure exit codes. the defaults are shown
#
exit_success # 0
exit_failure # 1
exit_warn # 42
#
# the usage object is rather complex. by default it's an object which can
# be built up in sections using the
#
# usage["BUGS"] = "something about bugs'
#
# syntax to append sections onto the already pre-built usage message which
# contains program, synopsis, parameter descriptions and the like
#
# however, you always replace the usage object wholesale with one of your
# chosing like so
#
usage <<-txt
my own usage message
txt
###########################################################################
# PARAMETER API #
###########################################################################
#
# all the parameter types of argument|keyword|option|environment share this
# api. you must specify the type when the parameter method is used.
# alternatively used one of the shortcut methods
# argument|keyword|option|environment. in otherwords
#
# parameter('foo'){ type ption }
#
# is synonymous with
#
# option('foo'){ }
#
option 'foo' {
#
# required - whether this paramter must by supplied on the command line.
# note that you can create 'required' options with this keyword
#
required # or required true
#
# argument_required - applies only to options.
#
argument_required # argument :required
#
# argument_optional - applies only to options.
#
argument_optional # argument ptional
#
# cast - should be either a lambda taking one argument, or a symbol
# designation one of the built in casts defined in Main::Cast. supported
# types are :boolean|:integer|:float|:numeric|:string|:uri. built-in
# casts can be abbreviated
#
cast :int
#
# validate - should be a lambda taking one argument and returning
# true|false
#
validate{|int| int == 42}
#
# synopsis - should be a concise characterization of the paramter. a
# default synopsis is built automatically from the parameter. this
# information is displayed in the usage message
#
synopsis '--foo'
#
# description - a longer description of the paramter. it appears in the
# usage also.
#
description 'a long description of foo'
#
# arity - indicates how many times the parameter should appear on the
# command line. the default is one.
#
arity 2
#
# default - you can provide a default value in case none is given. the
# alias 'defaults' reads a bit nicer when you are giving a list of
# defaults for paramters of > 1 arity
#
defaults 40, 2
}
###########################################################################
# INSTANCE LEVEL API #
###########################################################################
#
# you must define a run method. it is the only method you must define.
#
def run
#
# all parameters are available in the 'params' hash and via the alias
# 'param'. it can be indexed via string or symbol. the values are all
# Main:arameter objects
#
foo = params['foo']
#
# the given? method indicates whether or not the parameter was given on
# the commandline/environment, etc. in particular this will not be true
# when a default value was specified but no parameter was given
#
foo.given?
#
# the list of all values can be retrieved via 'values'. note that this
# is always an array.
#
p foo.values
#
# the __first__ value can be retrieved via 'value'. note that this
# never an array.
#
p foo.value
#
# the methods debug|info|warn|error|fatal are delegated to the logger
# object
#
info{ "this goes to the log" }
#
# you can set the exit_status at anytime. this status is used when
# exiting the program. exceptions cause this to be ext_failure if, and
# only if, the current value was exit_success. in otherwords an
# un-caught exception always results in a failing exit_status
#
exit_status exit_failure
#
# a few shortcuts both set the exit_status and exit the program.
#
exit_success!
exit_failure!
exit_warn!
end
}
enjoy.
-a
main.rb
SYNOPSIS
a class factory and dsl for generating real main programs real quick
URI
http://rubyforge.org/projects/codeforpeople/
http://codeforpeople.com/lib/ruby/
INSTALL
$sudo gem install main
DESCRIPTION
main.rb is a library which simplifies and unifies the details of creating
command line programs. for instance, this program
require 'main'
Main {
argument 'foo'
option 'bar'
def run
p params['foo']
p params['bar']
exit_success!
end
}
sets up a program which requires one argument, 'bar', and which may accept one
command line switch, '--foo' in addition to the single option which is always
accepted and handled appropriately: '--help', '-h'.
for simple programs this is a real time saver but it's for more complex
applications where main.rb's unification of parameter parsing, class
configuration dsl, and auto-generation of usage messages can really streamline
command line application development. for example the following 'a.rb'
program:
require 'main'
Main {
argument('foo'){
cast :int
}
keyword('bar'){
arity 2
cast :float
defaults 0.0, 1.0
}
option('foobar'){
argument ptional
description 'the foobar option is very handy'
}
environment('BARFOO'){
cast :list_of_bool
synopsis 'export barfoo=value'
}
def run
p params['foo'].value
p params['bar'].values
p params['foobar'].value
p params['BARFOO'].value
end
}
when run with a command line of
BARFOO=true,false,false ruby a.rb 42 bar=40 bar=2 --foobar=a
will produce
42
[40.0, 2.0]
"a"
[true, false, false]
while a command line of
ruby a.rb --help
will produce
NAME
a.rb
SYNOPSIS
a.rb foo [bar=bar] [options]+
PARAMETERS
* foo [ 1 -> int(foo) ]
* bar=bar [ 2 ~> float(bar=0.0,1.0) ]
* --foobar=[foobar] [ 1 ~> foobar ]
the foobar option is very handy
* --help, -h
* export barfoo=value
and this shows how all of argument, keyword, option, and environment parsing
can be declartively dealt with in a unified fashion - the dsl for all
parameter types is the same - and how auto synopsis and usage generation saves
keystrokes. the parameter synopsis is compact and can be read as
* foo [ 1 -> int(foo) ]
'one argument will get processed via int(argument_name)'
1 : one argument
-> : will get processed (the argument is required)
int(foo) : the cast is int, the arg name is foo
* bar=bar [ 2 ~> float(bar=0.0,1.0) ]
'two keyword arguments might be processed via float(bar=0.0,1.0)'
2 : two arguments
~> : might be processed (the argument is optional)
float(bar=0.0,1.0) : the cast will be float, the default values are
0.0 and 1.0
* --foobar=[foobar] [ 1 ~> foobar ]
'one option with optional argument may be given directly'
* --help, -h
no synopsis, simple switch takes no args and is not required
* export barfoo=value
a user defined synopsis
SAMPLES
<========< samples/a.rb >========>
~ > cat samples/a.rb
require 'main'
ARGV.replace %w( 42 ) if ARGV.empty?
Main {
argument('foo'){
required # this is the default
cast :int # value cast to Fixnum
validate{|foo| foo == 42} # raises error in failure case
description 'the foo param' # shown in --help
}
def run
p params['foo'].given?
p params['foo'].value
end
}
~ > ruby samples/a.rb
true
42
~ > ruby samples/a.rb --help
NAME
a.rb
SYNOPSIS
a.rb foo [options]+
PARAMETERS
* foo [ 1 -> int(foo) ]
the foo param
* --help, -h
<========< samples/b.rb >========>
~ > cat samples/b.rb
require 'main'
ARGV.replace %w( 40 1 1 ) if ARGV.empty?
Main {
argument('foo'){
arity 3 # foo will given three times
cast :int # value cast to Fixnum
validate{|foo| [40,1].include? foo} # raises error in failure case
description 'the foo param' # shown in --help
}
def run
p params['foo'].given?
p params['foo'].values
end
}
~ > ruby samples/b.rb
true
[40, 1, 1]
~ > ruby samples/b.rb --help
NAME
b.rb
SYNOPSIS
b.rb foo [options]+
PARAMETERS
* foo [ 3 -> int(foo) ]
the foo param
* --help, -h
<========< samples/c.rb >========>
~ > cat samples/c.rb
require 'main'
ARGV.replace %w( foo=40 foo=2 bar=false ) if ARGV.empty?
Main {
keyword('foo'){
required # by default keywords are not required
arity 2
cast :float
}
keyword('bar'){
cast :bool
}
def run
p params['foo'].given?
p params['foo'].values
p params['bar'].given?
p params['bar'].value
end
}
~ > ruby samples/c.rb
true
[40.0, 2.0]
true
false
~ > ruby samples/c.rb --help
NAME
c.rb
SYNOPSIS
c.rb foo=foo [bar=bar] [options]+
PARAMETERS
* foo=foo [ 2 -> float(foo) ]
* bar=bar [ 1 ~> bool(bar) ]
* --help, -h
<========< samples/d.rb >========>
~ > cat samples/d.rb
require 'main'
ARGV.replace %w( --foo=40 -f2 ) if ARGV.empty?
Main {
option('foo', 'f'){
required # by default options are not required, we could use 'foo=foo'
# above as a shortcut
argument_required
arity 2
cast :float
}
option('bar=[bar]', 'b'){ # note shortcut syntax for optional args
# argument_optional # we could also use this method
cast :bool
default false
}
def run
p params['foo'].given?
p params['foo'].values
p params['bar'].given?
p params['bar'].value
end
}
~ > ruby samples/d.rb
true
[40.0, 2.0]
nil
false
~ > ruby samples/d.rb --help
NAME
d.rb
SYNOPSIS
d.rb --foo=foo [options]+
PARAMETERS
* --foo=foo, -f [ 2 -> float(foo) ]
* --bar=[bar], -b [ 1 ~> bool(bar=false) ]
* --help, -h
DOCS
test/main.rb
find lib|xargs -n1 vi -R
API section below
HISTORY
0.0.2
- removed dependancy on attributes/arrayfields. main now has zero gem
dependancies.
- added support for io redirection. redirection of stdin, stdout, and
stderr can be done to any io like object or object that can be
inerpreted as a pathname (object.to_s)
- main objects can now easily be created and run on demand, which makes
testing a breeze
def test_unit_goodness!
main =
Main.new{
stdout StringIO.new
stderr '/dev/null'
def run
puts 42
end
}
main.run
main.stdout.rewind
assert main.stdout.read == "42\n"
end
- added API section to readme and called it 'docs'
- wrote a bunch more tests. there are now 42 of them.
0.0.1
initial version. this version extracts much of the functionality of alib's
(gen install alib) Alib.script main program generator and also some of jim's
freeze's excellent CommandLine::Aplication into what i hope is a simpler and
more unified interface
API
Main {
###########################################################################
# CLASS LEVEL API #
###########################################################################
#
# the name of the program, auto-set and used in usage
#
program 'foo.rb'
#
# a short description of program functionality, auto-set and used in usage
#
synopsis "foo.rb arg [options]+"
#
# long description of program functionality, used in usage iff set
#
description <<-hdoc
this text will automatically be indented to the right level.
it should describe how the program works in detail
hdoc
#
# used in usage iff set
#
author '(e-mail address removed)'
#
# used in usage
#
version '0.0.42'
#
# stdin/out/err can be anthing which responds to read/write or a string
# which will be opened as in the appropriate mode
#
stdin '/dev/null'
stdout '/dev/null'
stderr open('/dev/null', 'w')
#
# the logger should be a Logger object, something 'write'-able, or a string
# which will be used to open the logger. the logger_level specifies the
# initalize verbosity setting, the default is Logger::INFO
#
logger(( program + '.log' ))
logger_level Logger:EBUG
#
# you can configure exit codes. the defaults are shown
#
exit_success # 0
exit_failure # 1
exit_warn # 42
#
# the usage object is rather complex. by default it's an object which can
# be built up in sections using the
#
# usage["BUGS"] = "something about bugs'
#
# syntax to append sections onto the already pre-built usage message which
# contains program, synopsis, parameter descriptions and the like
#
# however, you always replace the usage object wholesale with one of your
# chosing like so
#
usage <<-txt
my own usage message
txt
###########################################################################
# PARAMETER API #
###########################################################################
#
# all the parameter types of argument|keyword|option|environment share this
# api. you must specify the type when the parameter method is used.
# alternatively used one of the shortcut methods
# argument|keyword|option|environment. in otherwords
#
# parameter('foo'){ type ption }
#
# is synonymous with
#
# option('foo'){ }
#
option 'foo' {
#
# required - whether this paramter must by supplied on the command line.
# note that you can create 'required' options with this keyword
#
required # or required true
#
# argument_required - applies only to options.
#
argument_required # argument :required
#
# argument_optional - applies only to options.
#
argument_optional # argument ptional
#
# cast - should be either a lambda taking one argument, or a symbol
# designation one of the built in casts defined in Main::Cast. supported
# types are :boolean|:integer|:float|:numeric|:string|:uri. built-in
# casts can be abbreviated
#
cast :int
#
# validate - should be a lambda taking one argument and returning
# true|false
#
validate{|int| int == 42}
#
# synopsis - should be a concise characterization of the paramter. a
# default synopsis is built automatically from the parameter. this
# information is displayed in the usage message
#
synopsis '--foo'
#
# description - a longer description of the paramter. it appears in the
# usage also.
#
description 'a long description of foo'
#
# arity - indicates how many times the parameter should appear on the
# command line. the default is one.
#
arity 2
#
# default - you can provide a default value in case none is given. the
# alias 'defaults' reads a bit nicer when you are giving a list of
# defaults for paramters of > 1 arity
#
defaults 40, 2
}
###########################################################################
# INSTANCE LEVEL API #
###########################################################################
#
# you must define a run method. it is the only method you must define.
#
def run
#
# all parameters are available in the 'params' hash and via the alias
# 'param'. it can be indexed via string or symbol. the values are all
# Main:arameter objects
#
foo = params['foo']
#
# the given? method indicates whether or not the parameter was given on
# the commandline/environment, etc. in particular this will not be true
# when a default value was specified but no parameter was given
#
foo.given?
#
# the list of all values can be retrieved via 'values'. note that this
# is always an array.
#
p foo.values
#
# the __first__ value can be retrieved via 'value'. note that this
# never an array.
#
p foo.value
#
# the methods debug|info|warn|error|fatal are delegated to the logger
# object
#
info{ "this goes to the log" }
#
# you can set the exit_status at anytime. this status is used when
# exiting the program. exceptions cause this to be ext_failure if, and
# only if, the current value was exit_success. in otherwords an
# un-caught exception always results in a failing exit_status
#
exit_status exit_failure
#
# a few shortcuts both set the exit_status and exit the program.
#
exit_success!
exit_failure!
exit_warn!
end
}
enjoy.
-a