Scripts framework

Discussion in 'Ruby' started by benjohn@fysh.org, Aug 15, 2006.

  1. Guest

    I've been writing a few unix scripts recently. I'm very much feeling the
    need for a framework that will let me build scripts and not worry about
    such common functionality as:

    * providing parameters through the command line and config files, or
    falling back to program specified defaults.
    * getting help on command usage (perhaps via embedded rdoc).

    I would imagine there is a developed framework out there to do this kind
    of thing?

    Cheers,
    Benjohn
    , Aug 15, 2006
    #1
    1. Advertising

  2. On Aug 15, 2006, at 14:45, wrote:

    >
    > I've been writing a few unix scripts recently. I'm very much
    > feeling the
    > need for a framework that will let me build scripts and not worry
    > about
    > such common functionality as:
    >
    > * providing parameters through the command line and config files, or
    > falling back to program specified defaults.
    > * getting help on command usage (perhaps via embedded rdoc).
    >
    > I would imagine there is a developed framework out there to do this
    > kind
    > of thing?


    The optparse library is distributed along with Ruby, and will
    probably do a large part of what you're after. And don't forget
    about ARGF.

    http://ruby-doc.org/stdlib/libdoc/optparse/rdoc/index.html

    matthew smillie.
    Matthew Smillie, Aug 15, 2006
    #2
    1. Advertising

  3. Guest

    > On Aug 15, 2006, at 14:45, wrote:
    >
    >>
    >> I've been writing a few unix scripts recently. I'm very much
    >> feeling the
    >> need for a framework that will let me build scripts and not worry
    >> about
    >> such common functionality as:
    >>
    >> * providing parameters through the command line and config files, or
    >> falling back to program specified defaults.
    >> * getting help on command usage (perhaps via embedded rdoc).
    >>
    >> I would imagine there is a developed framework out there to do this
    >> kind
    >> of thing?

    >
    > The optparse library is distributed along with Ruby, and will
    > probably do a large part of what you're after. And don't forget
    > about ARGF.


    :) Thanks for the ARGF pointer. Optparse is the right kind of thing to
    be using, but I was hoping for more.

    Cheers,
    Benjohn
    , Aug 15, 2006
    #3
  4. Trans Guest

    wrote:
    > I've been writing a few unix scripts recently. I'm very much feeling the
    > need for a framework that will let me build scripts and not worry about
    > such common functionality as:
    >
    > * providing parameters through the command line and config files, or
    > falling back to program specified defaults.
    > * getting help on command usage (perhaps via embedded rdoc).
    >
    > I would imagine there is a developed framework out there to do this kind
    > of thing?
    >
    > Cheers,
    > Benjohn


    There are a number of alternative out there. A couple in particluar are
    nice. Can't recall the name of it at the moment but one allows you to
    define the syntax via USAGE docs and it will parse the docs to setup
    the functionality for you. That's kind of cool, albeit not my style.
    Anyone recall this lib?

    Another way (my style) is Facets command.rb. It makes basic command
    line scripts as easy as silly putty.

    require 'facet/command'

    class MyCommand << Console::Command

    # define some options

    def __help
    puts "HELP!"
    exit 0
    end

    def __debug
    $DEBUG = true
    end
    alias :_d :__debug

    def __file( name )
    @name = name
    end

    # define some subcommands

    def sub1
    puts "This is a subcommand for #{@name}"
    end

    def sub2( req )
    puts "This is a subcommand with a required parameter #{req}"
    end

    end

    MyCommand.execute

    Running it:

    % mycmd --help
    HELP!

    % mycmd --file 'afile' sub1
    This is a subcommand for afile

    &c.

    It has some limitations in so far as sepcifying strict rules about
    which subcommand and options work together but often that proves
    unneccessary. Also, I haven't finished the automatic help system yet,
    but I've started it and should be availbe soon.

    Finally, yuo might be intersted in the project I'm working on right now
    called, Script Sake (as in the drink). It's a build tool like Rake and
    my clone of it, Reap, but it moves away from the monolithic "Rakefile"
    idea and instead focuses on bolstering standard scripts. Should have a
    realese ready shortly.

    T.
    Trans, Aug 15, 2006
    #4
  5. Guest

    , Aug 15, 2006
    #5
  6. Phrogz Guest

    Because it's hard to find the information (you have to install the GEM
    and then dive in and look at the README in the installed area) here's
    the documentation from Usage so you can get an idea how it's used:

    OVERVIEW

    The usage library is a library that is allows command line scripts to
    be written with
    a minimum of memorization necessary to access the arguments. I wrote
    it because I was
    tired of copying code from other command line programs that used
    GetOptLong. This was
    started before there was a profusion of different command line
    libraries. Even with this
    profusion, there is still some memorization that goes along with how to
    set up the
    framework process the arguments.

    LIMITATIONS

    This library really intended for simplish command line programs. It is
    not really
    capable of handling complex command line requirements like commands
    with sub-commands
    and the like (like CVS or SVN). It is really intended for that quick
    command that you
    are whipping up to do a simple task. It can grow with you for a while
    but at some
    point it may not be up to the task.

    USAGE ON USAGE

    A note on terminology. When I mention "arguments" it means command line
    arguments that
    are not prefixed by a "-" option. When I mention options, I mean those
    things following
    the "-". Options themselves can have arguments as well. So we have:

    program arguments - those non option arguments
    options - those dash things
    option arguments - those things that are after but associated with
    options

    1. Simple Usage

    The only thing you have remember to use usage are how commands are
    usually documented.
    First you need to require the usage library:

    require "Usage"

    Then set up the usage string for the command:

    usage = Usage.new "infile outfile"

    The above would be a command with two require arguments: an input file
    and an output file.
    To access those arguments, you just need to use the usage variable that
    was created and
    send the .infile or .outfile message to them.

    File.open(usage.infile) do |fi|
    File.open(usage.outfile, "w") do |fo|
    fo.write(fi.read)
    end
    end

    If the user doesn't supply the correct number of arguments, the program
    exits with an error
    and the usage for the program (hence the libraries name).

    PROGRAM: test.rb
    ERROR: too few arguments 2 expected, 0 given

    USAGE: test.rb infile outfile

    2. Lists of files (...)

    You can write a program that accepts a list of files by using elipses
    appended to an
    argument (the following program concatenates the input files into one
    output file).

    usage = Usage.new "outfile infiles..."

    File.open(usage.outfile, "w") do |fo|
    usage.infiles.each do |infile|
    File.open(usage.infile) { |fi| fo.write(fi.read)}
    end
    end

    3. Optional arguments

    You can have optional arguments by surounding them in square brackets.

    usage = Usage.new "[optional_arg] required_arg"

    These are accessed in the standard way

    usage.optional_arg # this is nil if it is not given by the user

    usage.required_arg

    4. Options

    You can have dash options that are either required or optional. Options
    can also have
    arguments associated with them.

    usage = Usage.new "[-y] [-x excluded_tags] (-z ztag) (-w warning_arg)
    files..."

    The options are accessed with "dash_" prefixing the option so that the
    -y is accessed
    via .dash_y. The -x can be accessed either with #dash_x (which would be
    either nil or
    true) or #excluded_tags (which would be either nil or the argument for
    the -x option).
    The -z option is required and has one argument, also the -w option is
    also required.
    They can appear in any order (-z option first or -w option first). The
    optional arguments
    can appear either before, interspersed with, or after the required
    options.

    5. Long Options

    You can also have long options by including lines following the initial
    usage line that
    associates the short options with the long ones. Example below:

    usage = Usage.new "-x files...", <<EOT
    -x,--exclusive specifies exclusive access to the files
    EOT

    With this case, now #dash_x and #exclusive give the same result when
    applied to the usage
    variable.

    6. Typed options

    Starting with version 0.3, typed options are handled by a plugin
    architecture. There
    are also more types supported than pre 0.3. Typed options are invoked
    by preceeding an
    argument with a string of non-alpha chars.

    The following are built into the library (some I took from BASIC which
    I programmed in
    long, long ago). Note: These can be replaced or added to.

    % - Integer
    $ - String (but this is unnecessary as this is default)
    # - Float
    @ - Date-Time
    < - A file that will be read
    > - A file that will be written to

    << - A file that will be read in by readlines
    >> - A file that will be appended to

    >? - A file that will be written to but the user is prompted if it

    already exists
    >>? - A file that will be appended to but the user is prompted if the

    file doesn't exist
    <@ - Either a local file or a URI that is http:// or ftp://. Also,
    filenames that
    start with www. are prepended automatically with http:// and
    filenames that
    start with ftp. are prepended automatically with ftp://

    So when you send the argument message to the usage object, you will get
    a value of that
    type and if the user does not give that type, then they get an error
    message.

    usage = Usage.new "%num_times @on_date"

    In this example, #num_times returns and Integer object and #on_date
    returns a Time object.

    6.1. Adding new types

    You can add a new type parser by declaring a sub-class of
    UsageMod::ArgumentParserPlugin or
    one of the other sub-classes of Usage::ArgumentParserPlugin such as
    UsageMod::IntegerArgumentPlugin.

    You need to define two methods:

    initialize(usage_ui, string)
    - usage_ui is used to ask the user yes/no questions
    - string is the argument as a string. If the parsing fails, then
    raise an exception
    that is a sub-class of UsageMod::Error

    close
    - use this to close any objects that need to be closed/released when
    the usage block ends

    6.2 Assigning them to a string of chars

    To assign a plugin class to a character, you only need to call
    UsageMod::Base.add_type_handler.

    So if the class that you created is called SpanishArgumentPlugin, and
    you would like to
    assign it to the '^^' character sequence, then you would call:

    UsageMod::Base.add_type_handler("^^", SpanishArgumentPlugin)

    6.3 Example

    #
    # This example is taken from the built-in class for arguments that are
    writable files
    # that don't want to be overwritten
    #

    # first define the file exists exception class
    class FileOutputExistsError < UsageMod::Error
    attr_reader :filename
    def initialize(filename)
    @filename = filename
    super("output file exists: '#{filename}'")
    end
    end

    # next define the argument parser plugin
    class FileOutputQueryPlugin < UsageMod::ArgumentParserPlugin
    def initialize(usage_ui, str)
    if FileTest.exist?(str) then
    raise FileOutputExistsError.new(str) if
    usage_ui.ask_yes_no(OVERWRITE_QUERY % str, NO_RESPONSE) == NO_RESPONSE
    end
    @value = File.open(str, "w")
    end

    def close
    @value.close
    end
    end

    # lastly attach that parser to the character sequence '>?'
    UsageMod::Base.add_type_handler(">?", FileOutputQueryPlugin)


    7. Choice options

    You can have optional options that have a set of values which they can
    be. The choices
    are separated by pipe symbols. See below:

    usage = Usage.new "[-a coffee|tea|milk]"

    After this #dash_a will give the string coffee, tea, or milk. If the
    value given isn't
    one of the given choices, then the user is given an error message with
    the
    appropriate choices.

    8. Usage blocks

    Starting with version 0.3, you can run usage within a block. This
    allows any objects that need
    clean-up when the block exits (such as open files). To do this it looks
    like the following:

    Usage.new "<infile >outfile" do |usage|
    usage.outfile.write(usage.infile.read)
    end # here the file objects are closed

    If you do it without a block, then you would have to do the following:

    usage = Usage.new "<infile >outfile"
    usage.outfile.write(usage.infile.read)
    usage.infile.close
    usage.outfile.close
    Phrogz, Aug 15, 2006
    #6
  7. Hi phrogz, justin, trans and matthew, thank you for all the pointers,
    they look like they'll be very useful.
    Benjohn Barnes, Aug 15, 2006
    #7
    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. Richard
    Replies:
    13
    Views:
    860
    Richard
    May 21, 2006
  2. Jan Danielsson
    Replies:
    8
    Views:
    613
    Mike Meyer
    Jul 22, 2005
  3. Jp Calderone
    Replies:
    0
    Views:
    445
    Jp Calderone
    Jul 21, 2005
  4. davidj411
    Replies:
    0
    Views:
    483
    davidj411
    Jun 27, 2008
  5. Replies:
    13
    Views:
    516
    Anno Siegel
    Sep 10, 2007
Loading...

Share This Page