Extending Optik
===============

Since the two major controlling factors in how Optik interprets
command-line options are the action and type of each option, the most
likely direction of extension is to add new actions and new types.


Adding new types
----------------

To add new types, you need to define your own subclass of Optik's Option
class.  This class has a couple of attributes that define Optik's types:
``TYPES`` and ``TYPE_CHECKER``.

``TYPES`` is a tuple of type names; in your subclass, simply define a new
tuple ``TYPES`` that builds on the standard one.

``TYPE_CHECKER`` is a dictionary mapping type names to type-checking
functions.  A type-checking function has the following signature::

  def check_mytype(option, opt, value)

where ``option`` is an ``Option`` instance, ``opt`` is an option string
(e.g., ``"-f"``), and ``value`` is the string from the command line that
must be checked and converted to your desired type.  ``check_mytype()``
should return an object of the hypothetical type ``mytype``.  The value
returned by a type-checking function will wind up in the OptionValues
instance returned by ``OptionParser.parse_args()``, or be passed to a
callback as the ``value`` parameter.

Your type-checking function should raise OptionValueError if it
encounters any problems.  OptionValueError takes a single string
argument, which is passed as-is to OptionParser's ``error()`` method,
which in turn prepends the program name and the string ``"error:"`` and
prints everything to stderr before terminating the process.

Here's a silly example that demonstrates adding a ``complex`` option
type to parse Python-style complex numbers on the command line.  (This
is even sillier than it used to be, because Optik 1.3 added built-in
support for complex numbers, but never mind.)

First, the necessary imports::

  from copy import copy
  from optik import Option, OptionValueError

You need to define your type-checker first, since it's referred to later
(in the ``TYPE_CHECKER`` class attribute of your Option subclass)::

  def check_complex(option, opt, value):
      try:
          return complex(value)
      except ValueError:
          raise OptionValueError(
              "option %s: invalid complex value: %r" % (opt, value))

Finally, the Option subclass::

  class MyOption (Option):
      TYPES = Option.TYPES + ("complex",)
      TYPE_CHECKER = copy(Option.TYPE_CHECKER)
      TYPE_CHECKER["complex"] = check_complex

(If we didn't make a ``copy()`` of ``Option.TYPE_CHECKER``, we would end
up modifying the ``TYPE_CHECKER`` attribute of Optik's Option class.
This being Python, nothing stops you from doing that except good manners
and common sense.)

That's it!  Now you can write a script that uses the new option type
just like any other Optik-based script, except you have to instruct your
OptionParser to use MyOption instead of Option::

  parser = OptionParser(option_class=MyOption)
  parser.add_option("-c", type="complex")

Alternately, you can build your own option list and pass it to
OptionParser; if you don't use ``add_option()`` in the above way, you
don't need to tell OptionParser which option class to use::

  option_list = [MyOption("-c", action="store", type="complex", dest="c")]
  parser = OptionParser(option_list=option_list)


Adding new actions
------------------

Adding new actions is a bit trickier, because you have to understand
that Optik has a couple of classifications for actions:

"store" actions
    actions that result in Optik storing a value to an attribute of the
    current OptionValues instance; these options require a ``dest``
    attribute to be supplied to the Option constructor
"typed" actions
    actions that take a value from the command line and expect it to be
    of a certain type; or rather, a string that can be converted to a
    certain type.  These options require a ``type`` attribute to the
    Option constructor.

These are overlapping sets: some default "store" actions are ``store``,
``store_const``, ``append``, and ``count``, while the default "typed"
actions are ``store``, ``append``, and ``callback``.

When you add an action, you need to categorize it by listing it in at
least one of the following class attributes of Option (all are lists of
strings):

``ACTIONS``
    all actions must be listed in ACTIONS
``STORE_ACTIONS``
    "store" actions are additionally listed here
``TYPED_ACTIONS``
    "typed" actions are additionally listed here
``ALWAYS_TYPED_ACTIONS``
    actions that always take a type (i.e. whose options always take a
    value) are additionally listed here.  The only effect of this is
    that Optik assigns the default type, ``string``, to options with no
    explicit type whose action is listed in ``ALWAYS_TYPED_ACTIONS``.

In order to actually implement your new action, you must override
Option's ``take_action()`` method and add a case that recognizes your
action.

For example, let's add an ``extend`` action.  This is similar to the
standard ``append`` action, but instead of taking a single value from
the command-line and appending it to an existing list, ``extend`` will
take multiple values in a single comma-delimited string, and extend an
existing list with them.  That is, if ``"--names"`` is an ``extend``
option of type ``string``, the command line ::

  --names=foo,bar --names blah --names ding,dong

would result in a list ::

  ["foo", "bar", "blah", "ding", "dong"]

Again we define a subclass of Option::

  class MyOption (Option):

      ACTIONS = Option.ACTIONS + ("extend",)
      STORE_ACTIONS = Option.STORE_ACTIONS + ("extend",)
      TYPED_ACTIONS = Option.TYPED_ACTIONS + ("extend",)
      ALWAYS_TYPED_ACTIONS = Option.ALWAYS_TYPED_ACTIONS + ("extend",)

      def take_action(self, action, dest, opt, value, values, parser):
          if action == "extend":
              lvalue = value.split(",")
              values.ensure_value(dest, []).extend(lvalue)
          else:
              Option.take_action(
                  self, action, dest, opt, value, values, parser)

Features of note:

* ``extend`` both expects a value on the command-line and stores that
  value somewhere, so it goes in both ``STORE_ACTIONS`` and
  ``TYPED_ACTIONS``

* to ensure that Optik assigns the default type of ``string`` to
  ``extend`` actions, we put the ``extend`` action in
  ``ALWAYS_TYPED_ACTIONS`` as well

* ``MyOption.take_action()`` implements just this one new action, and
  passes control back to ``Option.take_action()`` for the standard
  Optik actions

* ``values`` is an instance of the optik.option_parser.Values class,
  which provides the very useful ``ensure_value()`` method.
  ``ensure_value()`` is essentially ``getattr()`` with a safety valve;
  it is called as ::

    values.ensure_value(attr, value)

  If the ``attr`` attribute of ``values`` doesn't exist or is None, then
  ensure_value() first sets it to ``value``, and then returns 'value.
  This is very handy for actions like ``extend``, ``append``, and
  ``count``, all of which accumulate data in a variable and expect that
  variable to be of a certain type (a list for the first two, an integer
  for the latter).  Using ``ensure_value()`` means that scripts using
  your action don't have to worry about setting a default value for the
  option destinations in question; they can just leave the default as
  None and ``ensure_value()`` will take care of getting it right when
  it's needed.


.. $Id: extending.txt 517 2006-06-10 16:18:11Z gward $
