Optik: Callback Options ----------------------- If Optik's built-in actions and types just don't fit the bill for you, but it's not worth extending Optik to define your own actions or types, you'll probably need to define a callback option. Defining callback options is quite easy; the tricky part is writing a good callback (the function that is called when Optik encounters the option on the command line). Defining a callback option -------------------------- As always, you can define a callback option either by directly instantiating the Option class, or 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", 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. There are several other option attributes that you can supply when you define an option attribute: type has its usual meaning: as with the "store" or "append" actions, it instructs Optik to consume one argument that must be convertible to 'type'. Rather than storing the value(s) anywhere, though, Optik converts it to 'type' and passes it to your callback function. nargs also has its usual meaning: if it is supplied and > 1, Optik will consume 'nargs' arguments, each of which must be convertible to 'type'. It then passes a tuple of converted values to your callback. callback_args a tuple of extra positional arguments to pass to the callback callback_kwargs a dictionary of extra keyword arguments to pass to the callback How callbacks are called ------------------------ All callbacks are called as follows:: func(option : Option, opt : string, value : any, parser : OptionParser, *args, **kwargs) where option is the Option instance that's calling the callback opt is the option string seen on the command-line that's triggering the callback. (If an abbreviated long option was used, 'opt' will be the full, canonical option string -- e.g. if the user puts "--foo" on the command-line as an abbreviation for "--foobar", then 'opt' will be "--foobar".) value is the argument to this option seen on the command-line. Optik will only expect an argument if 'type' is set; the type of 'value' will be the type implied by the option's type (see "Option types" below). If 'type' for this option is None (no argument expected), then 'value' will be None. If nargs > 1, 'value' will be a tuple of values of the appropriate type. parser is the OptionParser instance driving the whole thing, mainly useful because you can access some other interesting data through it, as instance attributes: parser.rargs the current remaining argument list, ie. with 'opt' (and 'value', if any) removed, and only the arguments following them still there. Feel free to modify parser.rargs, e.g. by consuming more arguments. parser.largs the current set of leftover arguments, ie. arguments that have been processed but have not been consumed as options (or arguments to options). Feel free to modify parser.largs, e.g. by adding more arguments to it. parser.values the object where option values are by default stored. This is useful because it lets callbacks use the same mechanism as the rest of Optik for storing option values; you don't need to mess around with globals or closures. You can also access the value(s) of any options already encountered on the command-line. args is a tuple of arbitrary positional arguments supplied via the callback_args option attribute. kwargs is a dictionary of arbitrary keyword arguments supplied via callback_kwargs. Since 'args' and 'kwargs' are optional (they are only passed if you supply 'callback_args' and/or 'callback_kwargs' when you define your callback option), the minimal callback function is:: def my_callback (option, opt, value, parser): pass Error handling -------------- 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. Examples part 1: no arguments ----------------------------- 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 = 1 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_full_moon(): raise OptionValueError("%s option invalid when moon full" % opt) setattr(parser.values, option.dest, 1) [...] parser.add_option("--foo", action="callback", callback=check_moon, dest="foo") (The definition of is_full_moon() is left as an exercise for the reader.) Examples part 2: fixed arguments -------------------------------- 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 that many 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!) Examples part 3: variable arguments ----------------------------------- Things get hairy when you want an option to take a variable number of arguments. For this case, you have to write a callback; Optik doesn't provide any built-in capabilities for it. And starting with Optik 1.2, you have to deal with the full-blown syntax for conventional Unix command-line parsing. (Previously, Optik took care of this for you, but I got it wrong. It was fixed in Optik 1.2, at the cost of making this kind of callback more complex.) In particular, callbacks have to worry about bare "--" and "-" arguments; the convention is: * bare "--", if not the argument to some option, causes command-line processing to halt and the "--" itself is lost * bare "-" similarly causes command-line processing to halt, but the "-" itself is kept * either "--" or "-" can be option arguments (this was broken in Optik 1.1 and earlier, and fixed in Optik 1.2) 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 varargs (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.