comparing with http://hg.python.org/cpython searching for changes changeset: 68491:26d9046866e3 tag: tip user: ysj.ray date: Tue Mar 15 13:45:23 2011 +0800 summary: issue 2142 path diff -r 4c1a15ebebe5 -r 26d9046866e3 Doc/library/difflib.rst --- a/Doc/library/difflib.rst Tue Mar 15 06:03:08 2011 +0200 +++ b/Doc/library/difflib.rst Tue Mar 15 13:45:23 2011 +0800 @@ -132,7 +132,7 @@ contains a good example of its use. -.. function:: context_diff(a, b, fromfile='', tofile='', fromfiledate='', tofiledate='', n=3, lineterm='\\n') +.. function:: context_diff(a, b, fromfile='', tofile='', fromfiledate='', tofiledate='', n=3, lineterm='\\n', warn_no_newline_at_end=True) Compare *a* and *b* (lists of strings); return a delta (a :term:`generator` generating the delta lines) in context diff format. @@ -156,6 +156,10 @@ expressed in the ISO 8601 format. If not specified, the strings default to blanks. + If a or b misses a newline at end of file, an additional newline is generated at + end of last line, and an extra warning line "\ No newline at end of file" is + generated if warn_no_newline_at_end is True, to make the patch program happy. + >>> s1 = ['bacon\n', 'eggs\n', 'ham\n', 'guido\n'] >>> s2 = ['python\n', 'eggy\n', 'hamster\n', 'guido\n'] >>> for line in context_diff(s1, s2, fromfile='before.py', tofile='after.py'): @@ -176,6 +180,9 @@ See :ref:`difflib-interface` for a more detailed example. + .. versionadded:: 3.3 + The *warn_no_newline_at_end* parameter. + .. function:: get_close_matches(word, possibilities, n=3, cutoff=0.6) @@ -263,7 +270,7 @@ emu -.. function:: unified_diff(a, b, fromfile='', tofile='', fromfiledate='', tofiledate='', n=3, lineterm='\\n') +.. function:: unified_diff(a, b, fromfile='', tofile='', fromfiledate='', tofiledate='', n=3, lineterm='\\n', warn_no_newline_at_end=True) Compare *a* and *b* (lists of strings); return a delta (a :term:`generator` generating the delta lines) in unified diff format. @@ -288,6 +295,10 @@ expressed in the ISO 8601 format. If not specified, the strings default to blanks. + If a or b misses a newline at end of file, an additional newline is generated at + end of last line, and an extra warning line "\ No newline at end of file" is + generated if warn_no_newline_at_end is True, to make the patch program happy. + >>> s1 = ['bacon\n', 'eggs\n', 'ham\n', 'guido\n'] >>> s2 = ['python\n', 'eggy\n', 'hamster\n', 'guido\n'] @@ -306,6 +317,9 @@ See :ref:`difflib-interface` for a more detailed example. + .. versionadded:: 3.3 + The *warn_no_newline_at_end* parameter. + .. function:: IS_LINE_JUNK(line) diff -r 4c1a15ebebe5 -r 26d9046866e3 Lib/difflib.py --- a/Lib/difflib.py Tue Mar 15 06:03:08 2011 +0200 +++ b/Lib/difflib.py Tue Mar 15 13:45:23 2011 +0800 @@ -1144,8 +1144,11 @@ return ch in ws +no_newline_at_end_warning = r'\ No newline at end of file' + def unified_diff(a, b, fromfile='', tofile='', fromfiledate='', - tofiledate='', n=3, lineterm='\n'): + tofiledate='', n=3, lineterm='\n', + warn_no_newline_at_end=True): r""" Compare two sequences of lines; generate the delta as a unified diff. @@ -1167,6 +1170,11 @@ 'fromfile', 'tofile', 'fromfiledate', and 'tofiledate'. The modification times are normally expressed in the ISO 8601 format. + If a or b misses a newline at end of file, an additional newline is + generated at end of last line, and an extra warning line "\ No newline at + end of file" is generated if warn_no_newline_at_end is True, to make the + patch program happy. + Example: >>> for line in unified_diff('one two three four'.split(), @@ -1198,18 +1206,34 @@ 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 + if warn_no_newline_at_end: + yield no_newline_at_end_warning + 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 + if warn_no_newline_at_end: + yield no_newline_at_end_warning + 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 + if warn_no_newline_at_end: + yield no_newline_at_end_warning + 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'): + fromfiledate='', tofiledate='', n=3, lineterm='\n', + warn_no_newline_at_end=True): r""" Compare two sequences of lines; generate the delta as a context diff. @@ -1232,6 +1256,11 @@ The modification times are normally expressed in the ISO 8601 format. If not specified, the strings default to blanks. + If a or b misses a newline at end of file, an additional newline is + generated at end of last line, and an extra warning line "\ No newline at + end of file" is generated if warn_no_newline_at_end is True, to make the + patch program happy. + Example: >>> print(''.join(context_diff('one\ntwo\nthree\nfour\n'.splitlines(1), @@ -1272,7 +1301,12 @@ 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 + if warn_no_newline_at_end: + yield no_newline_at_end_warning + 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) @@ -1283,7 +1317,12 @@ 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 + if warn_no_newline_at_end: + yield no_newline_at_end_warning + lineterm + else: + yield prefixmap[tag] + line def ndiff(a, b, linejunk=None, charjunk=IS_CHARACTER_JUNK): r""" diff -r 4c1a15ebebe5 -r 26d9046866e3 Lib/test/test_difflib.py --- a/Lib/test/test_difflib.py Tue Mar 15 06:03:08 2011 +0200 +++ b/Lib/test/test_difflib.py Tue Mar 15 13:45:23 2011 +0800 @@ -237,12 +237,87 @@ self.assertEqual(list(cd)[0:2], ["*** Original", "--- Current"]) +class TestNoNewLineAtEnd(unittest.TestCase): + """ Test no newline at end of file, from issue2142 """ + def test_unified_diff(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) + lines = list(difflib.unified_diff( + "one\ntwo\nthree".splitlines(1), + "one\ntwo\ntrois".splitlines(1), + "Before", "After", warn_no_newline_at_end=False)) + expected = [ + '--- Before\n', + '+++ After\n', + '@@ -1,3 +1,3 @@\n', + ' one\n', + ' two\n', + '-three\n', + '+trois\n', + ] + self.assertEqual(lines, expected) + + def test_context_diff(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) + lines = list(difflib.context_diff( + "one\ntwo\nthree".splitlines(1), + "one\ntwo\ntrois".splitlines(1), + "Before", "After", warn_no_newline_at_end=False)) + expected = [ + '*** Before\n', + '--- After\n', + '***************\n', + '*** 1,3 ****\n', + ' one\n', + ' two\n', + '! three\n', + '--- 1,3 ----\n', + ' one\n', + ' two\n', + '! trois\n', + ] + self.assertEqual(lines, expected) + + def test_main(): difflib.HtmlDiff._default_prefix = 0 Doctests = doctest.DocTestSuite(difflib) run_unittest( TestWithAscii, TestAutojunk, TestSFpatches, TestSFbugs, - TestOutputFormat, Doctests) + TestOutputFormat, TestNoNewLineAtEnd, Doctests) if __name__ == '__main__': test_main()