[ANN] main-2.2.0

Discussion in 'Ruby' started by ara.t.howard, Oct 24, 2007.

  1. ara.t.howard

    ara.t.howard Guest

    NAME
    main.rb

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

    URI
    http://rubyforge.org/projects/codeforpeople/
    http://codeforpeople.com/lib/ruby/

    INSTALL
    gem install main

    HISTORY
    2.2.0
    - added ability for parameter dsl error handlers to accept an
    argument,
    this will be passed the current error. for example

    argument:)x) do
    arity 42

    error do |e|
    case e
    when Parameter::Arity
    ...
    end
    end

    - refined the mode parsing a bit: modes can now be abbreviated
    to uniqness
    and, when the mode is ambiuous, a nice error message is
    printed, for
    example:

    ambiguous mode: in = (inflate or install)?

    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 :eek:ptional
    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 ''
    #
    # 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 :eek:ption }
    #
    # 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 :eek:ptional
    #
    # 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/
    --
    we can deny everything, except that we have the possibility of being
    better. simply reflect on that.
    h.h. the 14th dalai lama
     
    ara.t.howard, Oct 24, 2007
    #1
    1. Advertising

  2. On 24 Oct 2007, at 16:55, ara.t.howard wrote:

    >
    > NAME
    > main.rb
    >
    > SYNOPSIS
    > a class factory and dsl for generating command line programs real
    > quick


    *snip*

    > DESCRIPTION
    > main.rb features the following:
    >
    > - unification of option, argument, keyword, and environment
    > parameter
    > parsing


    *snip*

    Splendid! I'll take it, thanks :)
     
    Benjohn Barnes, Oct 24, 2007
    #2
    1. Advertising

  3. On 10/24/07, ara.t.howard <> wrote:
    >
    > NAME
    > main.rb
    >
    > SYNOPSIS
    > a class factory and dsl for generating command line programs real
    > quick


    I am using this for every medium to complex command line script I
    write. Thanks and keep up the good work !!!!

    Jesus.
     
    Jesús Gabriel y Galán, Oct 25, 2007
    #3
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. John M
    Replies:
    1
    Views:
    1,243
    Kumar Reddi
    May 29, 2005
  2. Hal Styli
    Replies:
    14
    Views:
    1,686
    Old Wolf
    Jan 20, 2004
  3. Frederick Ding

    int main() or int main(void)?

    Frederick Ding, Dec 3, 2005, in forum: C Programming
    Replies:
    10
    Views:
    667
  4. Ravi
    Replies:
    17
    Views:
    939
    Kenneth Brody
    Apr 1, 2006
  5. main() called inside main()

    , May 4, 2006, in forum: C Programming
    Replies:
    14
    Views:
    1,449
    Richard Heathfield
    May 7, 2006
Loading...

Share This Page