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

On 3/20/07, Ara.T.Howard [email protected] 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.

Justin

On Wed, 21 Mar 2007, Justin B. wrote:

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.

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 Thu, 22 Mar 2007, Jason R. 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!

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

cheers.

-a

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