Package svnmailer :: Package notifier :: Module _text

[hide private]

# Source Code for Module svnmailer.notifier._text

      1  # -*- coding: utf-8 -*-
      2  #
      3  # Copyright 2004-2006 André Malo or his licensors, as applicable
      4  #
      5  # Licensed under the Apache License, Version 2.0 (the "License");
      6  # you may not use this file except in compliance with the License.
      7  # You may obtain a copy of the License at
      8  #
      9  #     http://www.apache.org/licenses/LICENSE-2.0
     10  #
     11  # Unless required by applicable law or agreed to in writing, software
     12  # distributed under the License is distributed on an "AS IS" BASIS,
     13  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14  # See the License for the specific language governing permissions and
     15  # limitations under the License.
     16  """
     17  Plain text notifier base
     18  """
     19  __author__    = "André Malo"
     20  __docformat__ = "epytext en"
     21  __all__       = ['TextNotifier']
     22
     23  # global imports
     24  from svnmailer.notifier import _base
     25
     26
     27 -class TextNotifier(_base.BaseNotifier):
     28      """ Base class for plain text notifications
     29
     30          The derived class must implement the run method.
     31
     32          @cvar OUTPUT_SEPARATOR: the separator between headline and diff
     33          @type OUTPUT_SEPARATOR: C{str}
     34
     35          @cvar OUTPUT_SEPARATOR_LIGHT: the separator between headline and
     36              property diff
     37          @type OUTPUT_SEPARATOR_LIGHT: C{str}
     38
     39          @ivar fp: The file to write to
     40          @type fp: file like object
     41
     42          @ivar config: The group config
     43          @type config: C{svnmailer.settings.GroupSettingsContainer}
     44
     45          @ivar changeset: The list of changes to process
     46          @type changeset: C{list}
     47
     48          @ivar differ: The differ object
     49          @type differ: C{svnmailer.differ.*}
     50      """
     51      __implements__ = [_base.BaseNotifier]
     52
     53      OUTPUT_SEPARATOR = "=" * 78 + "\n"
     54      OUTPUT_SEPARATOR_LIGHT = "-" * 78 + "\n"
     55      fp = None
     56
     57
     58 -    def __init__(self, settings, groupset):
     59          """ Initialization """
     60          _base.BaseNotifier.__init__(self, settings, groupset)
     61          groups, self.changeset = (groupset.groups, groupset.changes)
     62          self.config = groups[0]
     63          self.differ = self.getDiffer(self.config.diff_command)
     64
     65
     66 -    def run(self):
     67          """ Runs the notifier (abstract method) """
     68          raise NotImplementedError()
     69
     70
     71 -    def getDate(self, oftime = None):
     72          """ Returns the revision date in a human readable format
     73
     74              @return: The date
     75              @rtype: C{str}
     76          """
     77          import time
     78
     79          # TODO: make timeformat configurable? (keep locale issues in mind!)
     80          return time.ctime(oftime or self.getTime())
     81
     82
     83 -    def writeRevPropData(self, raw = False):
     84          """ Writes the revision property change data
     85
     86              @param raw: Don't recode the property?
     87              @type raw: C{bool}
     88          """
     89          runtime  = self._settings.runtime
     90
     91          name     = runtime.propname.encode('utf-8')
     92          revision = runtime.revision
     93          action = {
     94              u'A': self.ADD,
     95              u'M': self.MODIFY,
     96              u'D': self.DELETE,
     97          }.get(runtime.action, None)
     98
     99          desc = {
    100              self.ADD:    "Added",
    101              self.MODIFY: "Modified",
    102              self.DELETE: "Deleted",
    103          }.get(action, "Modified")
    104
    105          self.fp.write(
    106              "Author: %s\nRevision: %d\n%s property: %s\n\n" % (
    107              self.getAuthor() or "(unknown)", revision, desc, name
    108          ))
    109
    110          value2 = runtime._repos.getRevisionProperty(revision, name)
    111
    112          # svn 1.2 vs. 1.1
    113          if action:
    114              import time
    115              value1 = (action == self.ADD and [''] or [runtime.stdin])[0]
    116              value2 = value2 or ''
    117              oftime = int(time.time())
    118              self.fp.write("%s: %s at %s\n" % (
    119                  desc, name, self.getDate(oftime)
    120              ))
    121              self.writePropertyDiff(
    122                  action, name, value1, value2, time = oftime, raw = raw
    123              )
    124          else:
    125              self.fp.write("New value:")
    126              if value2 is None:
    127                  self.fp.write(" (removed)\n")
    128              else:
    129                  self.fp.write("\n")
    130                  # TODO: make revision property charset configurable?
    131                  # iso-8859-1 for now (translates 1:1 to unicode)
    132                  if not self.isUTF8Property(name) and not raw:
    133                      value2 = value2.decode('iso-8859-1').encode('utf-8')
    134                  self.fp.write(value2)
    135
    136          self.fp.write("\n")
    137
    138
    139 -    def writeLockData(self):
    140          """ Writes the locking metadata """
    141          from svnmailer.settings import modes
    142          runtime  = self._settings.runtime
    143          is_locked = bool(runtime.mode == modes.lock)
    144
    145          self.fp.write("Author: %s" % (
    146              self.getAuthor() or "(unknown)",)
    147          )
    148
    149          if is_locked:
    150              self.fp.write("\nComment:")
    151              comment = self.changeset[0].getComment().strip()
    152              if comment:
    153                  self.fp.write("\n%s\n" % comment)
    154              else:
    155                  self.fp.write(" (empty)")
    156
    157          self.fp.write("\n%s paths:\n" % (is_locked and "Locked" or "Unlocked",))
    158
    159          for change in self.changeset:
    160              self.fp.write("   %s\n" % change.path)
    161
    162          self.fp.write("\n")
    163
    164
    165 -    def writeDiffList(self):
    166          """ Writes the commit diffs """
    167          self.fp.write("\n")
    168
    169          cset = self.changeset + (self._groupset.xchanges or [])
    170          tokens, tests = self.getDiffTokens(self.config)
    171          tokentests = zip(tokens, tests)
    172
    173          for change in cset:
    174              diff_content, diff_prop = False, False
    175              for token, test in tokentests:
    176                  if test(change):
    177                      if token == self.PROPCHANGE:
    178                          diff_prop = True
    179                      else:
    180                          diff_content = True
    181
    182              if diff_content:
    183                  self.writeContentDiff(change)
    184              if diff_prop:
    185                  self.writePropertyDiffs(tokens, change)
    186
    187
    188 -    def writePropertyDiffs(self, diff_tokens, change, raw = False):
    189          """ Writes the property diffs for a particular change
    190
    191              @param diff_tokens: The valid diff tokens
    192              @type diff_tokens: C{list}
    193
    194              @param change: The particular change to process
    195              @type change: C{svnmailer.subversion.VersionedPathDescriptor}
    196
    197              @param raw: Don't recode the properties?
    198              @type raw: C{bool}
    199          """
    200          if change.wasDeleted():
    201              return # don't bother
    202
    203          propdict = change.getModifiedProperties()
    204          propnames = propdict.keys()
    205          propnames.sort()
    206
    207          for name in propnames:
    208              values = propdict[name]
    209              token = self.writePropertyDiffAction(
    210                  change, name, values, diff_tokens
    211              )
    212
    213              if token in diff_tokens:
    214                  self.writePropertyDiff(
    215                      token, name, values[0], values[1], raw = raw
    216                  )
    217
    218              self.fp.write("\n")
    219
    220
    221 -    def writePropertyDiff(self, token, name, value1, value2, time = None,
    222                            raw = False):
    223          """ Writes a property diff
    224
    225              @param token: The diff token
    226              @type token: C{unicode}
    227
    228              @param name: The name of the property
    229              @type name: C{str}
    230
    231              @param value1: The raw old value
    232              @type value1: C{str}
    233
    234              @param value2: The raw new value
    235              @type value2: C{str}
    236
    237              @param time: Time to display in the diff description
    238                  in seconds since epoch
    239              @type time: C{int}
    240
    241              @param raw: Don't recode the properties?
    242              @type raw: C{bool}
    243          """
    244          self.fp.write(self.OUTPUT_SEPARATOR_LIGHT)
    245
    246          # TODO: make property charset configurable?
    247          # (iso-8859-1 was chosen for now, because it
    248          # translates 1:1 to unicode)
    249          if not self.isUTF8Property(name) and not raw:
    250              if value1:
    251                  value1 = value1.decode('iso-8859-1').encode('utf-8')
    252              if value2:
    253                  value2 = value2.decode('iso-8859-1').encode('utf-8')
    254
    255          # now throw something out
    256          if not self.isUTF8Property(name) and \
    257                  self.isBinaryProperty((value1, value2)):
    258              self.fp.write(
    259                  "Binary property '%s' - no diff available.\n" % name
    260              )
    261
    262          elif token == self.ADD and self.isOneLineProperty(name, value2):
    263              self.fp.write("    %s = %s\n" % (name, value2))
    264
    265          else:
    266              # avoid "no newline at end of file" for props
    267              if value1 and not value1.endswith("\n"):
    268                  value1 = "%s\n" % value1
    269              if value2 and not value2.endswith("\n"):
    270                  value2 = "%s\n" % value2
    271              self.writeDiff(token, name, name, value1, value2, time = time)
    272
    273
    274 -    def writePropertyDiffAction(self, change, name, values, diff_tokens):
    275          """ Writes the property diff action for a particular change
    276
    277              @param change: The particular change to process
    278              @type change: C{svnmailer.subversion.VersionedPathDescriptor}
    279
    280              @param name: The property name
    281              @type name: C{str}
    282
    283              @param values: The values of the property
    284              @type values: C{tuple}
    285
    286              @param diff_tokens: Valid diff tokens
    287              @type diff_tokens: C{list}
    288
    289              @return: diff token that should be applied
    290              @rtype: C{str}
    291          """
    292          token = self.getPropertyDiffAction(values)
    293          desc = {
    294              self.ADD   : "added",
    295              self.DELETE: "removed",
    296              self.MODIFY: "modified",
    297          }[token]
    298
    299          self.fp.write("Propchange: %s%s\n" % (
    300              change.path, ["", "/"][change.isDirectory()]
    301          ))
    302          if token not in diff_tokens:
    303              self.fp.write("            ('%s' %s)\n" % (name, desc))
    304
    305          return token
    306
    307
    308 -    def writeContentDiff(self, change, raw = False):
    309          """ Writes the content diff for a particular change
    310
    311              @param change: The particular change to process
    312              @type change: C{svnmailer.subversion.VersioendPathDescriptor}
    313
    314              @param raw: Prefer no recoding?
    315              @type raw: C{bool}
    316          """
    317          if change.isDirectory():
    318              # 'nuff said already
    319              return
    320
    321          token = self.writeContentDiffAction(change)
    322          if token is None:
    323              # nothing more to say
    324              return
    325
    326          config = self.config
    327          url = self.getContentDiffUrl(config, change)
    328          if url is not None:
    329              self.fp.write("URL: %s\n" % url)
    330
    331          self.fp.write(self.OUTPUT_SEPARATOR)
    332
    333          if (change.isBinary()):
    334              self.fp.write(
    335                  "Binary file%s - no diff available.\n" % ["s", ""][
    336                      (change.wasAdded() and not change.wasCopied()) or
    337                      change.wasDeleted()
    338                  ]
    339              )
    340          else:
    341              from svnmailer.settings import showenc
    342
    343              if raw:
    344                  default = False
    345                  enc = None
    346              else:
    347                  enc = config.apply_charset_property and \
    348                      self.ENC_CONFIG or self.ENC_DEFAULT
    349                  default = bool(config.show_applied_charset == showenc.yes)
    350
    351              file1, file2, rec1, rec2 = self.dumpContent(
    352                  change, enc = enc, default = default
    353              )
    354              if config.show_applied_charset == showenc.no:
    355                  rec1 = rec2 = None
    356
    357              self.writeDiff(token,
    358                  (change.wasCopied() and
    359                      [change.getBasePath()] or [change.path])[0],
    360                  change.path, file1.name, file2.name, isfile = True,
    361                  rec1 = rec1, rec2 = rec2
    362              )
    363
    364          self.fp.write("\n")
    365
    366
    367 -    def writeContentDiffAction(self, change):
    368          """ Writes the content diff action for a particular change
    369
    370              @param change: The particular change to process
    371              @type change: C{svnmailer.subversion.VersionedPathDescriptor}
    372
    373              @return: The diff token (maybe C{None})
    374              @rtype: C{str}
    375          """
    376          token = self.getContentDiffAction(change)
    377
    378          if token == self.MODIFY:
    379              self.fp.write("Modified: %s\n" % change.path)
    380          elif token == self.ADD:
    381              self.fp.write("Added: %s\n" % change.path)
    382          elif token == self.DELETE:
    383              self.fp.write("Removed: %s\n" % change.path)
    384          elif token == self.COPY:
    385              self.fp.write("Copied: %s (from r%d, %s)\n" % (
    386                  change.path,
    387                  change.getBaseRevision(),
    388                  change.getBasePath(),
    389              ))
    390
    391          return token
    392
    393
    394 -    def writeDiff(self, token, name1, name2, value1, value2, isfile = False,
    395                    rec1 = None, rec2 = None, time = None):
    396          """ Writes a diff
    397
    398              By default L{value1} and L{value2} are strings to diff,
    399              but if L{isfile} is set and C{True}, these are treated as names
    400              of files to diff.
    401
    402              @param token: The diff token
    403              @type token: C{unicode}
    404
    405              @param name1: The (faked) first filename
    406              @type name1: C{str}
    407
    408              @param name2: The (faked) second filename
    409              @type name2: C{str}
    410
    411              @param value1: The first value
    412              @type value1: C{str}
    413
    414              @param value2: The second value
    415              @type value2: C{str}
    416
    417              @param isfile: are the values file names?
    418              @type isfile: C{bool}
    419          """
    420          date1 = ["(original)", "(added)"][token == self.ADD]
    421          date2 = [self.getDate(time), "(removed)"][token == self.DELETE]
    422
    423          if rec1 and token != self.ADD:
    424              date1 = "[%s] %s" % (rec1, date1)
    425          if rec2 and token != self.DELETE:
    426              date2 = "[%s] %s" % (rec2, date2)
    427
    428          meth = [
    429              self.differ.getStringDiff, self.differ.getFileDiff
    430          ][bool(isfile)]
    431
    432          diff_empty = True
    433          for line in meth(value1, value2, name1, name2, date1, date2):
    434              diff_empty = False
    435              self.fp.write(line)
    436              if not line.endswith("\n"):
    437                  self.fp.write("\n")
    438
    439          if diff_empty:
    440              self.fp.write("    (empty)\n")
    441
    442
    443 -    def writePathList(self):
    444          """ Writes the commit path list """
    445          self._doWritePathList(self.changeset)
    446
    447          xset = self._groupset.xchanges
    448          if xset is not None:
    449              from svnmailer.settings import xpath
    450
    451              if xset:
    452                  self.fp.write(
    453                      "\nChanges in other areas also in this revision:\n"
    454                  )
    455                  self._doWritePathList(xset)
    456              elif self._groupset.groups[0].show_nonmatching_paths == xpath.no:
    457                  self.fp.write(
    458                      "\n(There are changes in other areas, but they are not "
    459                      "listed here.)\n"
    460                  )
    461
    462
    463 -    def _doWritePathList(self, cset):
    464          """ Write the path list of a particular changeset
    465
    466              @param cset: The changeset to process
    467              @type cset: C{list}
    468          """
    469          for title, changes in [(title, changes) for title, changes in (
    470              ("Added",    [chg for chg in cset if chg.wasAdded()]),
    471              ("Removed",  [chg for chg in cset if chg.wasDeleted()]),
    472              ("Modified", [chg for chg in cset if chg.wasModified()]),
    473          ) if changes]:
    474              self.fp.write("%s:\n" % title)
    475              for change in changes:
    476                  self.writePathInfo(change)
    477
    478
    479 -    def writePathInfo(self, change):
    480          """ Writes a short info about the kind of change
    481
    482              @param change: The change info
    483              @type change: C{svnmailer.subversion.VersionedPathDescriptor}
    484          """
    485          slash = ["", "/"][change.isDirectory()]
    486
    487          self.fp.write("    %s%s" % (change.path, slash))
    488          if change.hasPropertyChanges():
    489              self.fp.write("   (")
    490              if not change.wasAdded() or change.wasCopied():
    491                  self.fp.write("%sprops changed)" %
    492                      ["", "contents, "][change.hasContentChanges()]
    493                  )
    494              else:
    495                  self.fp.write("with props)")
    496
    497          if change.wasCopied():
    498              self.fp.write("\n      - copied")
    499              if not change.isDirectory():
    500                  self.fp.write(
    501                      [" unchanged", ", changed"][change.hasContentChanges()]
    502                  )
    503              self.fp.write(" from r%d, %s%s" % (
    504                  change.getBaseRevision(),
    505                  change.getBasePath(),
    506                  slash
    507              ))
    508
    509          self.fp.write("\n")
    510
    511
    512 -    def writeMetaData(self):
    513          """ Writes the commit metadata output """
    514          author = self.getAuthor() or "(unknown)"
    515          if self.config.extract_x509_author:
    516              from svnmailer import util
    517              x509 = util.extractX509User(author)
    518              if x509 and x509[0]:
    519                  author = x509[0]
    520
    521          self.fp.write("Author: %s\n" % author)
    522          self.fp.write("Date: %s\n" % self.getDate())
    523          self.fp.write("New Revision: %d\n" % self._settings.runtime.revision)
    524
    525          self.fp.write("\n")
    526
    527          url = self.getUrl(self.config)
    528          if url is not None:
    529              self.fp.write('URL: %s\n' % url)
    530
    531          log_entry = self.getLog() or ''
    532          self.fp.write(
    533              "Log:%s" % [" (empty)", "\n%s" % log_entry][bool(log_entry)]
    534          )
    535          if not log_entry.endswith("\n"):
    536              self.fp.write("\n")
    537
    538          self.fp.write("\n")
    539