# Main-4.0.0 (for avdi)

**URL:** <https://www.ruby-forum.com/t/main-4-0-0-for-avdi/177618>\
**Category:** Ruby\
**Created:** [October 21, 2009, 2:23am UTC](https://www.ruby-forum.com/t/main-4-0-0-for-avdi/177618 "2009-10-21T02:23:05Z")\
**Posts on this page:** 1\
**Page:** 1

<div class="post-metadata">

**Author:** ![ahoward](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/a/779978/32.png) [@ahoward](https://www.ruby-forum.com/u/ahoward)\
**Post date:** [October 21, 2009, 2:23am UTC](https://www.ruby-forum.com/t/main-4-0-0-for-avdi/177618/1 "2009-10-21T02:23:05Z")

</div>

NAME  
main.rb

SYNOPSIS  
a class factory and dsl for generating command line programs real  
quick

URI  
[http://codeforpeople.com/lib/ruby/](http://codeforpeople.com/lib/ruby/)  
[http://rubyforge.org/projects/codeforpeople/](http://rubyforge.org/projects/codeforpeople/)  
[GitHub - ahoward/main: a class factory and dsl for generating command line programs real quick](http://github.com/ahoward/main)

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  
- leather pants

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

```

\<========\< 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]

```

\<========\< 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

```

\<========\< 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

```

\<========\< samples/e.rb \>========\>

~ \> cat samples/e.rb

```
require 'main'

ARGV.replace %w( x y argument )

Main {
  argument 'argument'
  option 'option'

  def run() puts 'run' end

  mode 'a' do
    option 'a-option'
    def run() puts 'a-run' end
  end

  mode 'x' do
    option 'x-option'

    def run() puts 'x-run' end

      mode 'y' do
        option 'y-option'

        def run() puts 'y-run' end
      end
  end
}

```

~ \> ruby samples/e.rb

```
y-run

```

\<========\< samples/f.rb \>========\>

~ \> cat samples/f.rb

```
require 'main'

ARGV.replace %W( compress /data )

Main {
  argument('directory'){ description 'the directory to operate on' }

  option('force'){ description 'use a bigger hammer' }

  def run
    puts 'this is how we run when no mode is specified'
  end

  mode 'compress' do
    option('bzip'){ description 'use bzip compression' }

    def run
      puts 'this is how we run in compress mode'
    end
  end

  mode 'uncompress' do
    option('delete-after'){ description 'delete orginal file after

```

uncompressing’ }

```
    def run
      puts 'this is how we run in un-compress mode'
    end
  end
}

```

~ \> ruby samples/f.rb

```
this is how we run in compress mode

```

\<========\< samples/g.rb \>========\>

~ \> cat samples/g.rb

```
require 'main'

ARGV.replace %w( 42 ) if ARGV.empty?

Main {
  argument( 'foo' )
  option( 'bar' )

  run { puts "This is what to_options produces:

```

#{params.to\_options.inspect}" }  
}

~ \> ruby samples/g.rb

```
This is what to_options produces: {"help"=>nil, "foo"=>"42", 

```

“bar”=\>nil}

\<========\< samples/h.rb \>========\>

~ \> cat samples/h.rb

```
require 'main'

# block-defaults are instance_eval'd in the main instance and be

```

combined with  
# mixins  
#  
# ./h.rb #=\> forty-two  
# ./h.rb a #=\> 42  
# ./h.rb b #=\> 42.0  
#

```
Main {
  fattr :default_for_foobar => 'forty-two'

  option(:foobar) do
    default{ default_for_foobar }
  end

  mixin :foo do
    fattr :default_for_foobar => 42
  end

  mixin :bar do
    fattr :default_for_foobar => 42.0
  end

  run{ p params[:foobar].value }

  mode :a do
    mixin :foo
  end

  mode :b do
    mixin :bar
  end
}

```

~ \> ruby samples/h.rb

```
"forty-two"

```

DOCS  
test/main.rb  
vim -p lib/main.rb lib/main/\*rb  
API section below

HISTORY  
4.0.0  
- avoid duping ios. new methods Main.push\_ios! and Main.pop\_ios!  
are  
utilized for testing. this was done to make it simple to wrap  
daemon/servolux programs around main, althought not strictly  
required.  
not the version bump - there is not reason to expect existing main  
programs to break, but it _is_ and interface change which requires a  
major  
version bump.

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

}
