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  from starcluster import static
    101
    102  debugfn = os.path.join(static.STARCLUSTER_LOG_DIR, 'completion-debug.log')
    103
    104
    105 -class AllCompleter(object):
    106
    107      """Completes by listing all possible files in current directory."""
    108
    109 -    def __call__(self, pwd, line, point, prefix, suffix):
    110          return os.listdir(pwd)
    111
    112
    113 -class NoneCompleter(object):
    114
    115      """Generates empty completion list."""
    116
    117 -    def __call__(self, pwd, line, point, prefix, suffix):
    118          return []
    119
    120
    121 -class DirCompleter(object):
    122
    123      """Completes by listing subdirectories only."""
    124
    125 -    def __call__(self, pwd, line, point, prefix, suffix):
    126          return filter(os.path.isdir, os.listdir(pwd))
    127
    128
    129 -class RegexCompleter(object):
    130
    131      """Completes by filtering all possible files with the given list of
    132      regexps."""
    133
    134 -    def __init__(self, regexlist, always_dirs=True):
    135          self.always_dirs = always_dirs
    136
    137          if isinstance(regexlist, types.StringType):
    138              regexlist = [regexlist]
    139          self.regexlist = []
    140          for r in regexlist:
    141              if isinstance(r, types.StringType):
    142                  r = re.compile(r)
    143              self.regexlist.append(r)
    144
    145 -    def __call__(self, pwd, line, point, prefix, suffix):
    146          dn = os.path.dirname(prefix)
    147          if dn:
    148              pwd = dn
    149          files = os.listdir(pwd)
    150          ofiles = []
    151          for fn in files:
    152              for r in self.regexlist:
    153                  if r.match(fn):
    154                      if dn:
    155                          fn = os.path.join(dn, fn)
    156                      ofiles.append(fn)
    157                      break
    158              if self.always_dirs and os.path.isdir(fn):
    159                  ofiles.append(fn + '/')
    160          return ofiles
    161
    162
    163 -class ListCompleter(object):
    164
    165      """Completes by filtering using a fixed list of strings."""
    166
    167 -    def __init__(self, stringlist):
    168          self.olist = stringlist
    169
    170 -    def __call__(self, pwd, line, point, prefix, suffix):
    171          return self.olist
    172
    173
    174 -def extract_word(line, point):
    175
    176      """Return a prefix and suffix of the enclosing word.  The character under
    177      the cursor is the first character of the suffix."""
    178
    179      wsre = re.compile('[ \t]')
    180
    181      if point < 0 or point > len(line):
    182          return '', ''
    183
    184      preii = point - 1
    185      while preii >= 0:
    186          if wsre.match(line[preii]):
    187              break
    188          preii -= 1
    189      preii += 1
    190
    191      sufii = point
    192      while sufii < len(line):
    193          if wsre.match(line[sufii]):
    194              break
    195          sufii += 1
    196
    197      return line[preii:point], line[point:sufii]
    198
    199
    200 -def autocomplete(parser,
    201                    arg_completer=None,  # means use default.
    202                    opt_completer=None,
    203                    subcmd_completer=None,
    204                    subcommands=None):
    205
    206      """Automatically detect if we are requested completing and if so generate
    207      completion automatically from given parser.
    208
    209      'parser' is the options parser to use.
    210
    211      'arg_completer' is a callable object that gets invoked to produce a list of
    212      completions for arguments completion (oftentimes files).
    213
    214      'opt_completer' is the default completer to the options that require a
    215      value. 'subcmd_completer' is the default completer for the subcommand
    216      arguments.
    217
    218      If 'subcommands' is specified, the script expects it to be a map of
    219      command-name to an object of any kind.  We are assuming that this object is
    220      a map from command name to a pair of (options parser, completer) for the
    221      command. If the value is not such a tuple, the method
    222      'autocomplete(completer)' is invoked on the resulting object.
    223
    224      This will attempt to match the first non-option argument into a subcommand
    225      name and if so will use the local parser in the corresponding map entry's
    226      value.  This is used to implement completion for subcommand syntax and will
    227      not be needed in most cases."""
    228
    229      # If we are not requested for complete, simply return silently, let the
    230      # code caller complete. This is the normal path of execution.
    231      if 'OPTPARSE_AUTO_COMPLETE' not in os.environ:
    232          return
    233
    234      # Set default completers.
    235      if arg_completer is None:
    236          arg_completer = NoneCompleter()
    237      if opt_completer is None:
    238          opt_completer = NoneCompleter()
    239      if subcmd_completer is None:
    240          ## subcmd_completer = arg_completer
    241          subcmd_completer = NoneCompleter()
    242
    243      # By default, completion will be arguments completion, unless we find out
    244      # later we're trying to complete for an option.
    245      completer = arg_completer
    246
    247      #
    248      # Completing...
    249      #
    250
    251      # Fetching inputs... not sure if we're going to use these.
    252
    253      # zsh's bashcompinit does not pass COMP_WORDS, replace with
    254      # COMP_LINE for now...
    255      if not 'COMP_WORDS' in os.environ:
    256          os.environ['COMP_WORDS'] = os.environ['COMP_LINE']
    257
    258      cwords = os.environ['COMP_WORDS'].split()
    259      cline = os.environ['COMP_LINE']
    260      cpoint = int(os.environ['COMP_POINT'])
    261      cword = int(os.environ['COMP_CWORD'])
    262
    263      # If requested, try subcommand syntax to find an options parser for that
    264      # subcommand.
    265      if subcommands:
    266          assert isinstance(subcommands, types.DictType)
    267          value = guess_first_nonoption(parser, subcommands)
    268          if value:
    269              if isinstance(value, types.ListType) or \
    270                 isinstance(value, types.TupleType):
    271                  parser = value[0]
    272                  if len(value) > 1 and value[1]:
    273                      # override completer for command if it is present.
    274                      completer = value[1]
    275                  else:
    276                      completer = subcmd_completer
    277                  return autocomplete(parser, completer)
    278              else:
    279                  # Call completion method on object. This should call
    280                  # autocomplete() recursively with appropriate arguments.
    281                  if hasattr(value, 'autocomplete'):
    282                      return value.autocomplete(subcmd_completer)
    283                  else:
    284                      sys.exit(1)  # no completions for that command object
    285
    286      # Extract word enclosed word.
    287      prefix, suffix = extract_word(cline, cpoint)
    288      # The following would be less exact, but will work nonetheless .
    289      # prefix, suffix = cwords[cword], None
    290
    291      # Look at previous word, if it is an option and it requires an argument,
    292      # check for a local completer.  If there is no completer, what follows
    293      # directly cannot be another option, so mark to not add those to
    294      # completions.
    295      optarg = False
    296      try:
    297          # Look for previous word, which will be containing word if the option
    298          # has an equals sign in it.
    299          prev = None
    300          if cword < len(cwords):
    301              mo = re.search('(--.*)=(.*)', cwords[cword])
    302              if mo:
    303                  prev, prefix = mo.groups()
    304          if not prev:
    305              prev = cwords[cword - 1]
    306
    307          if prev and prev.startswith('-'):
    308              option = parser.get_option(prev)
    309              if option:
    310                  if option.nargs > 0:
    311                      optarg = True
    312                      if hasattr(option, 'completer'):
    313                          completer = option.completer
    314                      elif option.type != 'string':
    315                          completer = NoneCompleter()
    316                      else:
    317                          completer = opt_completer
    318                  # Warn user at least, it could help him figure out the problem.
    319                  elif hasattr(option, 'completer'):
    320                      raise SystemExit(
    321                          "Error: optparse option with a completer "
    322                          "does not take arguments: %s" % str(option))
    323      except KeyError:
    324          pass
    325
    326      completions = []
    327
    328      # Options completion.
    329      if not optarg and (not prefix or prefix.startswith('-')):
    330          completions += parser._short_opt.keys()
    331          completions += parser._long_opt.keys()
    332          # Note: this will get filtered properly below.
    333
    334      # File completion.
    335      if completer and (not prefix or not prefix.startswith('-')):
    336
    337          # Call appropriate completer depending on type.
    338          if isinstance(completer, types.StringType) or \
    339                 isinstance(completer, types.ListType) or \
    340                 isinstance(completer, types.TupleType):
    341              completer = RegexCompleter(completer)
    342              completions += completer(os.getcwd(), cline,
    343                                       cpoint, prefix, suffix)
    344          elif isinstance(completer, types.FunctionType) or \
    345               isinstance(completer, types.LambdaType) or \
    346               isinstance(completer, types.ClassType) or \
    347               isinstance(completer, types.ObjectType):
    348              completions += completer(os.getcwd(), cline,
    349                                       cpoint, prefix, suffix)
    350
    351      # Filter using prefix.
    352      if prefix:
    353          completions = filter(lambda x: x.startswith(prefix), completions)
    354
    355      # Print result.
    356      print ' '.join(completions)
    357
    358      # Print debug output (if needed).  You can keep a shell with 'tail -f' to
    359      # the log file to monitor what is happening.
    360      if debugfn:
    361          f = open(debugfn, 'a')
    362          print >> f, '---------------------------------------------------------'
    363          print >> f, 'CWORDS', cwords
    364          print >> f, 'CLINE', cline
    365          print >> f, 'CPOINT', cpoint
    366          print >> f, 'CWORD', cword
    367          print >> f, '\nShort options'
    368          print >> f, pformat(parser._short_opt)
    369          print >> f, '\nLong options'
    370          print >> f, pformat(parser._long_opt)
    371          print >> f, 'Prefix/Suffix:', prefix, suffix
    372          print >> f, 'completions', completions
    373          f.close()
    374
    375      # Exit with error code (we do not let the caller continue on purpose, this
    376      # is a run for completions only.)
    377      sys.exit(1)
    378
    379
    380 -def error_override(self, msg):
    381      """Hack to keep OptionParser from writing to sys.stderr when
    382      calling self.exit from self.error"""
    383      self.exit(2, msg=None)
    384
    385
    386 -def guess_first_nonoption(gparser, subcmds_map):
    387
    388      """Given a global options parser, try to guess the first non-option without
    389      generating an exception. This is used for scripts that implement a
    390      subcommand syntax, so that we can generate the appropriate completions for
    391      the subcommand."""
    392
    393      import copy
    394      gparser = copy.deepcopy(gparser)
    395
    396      def print_usage_nousage(self, file=None):
    397          pass
    398      gparser.print_usage = print_usage_nousage
    399
    400      # save state to restore
    401      prev_interspersed = gparser.allow_interspersed_args
    402      gparser.disable_interspersed_args()
    403
    404      cwords = os.environ['COMP_WORDS'].split()
    405
    406      # save original error_func so we can put it back after the hack
    407      error_func = gparser.error
    408      try:
    409          try:
    410              instancemethod = type(OptionParser.error)
    411              # hack to keep OptionParser from wrinting to sys.stderr
    412              gparser.error = instancemethod(error_override,
    413                                             gparser, OptionParser)
    414              gopts, args = gparser.parse_args(cwords[1:])
    415          except SystemExit:
    416              return None
    417      finally:
    418          # undo the hack and restore original OptionParser error function
    419          gparser.error = instancemethod(error_func, gparser, OptionParser)
    420
    421      value = None
    422      if args:
    423          subcmdname = args[0]
    424          try:
    425              value = subcmds_map[subcmdname]
    426          except KeyError:
    427              pass
    428
    429      gparser.allow_interspersed_args = prev_interspersed  # restore state
    430
    431      return value  # can be None, indicates no command chosen.
    432
    433
    434 -class CmdComplete(object):
    435
    436      """Simple default base class implementation for a subcommand that supports
    437      command completion.  This class is assuming that there might be a method
    438      addopts(self, parser) to declare options for this subcommand, and an
    439      optional completer data member to contain command-specific completion.  Of
    440      course, you don't really have to use this, but if you do it is convenient
    441      to have it here."""
    442
    443 -    def autocomplete(self, completer):
    444          import logging
    445          logging.disable(logging.CRITICAL)
    446          import optparse
    447          parser = optparse.OptionParser(self.__doc__.strip())
    448          if hasattr(self, 'addopts'):
    449              self.addopts(parser)
    450          if hasattr(self, 'completer'):
    451              completer = self.completer
    452          logging.disable(logging.NOTSET)
    453          return autocomplete(parser, completer)
    454
    455
    456 -def test():
    457      print extract_word("extraire un mot d'une phrase", 11)
    458      print extract_word("extraire un mot d'une phrase", 12)
    459      print extract_word("extraire un mot d'une phrase", 13)
    460      print extract_word("extraire un mot d'une phrase", 14)
    461      print extract_word("extraire un mot d'une phrase", 0)
    462      print extract_word("extraire un mot d'une phrase", 28)
    463      print extract_word("extraire un mot d'une phrase", 29)
    464      print extract_word("extraire un mot d'une phrase", -2)
    465      print extract_word("optcomplete-test do", 19)
    466
    467  if __name__ == '__main__':
    468      test()
    469