git @ Cat's Eye Technologies klaus / master klaus / diff.py
master

Tree @master (Download .tar.gz)

diff.py @masterraw · history · blame

# -*- 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