Woblink
[redakcja.git] / src / wiki / nice_diff.py
1 # This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later.
2 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
3 #
4 import difflib
5 import re
6 from collections import deque
7
8 from django.template.loader import render_to_string
9 from django.utils.html import escape as html_escape
10
11 DIFF_RE = re.compile(r"""\x00([+^-])""", re.UNICODE)
12 NAMES = {'+': 'added', '-': 'removed', '^': 'changed'}
13
14
15 def diff_replace(match):
16     return """<span class="diff_mark diff_mark_%s">""" % NAMES[match.group(1)]
17
18
19 def filter_line(line):
20     return  DIFF_RE.sub(diff_replace, html_escape(line)).replace('\x01', '</span>')
21
22
23 def format_changeset(a, b, change):
24     return (a[0], filter_line(a[1]), b[0], filter_line(b[1]), change)
25
26
27 def html_diff_table(la, lb, context=None):
28     all_changes = difflib._mdiff(la, lb)
29
30     if context is None:
31         changes = (format_changeset(*c) for c in all_changes)
32     else:
33         changes = []
34         q = deque()
35         after_change = False
36
37         for changeset in all_changes:
38             q.append(changeset)
39
40             if changeset[2]:
41                 after_change = True
42                 if not after_change:
43                     changes.append((0, '-----', 0, '-----', False))
44                 changes.extend(format_changeset(*c) for c in q)
45                 q.clear()
46             else:
47                 if len(q) == context and after_change:
48                     changes.extend(format_changeset(*c) for c in q)
49                     q.clear()
50                     after_change = False
51                 elif len(q) > context:
52                     q.popleft()
53
54     return render_to_string("wiki/diff_table.html", {
55         "changes": changes,
56     })
57
58
59 __all__ = ['html_diff_table']