1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 """
18 Base notifier class
19
20 @var EMPTY_TABLE: provides an empty translation table
21 @type EMPTY_TABLE: C{str}
22
23 @var CTRL_CHARS: provides a list of control chars (< ascii 32)
24 @type CTRL_CHARS: C{str}
25 """
26 __author__ = "André Malo"
27 __docformat__ = "epytext en"
28 __all__ = ['BaseNotifier']
29
30
31 from svnmailer import util
32
33 import string
34 EMPTY_TABLE = string.maketrans('', '')
35 CTRL_CHARS = ''.join([
36 chr(num) for num in range(32)
37 if chr(num) not in "\r\n\t\f"
38 ])
39 del string
40
41
46
50
54
58
62
64 """ not diffable """
65 return False
66
67
69 """ Base class for notifiers
70
71 Custom notifiers must implement this interface
72 (that is just the run method, however).
73
74 Additionally it contains some useful utility methods,
75 which can be used.
76
77 @ivar _settings: The settings to use
78 @type _settings: C{svnmailer.settings.Settings}
79
80 @ivar _groupset: The groupset to process
81 @type _groupset: C{list}
82
83 @ivar _penc_cache: The (path, rev) -> property encoding cache
84 @type _penc_cache: C{dict}
85
86 @cvar _diffable_tests: Map C{generate_diffs} list entrys to change
87 methods
88 @type _diffable_tests: C{tuple}
89
90 @cvar ADD: "add" token
91 @type ADD: C{unicode}
92
93 @cvar DELETE: "delete" token
94 @type DELETE: C{unicode}
95
96 @cvar COPY: "copy" token
97 @type COPY: C{unicode}
98
99 @cvar MODIFY: "modify" token
100 @type MODIFY: C{unicode}
101
102 @cvar PROPCHANGE: "propchange" token
103 @type PROPCHANGE: C{unicode}
104
105 @cvar NONE: "none" token
106 @type NONE: C{unicode}
107
108 @cvar ENC_CONFIG: magic value, meaning that the content encoding should
109 be retrieved from the config
110 @type ENC_CONFIG: C{str}
111
112 @cvar ENC_PROPERTY: The property name, where encodings could be stored
113 @type ENC_PROPERTY: C{str}
114 """
115 ADD = u"add"
116 DELETE = u"delete"
117 COPY = u"copy"
118 MODIFY = u"modify"
119 PROPCHANGE = u"propchange"
120 NONE = u"none"
121
122 ENC_CONFIG = "retrieve encoding from config"
123 ENC_DEFAULT = "show default encoding"
124 ENC_PROPERTY = "svnmailer:content-charset"
125
126 _diffable_tests = (
127 (ADD, addFunc),
128 (DELETE, delFunc),
129 (COPY, copyFunc),
130 (MODIFY, modFunc),
131 (PROPCHANGE, propFunc),
132 (NONE, noneFunc),
133 )
134
135
136 - def __init__(self, settings, groupset):
137 """ Initialization """
138 self._settings = settings
139 self._groupset = groupset
140 self._penc_cache = {}
141
142
144 """ Runs the notifier """
145 raise NotImplementedError()
146
147
161
162
172
173
183
184
197
198
200 """ Returns the initialized differ
201
202 @param command: The diff command to use (if any)
203 @type command: C{tuple} or C{None}
204
205 @return: The differ type
206 @rtype: C{svnmailer.differ.*}
207 """
208 from svnmailer import differ
209
210 if command:
211 return differ.ExternalDiffer(command, self.getTempDir())
212 else:
213 return differ.InternalDiffer()
214
215
217 """ Returns an open temporary file container object
218
219 @return: The filename and an descriptor
220 @rtype: C{svnmailer.util.TempFile}
221 """
222 return util.TempFile(tempdir = self.getTempDir(), text = False)
223
224
226 """ Returns the temporary directory
227
228 @return: The directory or C{None}
229 @rtype: C{unicode} or {str}
230 """
231 if not self._settings.general.tempdir:
232 return None
233
234 return self._settings.general.tempdir
235
236
238 """ Returns valid diff tokens and tests
239
240 @param config: group config
241 @type config: C{svnmailer.settings.GroupSettingsContainer}
242
243 @return: The diff tokens and diffable tests
244 The first element of the tuple contains a list
245 of diff tokens, the second element the diff tests
246 @rtype: C{tuple}
247 """
248 diff_tokens = config.generate_diffs
249 if not diff_tokens:
250 diff_tokens = diff_tokens is not None and ['none'] or []
251
252
253
254
255
256 diff_test_list = [(token.lower(), test)
257 for token, test in self._diffable_tests
258 if token.lower() in diff_tokens
259 ] or self._diffable_tests
260
261 diff_tokens = []
262 diff_tests = []
263 for token, test in diff_test_list:
264 if token != self.NONE:
265 diff_tokens.append(token)
266 diff_tests.append(test)
267
268 return (diff_tokens, diff_tests)
269
270
271 - def dumpContent(self, change, enc = 'utf-8', default = False):
272 """ Dump the two revisions of a particular change
273
274 (This dumps the files, not the properties)
275
276 @param change: The particular change to process
277 @type change: C{svnmailer.subversion.VersionedPathDescriptor}
278
279 @param enc: The file data encoding (The data will be recoded
280 to UTF-8; but by default it isn't recoded, because utf-8
281 is assumed)
282 @type enc: C{str}
283
284 @param default: Return the default encoding (iso-8859-1) if the
285 determined is C{None}
286 @type default: C{bool}
287
288 @return: Two file container objects plus their recoding state
289 (file1, file2, rec1, rec2), where rec? is either the
290 accompanying original encoding or C{None}
291 @rtype: C{tuple}
292 """
293 from svnmailer import stream
294
295 rec1 = rec2 = None
296 if enc not in (self.ENC_CONFIG, self.ENC_DEFAULT):
297 rec1 = rec2 = enc1 = enc2 = enc
298 else:
299 if enc == self.ENC_CONFIG:
300 enc1, enc2 = self.getContentEncodings(change)
301 rec1, rec2 = (enc1, enc2)
302 enc1 = enc1 or 'iso-8859-1'
303 enc2 = enc2 or 'iso-8859-1'
304 if rec1 and not rec2:
305 rec2 = enc2
306 elif rec2 and not rec1:
307 rec1 = enc1
308 elif enc == self.ENC_DEFAULT:
309 enc1 = enc2 = 'iso-8859-1'
310
311 if default:
312 rec1 = rec1 or enc1
313 rec2 = rec2 or enc2
314
315 file1 = self.getTempFile()
316 if not change.wasAdded() or change.wasCopied():
317 fp = (enc1 and enc1.lower() != 'utf-8') and \
318 stream.UnicodeStream(file1.fp, enc1) or file1.fp
319 self._settings.runtime._repos.dumpPathContent(
320 fp, change.getBasePath(), change.getBaseRevision()
321 )
322 file1.close()
323
324 file2 = self.getTempFile()
325 if not change.wasDeleted():
326 fp = (enc2 and enc2.lower() != 'utf-8') and \
327 stream.UnicodeStream(file2.fp, enc2) or file2.fp
328 self._settings.runtime._repos.dumpPathContent(
329 fp, change.path, change.revision
330 )
331 file2.close()
332
333 return (file1, file2, rec1, rec2)
334
335
336 - def getContentEncodings(self, change, default = None):
337 """ Returns the encodings of the change content (base and current rev)
338
339 @param change: The change to process
340 @type change: C{svnmailer.subversion.VersionedPathDescriptor}
341
342 @param default: The default encoding, if nothing is specified
343 @type default: C{str}
344
345 @return: The two encodings
346 @rtype: C{tuple} of C{str}
347 """
348 enc1 = enc2 = default
349 if not change.wasAdded() or change.wasCopied():
350 try:
351 enc1 = self._getContentEncoding(
352 change.getBasePath(), change.getBaseRevision()
353 )
354 except LookupError:
355
356 pass
357
358 if change.wasDeleted():
359 enc2 = enc1
360 else:
361 try:
362 enc2 = self._getContentEncoding(
363 change.path, change.revision
364 )
365 except LookupError:
366
367 pass
368
369 if change.wasAdded() and not change.wasCopied():
370 enc1 = enc2
371
372 return (enc1, enc2)
373
374
375 - def _getContentEncoding(self, path, revision):
376 """ Returns the encoding for the specified path and revision
377
378 @param path: The path
379 @type path: C{str}
380
381 @param revision: The revision number
382 @type revision: C{int}
383
384 @return: The encoding
385 @rtype: C{str}
386
387 @exception LookupError: The specified encoding
388 is not implemented or no encoding was specified
389 """
390
391 enc = self.getEncodingFromMimeType(path, revision)
392 if enc:
393 enc = enc.strip()
394
395
396 if not enc:
397 enc = self.getContentEncodingProperty(path, revision)
398 if enc:
399 enc = enc.strip()
400
401
402 if not enc:
403 globs = []
404 for dirpath in util.getParentDirList(path):
405 globlist = self.getContentEncodingProperty(dirpath, revision)
406 if globlist:
407 globs.extend([(glob.strip(), supp_enc.strip())
408 for glob, supp_enc in [
409 glob.split('=', 1)
410 for glob in globlist.splitlines(False)
411 if glob and not glob.lstrip()[:1] == '#'
412 and '=' in glob
413 ] if glob.strip() and supp_enc.strip()
414 ])
415
416 if path[:1] != '/':
417 path = "/%s" % path
418 enc = util.getGlobValue(globs, path)
419
420 if enc:
421
422 import codecs
423 codecs.lookup(enc)
424 return enc
425
426 raise LookupError("No Encoding configured")
427
428
430 """ Returns the encoding extracted from svn:mime-type
431
432 @param path: The path
433 @type path: C{str}
434
435 @param revision: The revision number
436 @type revision: C{int}
437
438 @return: The encoding or C{None}
439 @rtype: C{str}
440 """
441 result = None
442 repos = self._settings.runtime._repos
443 mtype = repos.getPathMimeType(path, revision)
444
445 if mtype:
446 parsed = util.parseContentType(mtype)
447 enc = parsed and parsed[1].get('charset')
448 if enc and len(enc) == 1:
449 result = enc[0]
450
451 return result
452
453
454 - def getContentEncodingProperty(self, path, revision):
455 """ Returns the content encoding property for a path/rev
456
457 @param path: The path
458 @type path: C{str}
459
460 @param revision: The revision number
461 @type revision: C{int}
462
463 @return: The encoding or C{None}
464 @rtype: C{str}
465 """
466 try:
467 result = self._penc_cache[(path, revision)]
468 except KeyError:
469 repos = self._settings.runtime._repos
470 result = repos.getPathProperty(self.ENC_PROPERTY, path, revision)
471 if result is not None:
472 try:
473 result = result.decode('utf-8')
474 except UnicodeError:
475
476 result = result.decode('iso-8859-1')
477 result = result.encode('utf-8')
478
479 self._penc_cache[(path, revision)] = result
480
481 return result
482
483
484 - def getContentDiffUrl(self, config, change):
485 """ Returns the content diff url for a particular change
486
487 @param config: group config
488 @type config: C{svnmailer.settings.GroupSettingsContainer}
489
490 @param change: The particular change to process
491 @type change: C{svnmailer.subversion.VersionedPathDescriptor}
492
493 @return: The URL or C{None} if there's no base URL configured
494 @rtype: C{str}
495 """
496 from svnmailer import browser
497
498 generator = browser.getBrowserUrlGenerator(config)
499 if generator:
500 return generator.getContentDiffUrl(change)
501
502
504 """ Returns if the supplied property name represents an UTF-8 property
505
506 @param name: The property name
507 @type name: C{str}
508
509 @return: The decision
510 @rtype: C{bool}
511 """
512 from svnmailer import subversion
513 return subversion.isUnicodeProperty(name)
514
515
517 """ Returns if the supplied property seems to be binary
518
519 Note that is a very rudimentary check, just to not
520 pollute diff output with garbage
521
522 @param values: The value tuple
523 @type values: C{tuple}
524
525 @return: binary property?
526 @rtype: C{bool}
527 """
528 for value in values:
529 if value is None:
530 continue
531
532
533 if value != value.translate(EMPTY_TABLE, CTRL_CHARS):
534 return True
535
536
537 if len(value) > 255 and "\n" not in value[:255]:
538 return True
539
540
541 return False
542
543
545 """ Returns if the supplied property value takes just one line
546
547 @param value: The property value
548 @type value: C{str}
549
550 @param name: Property name
551 @type name: C{str}
552
553 @return: one line property?
554 @rtype: C{bool}
555 """
556
557 return bool(len(name + value) <= 75 and value.find("\n") == -1)
558
559
560 - def getContentDiffAction(self, change):
561 """ Returns the content diff action for a particular change
562
563 @param change: The particular change to process
564 @type change: C{svnmailer.subversion.VersionedPathDescriptor}
565
566 @return: The diff token or C{None} if there nothing
567 @rtype: C{unicode}
568 """
569 if change.wasDeleted():
570 return self.DELETE
571 else:
572 if change.wasCopied():
573 return self.COPY
574 elif change.wasAdded():
575 return self.ADD
576 elif change.hasContentChanges():
577 return self.MODIFY
578
579 return None
580
581
583 """ Returns the property diff action for a particular change
584
585 @param values: The two values of the property
586 @type values: C{tuple}
587
588 @return: The diff token
589 @rtype: C{unicode}
590 """
591 if values[0] is None:
592 return self.ADD
593 elif values[1] is None:
594 return self.DELETE
595
596 return self.MODIFY
597