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

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

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

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

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

  1. 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)

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

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