191 lines
5.3 KiB
Python
191 lines
5.3 KiB
Python
# -*-python-*-
|
|
#
|
|
# Copyright (C) 1999-2006 The ViewCVS Group. All Rights Reserved.
|
|
#
|
|
# By using this file, you agree to the terms and conditions set forth in
|
|
# the LICENSE.html file which can be found at the top level of the ViewVC
|
|
# distribution or at http://viewvc.org/license-1.html.
|
|
#
|
|
# For more information, visit http://viewvc.org/
|
|
#
|
|
# -----------------------------------------------------------------------
|
|
#
|
|
# idiff: display differences between files highlighting intraline changes
|
|
#
|
|
# -----------------------------------------------------------------------
|
|
|
|
from __future__ import generators
|
|
|
|
import difflib
|
|
import sys
|
|
import re
|
|
import ezt
|
|
import cgi
|
|
|
|
def sidebyside(fromlines, tolines, context):
|
|
"""Generate side by side diff"""
|
|
|
|
### for some reason mdiff chokes on \n's in input lines
|
|
line_strip = lambda line: line.rstrip("\n")
|
|
fromlines = map(line_strip, fromlines)
|
|
tolines = map(line_strip, tolines)
|
|
|
|
gap = False
|
|
for fromdata, todata, flag in difflib._mdiff(fromlines, tolines, context):
|
|
if fromdata is None and todata is None and flag is None:
|
|
gap = True
|
|
else:
|
|
from_item = _mdiff_split(flag, fromdata)
|
|
to_item = _mdiff_split(flag, todata)
|
|
yield _item(gap=ezt.boolean(gap), columns=(from_item, to_item))
|
|
gap = False
|
|
|
|
_re_mdiff = re.compile("\0([+-^])(.*?)\1")
|
|
|
|
def _mdiff_split(flag, (line_number, text)):
|
|
"""Break up row from mdiff output into segments"""
|
|
segments = []
|
|
pos = 0
|
|
while True:
|
|
m = _re_mdiff.search(text, pos)
|
|
if not m:
|
|
segments.append(_item(text=cgi.escape(text[pos:]), type=None))
|
|
break
|
|
|
|
if m.start() > pos:
|
|
segments.append(_item(text=cgi.escape(text[pos:m.start()]), type=None))
|
|
|
|
if m.group(1) == "+":
|
|
segments.append(_item(text=cgi.escape(m.group(2)), type="add"))
|
|
elif m.group(1) == "-":
|
|
segments.append(_item(text=cgi.escape(m.group(2)), type="remove"))
|
|
elif m.group(1) == "^":
|
|
segments.append(_item(text=cgi.escape(m.group(2)), type="change"))
|
|
|
|
pos = m.end()
|
|
|
|
return _item(segments=segments, line_number=line_number)
|
|
|
|
def unified(fromlines, tolines, context):
|
|
"""Generate unified diff"""
|
|
|
|
diff = difflib.Differ().compare(fromlines, tolines)
|
|
lastrow = None
|
|
|
|
for row in _trim_context(diff, context):
|
|
if row[0].startswith("? "):
|
|
yield _differ_split(lastrow, row[0])
|
|
lastrow = None
|
|
else:
|
|
if lastrow:
|
|
yield _differ_split(lastrow, None)
|
|
lastrow = row
|
|
|
|
if lastrow:
|
|
yield _differ_split(lastrow, None)
|
|
|
|
def _trim_context(lines, context_size):
|
|
"""Trim context lines that don't surround changes from Differ results
|
|
|
|
yields (line, leftnum, rightnum, gap) tuples"""
|
|
|
|
# circular buffer to hold context lines
|
|
context_buffer = [None] * (context_size or 0)
|
|
context_start = context_len = 0
|
|
|
|
# number of context lines left to print after encountering a change
|
|
context_owed = 0
|
|
|
|
# current line numbers
|
|
leftnum = rightnum = 0
|
|
|
|
# whether context lines have been dropped
|
|
gap = False
|
|
|
|
for line in lines:
|
|
row = save = None
|
|
|
|
if line.startswith("- "):
|
|
leftnum = leftnum + 1
|
|
row = line, leftnum, None
|
|
context_owed = context_size
|
|
|
|
elif line.startswith("+ "):
|
|
rightnum = rightnum + 1
|
|
row = line, None, rightnum
|
|
context_owed = context_size
|
|
|
|
else:
|
|
if line.startswith(" "):
|
|
leftnum = leftnum = leftnum + 1
|
|
rightnum = rightnum = rightnum + 1
|
|
if context_owed > 0:
|
|
context_owed = context_owed - 1
|
|
elif context_size is not None:
|
|
save = True
|
|
|
|
row = line, leftnum, rightnum
|
|
|
|
if save:
|
|
# don't yield row right away, store it in buffer
|
|
context_buffer[(context_start + context_len) % context_size] = row
|
|
if context_len == context_size:
|
|
context_start = (context_start + 1) % context_size
|
|
gap = True
|
|
else:
|
|
context_len = context_len + 1
|
|
else:
|
|
# yield row, but first drain stuff in buffer
|
|
context_len == context_size
|
|
while context_len:
|
|
yield context_buffer[context_start] + (gap,)
|
|
gap = False
|
|
context_start = (context_start + 1) % context_size
|
|
context_len = context_len - 1
|
|
yield row + (gap,)
|
|
gap = False
|
|
|
|
_re_differ = re.compile(r"[+-^]+")
|
|
|
|
def _differ_split(row, guide):
|
|
"""Break row into segments using guide line"""
|
|
line, left_number, right_number, gap = row
|
|
|
|
if left_number and right_number:
|
|
type = ""
|
|
elif left_number:
|
|
type = "remove"
|
|
elif right_number:
|
|
type = "add"
|
|
|
|
segments = []
|
|
pos = 2
|
|
|
|
if guide:
|
|
assert guide.startswith("? ")
|
|
|
|
for m in _re_differ.finditer(guide, pos):
|
|
if m.start() > pos:
|
|
segments.append(_item(text=cgi.escape(line[pos:m.start()]), type=None))
|
|
segments.append(_item(text=cgi.escape(line[m.start():m.end()]),
|
|
type="change"))
|
|
pos = m.end()
|
|
|
|
segments.append(_item(text=cgi.escape(line[pos:]), type=None))
|
|
|
|
return _item(gap=ezt.boolean(gap), type=type, segments=segments,
|
|
left_number=left_number, right_number=right_number)
|
|
|
|
class _item:
|
|
def __init__(self, **kw):
|
|
vars(self).update(kw)
|
|
|
|
try:
|
|
### Using difflib._mdiff function here was the easiest way of obtaining
|
|
### intraline diffs for use in ViewVC, but it doesn't exist prior to
|
|
### Python 2.4 and is not part of the public difflib API, so for now
|
|
### fall back if it doesn't exist.
|
|
difflib._mdiff
|
|
except AttributeError:
|
|
sidebyside = None
|