mirror of
https://github.com/python/cpython.git
synced 2025-09-26 18:29:57 +00:00
Issue 11747: Fix output format for context diffs.
This commit is contained in:
parent
d92232976e
commit
7c9d34722d
2 changed files with 102 additions and 28 deletions
|
@ -1140,6 +1140,21 @@ def IS_CHARACTER_JUNK(ch, ws=" \t"):
|
||||||
return ch in ws
|
return ch in ws
|
||||||
|
|
||||||
|
|
||||||
|
########################################################################
|
||||||
|
### Unified Diff
|
||||||
|
########################################################################
|
||||||
|
|
||||||
|
def _format_range_unified(start, stop):
|
||||||
|
'Convert range to the "ed" format'
|
||||||
|
# Per the diff spec at http://www.unix.org/single_unix_specification/
|
||||||
|
beginning = start + 1 # lines start numbering with one
|
||||||
|
length = stop - start
|
||||||
|
if length == 1:
|
||||||
|
return '{}'.format(beginning)
|
||||||
|
if not length:
|
||||||
|
beginning -= 1 # empty ranges begin at line just before the range
|
||||||
|
return '{},{}'.format(beginning, length)
|
||||||
|
|
||||||
def unified_diff(a, b, fromfile='', tofile='', fromfiledate='',
|
def unified_diff(a, b, fromfile='', tofile='', fromfiledate='',
|
||||||
tofiledate='', n=3, lineterm='\n'):
|
tofiledate='', n=3, lineterm='\n'):
|
||||||
r"""
|
r"""
|
||||||
|
@ -1184,25 +1199,45 @@ def unified_diff(a, b, fromfile='', tofile='', fromfiledate='',
|
||||||
started = False
|
started = False
|
||||||
for group in SequenceMatcher(None,a,b).get_grouped_opcodes(n):
|
for group in SequenceMatcher(None,a,b).get_grouped_opcodes(n):
|
||||||
if not started:
|
if not started:
|
||||||
fromdate = '\t%s' % fromfiledate if fromfiledate else ''
|
|
||||||
todate = '\t%s' % tofiledate if tofiledate else ''
|
|
||||||
yield '--- %s%s%s' % (fromfile, fromdate, lineterm)
|
|
||||||
yield '+++ %s%s%s' % (tofile, todate, lineterm)
|
|
||||||
started = True
|
started = True
|
||||||
i1, i2, j1, j2 = group[0][1], group[-1][2], group[0][3], group[-1][4]
|
fromdate = '\t{}'.format(fromfiledate) if fromfiledate else ''
|
||||||
yield "@@ -%d,%d +%d,%d @@%s" % (i1+1, i2-i1, j1+1, j2-j1, lineterm)
|
todate = '\t{}'.format(tofiledate) if tofiledate else ''
|
||||||
|
yield '--- {}{}{}'.format(fromfile, fromdate, lineterm)
|
||||||
|
yield '+++ {}{}{}'.format(tofile, todate, lineterm)
|
||||||
|
|
||||||
|
first, last = group[0], group[-1]
|
||||||
|
file1_range = _format_range_unified(first[1], last[2])
|
||||||
|
file2_range = _format_range_unified(first[3], last[4])
|
||||||
|
yield '@@ -{} +{} @@{}'.format(file1_range, file2_range, lineterm)
|
||||||
|
|
||||||
for tag, i1, i2, j1, j2 in group:
|
for tag, i1, i2, j1, j2 in group:
|
||||||
if tag == 'equal':
|
if tag == 'equal':
|
||||||
for line in a[i1:i2]:
|
for line in a[i1:i2]:
|
||||||
yield ' ' + line
|
yield ' ' + line
|
||||||
continue
|
continue
|
||||||
if tag == 'replace' or tag == 'delete':
|
if tag in ('replace', 'delete'):
|
||||||
for line in a[i1:i2]:
|
for line in a[i1:i2]:
|
||||||
yield '-' + line
|
yield '-' + line
|
||||||
if tag == 'replace' or tag == 'insert':
|
if tag in ('replace', 'insert'):
|
||||||
for line in b[j1:j2]:
|
for line in b[j1:j2]:
|
||||||
yield '+' + line
|
yield '+' + line
|
||||||
|
|
||||||
|
|
||||||
|
########################################################################
|
||||||
|
### Context Diff
|
||||||
|
########################################################################
|
||||||
|
|
||||||
|
def _format_range_context(start, stop):
|
||||||
|
'Convert range to the "ed" format'
|
||||||
|
# Per the diff spec at http://www.unix.org/single_unix_specification/
|
||||||
|
beginning = start + 1 # lines start numbering with one
|
||||||
|
length = stop - start
|
||||||
|
if not length:
|
||||||
|
beginning -= 1 # empty ranges begin at line just before the range
|
||||||
|
if length <= 1:
|
||||||
|
return '{}'.format(beginning)
|
||||||
|
return '{},{}'.format(beginning, beginning + length - 1)
|
||||||
|
|
||||||
# See http://www.unix.org/single_unix_specification/
|
# See http://www.unix.org/single_unix_specification/
|
||||||
def context_diff(a, b, fromfile='', tofile='',
|
def context_diff(a, b, fromfile='', tofile='',
|
||||||
fromfiledate='', tofiledate='', n=3, lineterm='\n'):
|
fromfiledate='', tofiledate='', n=3, lineterm='\n'):
|
||||||
|
@ -1247,38 +1282,36 @@ def context_diff(a, b, fromfile='', tofile='',
|
||||||
four
|
four
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
prefix = dict(insert='+ ', delete='- ', replace='! ', equal=' ')
|
||||||
started = False
|
started = False
|
||||||
prefixmap = {'insert':'+ ', 'delete':'- ', 'replace':'! ', 'equal':' '}
|
|
||||||
for group in SequenceMatcher(None,a,b).get_grouped_opcodes(n):
|
for group in SequenceMatcher(None,a,b).get_grouped_opcodes(n):
|
||||||
if not started:
|
if not started:
|
||||||
fromdate = '\t%s' % fromfiledate if fromfiledate else ''
|
|
||||||
todate = '\t%s' % tofiledate if tofiledate else ''
|
|
||||||
yield '*** %s%s%s' % (fromfile, fromdate, lineterm)
|
|
||||||
yield '--- %s%s%s' % (tofile, todate, lineterm)
|
|
||||||
started = True
|
started = True
|
||||||
|
fromdate = '\t{}'.format(fromfiledate) if fromfiledate else ''
|
||||||
|
todate = '\t{}'.format(tofiledate) if tofiledate else ''
|
||||||
|
yield '*** {}{}{}'.format(fromfile, fromdate, lineterm)
|
||||||
|
yield '--- {}{}{}'.format(tofile, todate, lineterm)
|
||||||
|
|
||||||
yield '***************%s' % (lineterm,)
|
first, last = group[0], group[-1]
|
||||||
if group[-1][2] - group[0][1] >= 2:
|
yield '***************' + lineterm
|
||||||
yield '*** %d,%d ****%s' % (group[0][1]+1, group[-1][2], lineterm)
|
|
||||||
else:
|
file1_range = _format_range_context(first[1], last[2])
|
||||||
yield '*** %d ****%s' % (group[-1][2], lineterm)
|
yield '*** {} ****{}'.format(file1_range, lineterm)
|
||||||
visiblechanges = [e for e in group if e[0] in ('replace', 'delete')]
|
|
||||||
if visiblechanges:
|
if any(tag in ('replace', 'delete') for tag, _, _, _, _ in group):
|
||||||
for tag, i1, i2, _, _ in group:
|
for tag, i1, i2, _, _ in group:
|
||||||
if tag != 'insert':
|
if tag != 'insert':
|
||||||
for line in a[i1:i2]:
|
for line in a[i1:i2]:
|
||||||
yield prefixmap[tag] + line
|
yield prefix[tag] + line
|
||||||
|
|
||||||
if group[-1][4] - group[0][3] >= 2:
|
file2_range = _format_range_context(first[3], last[4])
|
||||||
yield '--- %d,%d ----%s' % (group[0][3]+1, group[-1][4], lineterm)
|
yield '--- {} ----{}'.format(file2_range, lineterm)
|
||||||
else:
|
|
||||||
yield '--- %d ----%s' % (group[-1][4], lineterm)
|
if any(tag in ('replace', 'insert') for tag, _, _, _, _ in group):
|
||||||
visiblechanges = [e for e in group if e[0] in ('replace', 'insert')]
|
|
||||||
if visiblechanges:
|
|
||||||
for tag, _, _, j1, j2 in group:
|
for tag, _, _, j1, j2 in group:
|
||||||
if tag != 'delete':
|
if tag != 'delete':
|
||||||
for line in b[j1:j2]:
|
for line in b[j1:j2]:
|
||||||
yield prefixmap[tag] + line
|
yield prefix[tag] + line
|
||||||
|
|
||||||
def ndiff(a, b, linejunk=None, charjunk=IS_CHARACTER_JUNK):
|
def ndiff(a, b, linejunk=None, charjunk=IS_CHARACTER_JUNK):
|
||||||
r"""
|
r"""
|
||||||
|
|
|
@ -219,6 +219,47 @@ class TestOutputFormat(unittest.TestCase):
|
||||||
cd = difflib.context_diff(*args, lineterm='')
|
cd = difflib.context_diff(*args, lineterm='')
|
||||||
self.assertEqual(list(cd)[0:2], ["*** Original", "--- Current"])
|
self.assertEqual(list(cd)[0:2], ["*** Original", "--- Current"])
|
||||||
|
|
||||||
|
def test_range_format_unified(self):
|
||||||
|
# Per the diff spec at http://www.unix.org/single_unix_specification/
|
||||||
|
spec = '''\
|
||||||
|
Each <range> field shall be of the form:
|
||||||
|
%1d", <beginning line number> if the range contains exactly one line,
|
||||||
|
and:
|
||||||
|
"%1d,%1d", <beginning line number>, <number of lines> otherwise.
|
||||||
|
If a range is empty, its beginning line number shall be the number of
|
||||||
|
the line just before the range, or 0 if the empty range starts the file.
|
||||||
|
'''
|
||||||
|
fmt = difflib._format_range_unified
|
||||||
|
self.assertEqual(fmt(3,3), '3,0')
|
||||||
|
self.assertEqual(fmt(3,4), '4')
|
||||||
|
self.assertEqual(fmt(3,5), '4,2')
|
||||||
|
self.assertEqual(fmt(3,6), '4,3')
|
||||||
|
self.assertEqual(fmt(0,0), '0,0')
|
||||||
|
|
||||||
|
def test_range_format_context(self):
|
||||||
|
# Per the diff spec at http://www.unix.org/single_unix_specification/
|
||||||
|
spec = '''\
|
||||||
|
The range of lines in file1 shall be written in the following format
|
||||||
|
if the range contains two or more lines:
|
||||||
|
"*** %d,%d ****\n", <beginning line number>, <ending line number>
|
||||||
|
and the following format otherwise:
|
||||||
|
"*** %d ****\n", <ending line number>
|
||||||
|
The ending line number of an empty range shall be the number of the preceding line,
|
||||||
|
or 0 if the range is at the start of the file.
|
||||||
|
|
||||||
|
Next, the range of lines in file2 shall be written in the following format
|
||||||
|
if the range contains two or more lines:
|
||||||
|
"--- %d,%d ----\n", <beginning line number>, <ending line number>
|
||||||
|
and the following format otherwise:
|
||||||
|
"--- %d ----\n", <ending line number>
|
||||||
|
'''
|
||||||
|
fmt = difflib._format_range_context
|
||||||
|
self.assertEqual(fmt(3,3), '3')
|
||||||
|
self.assertEqual(fmt(3,4), '4')
|
||||||
|
self.assertEqual(fmt(3,5), '4,5')
|
||||||
|
self.assertEqual(fmt(3,6), '4,6')
|
||||||
|
self.assertEqual(fmt(0,0), '0')
|
||||||
|
|
||||||
|
|
||||||
def test_main():
|
def test_main():
|
||||||
difflib.HtmlDiff._default_prefix = 0
|
difflib.HtmlDiff._default_prefix = 0
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue