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.
- pelican_feed_minify-1.0.1/PKG-INFO +110 -0
- pelican_feed_minify-1.0.1/README.md +75 -0
- pelican_feed_minify-1.0.1/pelican/plugins/feed_minify/__init__.py +3 -0
- pelican_feed_minify-1.0.1/pelican/plugins/feed_minify/feed_minify.py +107 -0
- pelican_feed_minify-1.0.1/pelican_feed_minify.egg-info/PKG-INFO +110 -0
- pelican_feed_minify-1.0.1/pelican_feed_minify.egg-info/SOURCES.txt +10 -0
- pelican_feed_minify-1.0.1/pelican_feed_minify.egg-info/dependency_links.txt +1 -0
- pelican_feed_minify-1.0.1/pelican_feed_minify.egg-info/requires.txt +10 -0
- pelican_feed_minify-1.0.1/pelican_feed_minify.egg-info/top_level.txt +1 -0
- pelican_feed_minify-1.0.1/pyproject.toml +61 -0
- pelican_feed_minify-1.0.1/setup.cfg +4 -0
- pelican_feed_minify-1.0.1/tests/test_feed_minify.py +182 -0
|
@@ -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
|
+
[](https://github.com/ysard/feed-minify/releases/latest/)
|
|
37
|
+
[](https://pypi.python.org/pypi/pelican-feed-minify)
|
|
38
|
+
[](https://pypi.python.org/pypi/pelican-feed-minify)
|
|
39
|
+
[](https://pypi.org/project/pelican-feed-minify/)
|
|
40
|
+
[](https://github.com/ysard/feed-minify/blob/main/LICENSE)
|
|
41
|
+
[](https://docs.pytest.org/en/latest/)
|
|
42
|
+
[](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
|
+
[](https://github.com/ysard/feed-minify/releases/latest/)
|
|
2
|
+
[](https://pypi.python.org/pypi/pelican-feed-minify)
|
|
3
|
+
[](https://pypi.python.org/pypi/pelican-feed-minify)
|
|
4
|
+
[](https://pypi.org/project/pelican-feed-minify/)
|
|
5
|
+
[](https://github.com/ysard/feed-minify/blob/main/LICENSE)
|
|
6
|
+
[](https://docs.pytest.org/en/latest/)
|
|
7
|
+
[](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,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
|
+
[](https://github.com/ysard/feed-minify/releases/latest/)
|
|
37
|
+
[](https://pypi.python.org/pypi/pelican-feed-minify)
|
|
38
|
+
[](https://pypi.python.org/pypi/pelican-feed-minify)
|
|
39
|
+
[](https://pypi.org/project/pelican-feed-minify/)
|
|
40
|
+
[](https://github.com/ysard/feed-minify/blob/main/LICENSE)
|
|
41
|
+
[](https://docs.pytest.org/en/latest/)
|
|
42
|
+
[](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 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
pelican
|
|
@@ -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,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]
|