[ANN] main-2.5.0

A

ara.t.howard

NAME
main.rb

SYNOPSIS
a class factory and dsl for generating command line programs real
quick

URI
http://rubyforge.org/projects/codeforpeople/
http://codeforpeople.com/lib/ruby/

INSTALL
gem install main

DESCRIPTION
main.rb features the following:

- unification of option, argument, keyword, and environment
parameter
parsing
- auto generation of usage and help messages
- support for mode/sub-commands
- io redirection support
- logging hooks using ruby's built-in logging mechanism
- intelligent error handling and exit codes
- use as dsl or library for building Main objects
- parsing user defined ARGV and ENV
- zero requirements for understanding the obtuse apis of *any*
command
line option parsers

in short main.rb aims to drastically lower the barrier to writing
uniform
command line applications.

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/mode
which is always
accepted and handled appropriately: 'help', '--help', '-h'. for
the most
part main.rb stays out of your command line namespace but insists
that your
application has at least a help mode/option.

main.rb supports sub-commands in a very simple way

require 'main'

Main {
mode 'install' do
def run() puts 'installing...' end
end

mode 'uninstall' do
def run() puts 'uninstalling...' end
end
}

which allows you a program called 'a.rb' to be invoked as

ruby a.rb install

and

ruby a.rb uninstall

for simple programs main.rb 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 :eek: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

vim -o lib/main.rb lib/main/*

API section below

HISTORY
2.5.0
- added 'examples', 'samples', and 'api' kewords to main dsl. each
keyword takes a list of strings which will be included in the
help
message

Main {
examples "foobar example", "barfoo example"

samples <<-txt
do this

don't do that
txt

api %(
foobar string, hash

barfoo hash, string
)
}

results in a usage message with sections like

...

EXAMPLES
foobar example
barfoo example

SAMPLES
do this

don't do that

API
foobar string, hash

barfoo hash, string

...

2.4.0
- fixed bug where 'abort("message")' would print "message" twice
on exit
if running under a nested mode.

- allowed parameters to be overridden completely in subclasses
(modes)


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::DEBUG
#
# 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


########################################################################
###
# MODE
API #

########################################################################
###
#
# modes are class factories that inherit from their parent class.
they can
# be nested *arbitrarily* deep. usage messages are tailored for
each mode.
# modes are, for the most part, independant classes but parameters
are
# always a superset of the parent class - a mode accepts all of
it's parents
# paramters *plus* and additional ones
#
option 'inherited-option'
argument 'inherited-argument'

mode 'install' do
option 'force' do
description 'clobber existing installation'
end

def run
inherited_method()
puts 'installing...'
end

mode 'docs' do
description 'installs the docs'

def run
puts 'installing docs...'
end
end
end

mode 'un-install' do
option 'force' do
description 'remove even if dependancies exist'
end

def run
inherited_method()
puts 'un-installing...'
end
end

def run
puts 'no mode yo?'
end

def inherited_method
puts 'superclass_method...'
end



########################################################################
###
# 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 :eek: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 :eek: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. negative arities are
supported and
# follow the same rules as ruby methods/procs.
#
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
#
# you can add custom per-parameter error handlers using the
following
#
error :before do
puts 'this fires *before* normal error handling using
#instance_eval...'
end

error do
puts 'this fires *instead of* normal error handling using
#instance_eval...'
end

error :after do
puts 'this fires *after* normal error handling using
#instance_eval...'
end
}


########################################################################
###
# 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::parameter 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

}


a @ http://codeforpeople.com/
 
M

mailing.mr

mayby a changelog?

anyway i would like to thank you for this library ... it's great for
writing simple command line programs!!!
 
M

mailing.mr

I would like to mention that if you plan on using ActiveRecord with main
you might encounter LOTS of strange errors - i would also mention that
simply requiring main with -d gives you 2 terminal screens of warings -
ara please use () ;)
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Similar Threads

ANN main-4.4.0 0
[ANN] main-4.0.0 (for avdi) 0
[ANN] main-2.8.3 2
[ANN] main-3.0.1 0
[ANN] main-2.1.0 6
[ANN] main-0.0.1 - command line apps for the truly lazy 2
[ANN] main-0.0.2 5
[ANN] main-2.6.0 0

Members online

No members online now.

Forum statistics

Threads
473,744
Messages
2,569,482
Members
44,901
Latest member
Noble71S45

Latest Threads

Top