[ANN] main-2.0.0

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 :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 section below

HISTORY
   2.0.0
     - removed need for proxy.rb via Main::Base.wrap_run!
     - added error handling hooks for parameter parsing
     - bundled arrayfields, attributes, and pervasives although gems are tried
       first
     - softened error messages for parameter parsing errors: certain classes of
       errors are now 'softspoken' and print only the message, not the entire
       stacktrace, to stderr. much nicer for users. this is configurable.
     - added subcommand/mode support
     - added support for user defined exception handling on top level
       exceptions/exits
     - added support for negative arity. this users ruby's own arity
       semantics, for example:

         lambda{|*a|}.arity == -1
         lambda{|a,*b|}.arity == -2
         lambda{|a,b,*c|}.arity == -3
         ...

       in otherwords parameters now support 'zero or more', 'one or more' ...
       'n or more' argument semantics

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
     }

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

# Subject: [ANN] main-2.0.0

quick qs:

1. how to invoke -h inside program.
   currently i use something like [exec "ruby #{__FILE__} -h"]

   i tried using "usage[]" but i think it's reinventing since -h if fine for me already.

2. required args that fail just gives/raise errors which is not so user friendly. How can i capture error cleanly? so i can display a user friendly error (and i also want to invoke help thereafter). Maybe require may need a block for the error to display, like,

   required {|error| code_here}

   or
  
   def err err_obj
   end
   # ie, main calls err if it exists
   # but i still would not know what to put on err

Right now, i set required args back to optional and then i have to ask if args was given?

3. how can i access all the args and options i set? I'd like to change my synopsis automatically and beautifully :slight_smile:

sorry for the many questions, ara.
Thank you for main. It's very cool.

kind regards -botp

···

From: ara.t.howard [mailto:ara.t.howard@gmail.com]

Great, I'm looking forward to using this

Fantastic! I always thought there should be a more Ruby-ish way of
handling things like this.

This probably won't make it into any of my current projects, but I'll
definitely be using it in the future.

Thanks!
Jeremy

···

On 10/18/07, ara.t.howard <ara.t.howard@gmail.com> wrote:

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 :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 section below

HISTORY
   2.0.0
     - removed need for proxy.rb via Main::Base.wrap_run!
     - added error handling hooks for parameter parsing
     - bundled arrayfields, attributes, and pervasives although gems
are tried
       first
     - softened error messages for parameter parsing errors: certain
classes of
       errors are now 'softspoken' and print only the message, not
the entire
       stacktrace, to stderr. much nicer for users. this is
configurable.
     - added subcommand/mode support
     - added support for user defined exception handling on top level
       exceptions/exits
     - added support for negative arity. this users ruby's own arity
       semantics, for example:

         lambda{|*a|}.arity == -1
         lambda{|a,*b|}.arity == -2
         lambda{|a,b,*c|}.arity == -3
         ...

       in otherwords parameters now support 'zero or more', 'one or
more' ...
       'n or more' argument semantics

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
     }

########################################################################
###
   # 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://www.jeremymcanally.com/

My books:
Ruby in Practice

My free Ruby e-book

My blogs:

http://www.rubyinpractice.com/

From: ara.t.howard [mailto:ara.t.howard@gmail.com]
# Subject: [ANN] main-2.0.0

quick qs:

1. how to invoke -h inside program.
   currently i use something like [exec "ruby #{__FILE__} -h"]

   i tried using "usage" but i think it's reinventing since -h if fine for me already.

   cfp:~/src/ruby/main/main-2.0.0 > cat a.rb
   require 'main'

   Main {
     option 'foobar'

     def run
       print usage.to_s
       exit
     end
   }

   cfp:~/src/ruby/main/main-2.0.0 > ruby a.rb
   NAME
     a.rb

   SYNOPSIS
     a.rb [options]+

   PARAMETERS
     --foobar
     --help, -h

the reason that the 'to_s' is required is that the usage returns and object that inherits from array and ruby treats 'puts an_array' specially. i should changed that in the next version... regardless 'usage.to_s' will continue to work.

2. required args that fail just gives/raise errors which is not so user friendly.

i think 2.0.0 addresses this (common) complaint:

cfp:~/src/ruby/main/main-2.0.0 > cat a.rb
require 'main'

Main {
   argument('foobar'){ required }

   run(){ puts Main.version }
}

cfp:~/src/ruby/main/main-2.0.0 > ruby a.rb foobar
2.0.0

cfp:~/src/ruby/main/main-2.0.0 > ruby a.rb
argument(foobar) not given

is that ok?

How can i capture error cleanly? so i can display a user friendly error (and i also want to invoke help thereafter). Maybe require may need a block for the error to display, like,

hmmmm. right now you, in 2.0.0, you can define 'handle_exception' on your class but you will need to handle all exceptions in that method... that's not great i realize. in 2.0.0 i've added the concept of 'Softspoken' errors. these are errors that, rather than dumping a stacktrace in the log/stderr - simple print their message. that's how the 'argument(foobar) not given' above is printed - it's a 'Softspoken' error and this is it's message. now, as to the question of how to further handle errors but printing usage, etc... that's harder. i'd personally NOT print usage after the error and instead suggest to the user to run the program with '-h' because a long usage message printed after and error will cause the error to scroll off a small terminal... still it's a real need...

   required {|error| code_here}

   or

   def err err_obj
   end
   # ie, main calls err if it exists
   # but i still would not know what to put on err

ok i like this idea. the general concept is that parameter can have their own error handlers - let me chew on this a little bit and try to get a sample impl online for you to play with...

Right now, i set required args back to optional and then i have to ask if args was given?

oh that's hacking we don't want to doing that! :wink:

3. how can i access all the args and options i set? I'd like to change my synopsis automatically and beautifully :slight_smile:

cfp:~/src/ruby/main/main-2.0.0 > cat a.rb
require 'main'

Main {
   argument 'foobar'
   argument 'barfoo'
   option 'a'
   option 'b'

   run(){ params.each{|pm| p pm} }
}

cfp:~/src/ruby/main/main-2.0.0 > ruby a.rb foobar barfoo --a --b
#<Main::Parameter::Argument:0x6c034 @given=true, @type=:argument, @arity=1, @values=["foobar"], @names=["foobar"], @defaults=, @cast=nil, @required=true, @validate=nil>
#<Main::Parameter::Argument:0x6b558 @given=true, @type=:argument, @arity=1, @values=["barfoo"], @names=["barfoo"], @defaults=, @cast=nil, @required=true, @validate=nil>
#<Main::Parameter::Option:0x6aa7c @given=true, @type=:option, @arity=1, @values=[true], @names=["a"], @defaults=, @cast=nil, @required=false, @argument=nil, @validate=nil>
#<Main::Parameter::Option:0x69e24 @given=true, @type=:option, @arity=1, @values=[true], @names=["b"], @defaults=, @cast=nil, @required=false, @argument=nil, @validate=nil>
#<Main::Parameter::Option:0x67a0c @given=nil, @type=:option, @arity=1, @names=["help", "h"], @defaults=, @required=false, @argument=nil>

i'd be interested to see what you come up with for an auto usage message - that part was very hard for me and i'm not sure mine is the best.

sorry for the many questions, ara.
Thank you for main. It's very cool.

glad you are using it, and the feedback is really welcome - it's surprisingly difficult to get this working smoothly for even a modest sample of command line apps - there are a lot of variables! :wink:

look for main-2.0.1 later today...

cheers.

a @ http://codeforpeople.com/

···

On Oct 18, 2007, at 10:46 PM, Peña, Botp wrote:
--
it is not enough to be compassionate. you must act.
h.h. the 14th dalai lama

# def run
# print usage.to_s

arggh, i did not try the to_s :slight_smile:
thanks

# exit
# end
# the reason that the 'to_s' is required is that the usage returns and
# object that inherits from array and ruby treats 'puts an_array'
# specially. i should changed that in the next version... regardless
# 'usage.to_s' will continue to work.

no problem, ara. The usage.to_s is good enough.

# i'd be interested to see what you come up with for an auto usage
# message - that part was very hard for me and i'm not sure
# mine is the
# best.

···

From: ara.t.howard [mailto:ara.t.howard@gmail.com]
#

no, the automessage part is fine.
my plan really is to add more info on --help and i was looking where to add more :slight_smile:

eg, i want to add sections like "other info", "gotchas", "copyright", "reporting bugs", and "see also".

# glad you are using it, and the feedback is really welcome - it's
# surprisingly difficult to get this working smoothly for even
# a modest
# sample of command line apps - there are a lot of variables! :wink:
#
# look for main-2.0.1 later today...

it's working great. really. my suggestions were purely cosmetics. and btw, the new mode option is cool.
many thanks again.

kind regards -botp

From: ara.t.howard [mailto:ara.t.howard@gmail.com]
# def run
# print usage.to_s

arggh, i did not try the to_s :slight_smile:
thanks

# exit
# end
# the reason that the 'to_s' is required is that the usage returns and
# object that inherits from array and ruby treats 'puts an_array'
# specially. i should changed that in the next version... regardless
# 'usage.to_s' will continue to work.

no problem, ara. The usage.to_s is good enough.

great. in 2.1.0 (on rubyforge now) you can just use 'help!' which calls 'print usage.to_s; exit' for you. i'll rework the Usage class to be and Array delegate rather than an Array subclass soon. it annoys me that 'puts usage' does not work...

# i'd be interested to see what you come up with for an auto usage
# message - that part was very hard for me and i'm not sure
# mine is the
# best.
#

no, the automessage part is fine.
my plan really is to add more info on --help and i was looking where to add more :slight_smile:

eg, i want to add sections like "other info", "gotchas", "copyright", "reporting bugs", and "see also".

oh. the Usage class is already setup to do that. simply do

   usage['gotchas'] = huge_block_of_text

and it will automatically be included in a nicely formatted way.

# glad you are using it, and the feedback is really welcome - it's
# surprisingly difficult to get this working smoothly for even
# a modest
# sample of command line apps - there are a lot of variables! :wink:
#
# look for main-2.0.1 later today...

it's working great. really. my suggestions were purely cosmetics. and btw, the new mode option is cool.
many thanks again.

kind regards -botp

glad to hear it - 2.1.0 also addresses your error handling issue:

cfp:~/src/ruby/main/main-2.1.0 > cat a.rb
require 'main'

puts Main.version

Main {
   argument 'x' do
     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
   end

   run(){ p param['x'].given? }
}

cfp:~/src/ruby/main/main-2.1.0 > ruby -I lib a.rb
2.1.0
this fires *before* normal error handling using #instance_eval...
this fires *instead of* normal error handling using #instance_eval...
this fires *after* normal error handling using #instance_eval...

in fact, 2.1.0 allows *any* exception to specify it's own error handlers - check out lib/main/base.rb and grep for 'handler'.

kind regards.

a @ http://codeforpeople.com/

···

On Oct 19, 2007, at 9:41 PM, Peña, Botp wrote:
--
we can deny everything, except that we have the possibility of being better. simply reflect on that.
h.h. the 14th dalai lama