Main-4.0.0 (for avdi)

NAME
main.rb

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

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

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

}

This forum is not affiliated to the Ruby language, Ruby on Rails framework, nor any Ruby applications discussed here.

| Privacy Policy | Terms of Service | Remote Ruby Jobs