# Main-2.1.0

**URL:** <https://www.ruby-forum.com/t/main-2-1-0/196496>\
**Category:** Ruby\
**Created:** [October 20, 2007, 10:57pm UTC](https://www.ruby-forum.com/t/main-2-1-0/196496 "2007-10-20T22:57:48Z")\
**Posts on this page:** 7\
**Page:** 1

<div class="post-metadata">

**Author:** ![ara.t.howard](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/a/a8b319/32.png) [@ara.t.howard](https://www.ruby-forum.com/u/ara.t.howard)\
**Post date:** [October 20, 2007, 10:57pm UTC](https://www.ruby-forum.com/t/main-2-1-0/196496/1 "2007-10-20T22:57:48Z")

</div>

NAME  
main.rb

SYNOPSIS  
a class factory and dsl for generating command line 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  
gem install main

HISTORY  
2.1.0  
- added custom error handling dsl for parameters, this includes  
the ability  
to prepend, append, or replace the standard error handlers:

```
     require 'main'

     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? }
     }

 - added ability to exit at any time bypassing *all* error

```

handling using  
‘throw :exit, 42’ where 42 is the desired exit status. throw  
without a  
status simply exits with 0.

```
 - added 'help!' method which simply dumps out usage and exits

```

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

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

```

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

### 

# 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/](http://codeforpeople.com/)

---

<div class="post-metadata">

**Author:** ![ara.t.howard](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/a/a8b319/32.png) [@ara.t.howard](https://www.ruby-forum.com/u/ara.t.howard)\
**Post date:** [October 22, 2007, 6:03am UTC](https://www.ruby-forum.com/t/main-2-1-0/196496/2 "2007-10-22T06:03:30Z")

</div>

From: ara.t.howard [[mailto:removed\_email\_address@domain.invalid](mailto:removed_email_address@domain.invalid)]

# INSTALL

# gem install main

hi ara, i do not see a gem. only a tgz.

kind regards -botp

---

<div class="post-metadata">

**Author:** ![ara.t.howard](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/a/a8b319/32.png) [@ara.t.howard](https://www.ruby-forum.com/u/ara.t.howard)\
**Post date:** [October 22, 2007, 5:02pm UTC](https://www.ruby-forum.com/t/main-2-1-0/196496/3 "2007-10-22T17:02:39Z")

</div>

On Oct 21, 2007, at 10:02 PM, Peña, Botp wrote:

> From: ara.t.howard [[mailto:removed\_email\_address@domain.invalid](mailto:removed_email_address@domain.invalid)]
> 
> # INSTALL
> 
> # gem install main
> 
> hi ara, i do not see a gem. only a tgz.
> 
> kind regards -botp

thanks! just noticed the upload had failed… it’s on rubyforge now

- might take a bit to push out to all the mirrors

cheers.

a @ [http://codeforpeople.com/](http://codeforpeople.com/)

---

<div class="post-metadata">

**Author:** ![ara.t.howard](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/a/a8b319/32.png) [@ara.t.howard](https://www.ruby-forum.com/u/ara.t.howard)\
**Post date:** [October 23, 2007, 11:28am UTC](https://www.ruby-forum.com/t/main-2-1-0/196496/4 "2007-10-23T11:28:43Z")

</div>

ara wrote:

# argument ‘x’ do

# error do

# puts 'this fires _instead of_ normal error

# handling using #instance\_eval…’

# end

Hi ara,

sorry to disturb you again. i hit a problem here. On my case, argument x  
can give an “required” error or an “arity” error (also possible error in  
validation too).

how can i capture wc error then? can you pass it (as param) on the  
block? i do not care of the format, i’ll take care of the parsing. in  
fact, you can just pass the default softerror message so i could either

1. use it by default or 2) i can parse it and do whatever i want with  
it.

thanks and kind regards -botp

---

<div class="post-metadata">

**Author:** ![ara.t.howard](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/a/a8b319/32.png) [@ara.t.howard](https://www.ruby-forum.com/u/ara.t.howard)\
**Post date:** [October 23, 2007, 11:35am UTC](https://www.ruby-forum.com/t/main-2-1-0/196496/5 "2007-10-23T11:35:44Z")

</div>

On Oct 22, 2007, at 9:23 PM, Peña, Botp wrote:

> how can i capture wc error then? can you pass it (as param) on the  
> block? i do not care of the format, i’ll take care of the parsing.  
> in fact, you can just pass the default softerror message so i could  
> either 1) use it by default or 2) i can parse it and do whatever i  
> want with it.

what’s ‘wc’ above?

i happen to be working on 2.2.0 now…

cheers.

a @ [http://codeforpeople.com/](http://codeforpeople.com/)

---

<div class="post-metadata">

**Author:** ![ara.t.howard](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/a/a8b319/32.png) [@ara.t.howard](https://www.ruby-forum.com/u/ara.t.howard)\
**Post date:** [October 23, 2007, 12:42pm UTC](https://www.ruby-forum.com/t/main-2-1-0/196496/6 "2007-10-23T12:42:06Z")

</div>

On Oct 22, 2007, at 9:58 PM, Peña, Botp wrote:

> argghh, i hope i did not disturbed you much.

lol - not at all. just a quick fix when modes are ambiguous - i’ll  
include your fix too.

kind regards.

a @ [http://codeforpeople.com/](http://codeforpeople.com/)

---

<div class="post-metadata">

**Author:** ![ara.t.howard](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/a/a8b319/32.png) [@ara.t.howard](https://www.ruby-forum.com/u/ara.t.howard)\
**Post date:** [October 23, 2007, 12:39pm UTC](https://www.ruby-forum.com/t/main-2-1-0/196496/7 "2007-10-23T12:39:09Z")

</div>

From: ara.t.howard [[mailto:removed\_email\_address@domain.invalid](mailto:removed_email_address@domain.invalid)]

# what’s ‘wc’ above?

i meant, the argument error can be any of the ff

1. required argument (argument was not passed)
2. arity error (eg too few arguments)
3. validation (but i haven’t tried this yet)

i just want to know wc of those possible errors above triggered the  
error, so i was suggesting if you can just pass the error(softerror msg  
ie) on the block

so i can then eg do,

argument “arg1” do  
error do |err|  
#puts err  
case err  
when…  
# further explanation regarding err…  
end  
end  
end

# i happen to be working on 2.2.0 now…

argghh, i hope i did not disturbed you much.

kind regards -botp
