# Main-0.0.2

**URL:** <https://www.ruby-forum.com/t/main-0-0-2/89934>\
**Category:** Ruby\
**Created:** [March 23, 2007, 10:57pm UTC](https://www.ruby-forum.com/t/main-0-0-2/89934 "2007-03-23T22:57:26Z")\
**Posts on this page:** 6\
**Page:** 1

<div class="post-metadata">

**Author:** ![casper\_the\_ghost](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/c/58956e/32.png) [@casper\_the\_ghost](https://www.ruby-forum.com/u/casper_the_ghost)\
**Post date:** [March 23, 2007, 10:57pm UTC](https://www.ruby-forum.com/t/main-0-0-2/89934/1 "2007-03-23T22:57:26Z")

</div>

NAME  
main.rb

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

URI  
[http://rubyforge.org/projects/codeforpeople/](http://rubyforge.org/projects/codeforpeople/)  
[http://codeforpeople.com/lib/ruby/](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]
 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

find lib|xargs -n1 vi -R

API section below

HISTORY  
0.0.2  
- removed dependancy on attributes/arrayfields. main now has zero  
gem  
dependancies.

```
 - added support for io redirection. redirection of stdin, stdout, 

```

and  
stderr can be done to any io like object or object that can be  
inerpreted as a pathname (object.to\_s)

```
 - main objects can now easily be created and run on demand, which 

```

makes  
testing a breeze

```
     def test_unit_goodness!
       main =
         Main.new{
           stdout StringIO.new
           stderr '/dev/null'

           def run
             puts 42
           end
         }

       main.run
       main.stdout.rewind

       assert main.stdout.read == "42\n"
     end

 - added API section to readme and called it 'docs'

 - wrote a bunch more tests. there are now 42 of them.

```

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

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 'removed_email_address@domain.invalid'

```

# 

# 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

```

###########################################################################

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

---

<div class="post-metadata">

**Author:** ![casper\_the\_ghost](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/c/58956e/32.png) [@casper\_the\_ghost](https://www.ruby-forum.com/u/casper_the_ghost)\
**Post date:** [March 24, 2007, 5:02am UTC](https://www.ruby-forum.com/t/main-0-0-2/89934/2 "2007-03-24T05:02:18Z")

</div>

From: Ara.T.Howard [[mailto:removed\_email\_address@domain.invalid](mailto:removed_email_address@domain.invalid)]

# NAME

# main.rb

# 

# SYNOPSIS

# a class factory and dsl for generating real main programs

# real quick

# 

# URI

# [http://rubyforge.org/projects/codeforpeople/](http://rubyforge.org/projects/codeforpeople/)

# [http://codeforpeople.com/lib/ruby/](http://codeforpeople.com/lib/ruby/)

thanks ara. this is really very ruby-ish and cool. Looking at a glance,  
the interface looks like web params too. who knows if it could apply  
also to web 😉

kind regards -botp

---

<div class="post-metadata">

**Author:** ![casper\_the\_ghost](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/c/58956e/32.png) [@casper\_the\_ghost](https://www.ruby-forum.com/u/casper_the_ghost)\
**Post date:** [March 24, 2007, 10:00am UTC](https://www.ruby-forum.com/t/main-0-0-2/89934/3 "2007-03-24T10:00:33Z")

</div>

----- Original Message -----  
From: “Ara.T.Howard” [removed\_email\_address@domain.invalid](mailto:removed_email_address@domain.invalid)  
To: “ruby-talk ML” [removed\_email\_address@domain.invalid](mailto:removed_email_address@domain.invalid)  
Sent: Friday, March 23, 2007 5:56 PM  
Subject: [ANN] main-0.0.2

> INSTALL  
> $sudo gem install main  
> …skip…

why it installed v.0.0.1 ?

M:\>gem install main  
Bulk updating Gem source index for: [http://gems.rubyforge.org](http://gems.rubyforge.org)  
Successfully installed main-0.0.1

M:\>ruby -v  
ruby 1.8.6 (2007-03-13 patchlevel 0) [i386-mswin32]

thanks  
Sergey

---

<div class="post-metadata">

**Author:** ![casper\_the\_ghost](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/c/58956e/32.png) [@casper\_the\_ghost](https://www.ruby-forum.com/u/casper_the_ghost)\
**Post date:** [September 25, 2007, 11:05pm UTC](https://www.ruby-forum.com/t/main-0-0-2/89934/4 "2007-09-25T23:05:33Z")

</div>

On Sun, 25 Mar 2007, Bil K. wrote:

> Sergey V. wrote:
> 
> > why it installed v.0.0.1 ?
> 
> 'cause it’s not hoe’d? 😉

yeah - that is true. i manage my stuff with a wrapper, similar to hoe,  
called  
‘rfup’ (rubyforge up) which pushes my stuff both to rubyforge and to  
codeforpeople. migrating to hoe is on my list - but i have so many  
projects  
it’s a daunting task to undertake…

cheers.

-a

---

<div class="post-metadata">

**Author:** ![casper\_the\_ghost](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/c/58956e/32.png) [@casper\_the\_ghost](https://www.ruby-forum.com/u/casper_the_ghost)\
**Post date:** [September 25, 2007, 11:10pm UTC](https://www.ruby-forum.com/t/main-0-0-2/89934/5 "2007-09-25T23:10:48Z")

</div>

Sergey V. wrote:

> why it installed v.0.0.1 ?

'cause it’s not hoe’d? 😉

Later,

---

<div class="post-metadata">

**Author:** ![casper\_the\_ghost](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/c/58956e/32.png) [@casper\_the\_ghost](https://www.ruby-forum.com/u/casper_the_ghost)\
**Post date:** [March 24, 2007, 4:33pm UTC](https://www.ruby-forum.com/t/main-0-0-2/89934/6 "2007-03-24T16:33:51Z")

</div>

On Sat, 24 Mar 2007, Sergey V. wrote:

> > a class factory and dsl for generating real main programs real quick
> 
> M:\>gem install main  
> Bulk updating Gem source index for: [http://gems.rubyforge.org](http://gems.rubyforge.org)  
> Successfully installed main-0.0.1
> 
> M:\>ruby -v  
> ruby 1.8.6 (2007-03-13 patchlevel 0) [i386-mswin32]

ummm - probably because i forgot to push it up! 😉

i just uploaded, but it takes the mirrors a while to sync. you can wait  
a bit  
or just grab the gem here

[http://rubyforge.org/projects/codeforpeople/](http://rubyforge.org/projects/codeforpeople/)  
[http://codeforpeople.com/lib/ruby/](http://codeforpeople.com/lib/ruby/)

sorry bout that!

-a
