[ANN] main-0.0.1 - command line apps for the truly lazy

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

Excellent looking library. I have some fascination with writing CLI apps,
and have used the commandline gem extensively. My one complaint with that
library was the obscure syntax used for parsing command line options.

Can the options parsed be used outside the Main { } declaration? For small
scripts, the "run" method is fine, but for larger apps it becomes necessary
to break things up. Specifically, I am thinking of some highline driven apps
I have which would benefit from the option parsing provided here.

Justin

···

On 3/20/07, Ara.T.Howard <ara.t.howard@noaa.gov> wrote:

NAME
   main.rb

SYNOPSIS
   a class factory and dsl for generating real main programs real quick

harp:~ > cat a.rb
   require 'rubygems'
   require 'main'

   ARGV.replace %w( --foo=42 42.0 )

   main = Main.new{
     option('foo='){ cast :int }
     option('bar='){ cast :float }
   }

   p main.param['foo']
   p main.param['bar']

   harp:~ > ruby a.rb
   #<Main::Parameter::Option:0xb74ce1b4 @given=true, @validate=nil, @cast=:int, @names=["foo"], @arity=1, @argument=:required, @required=false, @values=[42], @type=:option, @defaults=[]>
   #<Main::Parameter::Option:0xb74cdd18 @given=nil, @cast=:float, @names=["bar"], @argument=:required, @required=false, @type=:option, @defaults=[]>

i realize, as i post this, that i'd meant for one to be able to say

   argv = %w( --foo=42 42.0 )

   env = {'PATH' => '/usr/loca/bin'}

   main = Main.new(argv, env){
     option('foo='){ cast :int }
     option('bar='){ cast :float }
   }

but neglected to pass some information through. i'll think about it and ensure
that's possible v.s directly manipulating ARGV

this is quite a re-work of what's in alib, so i'm all ears for
comments/feature-requests. btw. the usage can be overridden simply with

   Main{
     usage 'my usage string'
   }

regards.

  -a

···

On Wed, 21 Mar 2007, Justin Bailey wrote:

On 3/20/07, Ara.T.Howard <ara.t.howard@noaa.gov> wrote:

NAME
   main.rb

SYNOPSIS
   a class factory and dsl for generating real main programs real quick

Excellent looking library. I have some fascination with writing CLI apps,
and have used the commandline gem extensively. My one complaint with that
library was the obscure syntax used for parsing command line options.

Can the options parsed be used outside the Main { } declaration? For small
scripts, the "run" method is fine, but for larger apps it becomes necessary
to break things up. Specifically, I am thinking of some highline driven apps
I have which would benefit from the option parsing provided here.

--
be kind whenever possible... it is always possible.
- the dalai lama

Could you make the main gem dependent on other gems? I had to manually
install attributes and arrayfields to get main to run. Otherwise, this is a
cool little library, thanks!

Jason

will do. it was an oversight. i'm making a few mods today and should release
later in the aftertoon.

cheers.

-a

···

On Thu, 22 Mar 2007, Jason Roelofs wrote:

Could you make the main gem dependent on other gems? I had to manually
install attributes and arrayfields to get main to run. Otherwise, this is a
cool little library, thanks!

--
be kind whenever possible... it is always possible.
- the dalai lama