Optik Tutorial
==============

While Optik is quite flexible and powerful, it's also straightforward to
use in most cases.  This document covers the code patterns that are
common to any Optik-based program.

First, you need to import the OptionParser class; then, early in the
main program, create an OptionParser instance::

  from optik import OptionParser
  [...]
  parser = OptionParser()

Then you can start defining options.  The basic syntax is::

  parser.add_option(opt_str, ...,
                    attr=value, ...)

Each option has one or more option strings, such as ``"-f"`` or
``"--file"``, and several option attributes that tell Optik what to
expect and what to do when it encounters that option on the command
line.

Typically, each option will have one short option string and one long
option string, e.g.::

  parser.add_option("-f", "--file", ...)

You're free to define as many short option strings and as many long
option strings as you like (including zero), as long as there is at
least one option string overall.

The option strings passed to ``add_option()`` are effectively labels for
the option defined by that call.  For brevity, we will frequently refer
to *encountering an option* on the command line; in reality, Optik
encounters *option strings* and looks up options from them.

Once all of your options are defined, instruct Optik to parse your
program's command line::

  (options, args) = parser.parse_args()

(If you like, you can pass a custom argument list to ``parse_args()``,
but that's rarely necessary: by default it uses ``sys.argv[1:]``.)

``parse_args()`` returns two values:

* ``options``, an object containing values for all of your options
  -- e.g. if ``"--file"`` takes a single string argument, then
  ``options.file`` will be the filename supplied by the user, or
  ``None`` if the user did not supply that option

* ``args``, the list of positional arguments leftover after parsing
  options

This tutorial document only covers the four most important option
attributes: ``action``, ``type``, ``dest`` (destination), and ``help``.
Of these, ``action`` is the most fundamental.


Understanding option actions
----------------------------

Actions tell Optik what to do when it encounters an option on the
command line.  There is a fixed set of actions hard-coded into Optik;
adding new actions is an advanced topic covered in `Extending Optik`_.
Most actions tell Optik to store a value in some variable -- for
example, take a string from the command line and store it in an
attribute of ``options``.

If you don't specify an option action, Optik defaults to ``store``.

.. _Extending Optik: extending.html

The ``store`` action
~~~~~~~~~~~~~~~~~~~~

The most common option action is ``store``, which tells Optik to take
the next argument (or the remainder of the current argument), ensure
that it is of the correct type, and store it to your chosen destination.

For example::

  parser.add_option("-f", "--file",
                    action="store", type="string", dest="filename")

Now let's make up a fake command line and ask Optik to parse it::

  args = ["-f", "foo.txt"]
  (options, args) = parser.parse_args(args)

When Optik sees the option string ``"-f"``, it consumes the next
argument, ``"foo.txt"``, and stores it in ``options.filename``.  So,
after this call to ``parse_args()``, ``options.filename`` is
``"foo.txt"``.

Some other option types supported by Optik are ``int`` and ``float``.
Here's an option that expects an integer argument::

  parser.add_option("-n", type="int", dest="num")

Note that this option has no long option string, which is perfectly
acceptable.  Also, there's no explicit action, since the default is
``store``.
  
Let's parse another fake command-line.  This time, we'll jam the option
argument right up against the option: since ``"-n42"`` (one argument) is
equivalent to ``"-n 42"`` (two arguments), the code ::

  (options, args) = parser.parse_args(["-n42"])
  print options.num

will print ``"42"``.

If you don't specify a type, Optik assumes ``string``.  Combined with the
fact that the default action is ``store``, that means our first example
can be a lot shorter::

  parser.add_option("-f", "--file", dest="filename")

If you don't supply a destination, Optik figures out a sensible default
from the option strings: if the first long option string is
``"--foo-bar"``, then the default destination is ``foo_bar``.  If there
are no long option strings, Optik looks at the first short option
string: the default destination for ``"-f"`` is ``f``.

Optik also includes built-in ``long`` and ``complex`` types.  Adding
types is covered in `Extending Optik`_.

.. _Extending Optik: extending.html


Handling boolean (flag) options
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Flag options -- set a variable to true or false when a particular option
is seen -- are quite common.  Optik supports them with two separate
actions, ``store_true`` and ``store_false``.  For example, you might have a
``verbose`` flag that is turned on with ``"-v"`` and off with ``"-q"``::

  parser.add_option("-v", action="store_true", dest="verbose")
  parser.add_option("-q", action="store_false", dest="verbose")

Here we have two different options with the same destination, which is
perfectly OK.  (It just means you have to be a bit careful when setting
default values -- see below.)

When Optik encounters ``"-v"`` on the command line, it sets
``options.verbose`` to ``True``; when it encounters ``"-q"``,
``options.verbose`` is set to ``False``.


Other actions
~~~~~~~~~~~~~

Some other actions supported by Optik are:

``store_const``
    store a constant value
``append``
    append this option's argument to a list
``count``
    increment a counter by one
``callback``
    call a specified function

These are covered in `Reference Guide`_ and `Option Callbacks`_.

.. _Reference Guide: reference.html

.. _Option Callbacks: callbacks.html


.. _Default values: 

Default values
--------------

All of the above examples involve setting some variable (the
"destination") when certain command-line options are seen.  What happens
if those options are never seen?  Since we didn't supply any defaults,
they are all set to ``None``.  This is usually fine, but sometimes you
want more control.  Optik lets you supply a default value for each
destination, which is assigned before the command line is parsed.

First, consider the verbose/quiet example.  If we want Optik to set
``verbose`` to ``True`` unless ``"-q"`` is seen, then we can do this::

  parser.add_option("-v", action="store_true", dest="verbose", default=True)
  parser.add_option("-q", action="store_false", dest="verbose")

Since default values apply to the *destination* rather than to any
particular option, and these two options happen to have the same
destination, this is exactly equivalent::

  parser.add_option("-v", action="store_true", dest="verbose")
  parser.add_option("-q", action="store_false", dest="verbose", default=True)

Consider this::

  parser.add_option("-v", action="store_true", dest="verbose", default=False)
  parser.add_option("-q", action="store_false", dest="verbose", default=True)

Again, the default value for ``verbose`` will be ``True``: the last
default value supplied for any particular destination is the one that
counts.

A clearer way to specify default values is the ``set_defaults()``
method of OptionParser, which you can call at any time before calling
``parse_args()``::

  parser.set_defaults(verbose=True)
  parser.add_option(...)
  (options, args) = parser.parse_args()

As before, the last value specified for a given option destination is
the one that counts.  For clarity, try to use one method or the other of
setting default values, not both.


Generating help
---------------

Optik's ability to generate help and usage text automatically is useful
for creating user-friendly command-line interfaces.  All you have to do
is supply a ``help`` value for each option, and optionally a short usage
message for your whole program.  Here's an OptionParser populated with
user-friendly (documented) options::

  usage = "usage: %prog [options] arg1 arg2"
  parser = OptionParser(usage=usage)
  parser.add_option("-v", "--verbose",
                    action="store_true", dest="verbose", default=True,
                    help="make lots of noise [default]")
  parser.add_option("-q", "--quiet",
                    action="store_false", dest="verbose", 
                    help="be vewwy quiet (I'm hunting wabbits)")
  parser.add_option("-f", "--filename",
                    metavar="FILE", help="write output to FILE"),
  parser.add_option("-m", "--mode",
                    default="intermediate",
                    help="interaction mode: novice, intermediate, "
                         "or expert [default: %default]")

If Optik encounters either ``"-h"`` or ``"--help"`` on the command-line,
or if you just call ``parser.print_help()``, it prints the following to
standard output::

  usage: <yourscript> [options] arg1 arg2

  options:
    -h, --help            show this help message and exit
    -v, --verbose         make lots of noise [default]
    -q, --quiet           be vewwy quiet (I'm hunting wabbits)
    -f FILE, --filename=FILE
                          write output to FILE
    -m MODE, --mode=MODE  interaction mode: novice, intermediate, or
                          expert [default: intermediate]

(If the help output is triggered by a help option, Optik exits after
printing the help text.)

There's a lot going on here to help Optik generate the best possible
help message:

* the script defines its own usage message::

     usage = "usage: %prog [options] arg1 arg2"

  Optik expands ``"%prog"`` in the usage string to the name of the current
  program, i.e. ``os.path.basename(sys.argv[0])``.  The expanded string
  is then printed before the detailed option help.

  If you don't supply a usage string, Optik uses a bland but sensible
  default: "``usage: %prog [options]"``, which is fine if your script
  doesn't take any positional arguments.

* every option defines a help string, and doesn't worry about line-
  wrapping -- Optik takes care of wrapping lines and making the
  help output look good.

* options that take a value indicate this fact in their
  automatically-generated help message, e.g. for the "mode" option::

    -m MODE, --mode=MODE

  Here, "MODE" is called the meta-variable: it stands for the argument
  that the user is expected to supply to ``-m``/``--mode``.  By default,
  Optik converts the destination variable name to uppercase and uses
  that for the meta-variable.  Sometimes, that's not what you want --
  for example, the ``--filename`` option explicitly sets
  ``metavar="FILE"``, resulting in this automatically-generated option
  description::

    -f FILE, --filename=FILE

  This is important for more than just saving space, though: the
  manually written help text uses the meta-variable "FILE" to clue the
  user in that there's a connection between the semi-formal syntax "-f
  FILE" and the informal semantic description "write output to FILE".
  This is a simple but effective way to make your help text a lot
  clearer and more useful for end users.

* options that have a default value can include ``%default`` in
  the help string -- Optik will replace it with ``str()`` of the
  option's default value.  If an option has no default value (or the
  default value is ``None``), ``%default`` expands to ``none``.


Printing a version string
-------------------------

Similar to the brief usage string, Optik can also print a version string
for your program.  You have to supply the string as the ``version``
argument to OptionParser::

  parser = OptionParser(usage="%prog [-f] [-q]", version="%prog 1.0")

``"%prog"`` is expanded just like it is in ``usage``.  Apart
from that, ``version`` can contain anything you like.  When you supply
it, Optik automatically adds a ``"--version"`` option to your parser.
If it encounters this option on the command line, it expands your
``version`` string (by replacing ``"%prog"``), prints it to stdout, and
exits.

For example, if your script is called ``/usr/bin/foo``::

  $ /usr/bin/foo --version
  foo 1.0


How Optik handles errors
------------------------

There are two broad classes of errors that Optik has to worry about:
programmer errors and user errors.  Programmer errors are usually
erroneous calls to ``parser.add_option()``, e.g. invalid option strings,
unknown option attributes, missing option attributes, etc.  These are
dealt with in the usual way: raise an exception (either
``optik.OptionError`` or ``TypeError``) and let the program crash.

Handling user errors is much more important, since they are guaranteed
to happen no matter how stable your code is.  Optik can automatically
detect some user errors, such as bad option arguments (passing ``"-n
4x"`` where ``-n`` takes an integer argument), missing arguments
(``"-n"`` at the end of the command line, where ``-n`` takes an argument
of any type).  Also, you can call ``parser.error()`` to signal an
application-defined error condition::

  (options, args) = parser.parse_args()
  [...]
  if options.a and options.b:
      parser.error("options -a and -b are mutually exclusive")

In either case, Optik handles the error the same way: it prints the
program's usage message and an error message to standard error and
exits with error status 2.

Consider the first example above, where the user passes ``"4x"`` to an
option that takes an integer::

  $ /usr/bin/foo -n 4x
  usage: foo [options]

  foo: error: option -n: invalid integer value: '4x'

Or, where the user fails to pass a value at all::

  $ /usr/bin/foo -n
  usage: foo [options]

  foo: error: -n option requires an argument

Optik-generated error messages take care always to mention the option
involved in the error; be sure to do the same when calling
``parser.error()`` from your application code.

If Optik's default error-handling behaviour does not suite your needs,
you'll need to subclass OptionParser and override ``exit()`` and/or
``error()``.


Putting it all together
-----------------------

Here's what Optik-based scripts usually look like::

  from optik import OptionParser
  [...]
  def main():
      usage = "usage: %prog [options] arg"
      parser = OptionParser(usage)
      parser.add_option("-f", "--file", dest="filename",
                        help="read data from FILENAME")
      parser.add_option("-v", "--verbose",
                        action="store_true", dest="verbose")
      parser.add_option("-q", "--quiet",
                        action="store_false", dest="verbose")
      [...]
      (options, args) = parser.parse_args()
      if len(args) != 1:
          parser.error("incorrect number of arguments")
      if options.verbose:
          print "reading %s..." % options.filename
      [...]

  if __name__ == "__main__":
      main()

.. $Id: tutorial.txt 515 2006-06-10 15:37:45Z gward $
