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