From 9abd62d7c3e5cba4705a63cff7adb987f130b00b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?=
<10796600+picnixz@users.noreply.github.com>
Date: Tue, 25 Jul 2023 11:43:16 +0200
Subject: [PATCH] Fix ``SOURCE_DATE_EPOCH`` for multi-line copyright values
---
CHANGES | 3 +++
sphinx/config.py | 10 +++++++++-
tests/test_config.py | 45 +++++++++++++++++++++++++++++---------------
3 files changed, 42 insertions(+), 16 deletions(-)
diff --git a/CHANGES b/CHANGES
index 3a2f997ff47..e54a2b42bbf 100644
--- a/CHANGES
+++ b/CHANGES
@@ -21,6 +21,9 @@ Features added
Bugs fixed
----------
+* #11514: Fix ``SOURCE_DATE_EPOCH`` in multi-line copyright footer.
+ Patch by Bénédikt Tran.
+
Testing
-------
diff --git a/sphinx/config.py b/sphinx/config.py
index 3aa62651ca8..2d87ef8f615 100644
--- a/sphinx/config.py
+++ b/sphinx/config.py
@@ -429,7 +429,15 @@ def correct_copyright_year(app: Sphinx, config: Config) -> None:
for k in ('copyright', 'epub_copyright'):
if k in config:
replace = r'\g<1>%s' % format_date('%Y', language='en')
- config[k] = copyright_year_re.sub(replace, config[k])
+ value: str | list[str] | tuple[str, ...] = config[k]
+ if isinstance(value, str):
+ config[k] = copyright_year_re.sub(replace, value)
+ else:
+ items = (copyright_year_re.sub(replace, x) for x in value)
+ if isinstance(value, list):
+ config[k] = list(items)
+ else:
+ config[k] = tuple(items)
def check_confval_types(app: Sphinx | None, config: Config) -> None:
diff --git a/tests/test_config.py b/tests/test_config.py
index f01ea0c32d2..7ca276d8c9c 100644
--- a/tests/test_config.py
+++ b/tests/test_config.py
@@ -1,5 +1,7 @@
"""Test the sphinx.config.Config class."""
+import os
+import textwrap
from unittest import mock
import pytest
@@ -8,6 +10,7 @@
from sphinx.config import ENUM, Config, check_confval_types
from sphinx.errors import ConfigError, ExtensionError, VersionRequirementError
from sphinx.testing.path import path
+from sphinx.util.i18n import format_date
@pytest.mark.sphinx(testroot='config', confoverrides={
@@ -445,22 +448,34 @@ def test_conf_py_nitpick_ignore_list(tempdir):
@pytest.mark.sphinx(testroot='copyright-multiline')
-def test_multi_line_copyright(app, status, warning):
+def test_multi_line_copyright(app):
app.builder.build_all()
content = (app.outdir / 'index.html').read_text(encoding='utf-8')
- assert ' © Copyright 2006-2009, Alice.
' in content
- assert ' © Copyright 2010-2013, Bob.
' in content
- assert ' © Copyright 2014-2017, Charlie.
' in content
- assert ' © Copyright 2018-2021, David.
' in content
- assert ' © Copyright 2022-2025, Eve.' in content
-
- lines = (
- ' © Copyright 2006-2009, Alice.
\n \n'
- ' © Copyright 2010-2013, Bob.
\n \n'
- ' © Copyright 2014-2017, Charlie.
\n \n'
- ' © Copyright 2018-2021, David.
\n \n'
- ' © Copyright 2022-2025, Eve.\n \n'
- )
- assert lines in content
+ if os.getenv('SOURCE_DATE_EPOCH') is None:
+ copyright_footer = (
+ ' © Copyright 2006-2009, Alice.
\n',
+ ' © Copyright 2010-2013, Bob.
\n',
+ ' © Copyright 2014-2017, Charlie.
\n',
+ ' © Copyright 2018-2021, David.
\n',
+ ' © Copyright 2022-2025, Eve.',
+ )
+ else:
+ source_date_year = format_date('%Y', language='en')
+ copyright_footer = (
+ f' © Copyright 2006-{source_date_year}, Alice.
\n',
+ f' © Copyright 2010-{source_date_year}, Bob.
\n',
+ f' © Copyright 2014-{source_date_year}, Charlie.
\n',
+ f' © Copyright 2018-{source_date_year}, David.
\n',
+ f' © Copyright 2022-{source_date_year}, Eve.',
+ )
+
+ # check the copyright footer line by line (empty lines ignored)
+ for line in copyright_footer:
+ assert line in content
+
+ # check the raw copyright footer block (empty lines included)
+ expect = '\n'.join(copyright_footer)
+ expect = textwrap.indent(expect, ' ', lambda _: True)
+ assert expect in content