Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions Doc/c-api/complex.rst
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,16 @@ Complex Numbers as Python Objects

This subtype of :c:type:`PyObject` represents a Python complex number object.

.. c:member:: Py_complex cval

The complex number value, using the C :c:type:`Py_complex` representation.

.. deprecated-removed:: next 3.20
Use :c:func:`PyComplex_AsCComplex` and
:c:func:`PyComplex_FromCComplex` to convert a
Python complex number to/from the C :c:type:`Py_complex`
representation.


.. c:var:: PyTypeObject PyComplex_Type

Expand Down
7 changes: 7 additions & 0 deletions Doc/deprecations/c-api-pending-removal-in-3.20.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Pending removal in Python 3.20
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

* The ``cval`` field in :c:type:`PyComplexObject` (:gh:`128813`).
Use :c:func:`PyComplex_AsCComplex` and :c:func:`PyComplex_FromCComplex`
to convert a Python complex number to/from the C :c:type:`Py_complex`
representation.
2 changes: 2 additions & 0 deletions Doc/deprecations/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,6 @@ C API deprecations

.. include:: c-api-pending-removal-in-3.18.rst

.. include:: c-api-pending-removal-in-3.20.rst

.. include:: c-api-pending-removal-in-future.rst
10 changes: 9 additions & 1 deletion Doc/library/difflib.rst
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,7 @@ diffs. For comparing directories and files, see also, the :mod:`filecmp` module.
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.
Expand All @@ -297,6 +297,10 @@ diffs. For comparing directories and files, see also, the :mod:`filecmp` module.
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
Expand All @@ -319,6 +323,10 @@ diffs. For comparing directories and files, see also, the :mod:`filecmp` module.

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
Expand Down
2 changes: 1 addition & 1 deletion Doc/library/unittest.mock.rst
Original file line number Diff line number Diff line change
Expand Up @@ -639,7 +639,7 @@ the *new_callable* argument to :func:`patch`.
This is either ``None`` (if the mock hasn't been called), or the
arguments that the mock was last called with. This will be in the
form of a tuple: the first member, which can also be accessed through
the ``args`` property, is any ordered arguments the mock was
the ``args`` property, is any positional arguments the mock was
called with (or an empty tuple) and the second member, which can
also be accessed through the ``kwargs`` property, is any keyword
arguments (or an empty dictionary).
Expand Down
15 changes: 15 additions & 0 deletions Doc/whatsnew/3.15.rst
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,14 @@ dbm
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`.)
Expand Down Expand Up @@ -570,6 +578,13 @@ Deprecated C APIs
signed integer type of the same size is now deprecated.
(Contributed by Serhiy Storchaka in :gh:`132629`.)

* Deprecate :c:member:`~PyComplexObject.cval` field of the the
:c:type:`PyComplexObject` type.
Use :c:func:`PyComplex_AsCComplex` and :c:func:`PyComplex_FromCComplex`
to convert a Python complex number to/from the C :c:type:`Py_complex`
representation.
(Contributed by Sergey B Kirpichev in :gh:`128813`.)

* Functions :c:func:`_Py_c_sum`, :c:func:`_Py_c_diff`, :c:func:`_Py_c_neg`,
:c:func:`_Py_c_prod`, :c:func:`_Py_c_quot`, :c:func:`_Py_c_pow` and
:c:func:`_Py_c_abs` are :term:`soft deprecated`.
Expand Down
23 changes: 19 additions & 4 deletions Lib/_colorize.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,18 @@ class Argparse(ThemeSection):
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
Expand All @@ -186,7 +197,7 @@ class Syntax(ThemeSection):
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
Expand All @@ -198,7 +209,7 @@ class Traceback(ThemeSection):
reset: str = ANSIColors.RESET


@dataclass(frozen=True)
@dataclass(frozen=True, kw_only=True)
class Unittest(ThemeSection):
passed: str = ANSIColors.GREEN
warn: str = ANSIColors.YELLOW
Expand All @@ -207,14 +218,15 @@ class Unittest(ThemeSection):
reset: str = ANSIColors.RESET


@dataclass(frozen=True)
@dataclass(frozen=True, kw_only=True)
class Theme:
"""A suite of themes for all sections of Python.

When adding a new one, remember to also modify `copy_with` and `no_colors`
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)
Expand All @@ -223,6 +235,7 @@ def copy_with(
self,
*,
argparse: Argparse | None = None,
difflib: Difflib | None = None,
syntax: Syntax | None = None,
traceback: Traceback | None = None,
unittest: Unittest | None = None,
Expand All @@ -234,6 +247,7 @@ def copy_with(
"""
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,
Expand All @@ -249,6 +263,7 @@ def no_colors(cls) -> Self:
"""
return cls(
argparse=Argparse.no_colors(),
difflib=Difflib.no_colors(),
syntax=Syntax.no_colors(),
traceback=Traceback.no_colors(),
unittest=Unittest.no_colors(),
Expand Down
24 changes: 17 additions & 7 deletions Lib/difflib.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
'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
Expand Down Expand Up @@ -1094,7 +1095,7 @@ def _format_range_unified(start, stop):
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.

Expand All @@ -1111,6 +1112,10 @@ def unified_diff(a, b, fromfile='', tofile='', fromfiledate='',
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'.
Expand All @@ -1134,32 +1139,37 @@ def unified_diff(a, b, fromfile='', tofile='', fromfiledate='',
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):
if not started:
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}'


########################################################################
Expand Down
18 changes: 17 additions & 1 deletion Lib/test/test_difflib.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import difflib
from test.support import findfile
from test.support import findfile, force_colorized
import unittest
import doctest
import sys
Expand Down Expand Up @@ -355,6 +355,22 @@ def test_range_format_context(self):
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
Expand Down
1 change: 1 addition & 0 deletions Misc/ACKS
Original file line number Diff line number Diff line change
Expand Up @@ -1902,6 +1902,7 @@ Nicolas M. Thiéry
James Thomas
Reuben Thomas
Robin Thomas
Douglas Thor
Brian Thorne
Christopher Thorne
Stephen Thorne
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
Functions :c:func:`_Py_c_sum`, :c:func:`_Py_c_diff`, :c:func:`_Py_c_neg`,
:c:func:`_Py_c_prod`, :c:func:`_Py_c_quot`, :c:func:`_Py_c_pow` and
previously undocumented :c:func:`_Py_c_abs` are :term:`soft deprecated`.
:c:func:`_Py_c_prod`, :c:func:`_Py_c_quot`, :c:func:`_Py_c_pow` and previously
undocumented :c:func:`_Py_c_abs` are :term:`soft deprecated`. Deprecate also
:c:member:`~PyComplexObject.cval` field of the :c:type:`PyComplexObject` type.
Patch by Sergey B Kirpichev.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Added a *color* option to :func:`difflib.unified_diff` that colors output
similar to :program:`git diff`.
Loading