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', *, color=False)
Compare *a* and *b* (lists of strings); return a delta (a :term:`generator`
generating the delta lines) in unified diff format.
For inputs that do not have trailing newlines, set the *lineterm* argument to
``""`` so that the output will be uniformly newline free.
+ Set *color* to ``True`` to enable output in color, similar to
+ :program:`git diff --color`. Even if enabled, it can be
+ :ref:`controlled using environment variables <using-on-controlling-color>`.
+
The unified diff format normally has a header for filenames and modification
times. Any or all of these may be specified using strings for *fromfile*,
*tofile*, *fromfiledate*, and *tofiledate*. The modification times are normally
See :ref:`difflib-interface` for a more detailed example.
+ .. versionchanged:: next
+ Added the *color* parameter.
+
+
.. function:: diff_bytes(dfunc, a, b, fromfile=b'', tofile=b'', fromfiledate=b'', tofiledate=b'', n=3, lineterm=b'\n')
Compare *a* and *b* (lists of bytes objects) using *dfunc*; yield a
difflib
-------
+ .. _whatsnew315-color-difflib:
+
+* Introduced the optional *color* parameter to :func:`difflib.unified_diff`,
+ enabling color output similar to :program:`git diff`.
+ This can be controlled by :ref:`environment variables
+ <using-on-controlling-color>`.
+ (Contributed by Douglas Thor in :gh:`133725`.)
+
* Improved the styling of HTML diff pages generated by the :class:`difflib.HtmlDiff`
class, and migrated the output to the HTML5 standard.
(Contributed by Jiahao Li in :gh:`134580`.)
reset: str = ANSIColors.RESET
-@dataclass(frozen=True)
+@dataclass(frozen=True, kw_only=True)
+class Difflib(ThemeSection):
+ """A 'git diff'-like theme for `difflib.unified_diff`."""
+ added: str = ANSIColors.GREEN
+ context: str = ANSIColors.RESET # context lines
+ header: str = ANSIColors.BOLD # eg "---" and "+++" lines
+ hunk: str = ANSIColors.CYAN # the "@@" lines
+ removed: str = ANSIColors.RED
+ reset: str = ANSIColors.RESET
+
+
+@dataclass(frozen=True, kw_only=True)
class Syntax(ThemeSection):
prompt: str = ANSIColors.BOLD_MAGENTA
keyword: str = ANSIColors.BOLD_BLUE
reset: str = ANSIColors.RESET
-@dataclass(frozen=True)
+@dataclass(frozen=True, kw_only=True)
class Traceback(ThemeSection):
type: str = ANSIColors.BOLD_MAGENTA
message: str = ANSIColors.MAGENTA
reset: str = ANSIColors.RESET
-@dataclass(frozen=True)
+@dataclass(frozen=True, kw_only=True)
class Unittest(ThemeSection):
passed: str = ANSIColors.GREEN
warn: str = ANSIColors.YELLOW
reset: str = ANSIColors.RESET
-@dataclass(frozen=True)
+@dataclass(frozen=True, kw_only=True)
class Theme:
"""A suite of themes for all sections of Python.
below.
"""
argparse: Argparse = field(default_factory=Argparse)
+ difflib: Difflib = field(default_factory=Difflib)
syntax: Syntax = field(default_factory=Syntax)
traceback: Traceback = field(default_factory=Traceback)
unittest: Unittest = field(default_factory=Unittest)
self,
*,
argparse: Argparse | None = None,
+ difflib: Difflib | None = None,
syntax: Syntax | None = None,
traceback: Traceback | None = None,
unittest: Unittest | None = None,
"""
return type(self)(
argparse=argparse or self.argparse,
+ difflib=difflib or self.difflib,
syntax=syntax or self.syntax,
traceback=traceback or self.traceback,
unittest=unittest or self.unittest,
"""
return cls(
argparse=Argparse.no_colors(),
+ difflib=Difflib.no_colors(),
syntax=Syntax.no_colors(),
traceback=Traceback.no_colors(),
unittest=Unittest.no_colors(),
'Differ','IS_CHARACTER_JUNK', 'IS_LINE_JUNK', 'context_diff',
'unified_diff', 'diff_bytes', 'HtmlDiff', 'Match']
+from _colorize import can_colorize, get_theme
from heapq import nlargest as _nlargest
from collections import namedtuple as _namedtuple
from types import GenericAlias
return '{},{}'.format(beginning, length)
def unified_diff(a, b, fromfile='', tofile='', fromfiledate='',
- tofiledate='', n=3, lineterm='\n'):
+ tofiledate='', n=3, lineterm='\n', *, color=False):
r"""
Compare two sequences of lines; generate the delta as a unified diff.
For inputs that do not have trailing newlines, set the lineterm
argument to "" so that the output will be uniformly newline free.
+ Set 'color' to True to enable output in color, similar to
+ 'git diff --color'. Even if enabled, it can be
+ controlled using environment variables such as 'NO_COLOR'.
+
The unidiff format normally has a header for filenames and modification
times. Any or all of these may be specified using strings for
'fromfile', 'tofile', 'fromfiledate', and 'tofiledate'.
four
"""
+ if color and can_colorize():
+ t = get_theme(force_color=True).difflib
+ else:
+ t = get_theme(force_no_color=True).difflib
+
_check_types(a, b, fromfile, tofile, fromfiledate, tofiledate, lineterm)
started = False
for group in SequenceMatcher(None,a,b).get_grouped_opcodes(n):
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 f'{t.header}--- {fromfile}{fromdate}{lineterm}{t.reset}'
+ yield f'{t.header}+++ {tofile}{todate}{lineterm}{t.reset}'
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)
+ yield f'{t.hunk}@@ -{file1_range} +{file2_range} @@{lineterm}{t.reset}'
for tag, i1, i2, j1, j2 in group:
if tag == 'equal':
for line in a[i1:i2]:
- yield ' ' + line
+ yield f'{t.context} {line}{t.reset}'
continue
if tag in {'replace', 'delete'}:
for line in a[i1:i2]:
- yield '-' + line
+ yield f'{t.removed}-{line}{t.reset}'
if tag in {'replace', 'insert'}:
for line in b[j1:j2]:
- yield '+' + line
+ yield f'{t.added}+{line}{t.reset}'
########################################################################
import difflib
-from test.support import findfile
+from test.support import findfile, force_colorized
import unittest
import doctest
import sys
self.assertEqual(fmt(3,6), '4,6')
self.assertEqual(fmt(0,0), '0')
+ @force_colorized
+ def test_unified_diff_colored_output(self):
+ args = [['one', 'three'], ['two', 'three'], 'Original', 'Current',
+ '2005-01-26 23:30:50', '2010-04-02 10:20:52']
+ actual = list(difflib.unified_diff(*args, lineterm='', color=True))
+
+ expect = [
+ "\033[1m--- Original\t2005-01-26 23:30:50\033[0m",
+ "\033[1m+++ Current\t2010-04-02 10:20:52\033[0m",
+ "\033[36m@@ -1,2 +1,2 @@\033[0m",
+ "\033[31m-one\033[0m",
+ "\033[32m+two\033[0m",
+ "\033[0m three\033[0m",
+ ]
+ self.assertEqual(expect, actual)
+
class TestBytes(unittest.TestCase):
# don't really care about the content of the output, just the fact
James Thomas
Reuben Thomas
Robin Thomas
+Douglas Thor
Brian Thorne
Christopher Thorne
Stephen Thorne
--- /dev/null
+Added a *color* option to :func:`difflib.unified_diff` that colors output
+similar to :program:`git diff`.