Package svnmailer :: Module cli

[hide private]

# Source Code for Module svnmailer.cli

      1  # -*- coding: utf-8 -*-
      2  # pylint: disable-msg = C0103, W0221
      3  #
      4  # Copyright 2004-2006 André Malo or his licensors, as applicable
      5  #
      6  # Licensed under the Apache License, Version 2.0 (the "License");
      7  # you may not use this file except in compliance with the License.
      8  # You may obtain a copy of the License at
      9  #
     10  #     http://www.apache.org/licenses/LICENSE-2.0
     11  #
     12  # Unless required by applicable law or agreed to in writing, software
     13  # distributed under the License is distributed on an "AS IS" BASIS,
     14  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     15  # See the License for the specific language governing permissions and
     16  # limitations under the License.
     17  """
     18  Command line interface
     19  ======================
     20
     21  The svnmailer provides two different command line interfaces. On the one hand
     22  there's the compatibility command line to the mailer.py script, which has some
     23  limitations and problems because of its unflexibility. On the other hand
     24  you'll find the new-style command line, which contains no subcommands and
     25  fixed parameters at all.
     26
     27  The CLI simply transforms old-style command lines to the new format internally
     28  and processes these further using the optparse module::
     29
     30      svn-mailer commit <rep> <rev> [<config>]
     31   -> svn-mailer --commit --repository <rep> --revision <rev>
     32                [--config <config>]
     33
     34      svn-mailer propchange <rep> <rev> <author> <prop> [<conf>]
     35   -> svn-mailer --propchange --repository <rep> --revision <rev>
     36                 --author <author> --propname <prop>
     37                [--config <conf>]
     38
     39      # (useful with svn 1.2 and later)
     40      svn-mailer propchange2 <rep> <rev> <author> <prop> <action> [<conf>]
     41   -> svn-mailer --propchange --repository <rep> --revision <rev>
     42                 --author <author> --propname <prop> --action <action>
     43                [--config <conf>]
     44
     45      svn-mailer lock <rep> <author> [<conf>]
     46   -> svn-mailer --lock --repository <rep> --author <author>
     47                [--config <conf>]
     48
     49      svn-mailer unlock <rep> <author> [<conf>]
     50   -> svn-mailer --unlock --repository <rep> --author <author>
     51                [--config <conf>]
     52  """
     53  __author__    = "André Malo"
     54  __docformat__ = "epytext en"
     55  __all__       = ['getOptions', 'CommandlineError']
     56
     57  # global imports
     58  import optparse
     59
     60  # Exceptions
     61 -class Error(Exception):
     62      """ Base exception for this module """
     63      pass
     64
     65 -class CommandlineError(Error):
     66      """ Error in commandline """
     67      pass
     68
     69
     70 -def getOptions(argv = None):
     71      """ Parse commandline options
     72
     73          @param argv: Command line list. If argv is None,
     74              sys.argv[1:] is evaluated instead
     75          @type argv: C{list}
     76
     77          @return: option object
     78          @rtype: C{optparse.OptionParser}
     79
     80          @exception CommandlineError: Error in command line options
     81      """
     82      from svnmailer import util
     83      from svnmailer.settings import modes
     84
     85      usage = """%prog <options>"""
     86      parser = SvnmailerOptionParser(usage = usage, version = True)
     87      options, args = parser.parse_args(argv)
     88
     89      # Check parameter consistency
     90      if args:
     91          raise CommandlineError("Too much arguments")
     92
     93      if not options.repository:
     94          raise CommandlineError("Missing repository path")
     95
     96      if not options.revision:
     97          if options.mode in (modes.commit, modes.propchange):
     98              raise CommandlineError("Missing revision number")
     99
    100      if not options.author:
    101          if options.mode in (modes.propchange, modes.lock, modes.unlock):
    102              raise CommandlineError("Missing author parameter")
    103
    104      if not options.propname:
    105          if options.mode == modes.propchange:
    106              raise CommandlineError("Missing property name parameter")
    107
    108      # de-localize the paths
    109      try:
    110          options.repository = util.filename.fromLocale(
    111              options.repository, options.path_encoding
    112          )
    113      except UnicodeError, exc:
    114          raise CommandlineError("--repository recode problem: %s" % str(exc))
    115
    116      if options.config:
    117          try:
    118              options.config = util.filename.fromLocale(
    119                  options.config, options.path_encoding
    120              )
    121          except UnicodeError, exc:
    122              raise CommandlineError("--config recode problem: %s" % str(exc))
    123
    124      return options
    125
    126
    127 -class SvnmailerOptionParser(optparse.OptionParser):
    128      """ Fully initialized option parser
    129
    130          @ivar _svn: The svn version
    131          @type _svn: C{tuple}
    132      """
    133
    134 -    def __init__(self, *args, **kwargs):
    135          """ Initialization """
    136          from svnmailer import subversion
    137
    138          self._svn = subversion.version
    139          optparse.OptionParser.__init__(self, *args, **kwargs)
    140          self._initSvnmailer()
    141
    142
    143 -    def parse_args(self, args = None, *other_args, **kwargs):
    144          """ Accepts also the old command line """
    145          args = self._transformSvnmailerOldStyle(args)
    146          if not args:
    147              raise CommandlineError(
    148                  "Type '%s --help' for usage" % self.get_prog_name()
    149              )
    150
    151          options, fixed = optparse.OptionParser.parse_args(
    152              self, args, *other_args, **kwargs
    153          )
    154
    155          # fixup action attribute (expected later)
    156          if not self._svn.min_1_2:
    157              options.action = None
    158
    159          return (options, fixed)
    160
    161
    162 -    def error(self, msg):
    163          """ We raise an exception instead of exiting
    164
    165              @param msg: The error message
    166              @type msg: C{str}
    167
    168              @exception CommandlineError: command line error
    169          """
    170          raise CommandlineError(str(msg))
    171
    172
    173 -    def get_version(self):
    174          """ Returns the version string """
    175          from svnmailer import version
    176
    177          return "svnmailer-%s\nwith svn %d.%d.%d%s" % (
    178              version, self._svn.major, self._svn.minor, self._svn.patch,
    179              self._svn.tag
    180          )
    181
    182
    183 -    def get_prog_name(self):
    184          """ Returns the program name """
    185          try:
    186              # >= python 2.4
    187              return optparse.OptionParser.get_prog_name(self)
    188          except AttributeError:
    189              try:
    190                  # >= python 2.3.4
    191                  return optparse.OptionParser._get_prog_name(self)
    192              except AttributeError:
    193                  # <= python 2.3.3
    194                  if self.prog:
    195                      return self.prog
    196                  else:
    197                      import os, sys
    198                      return os.path.basename(sys.argv[0])
    199
    200
    201 -    def format_help(self, formatter = None):
    202          """ Adds a description of the old style options """
    203          import textwrap
    204
    205          width = (self._getTerminalWidth() or 80) - 1
    206          optionhelp = None
    207          while optionhelp is None:
    208              try:
    209                  formatter = optparse.IndentedHelpFormatter(width = width)
    210                  optionhelp = optparse.OptionParser.format_help(self, formatter)
    211              except ValueError:
    212                  # terminal too small *sigh*
    213                  if width < 79:
    214                      width = 79
    215                  else:
    216                      width += 10
    217
    218          oldstyle = textwrap.fill(
    219              "Alternatively you can use the old style compatibility "
    220              "command lines (options described above don't apply then):",
    221              width = width,
    222          )
    223
    224          prog = self.get_prog_name()
    225          indent = " " * (len(prog) + 1)
    226          clines = [
    227              "",
    228              "%(prog)s commit <repos> <revision> [<config>]",
    229              "%(prog)s propchange <repos> <revision> <author> <propname> "
    230                       "[<config>]",
    231          ]
    232          if self._svn.min_1_2:
    233              clines.extend([
    234                  "",
    235                  "svn 1.2 and later:",
    236                  "%(prog)s propchange2 <repos> <revision> <author> <propname> "
    237                           "<action> [<config>]",
    238                  "%(prog)s lock <repos> <author> [<config>]",
    239                  "%(prog)s unlock <repos> <author> [<config>]",
    240              ])
    241          clines = ["%s\n" % textwrap.fill(
    242              line % {'prog': prog}, width = width, subsequent_indent = indent
    243          ) for line in clines]
    244
    245          return "%s\n%s\n%s" % (optionhelp, oldstyle, ''.join(clines))
    246
    247
    248 -    def _getTerminalWidth(self):
    249          """ Returns terminal width if determined, None otherwise
    250
    251              @return: The width
    252              @rtype: C{int}
    253          """
    254          try:
    255              import errno, fcntl, struct, sys, termios
    256
    257              def getwidth(fd):
    258                  """ Returns the width for descriptor fd """
    259                  # struct winsize { /* on linux in asm/termios.h */
    260                  #     unsigned short ws_row;
    261                  #     unsigned short ws_col;
    262                  #     unsigned short ws_xpixel;
    263                  #     unsigned short ws_ypixel;
    264                  # }
    265                  return struct.unpack("4H", fcntl.ioctl(
    266                      fd, termios.TIOCGWINSZ, struct.pack("4H", 0, 0, 0, 0)
    267                  ))[1]
    268
    269              try:
    270                  return getwidth(sys.stdout.fileno())
    271              except IOError, exc:
    272                  if exc[0] == errno.EINVAL:
    273                      return getwidth(sys.stdin.fileno())
    274                  raise # otherwise
    275
    276          except (SystemExit, KeyboardInterrupt):
    277              raise
    278
    279          except:
    280              # don't even ignore
    281              pass
    282
    283          return None
    284
    285
    286 -    def _initSvnmailer(self):
    287          """ Builds the options from option groups """
    288          self._addSvnmailerCommonOptions()
    289          self._addSvnmailerBehaviourOptions()
    290          self._addSvnmailerSupplementalOptions()
    291
    292
    293 -    def _addSvnmailerCommonOptions(self):
    294          """ Adds the common options group """
    295          common_options = optparse.OptionGroup(
    296              self, 'COMMON PARAMETERS'
    297          )
    298          common_options.add_option('--debug',
    299              action = 'store_true',
    300              default = False,
    301              help = "Run in debug mode (means basically that all messages "
    302                  "are sent to STDOUT)",
    303          )
    304          common_options.add_option('-d', '--repository',
    305              help = 'The repository directory',
    306          )
    307          common_options.add_option('-f', '--config',
    308              help = 'The configuration file',
    309          )
    310          common_options.add_option('-e', '--path-encoding',
    311              help = 'Specifies the character encoding to be used for '
    312                  'filenames. By default the encoding is tried to be '
    313                  'determined automatically depending on the locale.'
    314          )
    315          self.add_option_group(common_options)
    316
    317
    318 -    def _addSvnmailerBehaviourOptions(self):
    319          """ Adds the behaviour options group """
    320          from svnmailer.settings import modes
    321
    322          behaviour_options = optparse.OptionGroup(
    323              self, 'BEHAVIOUR OPTIONS',
    324              description = "The behaviour options are mutually exclusive, "
    325                  "i.e. the last one wins."
    326          )
    327          behaviour_options.add_option('-c', '--commit',
    328              dest = 'mode',
    329              action = 'store_const',
    330              const = modes.commit,
    331              default = modes.commit,
    332              help = 'This is a regular commit of versioned data '
    333                     '(post-commit hook). This is default.',
    334          )
    335          behaviour_options.add_option('-p', '--propchange',
    336              dest = 'mode',
    337              action = 'store_const',
    338              const = modes.propchange,
    339              help = 'This is a modification of unversioned properties '
    340                  '(post-revprop-change hook)',
    341          )
    342
    343          if self._svn.min_1_2:
    344              behaviour_options.add_option('-l', '--lock',
    345                  dest = 'mode',
    346                  action = 'store_const',
    347                  const = modes.lock,
    348                  help = '(svn 1.2 and later) This is a locking call '
    349                      '(post-lock hook)',
    350              )
    351              behaviour_options.add_option('-u', '--unlock',
    352                  dest = 'mode',
    353                  action = 'store_const',
    354                  const = modes.unlock,
    355                  help = '(svn 1.2 and later) This is a unlocking call '
    356                      '(post-unlock hook)',
    357              )
    358
    359          self.add_option_group(behaviour_options)
    360
    361
    362 -    def _addSvnmailerSupplementalOptions(self):
    363          """ Adds the supplemental options """
    364          supp_options = optparse.OptionGroup(
    365              self, 'SUPPLEMENTAL PARAMETERS'
    366          )
    367
    368          supp_options.add_option('-r', '--revision',
    369              action = 'store',
    370              type = 'int',
    371              help = 'The modified/committed revision number',
    372          )
    373          supp_options.add_option('-a', '--author',
    374              help = 'The author of the modification',
    375          )
    376          supp_options.add_option('-n', '--propname',
    377              help = 'The name of the modified property',
    378          )
    379
    380          if self._svn.min_1_2:
    381              supp_options.add_option('-o', '--action',
    382                  help = '(svn 1.2 and later) The property change action',
    383              )
    384
    385          self.add_option_group(supp_options)
    386
    387
    388 -    def _transformSvnmailerOldStyle(self, argv):
    389          """ Parses the command line according to old style rules
    390
    391              @param argv: Command line list. If argv is None,
    392                  sys.argv[1:] is evaluated instead
    393              @type argv: C{list}
    394
    395              @return: The commandline, possibly transformed to new style
    396              @rtype: C{list}
    397          """
    398          if argv is None:
    399              import sys
    400              argv = sys.argv[1:]
    401
    402          if argv:
    403              length = len(argv)
    404
    405              # svn-mailer commit <rep> <rev> [<conf>]
    406              if argv[0] == "commit" and 3 <= length <= 4:
    407                  config = argv[3:]
    408                  argv = ["--commit",
    409                          "--repository", argv[1], "--revision", argv[2],
    410                  ]
    411                  if config:
    412                      argv.extend(["--config", config[0]])
    413
    414              # svn-mailer propchange <rep> <rev> <author> <prop> [<conf>]
    415              elif argv[0] == "propchange" and 5 <= length <= 6:
    416                  config = argv[5:]
    417                  argv = ["--propchange",
    418                          "--repository", argv[1], "--revision", argv[2],
    419                          "--author", argv[3], "--propname", argv[4],
    420                  ]
    421                  if config:
    422                      argv.extend(["--config", config[0]])
    423
    424              else:
    425                  if self._svn.min_1_2:
    426                      # svn-mailer propchange2 <rep> <rev> <author> <prop> <act>
    427                      #            [<conf>]
    428                      if argv[0] == "propchange2" and 6 <= length <= 7:
    429                          config = argv[6:]
    430                          argv = ["--propchange",
    431                                  "--repository", argv[1], "--revision", argv[2],
    432                                  "--author", argv[3], "--propname", argv[4],
    433                                  "--action", argv[5],
    434                          ]
    435                          if config:
    436                              argv.extend(["--config", config[0]])
    437
    438                      # svn-mailer lock <rep> <author> [<conf>]
    439                      # svn-mailer unlock <rep> <author> [<conf>]
    440                      elif argv[0] in ("lock", "unlock") and 3 <= length <= 4:
    441                          config = argv[3:]
    442                          argv = ["--%s" % argv[0],
    443                                  "--repository", argv[1], "--author", argv[2]
    444                          ]
    445                          if config:
    446                              argv.extend(["--config", config[0]])
    447
    448          return argv
    449