1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 """
17 Plain text notifier base
18 """
19 __author__ = "André Malo"
20 __docformat__ = "epytext en"
21 __all__ = ['TextNotifier']
22
23
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
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
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
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
131
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
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
247
248
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
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
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
319 return
320
321 token = self.writeContentDiffAction(change)
322 if token is None:
323
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
539