starcluster.optcomplete

Package starcluster :: Module optcomplete |
[hide private]
[frames] | no frames]

# Source Code for Module starcluster.optcomplete

      1  #*****************************************************************************\
      2  #* Copyright (c) 2003-2004, Martin Blais
      3  #* All rights reserved.
      4  #*
      5  #* Redistribution and use in source and binary forms, with or without
      6  #* modification, are permitted provided that the following conditions are
      7  #* met:
      8  #*
      9  #* * Redistributions of source code must retain the above copyright
     10  #*   notice, this list of conditions and the following disclaimer.
     11  #*
     12  #* * Redistributions in binary form must reproduce the above copyright
     13  #*   notice, this list of conditions and the following disclaimer in the
     14  #*   documentation and/or other materials provided with the distribution.
     15  #*
     16  #* * Neither the name of the Martin Blais, Furius, nor the names of its
     17  #*   contributors may be used to endorse or promote products derived from
     18  #*   this software without specific prior written permission.
     19  #*
     20  #* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     21  #* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
     22  #* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
     23  #* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
     24  #* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
     25  #* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
     26  #* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     27  #* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     28  #* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     29  #* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     30  #* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     31  #*****************************************************************************\
     32
     33  """Automatic completion for optparse module.
     34
     35  This module provide automatic bash completion support for programs that use the
     36  optparse module.  The premise is that the optparse options parser specifies
     37  enough information (and more) for us to be able to generate completion strings
     38  esily.  Another advantage of this over traditional completion schemes where the
     39  completion strings are hard-coded in a separate bash source file, is that the
     40  same code that parses the options is used to generate the completions, so the
     41  completions is always up-to-date with the program itself.
     42
     43  In addition, we allow you specify a list of regular expressions or code that
     44  define what kinds of files should be proposed as completions to this file if
     45  needed.  If you want to implement more complex behaviour, you can instead
     46  specify a function, which will be called with the current directory as an
     47  argument.
     48
     49  You need to activate bash completion using the shell script function that comes
     50  with optcomplete (see http://furius.ca/optcomplete for more details).
     51
     52  """
     53
     54  __version__ = "$Revision$"
     55  __author__ = "Martin Blais <blais@furius.ca>"
     56
     57  ## Bash Protocol Description
     58  ## -------------------------
     59  ##
     60  ## `COMP_CWORD'
     61  ##      An index into `${COMP_WORDS}' of the word containing the current
     62  ##      cursor position.  This variable is available only in shell
     63  ##      functions invoked by the programmable completion facilities (*note
     64  ##      Programmable Completion::).
     65  ##
     66  ## `COMP_LINE'
     67  ##      The current command line.  This variable is available only in
     68  ##      shell functions and external commands invoked by the programmable
     69  ##      completion facilities (*note Programmable Completion::).
     70  ##
     71  ## `COMP_POINT'
     72  ##      The index of the current cursor position relative to the beginning
     73  ##      of the current command.  If the current cursor position is at the
     74  ##      end of the current command, the value of this variable is equal to
     75  ##      `${#COMP_LINE}'.  This variable is available only in shell
     76  ##      functions and external commands invoked by the programmable
     77  ##      completion facilities (*note Programmable Completion::).
     78  ##
     79  ## `COMP_WORDS'
     80  ##      An array variable consisting of the individual words in the
     81  ##      current command line.  This variable is available only in shell
     82  ##      functions invoked by the programmable completion facilities (*note
     83  ##      Programmable Completion::).
     84  ##
     85  ## `COMPREPLY'
     86  ##      An array variable from which Bash reads the possible completions
     87  ##      generated by a shell function invoked by the programmable
     88  ##      completion facility (*note Programmable Completion::).
     89
     90
     91  import os
     92  import sys
     93  import types
     94  import re
     95
     96  from pprint import pformat
     97
     98  from optparse import OptionParser
     99
    100  debugfn = '/tmp/completion-debug.log'  # for debugging only
    101
    102
    103 -class AllCompleter(object):
    104
    105      """Completes by listing all possible files in current directory."""
    106
    107 -    def __call__(self, pwd, line, point, prefix, suffix):
    108          return os.listdir(pwd)
    109
    110
    111 -class NoneCompleter(object):
    112
    113      """Generates empty completion list."""
    114
    115 -    def __call__(self, pwd, line, point, prefix, suffix):
    116          return []
    117
    118
    119 -class DirCompleter(object):
    120
    121      """Completes by listing subdirectories only."""
    122
    123 -    def __call__(self, pwd, line, point, prefix, suffix):
    124          return filter(os.path.isdir, os.listdir(pwd))
    125
    126
    127 -class RegexCompleter(object):
    128
    129      """Completes by filtering all possible files with the given list of
    130      regexps."""
    131
    132 -    def __init__(self, regexlist, always_dirs=True):
    133          self.always_dirs = always_dirs
    134
    135          if isinstance(regexlist, types.StringType):
    136              regexlist = [regexlist]
    137          self.regexlist = []
    138          for r in regexlist:
    139              if isinstance(r, types.StringType):
    140                  r = re.compile(r)
    141              self.regexlist.append(r)
    142
    143 -    def __call__(self, pwd, line, point, prefix, suffix):
    144          dn = os.path.dirname(prefix)
    145          if dn:
    146              pwd = dn
    147          files = os.listdir(pwd)
    148          ofiles = []
    149          for fn in files:
    150              for r in self.regexlist:
    151                  if r.match(fn):
    152                      if dn:
    153                          fn = os.path.join(dn, fn)
    154                      ofiles.append(fn)
    155                      break
    156              if self.always_dirs and os.path.isdir(fn):
    157                  ofiles.append(fn + '/')
    158          return ofiles
    159
    160
    161 -class ListCompleter(object):
    162
    163      """Completes by filtering using a fixed list of strings."""
    164
    165 -    def __init__(self, stringlist):
    166          self.olist = stringlist
    167
    168 -    def __call__(self, pwd, line, point, prefix, suffix):
    169          return self.olist
    170
    171
    172 -def extract_word(line, point):
    173
    174      """Return a prefix and suffix of the enclosing word.  The character under
    175      the cursor is the first character of the suffix."""
    176
    177      wsre = re.compile('[ \t]')
    178
    179      if point < 0 or point > len(line):
    180          return '', ''
    181
    182      preii = point - 1
    183      while preii >= 0:
    184          if wsre.match(line[preii]):
    185              break
    186          preii -= 1
    187      preii += 1
    188
    189      sufii = point
    190      while sufii < len(line):
    191          if wsre.match(line[sufii]):
    192              break
    193          sufii += 1
    194
    195      return line[preii:point], line[point:sufii]
    196
    197
    198 -def autocomplete(parser,
    199                    arg_completer=None,  # means use default.
    200                    opt_completer=None,
    201                    subcmd_completer=None,
    202                    subcommands=None):
    203
    204      """Automatically detect if we are requested completing and if so generate
    205      completion automatically from given parser.
    206
    207      'parser' is the options parser to use.
    208
    209      'arg_completer' is a callable object that gets invoked to produce a list of
    210      completions for arguments completion (oftentimes files).
    211
    212      'opt_completer' is the default completer to the options that require a
    213      value. 'subcmd_completer' is the default completer for the subcommand
    214      arguments.
    215
    216      If 'subcommands' is specified, the script expects it to be a map of
    217      command-name to an object of any kind.  We are assuming that this object is
    218      a map from command name to a pair of (options parser, completer) for the
    219      command. If the value is not such a tuple, the method
    220      'autocomplete(completer)' is invoked on the resulting object.
    221
    222      This will attempt to match the first non-option argument into a subcommand
    223      name and if so will use the local parser in the corresponding map entry's
    224      value.  This is used to implement completion for subcommand syntax and will
    225      not be needed in most cases."""
    226
    227      # If we are not requested for complete, simply return silently, let the
    228      # code caller complete. This is the normal path of execution.
    229      if 'OPTPARSE_AUTO_COMPLETE' not in os.environ:
    230          return
    231
    232      # Set default completers.
    233      if arg_completer is None:
    234          arg_completer = NoneCompleter()
    235      if opt_completer is None:
    236          opt_completer = NoneCompleter()
    237      if subcmd_completer is None:
    238          ## subcmd_completer = arg_completer
    239          subcmd_completer = NoneCompleter()
    240
    241      # By default, completion will be arguments completion, unless we find out
    242      # later we're trying to complete for an option.
    243      completer = arg_completer
    244
    245      #
    246      # Completing...
    247      #
    248
    249      # Fetching inputs... not sure if we're going to use these.
    250
    251      # zsh's bashcompinit does not pass COMP_WORDS, replace with
    252      # COMP_LINE for now...
    253      if not 'COMP_WORDS' in os.environ:
    254          os.environ['COMP_WORDS'] = os.environ['COMP_LINE']
    255
    256      cwords = os.environ['COMP_WORDS'].split()
    257      cline = os.environ['COMP_LINE']
    258      cpoint = int(os.environ['COMP_POINT'])
    259      cword = int(os.environ['COMP_CWORD'])
    260
    261      # If requested, try subcommand syntax to find an options parser for that
    262      # subcommand.
    263      if subcommands:
    264          assert isinstance(subcommands, types.DictType)
    265          value = guess_first_nonoption(parser, subcommands)
    266          if value:
    267              if isinstance(value, types.ListType) or \
    268                 isinstance(value, types.TupleType):
    269                  parser = value[0]
    270                  if len(value) > 1 and value[1]:
    271                      # override completer for command if it is present.
    272                      completer = value[1]
    273                  else:
    274                      completer = subcmd_completer
    275                  return autocomplete(parser, completer)
    276              else:
    277                  # Call completion method on object. This should call
    278                  # autocomplete() recursively with appropriate arguments.
    279                  if hasattr(value, 'autocomplete'):
    280                      return value.autocomplete(subcmd_completer)
    281                  else:
    282                      sys.exit(1)  # no completions for that command object
    283
    284      # Extract word enclosed word.
    285      prefix, suffix = extract_word(cline, cpoint)
    286      # The following would be less exact, but will work nonetheless .
    287      # prefix, suffix = cwords[cword], None
    288
    289      # Look at previous word, if it is an option and it requires an argument,
    290      # check for a local completer.  If there is no completer, what follows
    291      # directly cannot be another option, so mark to not add those to
    292      # completions.
    293      optarg = False
    294      try:
    295          # Look for previous word, which will be containing word if the option
    296          # has an equals sign in it.
    297          prev = None
    298          if cword < len(cwords):
    299              mo = re.search('(--.*)=(.*)', cwords[cword])
    300              if mo:
    301                  prev, prefix = mo.groups()
    302          if not prev:
    303              prev = cwords[cword - 1]
    304
    305          if prev and prev.startswith('-'):
    306              option = parser.get_option(prev)
    307              if option:
    308                  if option.nargs > 0:
    309                      optarg = True
    310                      if hasattr(option, 'completer'):
    311                          completer = option.completer
    312                      elif option.type != 'string':
    313                          completer = NoneCompleter()
    314                      else:
    315                          completer = opt_completer
    316                  # Warn user at least, it could help him figure out the problem.
    317                  elif hasattr(option, 'completer'):
    318                      raise SystemExit(
    319                          "Error: optparse option with a completer "
    320                          "does not take arguments: %s" % str(option))
    321      except KeyError:
    322          pass
    323
    324      completions = []
    325
    326      # Options completion.
    327      if not optarg and (not prefix or prefix.startswith('-')):
    328          completions += parser._short_opt.keys()
    329          completions += parser._long_opt.keys()
    330          # Note: this will get filtered properly below.
    331
    332      # File completion.
    333      if completer and (not prefix or not prefix.startswith('-')):
    334
    335          # Call appropriate completer depending on type.
    336          if isinstance(completer, types.StringType) or \
    337                 isinstance(completer, types.ListType) or \
    338                 isinstance(completer, types.TupleType):
    339              completer = RegexCompleter(completer)
    340              completions += completer(os.getcwd(), cline,
    341                                       cpoint, prefix, suffix)
    342          elif isinstance(completer, types.FunctionType) or \
    343               isinstance(completer, types.LambdaType) or \
    344               isinstance(completer, types.ClassType) or \
    345               isinstance(completer, types.ObjectType):
    346              completions += completer(os.getcwd(), cline,
    347                                       cpoint, prefix, suffix)
    348
    349      # Filter using prefix.
    350      if prefix:
    351          completions = filter(lambda x: x.startswith(prefix), completions)
    352
    353      # Print result.
    354      print ' '.join(completions)
    355
    356      # Print debug output (if needed).  You can keep a shell with 'tail -f' to
    357      # the log file to monitor what is happening.
    358      if debugfn:
    359          f = open(debugfn, 'a')
    360          print >> f, '---------------------------------------------------------'
    361          print >> f, 'CWORDS', cwords
    362          print >> f, 'CLINE', cline
    363          print >> f, 'CPOINT', cpoint
    364          print >> f, 'CWORD', cword
    365          print >> f, '\nShort options'
    366          print >> f, pformat(parser._short_opt)
    367          print >> f, '\nLong options'
    368          print >> f, pformat(parser._long_opt)
    369          print >> f, 'Prefix/Suffix:', prefix, suffix
    370          print >> f, 'completions', completions
    371          f.close()
    372
    373      # Exit with error code (we do not let the caller continue on purpose, this
    374      # is a run for completions only.)
    375      sys.exit(1)
    376
    377
    378 -def error_override(self, msg):
    379      """Hack to keep OptionParser from writing to sys.stderr when
    380      calling self.exit from self.error"""
    381      self.exit(2, msg=None)
    382
    383
    384 -def guess_first_nonoption(gparser, subcmds_map):
    385
    386      """Given a global options parser, try to guess the first non-option without
    387      generating an exception. This is used for scripts that implement a
    388      subcommand syntax, so that we can generate the appropriate completions for
    389      the subcommand."""
    390
    391      import copy
    392      gparser = copy.deepcopy(gparser)
    393
    394      def print_usage_nousage(self, file=None):
    395          pass
    396      gparser.print_usage = print_usage_nousage
    397
    398      # save state to restore
    399      prev_interspersed = gparser.allow_interspersed_args
    400      gparser.disable_interspersed_args()
    401
    402      cwords = os.environ['COMP_WORDS'].split()
    403
    404      # save original error_func so we can put it back after the hack
    405      error_func = gparser.error
    406      try:
    407          try:
    408              instancemethod = type(OptionParser.error)
    409              # hack to keep OptionParser from wrinting to sys.stderr
    410              gparser.error = instancemethod(error_override,
    411                                             gparser, OptionParser)
    412              gopts, args = gparser.parse_args(cwords[1:])
    413          except SystemExit:
    414              return None
    415      finally:
    416          # undo the hack and restore original OptionParser error function
    417          gparser.error = instancemethod(error_func, gparser, OptionParser)
    418
    419      value = None
    420      if args:
    421          subcmdname = args[0]
    422          try:
    423              value = subcmds_map[subcmdname]
    424          except KeyError:
    425              pass
    426
    427      gparser.allow_interspersed_args = prev_interspersed  # restore state
    428
    429      return value  # can be None, indicates no command chosen.
    430
    431
    432 -class CmdComplete(object):
    433
    434      """Simple default base class implementation for a subcommand that supports
    435      command completion.  This class is assuming that there might be a method
    436      addopts(self, parser) to declare options for this subcommand, and an
    437      optional completer data member to contain command-specific completion.  Of
    438      course, you don't really have to use this, but if you do it is convenient
    439      to have it here."""
    440
    441 -    def autocomplete(self, completer):
    442          import logging
    443          logging.disable(logging.CRITICAL)
    444          import optparse
    445          parser = optparse.OptionParser(self.__doc__.strip())
    446          if hasattr(self, 'addopts'):
    447              self.addopts(parser)
    448          if hasattr(self, 'completer'):
    449              completer = self.completer
    450          logging.disable(logging.NOTSET)
    451          return autocomplete(parser, completer)
    452
    453
    454 -def test():
    455      print extract_word("extraire un mot d'une phrase", 11)
    456      print extract_word("extraire un mot d'une phrase", 12)
    457      print extract_word("extraire un mot d'une phrase", 13)
    458      print extract_word("extraire un mot d'une phrase", 14)
    459      print extract_word("extraire un mot d'une phrase", 0)
    460      print extract_word("extraire un mot d'une phrase", 28)
    461      print extract_word("extraire un mot d'une phrase", 29)
    462      print extract_word("extraire un mot d'une phrase", -2)
    463      print extract_word("optcomplete-test do", 19)
    464
    465  if __name__ == '__main__':
    466      test()
    467