123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997 |
- # -*- test-case-name: twisted.test.test_usage -*-
- # Copyright (c) Twisted Matrix Laboratories.
- # See LICENSE for details.
- """
- twisted.python.usage is a module for parsing/handling the
- command line of your program.
- For information on how to use it, see
- U{http://twistedmatrix.com/projects/core/documentation/howto/options.html},
- or doc/core/howto/options.xhtml in your Twisted directory.
- """
- from __future__ import print_function
- from __future__ import division, absolute_import
- # System Imports
- import inspect
- import os
- import sys
- import getopt
- from os import path
- import textwrap
- # Sibling Imports
- from twisted.python import reflect, util
- from twisted.python.compat import _PY3
- class UsageError(Exception):
- pass
- error = UsageError
- class CoerceParameter(object):
- """
- Utility class that can corce a parameter before storing it.
- """
- def __init__(self, options, coerce):
- """
- @param options: parent Options object
- @param coerce: callable used to coerce the value.
- """
- self.options = options
- self.coerce = coerce
- self.doc = getattr(self.coerce, 'coerceDoc', '')
- def dispatch(self, parameterName, value):
- """
- When called in dispatch, do the coerce for C{value} and save the
- returned value.
- """
- if value is None:
- raise UsageError("Parameter '%s' requires an argument."
- % (parameterName,))
- try:
- value = self.coerce(value)
- except ValueError as e:
- raise UsageError("Parameter type enforcement failed: %s" % (e,))
- self.options.opts[parameterName] = value
- class Options(dict):
- """
- An option list parser class
- C{optFlags} and C{optParameters} are lists of available parameters
- which your program can handle. The difference between the two
- is the 'flags' have an on(1) or off(0) state (off by default)
- whereas 'parameters' have an assigned value, with an optional
- default. (Compare '--verbose' and '--verbosity=2')
- optFlags is assigned a list of lists. Each list represents
- a flag parameter, as so::
- optFlags = [['verbose', 'v', 'Makes it tell you what it doing.'],
- ['quiet', 'q', 'Be vewy vewy quiet.']]
- As you can see, the first item is the long option name
- (prefixed with '--' on the command line), followed by the
- short option name (prefixed with '-'), and the description.
- The description is used for the built-in handling of the
- --help switch, which prints a usage summary.
- C{optParameters} is much the same, except the list also contains
- a default value::
- optParameters = [['outfile', 'O', 'outfile.log', 'Description...']]
- A coerce function can also be specified as the last element: it will be
- called with the argument and should return the value that will be stored
- for the option. This function can have a C{coerceDoc} attribute which
- will be appended to the documentation of the option.
- subCommands is a list of 4-tuples of (command name, command shortcut,
- parser class, documentation). If the first non-option argument found is
- one of the given command names, an instance of the given parser class is
- instantiated and given the remainder of the arguments to parse and
- self.opts[command] is set to the command name. For example::
- subCommands = [
- ['inquisition', 'inquest', InquisitionOptions,
- 'Perform an inquisition'],
- ['holyquest', 'quest', HolyQuestOptions,
- 'Embark upon a holy quest']
- ]
- In this case, C{"<program> holyquest --horseback --for-grail"} will cause
- C{HolyQuestOptions} to be instantiated and asked to parse
- C{['--horseback', '--for-grail']}. Currently, only the first sub-command
- is parsed, and all options following it are passed to its parser. If a
- subcommand is found, the subCommand attribute is set to its name and the
- subOptions attribute is set to the Option instance that parses the
- remaining options. If a subcommand is not given to parseOptions,
- the subCommand attribute will be None. You can also mark one of
- the subCommands to be the default::
- defaultSubCommand = 'holyquest'
- In this case, the subCommand attribute will never be None, and
- the subOptions attribute will always be set.
- If you want to handle your own options, define a method named
- C{opt_paramname} that takes C{(self, option)} as arguments. C{option}
- will be whatever immediately follows the parameter on the
- command line. Options fully supports the mapping interface, so you
- can do things like C{'self["option"] = val'} in these methods.
- Shell tab-completion is supported by this class, for zsh only at present.
- Zsh ships with a stub file ("completion function") which, for Twisted
- commands, performs tab-completion on-the-fly using the support provided
- by this class. The stub file lives in our tree at
- C{twisted/python/twisted-completion.zsh}, and in the Zsh tree at
- C{Completion/Unix/Command/_twisted}.
- Tab-completion is based upon the contents of the optFlags and optParameters
- lists. And, optionally, additional metadata may be provided by assigning a
- special attribute, C{compData}, which should be an instance of
- C{Completions}. See that class for details of what can and should be
- included - and see the howto for additional help using these features -
- including how third-parties may take advantage of tab-completion for their
- own commands.
- Advanced functionality is covered in the howto documentation,
- available at
- U{http://twistedmatrix.com/projects/core/documentation/howto/options.html},
- or doc/core/howto/options.xhtml in your Twisted directory.
- """
- subCommand = None
- defaultSubCommand = None
- parent = None
- completionData = None
- _shellCompFile = sys.stdout # file to use if shell completion is requested
- def __init__(self):
- super(Options, self).__init__()
- self.opts = self
- self.defaults = {}
- # These are strings/lists we will pass to getopt
- self.longOpt = []
- self.shortOpt = ''
- self.docs = {}
- self.synonyms = {}
- self._dispatch = {}
- collectors = [
- self._gather_flags,
- self._gather_parameters,
- self._gather_handlers,
- ]
- for c in collectors:
- (longOpt, shortOpt, docs, settings, synonyms, dispatch) = c()
- self.longOpt.extend(longOpt)
- self.shortOpt = self.shortOpt + shortOpt
- self.docs.update(docs)
- self.opts.update(settings)
- self.defaults.update(settings)
- self.synonyms.update(synonyms)
- self._dispatch.update(dispatch)
- __hash__ = object.__hash__
- def opt_help(self):
- """
- Display this help and exit.
- """
- print(self.__str__())
- sys.exit(0)
- def opt_version(self):
- """
- Display Twisted version and exit.
- """
- from twisted import copyright
- print("Twisted version:", copyright.version)
- sys.exit(0)
- #opt_h = opt_help # this conflicted with existing 'host' options.
- def parseOptions(self, options=None):
- """
- The guts of the command-line parser.
- """
- if options is None:
- options = sys.argv[1:]
- # we really do need to place the shell completion check here, because
- # if we used an opt_shell_completion method then it would be possible
- # for other opt_* methods to be run first, and they could possibly
- # raise validation errors which would result in error output on the
- # terminal of the user performing shell completion. Validation errors
- # would occur quite frequently, in fact, because users often initiate
- # tab-completion while they are editing an unfinished command-line.
- if len(options) > 1 and options[-2] == "--_shell-completion":
- from twisted.python import _shellcomp
- cmdName = path.basename(sys.argv[0])
- _shellcomp.shellComplete(self, cmdName, options,
- self._shellCompFile)
- sys.exit(0)
- try:
- opts, args = getopt.getopt(options,
- self.shortOpt, self.longOpt)
- except getopt.error as e:
- raise UsageError(str(e))
- for opt, arg in opts:
- if opt[1] == '-':
- opt = opt[2:]
- else:
- opt = opt[1:]
- optMangled = opt
- if optMangled not in self.synonyms:
- optMangled = opt.replace("-", "_")
- if optMangled not in self.synonyms:
- raise UsageError("No such option '%s'" % (opt,))
- optMangled = self.synonyms[optMangled]
- if isinstance(self._dispatch[optMangled], CoerceParameter):
- self._dispatch[optMangled].dispatch(optMangled, arg)
- else:
- self._dispatch[optMangled](optMangled, arg)
- if (getattr(self, 'subCommands', None)
- and (args or self.defaultSubCommand is not None)):
- if not args:
- args = [self.defaultSubCommand]
- sub, rest = args[0], args[1:]
- for (cmd, short, parser, doc) in self.subCommands:
- if sub == cmd or sub == short:
- self.subCommand = cmd
- self.subOptions = parser()
- self.subOptions.parent = self
- self.subOptions.parseOptions(rest)
- break
- else:
- raise UsageError("Unknown command: %s" % sub)
- else:
- try:
- self.parseArgs(*args)
- except TypeError:
- raise UsageError("Wrong number of arguments.")
- self.postOptions()
- def postOptions(self):
- """
- I am called after the options are parsed.
- Override this method in your subclass to do something after
- the options have been parsed and assigned, like validate that
- all options are sane.
- """
- def parseArgs(self):
- """
- I am called with any leftover arguments which were not options.
- Override me to do something with the remaining arguments on
- the command line, those which were not flags or options. e.g.
- interpret them as a list of files to operate on.
- Note that if there more arguments on the command line
- than this method accepts, parseArgs will blow up with
- a getopt.error. This means if you don't override me,
- parseArgs will blow up if I am passed any arguments at
- all!
- """
- def _generic_flag(self, flagName, value=None):
- if value not in ('', None):
- raise UsageError("Flag '%s' takes no argument."
- " Not even \"%s\"." % (flagName, value))
- self.opts[flagName] = 1
- def _gather_flags(self):
- """
- Gather up boolean (flag) options.
- """
- longOpt, shortOpt = [], ''
- docs, settings, synonyms, dispatch = {}, {}, {}, {}
- flags = []
- reflect.accumulateClassList(self.__class__, 'optFlags', flags)
- for flag in flags:
- long, short, doc = util.padTo(3, flag)
- if not long:
- raise ValueError("A flag cannot be without a name.")
- docs[long] = doc
- settings[long] = 0
- if short:
- shortOpt = shortOpt + short
- synonyms[short] = long
- longOpt.append(long)
- synonyms[long] = long
- dispatch[long] = self._generic_flag
- return longOpt, shortOpt, docs, settings, synonyms, dispatch
- def _gather_parameters(self):
- """
- Gather options which take a value.
- """
- longOpt, shortOpt = [], ''
- docs, settings, synonyms, dispatch = {}, {}, {}, {}
- parameters = []
- reflect.accumulateClassList(self.__class__, 'optParameters',
- parameters)
- synonyms = {}
- for parameter in parameters:
- long, short, default, doc, paramType = util.padTo(5, parameter)
- if not long:
- raise ValueError("A parameter cannot be without a name.")
- docs[long] = doc
- settings[long] = default
- if short:
- shortOpt = shortOpt + short + ':'
- synonyms[short] = long
- longOpt.append(long + '=')
- synonyms[long] = long
- if paramType is not None:
- dispatch[long] = CoerceParameter(self, paramType)
- else:
- dispatch[long] = CoerceParameter(self, str)
- return longOpt, shortOpt, docs, settings, synonyms, dispatch
- def _gather_handlers(self):
- """
- Gather up options with their own handler methods.
- This returns a tuple of many values. Amongst those values is a
- synonyms dictionary, mapping all of the possible aliases (C{str})
- for an option to the longest spelling of that option's name
- C({str}).
- Another element is a dispatch dictionary, mapping each user-facing
- option name (with - substituted for _) to a callable to handle that
- option.
- """
- longOpt, shortOpt = [], ''
- docs, settings, synonyms, dispatch = {}, {}, {}, {}
- dct = {}
- reflect.addMethodNamesToDict(self.__class__, dct, "opt_")
- for name in dct.keys():
- method = getattr(self, 'opt_'+name)
- takesArg = not flagFunction(method, name)
- prettyName = name.replace('_', '-')
- doc = getattr(method, '__doc__', None)
- if doc:
- ## Only use the first line.
- #docs[name] = doc.split('\n')[0]
- docs[prettyName] = doc
- else:
- docs[prettyName] = self.docs.get(prettyName)
- synonyms[prettyName] = prettyName
- # A little slight-of-hand here makes dispatching much easier
- # in parseOptions, as it makes all option-methods have the
- # same signature.
- if takesArg:
- fn = lambda name, value, m=method: m(value)
- else:
- # XXX: This won't raise a TypeError if it's called
- # with a value when it shouldn't be.
- fn = lambda name, value=None, m=method: m()
- dispatch[prettyName] = fn
- if len(name) == 1:
- shortOpt = shortOpt + name
- if takesArg:
- shortOpt = shortOpt + ':'
- else:
- if takesArg:
- prettyName = prettyName + '='
- longOpt.append(prettyName)
- reverse_dct = {}
- # Map synonyms
- for name in dct.keys():
- method = getattr(self, 'opt_' + name)
- if method not in reverse_dct:
- reverse_dct[method] = []
- reverse_dct[method].append(name.replace('_', '-'))
- for method, names in reverse_dct.items():
- if len(names) < 2:
- continue
- longest = max(names, key=len)
- for name in names:
- synonyms[name] = longest
- return longOpt, shortOpt, docs, settings, synonyms, dispatch
- def __str__(self):
- return self.getSynopsis() + '\n' + self.getUsage(width=None)
- def getSynopsis(self):
- """
- Returns a string containing a description of these options and how to
- pass them to the executed file.
- """
- default = "%s%s" % (path.basename(sys.argv[0]),
- (self.longOpt and " [options]") or '')
- if self.parent is None:
- default = "Usage: %s%s" % (path.basename(sys.argv[0]),
- (self.longOpt and " [options]") or '')
- else:
- default = '%s' % ((self.longOpt and "[options]") or '')
- synopsis = getattr(self, "synopsis", default)
- synopsis = synopsis.rstrip()
- if self.parent is not None:
- synopsis = ' '.join((self.parent.getSynopsis(),
- self.parent.subCommand, synopsis))
- return synopsis
- def getUsage(self, width=None):
- # If subOptions exists by now, then there was probably an error while
- # parsing its options.
- if hasattr(self, 'subOptions'):
- return self.subOptions.getUsage(width=width)
- if not width:
- width = int(os.environ.get('COLUMNS', '80'))
- if hasattr(self, 'subCommands'):
- cmdDicts = []
- for (cmd, short, parser, desc) in self.subCommands:
- cmdDicts.append(
- {'long': cmd,
- 'short': short,
- 'doc': desc,
- 'optType': 'command',
- 'default': None
- })
- chunks = docMakeChunks(cmdDicts, width)
- commands = 'Commands:\n' + ''.join(chunks)
- else:
- commands = ''
- longToShort = {}
- for key, value in self.synonyms.items():
- longname = value
- if (key != longname) and (len(key) == 1):
- longToShort[longname] = key
- else:
- if longname not in longToShort:
- longToShort[longname] = None
- else:
- pass
- optDicts = []
- for opt in self.longOpt:
- if opt[-1] == '=':
- optType = 'parameter'
- opt = opt[:-1]
- else:
- optType = 'flag'
- optDicts.append(
- {'long': opt,
- 'short': longToShort[opt],
- 'doc': self.docs[opt],
- 'optType': optType,
- 'default': self.defaults.get(opt, None),
- 'dispatch': self._dispatch.get(opt, None)
- })
- if not (getattr(self, "longdesc", None) is None):
- longdesc = self.longdesc
- else:
- import __main__
- if getattr(__main__, '__doc__', None):
- longdesc = __main__.__doc__
- else:
- longdesc = ''
- if longdesc:
- longdesc = ('\n' +
- '\n'.join(textwrap.wrap(longdesc, width)).strip()
- + '\n')
- if optDicts:
- chunks = docMakeChunks(optDicts, width)
- s = "Options:\n%s" % (''.join(chunks))
- else:
- s = "Options: None\n"
- return s + longdesc + commands
- #def __repr__(self):
- # XXX: It'd be cool if we could return a succinct representation
- # of which flags and options are set here.
- _ZSH = 'zsh'
- _BASH = 'bash'
- class Completer(object):
- """
- A completion "action" - provides completion possibilities for a particular
- command-line option. For example we might provide the user a fixed list of
- choices, or files/dirs according to a glob.
- This class produces no completion matches itself - see the various
- subclasses for specific completion functionality.
- """
- _descr = None
- def __init__(self, descr=None, repeat=False):
- """
- @type descr: C{str}
- @param descr: An optional descriptive string displayed above matches.
- @type repeat: C{bool}
- @param repeat: A flag, defaulting to False, indicating whether this
- C{Completer} should repeat - that is, be used to complete more
- than one command-line word. This may ONLY be set to True for
- actions in the C{extraActions} keyword argument to C{Completions}.
- And ONLY if it is the LAST (or only) action in the C{extraActions}
- list.
- """
- if descr is not None:
- self._descr = descr
- self._repeat = repeat
- def _getRepeatFlag(self):
- if self._repeat:
- return "*"
- else:
- return ""
- _repeatFlag = property(_getRepeatFlag)
- def _description(self, optName):
- if self._descr is not None:
- return self._descr
- else:
- return optName
- def _shellCode(self, optName, shellType):
- """
- Fetch a fragment of shell code representing this action which is
- suitable for use by the completion system in _shellcomp.py
- @type optName: C{str}
- @param optName: The long name of the option this action is being
- used for.
- @type shellType: C{str}
- @param shellType: One of the supported shell constants e.g.
- C{twisted.python.usage._ZSH}
- """
- if shellType == _ZSH:
- return "%s:%s:" % (self._repeatFlag,
- self._description(optName))
- raise NotImplementedError("Unknown shellType %r" % (shellType,))
- class CompleteFiles(Completer):
- """
- Completes file names based on a glob pattern
- """
- def __init__(self, globPattern='*', **kw):
- Completer.__init__(self, **kw)
- self._globPattern = globPattern
- def _description(self, optName):
- if self._descr is not None:
- return "%s (%s)" % (self._descr, self._globPattern)
- else:
- return "%s (%s)" % (optName, self._globPattern)
- def _shellCode(self, optName, shellType):
- if shellType == _ZSH:
- return "%s:%s:_files -g \"%s\"" % (self._repeatFlag,
- self._description(optName),
- self._globPattern,)
- raise NotImplementedError("Unknown shellType %r" % (shellType,))
- class CompleteDirs(Completer):
- """
- Completes directory names
- """
- def _shellCode(self, optName, shellType):
- if shellType == _ZSH:
- return "%s:%s:_directories" % (self._repeatFlag,
- self._description(optName))
- raise NotImplementedError("Unknown shellType %r" % (shellType,))
- class CompleteList(Completer):
- """
- Completes based on a fixed list of words
- """
- def __init__(self, items, **kw):
- Completer.__init__(self, **kw)
- self._items = items
- def _shellCode(self, optName, shellType):
- if shellType == _ZSH:
- return "%s:%s:(%s)" % (self._repeatFlag,
- self._description(optName),
- " ".join(self._items,))
- raise NotImplementedError("Unknown shellType %r" % (shellType,))
- class CompleteMultiList(Completer):
- """
- Completes multiple comma-separated items based on a fixed list of words
- """
- def __init__(self, items, **kw):
- Completer.__init__(self, **kw)
- self._items = items
- def _shellCode(self, optName, shellType):
- if shellType == _ZSH:
- return "%s:%s:_values -s , '%s' %s" % (self._repeatFlag,
- self._description(optName),
- self._description(optName),
- " ".join(self._items))
- raise NotImplementedError("Unknown shellType %r" % (shellType,))
- class CompleteUsernames(Completer):
- """
- Complete usernames
- """
- def _shellCode(self, optName, shellType):
- if shellType == _ZSH:
- return "%s:%s:_users" % (self._repeatFlag,
- self._description(optName))
- raise NotImplementedError("Unknown shellType %r" % (shellType,))
- class CompleteGroups(Completer):
- """
- Complete system group names
- """
- _descr = 'group'
- def _shellCode(self, optName, shellType):
- if shellType == _ZSH:
- return "%s:%s:_groups" % (self._repeatFlag,
- self._description(optName))
- raise NotImplementedError("Unknown shellType %r" % (shellType,))
- class CompleteHostnames(Completer):
- """
- Complete hostnames
- """
- def _shellCode(self, optName, shellType):
- if shellType == _ZSH:
- return "%s:%s:_hosts" % (self._repeatFlag,
- self._description(optName))
- raise NotImplementedError("Unknown shellType %r" % (shellType,))
- class CompleteUserAtHost(Completer):
- """
- A completion action which produces matches in any of these forms::
- <username>
- <hostname>
- <username>@<hostname>
- """
- _descr = 'host | user@host'
- def _shellCode(self, optName, shellType):
- if shellType == _ZSH:
- # Yes this looks insane but it does work. For bonus points
- # add code to grep 'Hostname' lines from ~/.ssh/config
- return ('%s:%s:{_ssh;if compset -P "*@"; '
- 'then _wanted hosts expl "remote host name" _ssh_hosts '
- '&& ret=0 elif compset -S "@*"; then _wanted users '
- 'expl "login name" _ssh_users -S "" && ret=0 '
- 'else if (( $+opt_args[-l] )); then tmp=() '
- 'else tmp=( "users:login name:_ssh_users -qS@" ) fi; '
- '_alternative "hosts:remote host name:_ssh_hosts" "$tmp[@]"'
- ' && ret=0 fi}' % (self._repeatFlag,
- self._description(optName)))
- raise NotImplementedError("Unknown shellType %r" % (shellType,))
- class CompleteNetInterfaces(Completer):
- """
- Complete network interface names
- """
- def _shellCode(self, optName, shellType):
- if shellType == _ZSH:
- return "%s:%s:_net_interfaces" % (self._repeatFlag,
- self._description(optName))
- raise NotImplementedError("Unknown shellType %r" % (shellType,))
- class Completions(object):
- """
- Extra metadata for the shell tab-completion system.
- @type descriptions: C{dict}
- @ivar descriptions: ex. C{{"foo" : "use this description for foo instead"}}
- A dict mapping long option names to alternate descriptions. When this
- variable is defined, the descriptions contained here will override
- those descriptions provided in the optFlags and optParameters
- variables.
- @type multiUse: C{list}
- @ivar multiUse: ex. C{ ["foo", "bar"] }
- An iterable containing those long option names which may appear on the
- command line more than once. By default, options will only be completed
- one time.
- @type mutuallyExclusive: C{list} of C{tuple}
- @ivar mutuallyExclusive: ex. C{ [("foo", "bar"), ("bar", "baz")] }
- A sequence of sequences, with each sub-sequence containing those long
- option names that are mutually exclusive. That is, those options that
- cannot appear on the command line together.
- @type optActions: C{dict}
- @ivar optActions: A dict mapping long option names to shell "actions".
- These actions define what may be completed as the argument to the
- given option. By default, all files/dirs will be completed if no
- action is given. For example::
- {"foo" : CompleteFiles("*.py", descr="python files"),
- "bar" : CompleteList(["one", "two", "three"]),
- "colors" : CompleteMultiList(["red", "green", "blue"])}
- Callables may instead be given for the values in this dict. The
- callable should accept no arguments, and return a C{Completer}
- instance used as the action in the same way as the literal actions in
- the example above.
- As you can see in the example above. The "foo" option will have files
- that end in .py completed when the user presses Tab. The "bar"
- option will have either of the strings "one", "two", or "three"
- completed when the user presses Tab.
- "colors" will allow multiple arguments to be completed, separated by
- commas. The possible arguments are red, green, and blue. Examples::
- my_command --foo some-file.foo --colors=red,green
- my_command --colors=green
- my_command --colors=green,blue
- Descriptions for the actions may be given with the optional C{descr}
- keyword argument. This is separate from the description of the option
- itself.
- Normally Zsh does not show these descriptions unless you have
- "verbose" completion turned on. Turn on verbosity with this in your
- ~/.zshrc::
- zstyle ':completion:*' verbose yes
- zstyle ':completion:*:descriptions' format '%B%d%b'
- @type extraActions: C{list}
- @ivar extraActions: Extra arguments are those arguments typically
- appearing at the end of the command-line, which are not associated
- with any particular named option. That is, the arguments that are
- given to the parseArgs() method of your usage.Options subclass. For
- example::
- [CompleteFiles(descr="file to read from"),
- Completer(descr="book title")]
- In the example above, the 1st non-option argument will be described as
- "file to read from" and all file/dir names will be completed (*). The
- 2nd non-option argument will be described as "book title", but no
- actual completion matches will be produced.
- See the various C{Completer} subclasses for other types of things which
- may be tab-completed (users, groups, network interfaces, etc).
- Also note the C{repeat=True} flag which may be passed to any of the
- C{Completer} classes. This is set to allow the C{Completer} instance
- to be re-used for subsequent command-line words. See the C{Completer}
- docstring for details.
- """
- def __init__(self, descriptions={}, multiUse=[],
- mutuallyExclusive=[], optActions={}, extraActions=[]):
- self.descriptions = descriptions
- self.multiUse = multiUse
- self.mutuallyExclusive = mutuallyExclusive
- self.optActions = optActions
- self.extraActions = extraActions
- def docMakeChunks(optList, width=80):
- """
- Makes doc chunks for option declarations.
- Takes a list of dictionaries, each of which may have one or more
- of the keys 'long', 'short', 'doc', 'default', 'optType'.
- Returns a list of strings.
- The strings may be multiple lines,
- all of them end with a newline.
- """
- # XXX: sanity check to make sure we have a sane combination of keys.
- maxOptLen = 0
- for opt in optList:
- optLen = len(opt.get('long', ''))
- if optLen:
- if opt.get('optType', None) == "parameter":
- # these take up an extra character
- optLen = optLen + 1
- maxOptLen = max(optLen, maxOptLen)
- colWidth1 = maxOptLen + len(" -s, -- ")
- colWidth2 = width - colWidth1
- # XXX - impose some sane minimum limit.
- # Then if we don't have enough room for the option and the doc
- # to share one line, they can take turns on alternating lines.
- colFiller1 = " " * colWidth1
- optChunks = []
- seen = {}
- for opt in optList:
- if opt.get('short', None) in seen or opt.get('long', None) in seen:
- continue
- for x in opt.get('short', None), opt.get('long', None):
- if x is not None:
- seen[x] = 1
- optLines = []
- comma = " "
- if opt.get('short', None):
- short = "-%c" % (opt['short'],)
- else:
- short = ''
- if opt.get('long', None):
- long = opt['long']
- if opt.get("optType", None) == "parameter":
- long = long + '='
- long = "%-*s" % (maxOptLen, long)
- if short:
- comma = ","
- else:
- long = " " * (maxOptLen + len('--'))
- if opt.get('optType', None) == 'command':
- column1 = ' %s ' % long
- else:
- column1 = " %2s%c --%s " % (short, comma, long)
- if opt.get('doc', ''):
- doc = opt['doc'].strip()
- else:
- doc = ''
- if (opt.get("optType", None) == "parameter") \
- and not (opt.get('default', None) is None):
- doc = "%s [default: %s]" % (doc, opt['default'])
- if (opt.get("optType", None) == "parameter") \
- and opt.get('dispatch', None) is not None:
- d = opt['dispatch']
- if isinstance(d, CoerceParameter) and d.doc:
- doc = "%s. %s" % (doc, d.doc)
- if doc:
- column2_l = textwrap.wrap(doc, colWidth2)
- else:
- column2_l = ['']
- optLines.append("%s%s\n" % (column1, column2_l.pop(0)))
- for line in column2_l:
- optLines.append("%s%s\n" % (colFiller1, line))
- optChunks.append(''.join(optLines))
- return optChunks
- def flagFunction(method, name=None):
- """
- Determine whether a function is an optional handler for a I{flag} or an
- I{option}.
- A I{flag} handler takes no additional arguments. It is used to handle
- command-line arguments like I{--nodaemon}.
- An I{option} handler takes one argument. It is used to handle command-line
- arguments like I{--path=/foo/bar}.
- @param method: The bound method object to inspect.
- @param name: The name of the option for which the function is a handle.
- @type name: L{str}
- @raise UsageError: If the method takes more than one argument.
- @return: If the method is a flag handler, return C{True}. Otherwise return
- C{False}.
- """
- if _PY3:
- reqArgs = len(inspect.signature(method).parameters)
- if reqArgs > 1:
- raise UsageError('Invalid Option function for %s' %
- (name or method.__name__))
- if reqArgs == 1:
- return False
- else:
- reqArgs = len(inspect.getargspec(method).args)
- if reqArgs > 2:
- raise UsageError('Invalid Option function for %s' %
- (name or method.__name__))
- if reqArgs == 2:
- return False
- return True
- def portCoerce(value):
- """
- Coerce a string value to an int port number, and checks the validity.
- """
- value = int(value)
- if value < 0 or value > 65535:
- raise ValueError("Port number not in range: %s" % (value,))
- return value
- portCoerce.coerceDoc = "Must be an int between 0 and 65535."
|