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

HISTORY
   2.2.0
     - added ability for parameter dsl error handlers to accept an  
argument,
       this will be passed the current error.  for example

         argument(:x) do
           arity 42

           error do |e|
             case e
               when Parameter::Arity
             ...
           end
         end

     - refined the mode parsing a bit: modes can now be abbreviated  
to uniqness
       and, when the mode is ambiuous, a nice error message is  
printed, for
       example:

         ambiguous mode: in = (inflate or install)?

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 :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]
     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

   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 'ara.t.howard / gmail.com'
   #
   # 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 :option }
   #
   # 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 :optional
     #
     # 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

   }


enjoy.

a @ http://codeforpeople.com/
--
we can deny everything, except that we have the possibility of being  
better. simply reflect on that.
h.h. the 14th dalai lama