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