Index: Lib/difflib.py =================================================================== --- Lib/difflib.py (revision 72930) +++ Lib/difflib.py (working copy) @@ -1185,28 +1185,40 @@ for group in SequenceMatcher(None,a,b).get_grouped_opcodes(n): if not started: yield '--- %s %s%s' % (fromfile, fromfiledate, lineterm) yield '+++ %s %s%s' % (tofile, tofiledate, lineterm) started = True i1, i2, j1, j2 = group[0][1], group[-1][2], group[0][3], group[-1][4] yield "@@ -%d,%d +%d,%d @@%s" % (i1+1, i2-i1, j1+1, j2-j1, lineterm) for tag, i1, i2, j1, j2 in group: if tag == 'equal': for line in a[i1:i2]: - yield ' ' + line + if not line.endswith(lineterm): + yield ' ' + line + lineterm + yield r'\ No newline at end of file' + lineterm + else: + yield ' ' + line continue if tag == 'replace' or tag == 'delete': for line in a[i1:i2]: - yield '-' + line + if not line.endswith(lineterm): + yield '-' + line + lineterm + yield r'\ No newline at end of file' + lineterm + else: + yield '-' + line if tag == 'replace' or tag == 'insert': for line in b[j1:j2]: - yield '+' + line + if not line.endswith(lineterm): + yield '+' + line + lineterm + yield r'\ No newline at end of file' + lineterm + else: + yield '+' + line # See http://www.unix.org/single_unix_specification/ def context_diff(a, b, fromfile='', tofile='', fromfiledate='', tofiledate='', n=3, lineterm='\n'): r""" Compare two sequences of lines; generate the delta as a context diff. Context diffs are a compact way of showing line changes and a few lines of context. The number of context lines is set by 'n' which defaults to three. @@ -1257,32 +1269,40 @@ yield '***************%s' % (lineterm,) if group[-1][2] - group[0][1] >= 2: yield '*** %d,%d ****%s' % (group[0][1]+1, group[-1][2], lineterm) else: yield '*** %d ****%s' % (group[-1][2], lineterm) visiblechanges = [e for e in group if e[0] in ('replace', 'delete')] if visiblechanges: for tag, i1, i2, _, _ in group: if tag != 'insert': for line in a[i1:i2]: - yield prefixmap[tag] + line + if not line.endswith(lineterm): + yield prefixmap[tag] + line + lineterm + yield r'\ No newline at end of file' + lineterm + else: + yield prefixmap[tag] + line if group[-1][4] - group[0][3] >= 2: yield '--- %d,%d ----%s' % (group[0][3]+1, group[-1][4], lineterm) else: yield '--- %d ----%s' % (group[-1][4], lineterm) visiblechanges = [e for e in group if e[0] in ('replace', 'insert')] if visiblechanges: for tag, _, _, j1, j2 in group: if tag != 'delete': for line in b[j1:j2]: - yield prefixmap[tag] + line + if not line.endswith(lineterm): + yield prefixmap[tag] + line + lineterm + yield r'\ No newline at end of file' + lineterm + else: + yield prefixmap[tag] + line def ndiff(a, b, linejunk=None, charjunk=IS_CHARACTER_JUNK): r""" Compare `a` and `b` (lists of strings); return a `Differ`-style delta. Optional keyword parameters `linejunk` and `charjunk` are for filter functions (or None): - linejunk: A function that should accept a single string argument, and return true iff the string is junk. The default is None, and is Index: Lib/test/test_difflib.py =================================================================== --- Lib/test/test_difflib.py (revision 72930) +++ Lib/test/test_difflib.py (working copy) @@ -144,18 +144,64 @@ self.assertEqual(actual,expect) def test_recursion_limit(self): # Check if the problem described in patch #1413711 exists. limit = sys.getrecursionlimit() old = [(i%2 and "K:%d" or "V:A:%d") % i for i in range(limit*2)] new = [(i%2 and "K:%d" or "V:B:%d") % i for i in range(limit*2)] difflib.SequenceMatcher(None, old, new).get_opcodes() +class TestDifflib(unittest.TestCase): + def test_unified_no_eol(self): + lines = list(difflib.unified_diff( + "one\ntwo\nthree".splitlines(1), + "one\ntwo\ntrois".splitlines(1), + "Before", "After")) + expected = [ + '--- Before \n', + '+++ After \n', + '@@ -1,3 +1,3 @@\n', + ' one\n', + ' two\n', + '-three\n', + '\\ No newline at end of file\n', + '+trois\n', + '\\ No newline at end of file\n' + ] + self.assertEqual(lines, expected, + "unexpected diff output:\n-- expected this --\n%s\n" + "-- got this --\n%s\n" + % (''.join(expected), ''.join(lines))) + + def test_context_no_eol(self): + lines = list(difflib.context_diff( + "one\ntwo\nthree".splitlines(1), + "one\ntwo\ntrois".splitlines(1), + "Before", "After")) + expected = [ + '*** Before \n', + '--- After \n', + '***************\n', + '*** 1,3 ****\n', + ' one\n', + ' two\n', + '! three\n', + '\\ No newline at end of file\n', + '--- 1,3 ----\n', + ' one\n', + ' two\n', + '! trois\n', + '\\ No newline at end of file\n', + ] + self.assertEqual(lines, expected, + "unexpected diff output:\n-- expected this --\n%s\n" + "-- got this --\n%s\n" + % (''.join(expected), ''.join(lines))) def test_main(): difflib.HtmlDiff._default_prefix = 0 Doctests = doctest.DocTestSuite(difflib) - run_unittest(TestSFpatches, TestSFbugs, Doctests) + run_unittest(TestSFpatches, TestSFbugs, TestDifflib, Doctests) if __name__ == '__main__': test_main()