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