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 :optional 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] true 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 HISTORY 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 -a -- be kind whenever possible... it is always possible. - the dalai lama