When Optik's built-in actions and types aren't quite enough for your needs, you have two choices: extend Optik or define a callback option. Extending Optik is more general, but overkill for a lot of simple cases. Quite often a simple callback is all you need.
You define a callback in two steps:
As always, the easiest way to define a callback option is by using the add_option() method of your OptionParser object. The only option attribute you must specify is callback, the function to call:
parser.add_option("-c", action="callback", callback=my_callback)
Note that you supply a function object here -- so you must have already defined a function 'my_callback()' when you define the callback option. In this simple case, Optik knows nothing about the arguments the "-c" option expects to take. Usually, this means that the option doesn't take any arguments -- the mere presence of "-c" on the command-line is all it needs to know. In some circumstances, though, you might want your callback to consume an arbitrary number of command-line arguments. This is where writing callbacks gets tricky; it's covered later in this document.
Optik always passes four particular arguments to your callback, and it will only pass additional arguments if you specify them via callback_args and callback_kwargs. Thus, the minimal callback function signature is:
def my_callback(option, opt, value, parser):
The four arguments to a callback are described below.
There are several other option attributes that you can supply when you define an option attribute:
All callbacks are called as follows:
func(option : Option, opt : string, value : any, parser : OptionParser, *args, **kwargs)
where
is the OptionParser instance driving the whole thing, mainly useful because you can access some other interesting data through it, as instance attributes:
The callback function should raise OptionValueError if there are any problems with the option or its argument(s). Optik catches this and terminates the program, printing the error message you supply to stderr. Your message should be clear, concise, accurate, and mention the option at fault. Otherwise, the user will have a hard time figuring out what he did wrong.
Here's an example of a callback option that takes no arguments, and simply records that the option was seen:
def record_foo_seen(option, opt, value, parser): parser.saw_foo = True parser.add_option("--foo", action="callback", callback=record_foo_seen)
Of course, you could do that with the store_true action. Here's a slightly more interesting example: record the fact that "-a" is seen, but blow up if it comes after "-b" in the command-line.
def check_order(option, opt, value, parser): if parser.values.b: raise OptionValueError("can't use -a after -b") parser.values.a = 1 [...] parser.add_option("-a", action="callback", callback=check_order) parser.add_option("-b", action="store_true", dest="b")
If you want to re-use this callback for several similar options (set a flag, but blow up if "-b" has already been seen), it needs a bit of work: the error message and the flag that it sets must be generalized.
def check_order(option, opt, value, parser): if parser.values.b: raise OptionValueError("can't use %s after -b" % opt) setattr(parser.values, option.dest, 1) [...] parser.add_option("-a", action="callback", callback=check_order, dest='a') parser.add_option("-b", action="store_true", dest="b") parser.add_option("-c", action="callback", callback=check_order, dest='c')
Of course, you could put any condition in there -- you're not limited to checking the values of already-defined options. For example, if you have options that should not be called when the moon is full, all you have to do is this:
def check_moon(option, opt, value, parser): if is_moon_full(): raise OptionValueError("%s option invalid when moon is full" % opt) setattr(parser.values, option.dest, 1) [...] parser.add_option("--foo", action="callback", callback=check_moon, dest="foo")
(The definition of is_moon_full() is left as an exercise for the reader.)
Things get slightly more interesting when you define callback options that take a fixed number of arguments. Specifying that a callback option takes arguments is similar to defining a store or append option: if you define type, then the option takes one argument that must be convertible to that type; if you further define nargs, then the option takes nargs arguments.
Here's an example that just emulates the standard store action:
def store_value(option, opt, value, parser): setattr(parser.values, option.dest, value) [...] parser.add_option("--foo", action="callback", callback=store_value, type="int", nargs=3, dest="foo")
Note that Optik takes care of consuming 3 arguments and converting them to integers for you; all you have to do is store them. (Or whatever: obviously you don't need a callback for this example. Use your imagination!)
Things get hairy when you want an option to take a variable number of arguments. For this case, you must write a callback, as Optik doesn't provide any built-in capabilities for it. And you have to deal with certain intricacies of conventional Unix command-line parsing that Optik normally handles for you. In particular, callbacks have to worry about bare "--" and "-" arguments; the convention is:
If you want an option that takes a variable number of arguments, there are several subtle, tricky issues to worry about. The exact implementation you choose will be based on which trade-offs you're willing to make for your application (which is why Optik doesn't support this sort of thing directly).
Nevertheless, here's a stab at a callback for an option with variable arguments:
def vararg_callback(option, opt, value, parser): assert value is None done = 0 value = [] rargs = parser.rargs while rargs: arg = rargs[0] # Stop if we hit an arg like "--foo", "-a", "-fx", "--file=f", # etc. Note that this also stops on "-3" or "-3.0", so if # your option takes numeric values, you will need to handle # this. if ((arg[:2] == "--" and len(arg) > 2) or (arg[:1] == "-" and len(arg) > 1 and arg[1] != "-")): break else: value.append(arg) del rargs[0] setattr(parser.values, option.dest, value) [...] parser.add_option("-c", "--callback", action="callback", callback=varargs)
The main weakness with this particular implementation is that negative numbers in the arguments following "-c" will be interpreted as further options, rather than as arguments to "-c". Fixing this is left as an exercise for the reader.