[ANN] main-2.8.3

NAME
   main.rb

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

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

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
     - leather pants

   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 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 foo 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 (0 ~> bool(bar=false))
       --help, -h

   <========< samples/g.rb >========>

   ~ > cat samples/g.rb

     require 'main'

     ARGV.replace %w( 42 ) if ARGV.empty?

     Main {
       argument( 'foo' )
       option( 'bar' )

       run { puts "This is what to_options produces: #{params.to_options.inspect}" }
     }

   ~ > ruby samples/g.rb

     This is what to_options produces: {"help"=>nil, "foo"=>"42", "bar"=>nil}

   ~ > ruby samples/g.rb --help

     NAME
       g.rb

     SYNOPSIS
       g.rb foo [options]+

     PARAMETERS
       foo (1 -> foo)
       --bar
       --help, -h

   <========< samples/h.rb >========>

   ~ > cat samples/h.rb

     require 'main'

     # block-defaults are instance_eval'd in the main instance and can be combined with
     # mixins

···

#
     # ./h.rb #=> forty-two
     # ./h.rb a #=> 42
     # ./h.rb b #=> 42.0
     #

     Main {
       fattr :default_for_foobar => 'forty-two'

       option(:foobar) do
         default{ default_for_foobar }
       end

       mixin :foo do
         fattr :default_for_foobar => 42
       end

       mixin :bar do
         fattr :default_for_foobar => 42.0
       end

       run{ p params[:foobar].value }

       mode :a do
         mixin :foo
       end

       mode :b do
         mixin :bar
       end
     }

   ~ > ruby samples/h.rb

     "forty-two"

   ~ > ruby samples/h.rb --help

     NAME
       h.rb

     SYNOPSIS
       h.rb (a|b) [options]+

     PARAMETERS
       --foobar
       --help, -h

DOCS
   test/main.rb

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

   API section below

HISTORY
   2.8.3
     - support for block defaults

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

   http://rubyforge.org/projects/codeforpeople/

Thanks a lot. The above link gives a long list of libraries. Is there a
page giving a synopsis/short description of these libraries. Thanks.

···

--
Posted via http://www.ruby-forum.com/\.

no there isn't, though there should be... it's relatively quick to puruse

http://codeforpeople.com/lib/ruby/

here, at least, you can click on the README file that 90% of the libs will contain in a recent release. for example

http://codeforpeople.com/lib/ruby/main/main-2.8.3/README

cheers.

a @ http://codeforpeople.com/

···

On Oct 23, 2008, at 8:19 AM, Nit Khair wrote:

  http://rubyforge.org/projects/codeforpeople/

Thanks a lot. The above link gives a long list of libraries. Is there a
page giving a synopsis/short description of these libraries. Thanks.
--

--
we can deny everything, except that we have the possibility of being better. simply reflect on that.
h.h. the 14th dalai lama