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.
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 is a bit trickier, because you have to understand that Optik has a couple of classifications for actions:
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):
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.