From c4e4e56c1a12114c4f108ce779953022c61d9590 Mon Sep 17 00:00:00 2001 From: Greg Ward Date: Sat, 19 Mar 2005 17:15:03 +0000 Subject: SF #1086675: restore "Extending optparse" section (which was dropped in Python 2.4). --- Doc/lib/liboptparse.tex | 244 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 244 insertions(+) diff --git a/Doc/lib/liboptparse.tex b/Doc/lib/liboptparse.tex index 2feae5d..5e7841d 100644 --- a/Doc/lib/liboptparse.tex +++ b/Doc/lib/liboptparse.tex @@ -1648,3 +1648,247 @@ further options (probably causing an error), rather than as arguments to \code{"-c"}. Fixing this is left as an exercise for the reader. % $Id: callbacks.txt 415 2004-09-30 02:26:17Z greg $ + +\subsection{Extending \module{optparse}\label{optparse-extending-optparse}} + +Since the two major controlling factors in how \module{optparse} 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. + +Also, the \code{examples/} directory of the source distribution includes +several demonstrations of extending \module{optparse} in different ways: e.g. a +case-insensitive option parser, or two kinds of option parsers that +implement ``required options''. + + +\subsubsection{Adding new types\label{optparse-adding-new-types}} + +To add new types, you need to define your own subclass of \module{optparse}'s Option +class. This class has a couple of attributes that define \module{optparse}'s types: +\member{TYPES} and \member{TYPE{\_}CHECKER}. + +\member{TYPES} is a tuple of type names; in your subclass, simply define a new +tuple \member{TYPES} that builds on the standard one. + +\member{TYPE{\_}CHECKER} is a dictionary mapping type names to type-checking +functions. A type-checking function has the following signature: +\begin{verbatim} +def check_mytype(option, opt, value) +\end{verbatim} + +where \code{option} is an \class{Option} instance, \code{opt} is an option string +(e.g., \code{"-f"}), and \code{value} is the string from the command line that +must be checked and converted to your desired type. \code{check{\_}mytype()} +should return an object of the hypothetical type \code{mytype}. The value +returned by a type-checking function will wind up in the OptionValues +instance returned by \method{OptionParser.parse{\_}args()}, or be passed to a +callback as the \code{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 \method{error()} method, +which in turn prepends the program name and the string \code{"error:"} and +prints everything to stderr before terminating the process. + +Here's a silly example that demonstrates adding a \code{complex} option +type to parse Python-style complex numbers on the command line. (This +is even sillier than it used to be, because \module{optparse} 1.3 added built-in +support for complex numbers, but never mind.) + +First, the necessary imports: +\begin{verbatim} +from copy import copy +from optparse import Option, OptionValueError +\end{verbatim} + +You need to define your type-checker first, since it's referred to later +(in the \member{TYPE{\_}CHECKER} class attribute of your Option subclass): +\begin{verbatim} +def check_complex(option, opt, value): + try: + return complex(value) + except ValueError: + raise OptionValueError( + "option %s: invalid complex value: %r" % (opt, value)) +\end{verbatim} + +Finally, the Option subclass: +\begin{verbatim} +class MyOption (Option): + TYPES = Option.TYPES + ("complex",) + TYPE_CHECKER = copy(Option.TYPE_CHECKER) + TYPE_CHECKER["complex"] = check_complex +\end{verbatim} + +(If we didn't make a \function{copy()} of \member{Option.TYPE{\_}CHECKER}, we would end +up modifying the \member{TYPE{\_}CHECKER} attribute of \module{optparse}'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 \module{optparse}-based script, except you have to instruct your +OptionParser to use MyOption instead of Option: +\begin{verbatim} +parser = OptionParser(option_class=MyOption) +parser.add_option("-c", type="complex") +\end{verbatim} + +Alternately, you can build your own option list and pass it to +OptionParser; if you don't use \method{add{\_}option()} in the above way, you +don't need to tell OptionParser which option class to use: +\begin{verbatim} +option_list = [MyOption("-c", action="store", type="complex", dest="c")] +parser = OptionParser(option_list=option_list) +\end{verbatim} + + +\subsubsection{Adding new actions\label{optparse-adding-new-actions}} + +Adding new actions is a bit trickier, because you have to understand +that \module{optparse} has a couple of classifications for actions: +\begin{description} +\item[``store'' actions] +actions that result in \module{optparse} storing a value to an attribute of the +current OptionValues instance; these options require a \member{dest} +attribute to be supplied to the Option constructor +\item[``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 \member{type} attribute to the +Option constructor. +\end{description} + +These are overlapping sets: some default ``store'' actions are \code{store}, +\code{store{\_}const}, \code{append}, and \code{count}, while the default ``typed'' +actions are \code{store}, \code{append}, and \code{callback}. + +When you add an action, you need to decide if it's a ``store'' action, a +``typed'' action, neither, or both. Three class attributes of +Option (or your Option subclass) control this: +\begin{description} +\item[\member{ACTIONS}] +all actions must be listed in ACTIONS +\item[\member{STORE{\_}ACTIONS}] +``store'' actions are additionally listed here +\item[\member{TYPED{\_}ACTIONS}] +``typed'' actions are additionally listed here +\end{description} + +In order to actually implement your new action, you must override +Option's \method{take{\_}action()} method and add a case that recognizes your +action. + +For example, let's add an \code{extend} action. This is similar to the +standard \code{append} action, but instead of taking a single value from +the command-line and appending it to an existing list, \code{extend} will +take multiple values in a single comma-delimited string, and extend an +existing list with them. That is, if \code{"-{}-names"} is an \code{extend} +option of type \code{string}, the command line +\begin{verbatim} +--names=foo,bar --names blah --names ding,dong +\end{verbatim} + +would result in a list +\begin{verbatim} +["foo", "bar", "blah", "ding", "dong"] +\end{verbatim} + +Again we define a subclass of Option: +\begin{verbatim} +class MyOption (Option): + + ACTIONS = Option.ACTIONS + ("extend",) + STORE_ACTIONS = Option.STORE_ACTIONS + ("extend",) + TYPED_ACTIONS = Option.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) +\end{verbatim} + +Features of note: +\begin{itemize} +\item {} +\code{extend} both expects a value on the command-line and stores that +value somewhere, so it goes in both \member{STORE{\_}ACTIONS} and +\member{TYPED{\_}ACTIONS} + +\item {} +\method{MyOption.take{\_}action()} implements just this one new action, and +passes control back to \method{Option.take{\_}action()} for the standard +\module{optparse} actions + +\item {} +\code{values} is an instance of the optparse{\_}parser.Values class, +which provides the very useful \method{ensure{\_}value()} method. +\method{ensure{\_}value()} is essentially \function{getattr()} with a safety valve; +it is called as +\begin{verbatim} +values.ensure_value(attr, value) +\end{verbatim} + +If the \code{attr} attribute of \code{values} doesn't exist or is None, then +ensure{\_}value() first sets it to \code{value}, and then returns 'value. +This is very handy for actions like \code{extend}, \code{append}, and +\code{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 \method{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 \method{ensure{\_}value()} will take care of getting it right when +it's needed. + +\end{itemize} + + +\subsubsection{Other reasons to extend \module{optparse}\label{optparse-other-reasons-to-extend-optparse}} + +Adding new types and new actions are the big, obvious reasons why you +might want to extend \module{optparse}. I can think of at least two other areas to +play with. + +First, the simple one: OptionParser tries to be helpful by calling +\function{sys.exit()} when appropriate, i.e. when there's an error on the +command line or when the user requests help. In the former case, the +traditional course of letting the script crash with a traceback is +unacceptable; it will make users think there's a bug in your script when +they make a command-line error. In the latter case, there's generally +not much point in carrying on after printing a help message. + +If this behaviour bothers you, it shouldn't be too hard to ``fix'' it. +You'll have to +\newcounter{listcnt1} +\begin{list}{\arabic{listcnt1})} +{ +\usecounter{listcnt1} +\setlength{\rightmargin}{\leftmargin} +} +\item {} +subclass OptionParser and override \method{error()} + +\item {} +subclass Option and override \method{take{\_}action()}{---}you'll +need to provide your own handling of the \member{help} action that +doesn't call \function{sys.exit()} + +\end{list} + +The second, much more complex, possibility is to override the +command-line syntax implemented by \module{optparse}. In this case, you'd leave the +whole machinery of option actions and types alone, but rewrite the code +that processes \var{sys.argv}. You'll need to subclass OptionParser in any +case; depending on how radical a rewrite you want, you'll probably need +to override one or all of \method{parse{\_}args()}, \method{{\_}process{\_}long{\_}opt()}, and +\method{{\_}process{\_}short{\_}opts()}. + +Both of these are left as an exercise for the reader. I have not tried +to implement either myself, since I'm quite happy with \module{optparse}'s default +behaviour (naturally). + +Happy hacking, and don't forget: Use the Source, Luke. +% $Id: extending.txt 413 2004-09-28 00:59:13Z greg $ + -- cgit v0.12