pelican-feed-minify 1.0.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,110 @@
1
+ Metadata-Version: 2.4
2
+ Name: pelican-feed-minify
3
+ Version: 1.0.1
4
+ Summary: A Pelican plugin to check & minify HTML fields before XML serialization into RSS & Atom feeds.
5
+ Author-email: Ysard <ysard@users.noreply.github.com>
6
+ License-Expression: AGPL-3.0-or-later
7
+ Project-URL: Homepage, https://github.com/ysard/feed-minify
8
+ Project-URL: Repository, https://github.com/ysard/feed-minify
9
+ Keywords: pelican,feed,RSS,Atom,minify,minification
10
+ Classifier: Development Status :: 5 - Production/Stable
11
+ Classifier: Framework :: Pelican
12
+ Classifier: Framework :: Pelican :: Plugins
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Intended Audience :: System Administrators
15
+ Classifier: Intended Audience :: End Users/Desktop
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Programming Language :: Python :: 3.14
22
+ Classifier: Topic :: Internet :: WWW/HTTP
23
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
24
+ Requires-Python: >3.11
25
+ Description-Content-Type: text/markdown
26
+ Requires-Dist: pelican>=4
27
+ Requires-Dist: minify-html>=0.18
28
+ Provides-Extra: dev
29
+ Requires-Dist: pytest-cov>=2.6.1; extra == "dev"
30
+ Requires-Dist: pytest>=5.2.0; extra == "dev"
31
+ Requires-Dist: pytest-runner; extra == "dev"
32
+ Requires-Dist: zest.releaser[recommended]; extra == "dev"
33
+ Requires-Dist: prospector; extra == "dev"
34
+ Requires-Dist: coverage-badge; extra == "dev"
35
+
36
+ [![GitHub release (latest SemVer)](https://img.shields.io/github/v/release/ysard/feed-minify)](https://github.com/ysard/feed-minify/releases/latest/)
37
+ [![python versions on pypi](https://img.shields.io/pypi/pyversions/pelican-feed-minify)](https://pypi.python.org/pypi/pelican-feed-minify)
38
+ [![version on pypi](https://img.shields.io/pypi/v/pelican-feed-minify)](https://pypi.python.org/pypi/pelican-feed-minify)
39
+ [![Downloads](https://img.shields.io/pypi/dm/pelican-feed-minify)](https://pypi.org/project/pelican-feed-minify/)
40
+ [![license](https://img.shields.io/pypi/l/pelican-feed-minify)](https://github.com/ysard/feed-minify/blob/main/LICENSE)
41
+ [![python test coverage](./images/coverage.svg)](https://docs.pytest.org/en/latest/)
42
+ [![Donate](./images/liberapay.svg)](https://liberapay.com/Ysard/donate)
43
+
44
+
45
+ # Pelican Feed Minify
46
+
47
+ A small [Pelican](https://getpelican.com/) plugin that minifies HTML embedded in
48
+ RSS and Atom feeds before Pelican serializes those feeds.
49
+
50
+ The plugin uses [`minify-html`](https://github.com/wilsonzlin/minify-html) to
51
+ remove HTML comments and compact the markup. This is useful when comments used
52
+ in Markdown or reStructuredText source files should not be exposed to feed
53
+ readers.
54
+
55
+ ## Features
56
+
57
+ - Removes HTML comments from feed item `content` and `description` fields.
58
+ - Minifies HTML in both Atom and RSS feed items.
59
+ - Leaves titles and other feed metadata untouched.
60
+ - Allows summaries only to be displayed in the Atom feeds (allowed only for RSS feeds by Pelican).
61
+ - Performs a detailed validation of the HTML content to check for characters not
62
+ allowed by the XML 1.0 standard (useful to track errors).
63
+ - Does not modify the HTML files generated for the website or the source
64
+ Markdown/reStructuredText files.
65
+ - Does not ask `minify-html` to minify embedded CSS or JavaScript.
66
+
67
+ > **Note:** This is HTML minification, not just comment removal. Whitespace,
68
+ > attributes, and some character/entity representations may also be normalized
69
+ > by `minify-html`. Review the output if your articles rely on unusual HTML.
70
+
71
+ ## Requirements
72
+
73
+ - A Python version supported by both Pelican and `minify-html`.
74
+ - Pelican 4.5 or newer (uses the namespace-plugin structure).
75
+
76
+ ## Installation
77
+
78
+ This plugin can be installed via:
79
+
80
+ python -m pip install pelican-feed-minify
81
+
82
+ Enable the plugin in `pelicanconf.py`:
83
+
84
+ ```python
85
+ PLUGINS = [..., "feed_minify"]
86
+ ```
87
+
88
+ To restrict Atom feeds to summaries only, set the following variable:
89
+
90
+ ```python
91
+ ATOM_FEED_SUMMARY_ONLY = True
92
+ ```
93
+
94
+ ## Tests
95
+
96
+ Install the test dependencies:
97
+
98
+ ```console
99
+ pip install -e ".[dev]"
100
+ ```
101
+
102
+ Run the tests from the repository root:
103
+
104
+ ```console
105
+ python -m pytest -v
106
+ ```
107
+
108
+ ## License
109
+
110
+ This program is released under the GNU Affero General Public License.
@@ -0,0 +1,75 @@
1
+ [![GitHub release (latest SemVer)](https://img.shields.io/github/v/release/ysard/feed-minify)](https://github.com/ysard/feed-minify/releases/latest/)
2
+ [![python versions on pypi](https://img.shields.io/pypi/pyversions/pelican-feed-minify)](https://pypi.python.org/pypi/pelican-feed-minify)
3
+ [![version on pypi](https://img.shields.io/pypi/v/pelican-feed-minify)](https://pypi.python.org/pypi/pelican-feed-minify)
4
+ [![Downloads](https://img.shields.io/pypi/dm/pelican-feed-minify)](https://pypi.org/project/pelican-feed-minify/)
5
+ [![license](https://img.shields.io/pypi/l/pelican-feed-minify)](https://github.com/ysard/feed-minify/blob/main/LICENSE)
6
+ [![python test coverage](./images/coverage.svg)](https://docs.pytest.org/en/latest/)
7
+ [![Donate](./images/liberapay.svg)](https://liberapay.com/Ysard/donate)
8
+
9
+
10
+ # Pelican Feed Minify
11
+
12
+ A small [Pelican](https://getpelican.com/) plugin that minifies HTML embedded in
13
+ RSS and Atom feeds before Pelican serializes those feeds.
14
+
15
+ The plugin uses [`minify-html`](https://github.com/wilsonzlin/minify-html) to
16
+ remove HTML comments and compact the markup. This is useful when comments used
17
+ in Markdown or reStructuredText source files should not be exposed to feed
18
+ readers.
19
+
20
+ ## Features
21
+
22
+ - Removes HTML comments from feed item `content` and `description` fields.
23
+ - Minifies HTML in both Atom and RSS feed items.
24
+ - Leaves titles and other feed metadata untouched.
25
+ - Allows summaries only to be displayed in the Atom feeds (allowed only for RSS feeds by Pelican).
26
+ - Performs a detailed validation of the HTML content to check for characters not
27
+ allowed by the XML 1.0 standard (useful to track errors).
28
+ - Does not modify the HTML files generated for the website or the source
29
+ Markdown/reStructuredText files.
30
+ - Does not ask `minify-html` to minify embedded CSS or JavaScript.
31
+
32
+ > **Note:** This is HTML minification, not just comment removal. Whitespace,
33
+ > attributes, and some character/entity representations may also be normalized
34
+ > by `minify-html`. Review the output if your articles rely on unusual HTML.
35
+
36
+ ## Requirements
37
+
38
+ - A Python version supported by both Pelican and `minify-html`.
39
+ - Pelican 4.5 or newer (uses the namespace-plugin structure).
40
+
41
+ ## Installation
42
+
43
+ This plugin can be installed via:
44
+
45
+ python -m pip install pelican-feed-minify
46
+
47
+ Enable the plugin in `pelicanconf.py`:
48
+
49
+ ```python
50
+ PLUGINS = [..., "feed_minify"]
51
+ ```
52
+
53
+ To restrict Atom feeds to summaries only, set the following variable:
54
+
55
+ ```python
56
+ ATOM_FEED_SUMMARY_ONLY = True
57
+ ```
58
+
59
+ ## Tests
60
+
61
+ Install the test dependencies:
62
+
63
+ ```console
64
+ pip install -e ".[dev]"
65
+ ```
66
+
67
+ Run the tests from the repository root:
68
+
69
+ ```console
70
+ python -m pytest -v
71
+ ```
72
+
73
+ ## License
74
+
75
+ This program is released under the GNU Affero General Public License.
@@ -0,0 +1,3 @@
1
+ from .feed_minify import * # NOQA
2
+
3
+ __version__ = "1.0.1"
@@ -0,0 +1,107 @@
1
+ # This file is part of Feed-Minify for Pelican
2
+ #
3
+ # Copyright (C) 2026 Ysard
4
+ #
5
+ # Shinxsearch for Pelican is free software: you can redistribute it and/or modify
6
+ # it under the terms of the GNU Affero General Public License as
7
+ # published by the Free Software Foundation, either version 3 of the
8
+ # License, or (at your option) any later version.
9
+ #
10
+ # Shinxsearch for Pelican is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ # GNU Affero General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU Affero General Public License
16
+ # along with Shinxsearch for Pelican.
17
+ # If not, see <http://www.gnu.org/licenses/>.
18
+ """A Pelican plugin to check & minify HTML fields before XML serialization into RSS & Atom feeds"""
19
+
20
+ import re
21
+ import logging
22
+
23
+ from pelican.plugins import signals # pylint: disable=E0401
24
+ from feedgenerator import Atom1Feed # pylint: disable=E0611
25
+ import minify_html # pylint: disable=E0401
26
+
27
+ LOGGER = logging.getLogger(__name__)
28
+
29
+ # HTML fields used by feedgenerator:
30
+ # - Atom: content and description
31
+ # - RSS: description
32
+ HTML_FIELDS = ("content", "description")
33
+
34
+ INVALID_XML_CHARS = re.compile(
35
+ r"[^\x09\x0A\x0D\x20-\uD7FF\uE000-\uFFFD\U00010000-\U0010FFFF]"
36
+ )
37
+
38
+
39
+ def xml_validation(item: dict, field: str, content: str) -> None:
40
+ """Check XML feed for forbidden characters
41
+
42
+ .. seealso:: https://www.w3.org/TR/xml/#charsets
43
+ """
44
+ match = INVALID_XML_CHARS.search(content) if content else None
45
+ if match:
46
+ pos = match.start()
47
+ char = content[pos]
48
+ err_context = content[max(0, pos - 30) : pos + 30]
49
+
50
+ log_text = "[FEED DEBUG] Character not supported in XML 1.0!\n"
51
+ log_text += f" Article : {item.get('title')!r}\n"
52
+ log_text += f" URL : {item.get('link')}\n"
53
+ log_text += f" Field : {field}\n"
54
+ log_text += f" Position: {pos}\n"
55
+ log_text += f" Unicode : U+{ord(char):04X}\n"
56
+ log_text += f" Context : {err_context!r}"
57
+
58
+ LOGGER.error(log_text)
59
+
60
+
61
+ def minify_feed(
62
+ context: dict, feed: "Rss201rev2Feed | Atom1Feed", **kwargs # pylint:disable=W0613
63
+ ) -> None:
64
+ """Check and minify HTML content before the feed is serialized"""
65
+ if isinstance(feed, Atom1Feed):
66
+ LOGGER.debug("Process Atom feed.")
67
+ else:
68
+ LOGGER.debug("Process RSS feed.")
69
+
70
+ for item in feed.items:
71
+ for field in HTML_FIELDS:
72
+ content = item.get(field)
73
+ if not isinstance(content, str) or not content:
74
+ continue
75
+
76
+ xml_validation(item, field, content)
77
+
78
+ # Limit atom feeds to summaries only
79
+ if (
80
+ field == "content"
81
+ and isinstance(feed, Atom1Feed)
82
+ and context.get("ATOM_FEED_SUMMARY_ONLY")
83
+ ):
84
+ LOGGER.debug("Remove content from the Atom feed.")
85
+ item["content"] = None
86
+ continue
87
+
88
+ # Minification
89
+ minified = minify_html.minify(
90
+ content,
91
+ keep_comments=False,
92
+ minify_css=False,
93
+ minify_js=False,
94
+ )
95
+
96
+ if minified != content:
97
+ LOGGER.debug(
98
+ "Feed HTML minified: %r (%s)",
99
+ item.get("title", "(untitled)"),
100
+ field,
101
+ )
102
+ item[field] = minified
103
+
104
+
105
+ def register():
106
+ """Register callback invoked each time a feed gets generated"""
107
+ signals.feed_generated.connect(minify_feed)
@@ -0,0 +1,110 @@
1
+ Metadata-Version: 2.4
2
+ Name: pelican-feed-minify
3
+ Version: 1.0.1
4
+ Summary: A Pelican plugin to check & minify HTML fields before XML serialization into RSS & Atom feeds.
5
+ Author-email: Ysard <ysard@users.noreply.github.com>
6
+ License-Expression: AGPL-3.0-or-later
7
+ Project-URL: Homepage, https://github.com/ysard/feed-minify
8
+ Project-URL: Repository, https://github.com/ysard/feed-minify
9
+ Keywords: pelican,feed,RSS,Atom,minify,minification
10
+ Classifier: Development Status :: 5 - Production/Stable
11
+ Classifier: Framework :: Pelican
12
+ Classifier: Framework :: Pelican :: Plugins
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Intended Audience :: System Administrators
15
+ Classifier: Intended Audience :: End Users/Desktop
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Programming Language :: Python :: 3.14
22
+ Classifier: Topic :: Internet :: WWW/HTTP
23
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
24
+ Requires-Python: >3.11
25
+ Description-Content-Type: text/markdown
26
+ Requires-Dist: pelican>=4
27
+ Requires-Dist: minify-html>=0.18
28
+ Provides-Extra: dev
29
+ Requires-Dist: pytest-cov>=2.6.1; extra == "dev"
30
+ Requires-Dist: pytest>=5.2.0; extra == "dev"
31
+ Requires-Dist: pytest-runner; extra == "dev"
32
+ Requires-Dist: zest.releaser[recommended]; extra == "dev"
33
+ Requires-Dist: prospector; extra == "dev"
34
+ Requires-Dist: coverage-badge; extra == "dev"
35
+
36
+ [![GitHub release (latest SemVer)](https://img.shields.io/github/v/release/ysard/feed-minify)](https://github.com/ysard/feed-minify/releases/latest/)
37
+ [![python versions on pypi](https://img.shields.io/pypi/pyversions/pelican-feed-minify)](https://pypi.python.org/pypi/pelican-feed-minify)
38
+ [![version on pypi](https://img.shields.io/pypi/v/pelican-feed-minify)](https://pypi.python.org/pypi/pelican-feed-minify)
39
+ [![Downloads](https://img.shields.io/pypi/dm/pelican-feed-minify)](https://pypi.org/project/pelican-feed-minify/)
40
+ [![license](https://img.shields.io/pypi/l/pelican-feed-minify)](https://github.com/ysard/feed-minify/blob/main/LICENSE)
41
+ [![python test coverage](./images/coverage.svg)](https://docs.pytest.org/en/latest/)
42
+ [![Donate](./images/liberapay.svg)](https://liberapay.com/Ysard/donate)
43
+
44
+
45
+ # Pelican Feed Minify
46
+
47
+ A small [Pelican](https://getpelican.com/) plugin that minifies HTML embedded in
48
+ RSS and Atom feeds before Pelican serializes those feeds.
49
+
50
+ The plugin uses [`minify-html`](https://github.com/wilsonzlin/minify-html) to
51
+ remove HTML comments and compact the markup. This is useful when comments used
52
+ in Markdown or reStructuredText source files should not be exposed to feed
53
+ readers.
54
+
55
+ ## Features
56
+
57
+ - Removes HTML comments from feed item `content` and `description` fields.
58
+ - Minifies HTML in both Atom and RSS feed items.
59
+ - Leaves titles and other feed metadata untouched.
60
+ - Allows summaries only to be displayed in the Atom feeds (allowed only for RSS feeds by Pelican).
61
+ - Performs a detailed validation of the HTML content to check for characters not
62
+ allowed by the XML 1.0 standard (useful to track errors).
63
+ - Does not modify the HTML files generated for the website or the source
64
+ Markdown/reStructuredText files.
65
+ - Does not ask `minify-html` to minify embedded CSS or JavaScript.
66
+
67
+ > **Note:** This is HTML minification, not just comment removal. Whitespace,
68
+ > attributes, and some character/entity representations may also be normalized
69
+ > by `minify-html`. Review the output if your articles rely on unusual HTML.
70
+
71
+ ## Requirements
72
+
73
+ - A Python version supported by both Pelican and `minify-html`.
74
+ - Pelican 4.5 or newer (uses the namespace-plugin structure).
75
+
76
+ ## Installation
77
+
78
+ This plugin can be installed via:
79
+
80
+ python -m pip install pelican-feed-minify
81
+
82
+ Enable the plugin in `pelicanconf.py`:
83
+
84
+ ```python
85
+ PLUGINS = [..., "feed_minify"]
86
+ ```
87
+
88
+ To restrict Atom feeds to summaries only, set the following variable:
89
+
90
+ ```python
91
+ ATOM_FEED_SUMMARY_ONLY = True
92
+ ```
93
+
94
+ ## Tests
95
+
96
+ Install the test dependencies:
97
+
98
+ ```console
99
+ pip install -e ".[dev]"
100
+ ```
101
+
102
+ Run the tests from the repository root:
103
+
104
+ ```console
105
+ python -m pytest -v
106
+ ```
107
+
108
+ ## License
109
+
110
+ This program is released under the GNU Affero General Public License.
@@ -0,0 +1,10 @@
1
+ README.md
2
+ pyproject.toml
3
+ pelican/plugins/feed_minify/__init__.py
4
+ pelican/plugins/feed_minify/feed_minify.py
5
+ pelican_feed_minify.egg-info/PKG-INFO
6
+ pelican_feed_minify.egg-info/SOURCES.txt
7
+ pelican_feed_minify.egg-info/dependency_links.txt
8
+ pelican_feed_minify.egg-info/requires.txt
9
+ pelican_feed_minify.egg-info/top_level.txt
10
+ tests/test_feed_minify.py
@@ -0,0 +1,10 @@
1
+ pelican>=4
2
+ minify-html>=0.18
3
+
4
+ [dev]
5
+ pytest-cov>=2.6.1
6
+ pytest>=5.2.0
7
+ pytest-runner
8
+ zest.releaser[recommended]
9
+ prospector
10
+ coverage-badge
@@ -0,0 +1,61 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "pelican-feed-minify"
7
+ dynamic = ["version"]
8
+ description = "A Pelican plugin to check & minify HTML fields before XML serialization into RSS & Atom feeds."
9
+ readme = "README.md"
10
+ authors = [
11
+ { name = "Ysard", email = "ysard@users.noreply.github.com" }
12
+ ]
13
+ keywords = ["pelican", "feed", "RSS", "Atom", "minify", "minification"]
14
+ classifiers = [
15
+ "Development Status :: 5 - Production/Stable",
16
+ "Framework :: Pelican",
17
+ "Framework :: Pelican :: Plugins",
18
+ "Intended Audience :: Developers",
19
+ "Intended Audience :: System Administrators",
20
+ "Intended Audience :: End Users/Desktop",
21
+ "Operating System :: OS Independent",
22
+ "Programming Language :: Python :: 3",
23
+ "Programming Language :: Python :: 3.11",
24
+ "Programming Language :: Python :: 3.12",
25
+ "Programming Language :: Python :: 3.13",
26
+ "Programming Language :: Python :: 3.14",
27
+ "Topic :: Internet :: WWW/HTTP",
28
+ "Topic :: Software Development :: Libraries :: Python Modules",
29
+ ]
30
+ license = "AGPL-3.0-or-later"
31
+ dependencies = [
32
+ "pelican>=4",
33
+ "minify-html>=0.18"
34
+ ]
35
+ requires-python = ">3.11"
36
+
37
+ [project.urls]
38
+ Homepage = "https://github.com/ysard/feed-minify"
39
+ Repository = "https://github.com/ysard/feed-minify"
40
+
41
+ [project.optional-dependencies]
42
+ dev = [
43
+ "pytest-cov>=2.6.1",
44
+ "pytest>=5.2.0",
45
+ "pytest-runner",
46
+ "zest.releaser[recommended]",
47
+ "prospector",
48
+ "coverage-badge"
49
+ ]
50
+
51
+ [tool.setuptools.packages.find]
52
+ # These lines are mandatory for editable mode:
53
+ # Scans and includes absolutely everything inside `pelican/` no matter the depth.
54
+ include = ["pelican.*"]
55
+ namespaces = true
56
+
57
+ [tool.setuptools.dynamic]
58
+ version = { attr = "pelican.plugins.feed_minify.__version__" }
59
+
60
+ [tool.zest-releaser]
61
+ python-file-with-version = "pelican/plugins/feed_minify/__init__.py"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,182 @@
1
+ # This file is part of Feed-Minify for Pelican
2
+ #
3
+ # Copyright (C) 2026 Ysard
4
+ #
5
+ # Shinxsearch for Pelican is free software: you can redistribute it and/or modify
6
+ # it under the terms of the GNU Affero General Public License as
7
+ # published by the Free Software Foundation, either version 3 of the
8
+ # License, or (at your option) any later version.
9
+ #
10
+ # Shinxsearch for Pelican is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ # GNU Affero General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU Affero General Public License
16
+ # along with Shinxsearch for Pelican.
17
+ # If not, see <http://www.gnu.org/licenses/>.
18
+ """The test suite covers comment removal in both feed HTML fields, processing of
19
+ multiple feed items, preservation of non-HTML metadata, skipping missing/empty
20
+ or non-string values and registration on Pelican's `feed_generated` signal.
21
+ Tests also cover HTML validation and content removal for Atom feeds.
22
+ """
23
+ import logging
24
+ from types import SimpleNamespace
25
+
26
+ import pytest
27
+
28
+ from pelican.plugins import feed_minify
29
+
30
+
31
+ @pytest.mark.parametrize(
32
+ "codepoint",
33
+ [
34
+ 0x0000,
35
+ 0x0008,
36
+ 0x000B,
37
+ ],
38
+ ids=lambda cp: f"U+{cp:04X}",
39
+ )
40
+ def test_characters_rejects_invalid_xml_10_characters(codepoint, caplog):
41
+ """Characters forbidden by XML 1.0 must be detected"""
42
+ item = {
43
+ "title": "An article",
44
+ "content": f"before{chr(codepoint)}after",
45
+ }
46
+ feed = SimpleNamespace(items=[item])
47
+
48
+ with caplog.at_level(logging.ERROR):
49
+ feed_minify.minify_feed(context={}, feed=feed)
50
+
51
+ message = caplog.text
52
+
53
+ # The error must identify the offending character.
54
+ assert f"U+{codepoint:04X}" in message
55
+
56
+ # The error must report its position in the input string.
57
+ assert "Position: 6" in message
58
+
59
+ # The error must provide the surrounding context.
60
+ assert repr(item["content"]) in message
61
+
62
+
63
+ def test_atom_remove_content(monkeypatch):
64
+
65
+ class FakeAtomFeed:
66
+ def __init__(self, items=None):
67
+ self.items = items or []
68
+
69
+ item = {
70
+ "title": "An article",
71
+ "link": "https://example.com/",
72
+ "content": "<p>Body</p>",
73
+ "description": "<p>Summary</p>",
74
+ }
75
+
76
+ feed = FakeAtomFeed(items=[item])
77
+
78
+ monkeypatch.setattr(
79
+ # Patch the real namespace not the imported one via __init__.py
80
+ feed_minify.feed_minify,
81
+ "Atom1Feed",
82
+ FakeAtomFeed,
83
+ )
84
+
85
+ feed_minify.minify_feed(context={"ATOM_FEED_SUMMARY_ONLY": True}, feed=feed)
86
+
87
+ # The item is modified in place
88
+ item = feed.items[0]
89
+ assert item["content"] is None
90
+ assert "Summary" in item["description"]
91
+
92
+
93
+ def test_minify_feed_processes_content_and_description():
94
+ item = {
95
+ "title": "An article",
96
+ "content": "<p>Body</p><!-- body comment -->",
97
+ "description": "<p>Summary</p><!-- summary comment -->",
98
+ }
99
+ feed = SimpleNamespace(items=[item])
100
+
101
+ feed_minify.minify_feed(context={}, feed=feed)
102
+
103
+ assert "<!--" not in item["content"]
104
+ assert "<!--" not in item["description"]
105
+ assert "Body" in item["content"]
106
+ assert "Summary" in item["description"]
107
+
108
+
109
+ def test_minify_feed_processes_multiple_items():
110
+ feed = SimpleNamespace(
111
+ items=[
112
+ {"title": "First", "content": "<p>First</p><!-- comment -->"},
113
+ {"title": "Second", "content": "<p>Second</p><!-- comment -->"},
114
+ ]
115
+ )
116
+
117
+ feed_minify.minify_feed(context={}, feed=feed)
118
+
119
+ assert all("<!--" not in item["content"] for item in feed.items)
120
+ assert "First" in feed.items[0]["content"]
121
+ assert "Second" in feed.items[1]["content"]
122
+
123
+
124
+ def test_minify_feed_does_not_modify_title_or_other_metadata():
125
+ title = "<b>Title</b><!-- deliberately not treated as a feed HTML field -->"
126
+ item = {
127
+ "title": title,
128
+ "link": "https://example.test/article/",
129
+ "author_name": "Author",
130
+ "content": "<p>Body</p><!-- remove this -->",
131
+ "custom_metadata": "<p>Leave this untouched</p><!-- keep this -->",
132
+ }
133
+ feed = SimpleNamespace(items=[item])
134
+
135
+ feed_minify.minify_feed(context={}, feed=feed)
136
+
137
+ assert item["title"] == title
138
+ assert item["link"] == "https://example.test/article/"
139
+ assert item["author_name"] == "Author"
140
+ assert item["custom_metadata"] == "<p>Leave this untouched</p><!-- keep this -->"
141
+ assert "<!--" not in item["content"]
142
+
143
+
144
+ def test_minify_feed_skips_missing_empty_and_non_string_values(monkeypatch):
145
+ def unexpected_minify(*args, **kwargs):
146
+ pytest.fail("minify_html.minify must not be called for empty/non-string values")
147
+
148
+ monkeypatch.setattr(feed_minify.minify_html, "minify", unexpected_minify)
149
+
150
+ feed = SimpleNamespace(
151
+ items=[
152
+ {},
153
+ {"content": None, "description": None},
154
+ {"content": "", "description": ""},
155
+ {"content": 123, "description": ["not", "HTML"]},
156
+ ]
157
+ )
158
+
159
+ feed_minify.minify_feed(context={}, feed=feed)
160
+
161
+ assert feed.items[1]["content"] is None
162
+ assert feed.items[2]["content"] == ""
163
+ assert feed.items[3]["content"] == 123
164
+ assert feed.items[3]["description"] == ["not", "HTML"]
165
+
166
+
167
+ def test_register_connects_minifier_to_feed_generated(monkeypatch):
168
+ connected_receivers = []
169
+
170
+ class FakeSignal:
171
+ def connect(self, receiver):
172
+ connected_receivers.append(receiver)
173
+
174
+ monkeypatch.setattr(
175
+ feed_minify.signals,
176
+ "feed_generated",
177
+ FakeSignal(),
178
+ )
179
+
180
+ feed_minify.register()
181
+
182
+ assert connected_receivers == [feed_minify.minify_feed]