File: diff.py

package info (click to toggle)
klaus 2.0.3-1
  • links: PTS, VCS
  • area: main
  • in suites: sid, trixie
  • size: 464 kB
  • sloc: python: 2,091; javascript: 56; makefile: 10
file content (84 lines) | stat: -rw-r--r-- 2,605 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
# -*- coding: utf-8 -*-
"""
    lodgeit.lib.diff
    ~~~~~~~~~~~~~~~~

    Render a nice diff between two things.

    :copyright: 2007 by Armin Ronacher.
    :license: BSD
"""

from difflib import SequenceMatcher
from klaus.utils import escape_html as e


def highlight_line(old_line, new_line):
    """Highlight inline changes in both lines."""
    start = 0
    limit = min(len(old_line), len(new_line))
    while start < limit and old_line[start] == new_line[start]:
        start += 1
    end = -1
    limit -= start
    while -end <= limit and old_line[end] == new_line[end]:
        end -= 1
    end += 1
    if start or end:

        def do(l, tag):
            last = end + len(l)
            return b"".join(
                [l[:start], b"<", tag, b">", l[start:last], b"</", tag, b">", l[last:]]
            )

        old_line = do(old_line, b"del")
        new_line = do(new_line, b"ins")
    return old_line, new_line


def render_diff(a, b, n=3):
    """Parse the diff an return data for the template."""
    actions = []
    chunks = []
    for group in SequenceMatcher(None, a, b).get_grouped_opcodes(n):
        old_line, old_end, new_line, new_end = (
            group[0][1],
            group[-1][2],
            group[0][3],
            group[-1][4],
        )
        lines = []

        def add_line(old_lineno, new_lineno, action, line):
            actions.append(action)
            lines.append(
                {
                    "old_lineno": old_lineno,
                    "new_lineno": new_lineno,
                    "action": action,
                    "line": line,
                    "no_newline": not line.endswith(b"\n"),
                }
            )

        chunks.append(lines)
        for tag, i1, i2, j1, j2 in group:
            if tag == "equal":
                for c, line in enumerate(a[i1:i2]):
                    add_line(i1 + c, j1 + c, "unmod", e(line))
            elif tag == "insert":
                for c, line in enumerate(b[j1:j2]):
                    add_line(None, j1 + c, "add", e(line))
            elif tag == "delete":
                for c, line in enumerate(a[i1:i2]):
                    add_line(i1 + c, None, "del", e(line))
            elif tag == "replace":
                for c, line in enumerate(a[i1:i2]):
                    add_line(i1 + c, None, "del", e(line))
                for c, line in enumerate(b[j1:j2]):
                    add_line(None, j1 + c, "add", e(line))
            else:
                raise AssertionError("unknown tag %s" % tag)

    return actions.count("add"), actions.count("del"), chunks