-
-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Refactor for Pelican 4 + inline minification (#3)
* sort imports * Fix svg compress fault due to csscompressor For at least all versions under 0.9.5 of csscompressor, there is a faulty compression on the url() func of css rules; spaces are wrongly removed, thus destroying the meaning of any embedded svg file. See sprymix/csscompressor#9 * fix typos with black * Add logger/logging * Ability to minify inline JS & CSS tags jsmin is used for JavaScript minification; csscompressor is used as for css files * fix typos * packaging: add setup.cfg * packaging: drop old setup.py (replaced by setup.cfg) * add version to __init__ * Move src files to new pelican plugins architecture * Drop python2.7 open method * Set pelican 4 dep * Add license text in src file as it should be * README: update; - simplify maintenance process by removing references to specific versions of dependencies (See setup.cfg instead) - Add jsmin dep - Add script & style related minification * Add Makefile for release & test process * Remove useless lambda * Update changelog * README: More complete plugin description * README: mention to beautifulsoup dep * packaging: add beautifulsoup dep * Fix rest typo
- Loading branch information
Showing
7 changed files
with
257 additions
and
120 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
|
||
# development & release cycle | ||
fullrelease: | ||
fullrelease | ||
|
||
check_setups: | ||
pyroma . | ||
|
||
check_code: | ||
prospector pelican/plugins/minification/ | ||
check-manifest | ||
|
||
sdist: | ||
python setup.py sdist |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,167 @@ | ||
# -*- coding: utf-8 -*- | ||
# MIT License | ||
# | ||
# Copyright (c) 2014-2015 Alexander Herrmann <[email protected]> | ||
# Copyright (c) 2022 Ysard <[email protected]> | ||
# | ||
# Permission is hereby granted, free of charge, to any person obtaining a copy | ||
# of this software and associated documentation files (the "Software"), to deal | ||
# in the Software without restriction, including without limitation the rights | ||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
# copies of the Software, and to permit persons to whom the Software is | ||
# furnished to do so, subject to the following conditions: | ||
# | ||
# The above copyright notice and this permission notice shall be included in all | ||
# copies or substantial portions of the Software. | ||
# | ||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
# SOFTWARE. | ||
# Standard imports | ||
import os | ||
from packaging import version | ||
from functools import lru_cache | ||
from fnmatch import fnmatch | ||
import logging | ||
|
||
# Custom imports | ||
import csscompressor | ||
from jsmin import jsmin | ||
import htmlmin | ||
from bs4 import BeautifulSoup | ||
|
||
# Pelican imports | ||
from pelican import signals | ||
|
||
__version__ = "0.2.0" | ||
|
||
LOGGER = logging.getLogger(__name__) | ||
|
||
if version.parse(csscompressor.__version__) <= version.parse("0.9.5"): | ||
# Monkey patch csscompressor 0.9.5 | ||
_preserve_call_tokens_original = csscompressor._preserve_call_tokens | ||
_url_re = csscompressor._url_re | ||
|
||
def my_new_preserve_call_tokens(*args, **kwargs): | ||
"""If regex is for url pattern, switch the keyword remove_ws to False | ||
Such configuration will preserve svg code in url() pattern of CSS file. | ||
""" | ||
if _url_re == args[1]: | ||
kwargs["remove_ws"] = False | ||
return _preserve_call_tokens_original(*args, **kwargs) | ||
|
||
csscompressor._preserve_call_tokens = my_new_preserve_call_tokens | ||
|
||
assert csscompressor._preserve_call_tokens == my_new_preserve_call_tokens | ||
|
||
|
||
class Minification: | ||
"""File content minification""" | ||
|
||
def __init__(self, pelican): | ||
"""Minifies the files | ||
:param pelican: the pelican object | ||
:type pelican: pelican.Pelican | ||
""" | ||
LOGGER.info("Minification in progress...") | ||
|
||
for path, subdirs, files in os.walk(pelican.output_path): | ||
for name in files: | ||
path_file = os.path.join(path, name) | ||
|
||
if fnmatch(name, "*.html"): | ||
self.write_to_file( | ||
path_file, | ||
lambda content: self.minify_inline_script_style( | ||
htmlmin.minify( | ||
content, | ||
remove_comments=True, | ||
remove_empty_space=True, | ||
reduce_boolean_attributes=True, | ||
keep_pre=True, | ||
remove_optional_attribute_quotes=False, | ||
) | ||
), | ||
) | ||
elif fnmatch(name, "*.css"): | ||
self.write_to_file(path_file, csscompressor.compress) | ||
|
||
def minify_inline_script_style(self, content): | ||
"""Minify inline JavaScript and CSS in HTML content | ||
:param content: HTML data | ||
:type content: <str> | ||
:return: HTML data with <script> and <style> tags minified | ||
:rtype: <str> | ||
""" | ||
soup = BeautifulSoup(content, "html.parser") | ||
|
||
# Compression methods according to specific HTML tags | ||
tags_methods = {"script": jsmin, "style": csscompressor.compress} | ||
|
||
content_modified = False | ||
for tag, method in tags_methods.items(): | ||
|
||
found_tags = soup.find_all(tag) | ||
if not found_tags: | ||
continue | ||
|
||
for found_tag in found_tags: | ||
# Exclude empty tags | ||
if not found_tag.string: | ||
continue | ||
content_modified = True | ||
found_tag.string.replace_with( | ||
minification_method(method, found_tag.string) | ||
) | ||
|
||
# Return content as is if there have been no changes | ||
return str(soup) if content_modified else content | ||
|
||
@staticmethod | ||
def write_to_file(path_file, callback): | ||
"""Read the content of the given file, put the content into the callback | ||
and writes the result back to the file. | ||
:param path_file: the path to the file | ||
:type path_file: str | ||
:param callback: the callback function | ||
:type callback: function | ||
""" | ||
try: | ||
with open(path_file, "r+", encoding="utf-8") as f: | ||
content = callback(f.read()) | ||
f.seek(0) | ||
f.write(content) | ||
f.truncate() | ||
except Exception as e: | ||
raise Exception( | ||
"unable to minify file %(file)s, exception was %(exception)r" | ||
% {"file": path_file, "exception": e} | ||
) | ||
|
||
|
||
@lru_cache(maxsize=None) | ||
def minification_method(method, content): | ||
"""Cached wrapper for minification method | ||
Some JavaScript or CSS tags may be similar from page to page; | ||
so caching the return of this function can speed up the minification process. | ||
:param content: JavaScript or CSS code | ||
:type content: <str> | ||
:return: Minified code | ||
:rtype: <str> | ||
""" | ||
return method(content) | ||
|
||
|
||
def register(): | ||
"""Register the plugin after the content was generated""" | ||
signals.finalized.connect(Minification) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
[metadata] | ||
name = pelican-minification | ||
version = attr: pelican.plugins.minification.__version__ | ||
description = Minifies HTML, CSS and JS of generated Pelican content. | ||
long_description = file: README.rst | ||
long_description_content_type = text/x-rst | ||
author = Alexander Herrmann | ||
author_email = [email protected] | ||
url = https://github.com/dArignac/pelican-minification | ||
|
||
license_files = LICENSE | ||
keywords = pelican, javascript, css, html, minification | ||
classifiers = | ||
Development Status :: 4 - Beta | ||
Intended Audience :: Developers | ||
Intended Audience :: System Administrators | ||
Intended Audience :: End Users/Desktop | ||
Natural Language :: English | ||
Operating System :: POSIX :: Linux | ||
Topic :: Text Processing :: Markup | ||
Programming Language :: Python :: 3 | ||
Programming Language :: Python :: 3.6 | ||
Programming Language :: Python :: 3.7 | ||
Programming Language :: Python :: 3.8 | ||
Programming Language :: Python :: 3.9 | ||
Programming Language :: Python :: 3.10 | ||
License :: OSI Approved :: MIT License | ||
|
||
[options] | ||
zip_safe = False | ||
include_package_data = True | ||
packages = pelican.plugins.minification | ||
install_requires = | ||
csscompressor>=0.9.5 | ||
htmlmin>=0.1.12 | ||
jsmin>=3.0.1 | ||
beautifulsoup4 | ||
pelican>=4 | ||
|
||
[options.extras_require] | ||
dev = | ||
pytest-cov>=2.6.1 | ||
pytest>=5.2.0 | ||
pytest-runner | ||
zest.releaser[recommended] | ||
|
||
[zest.releaser] | ||
create-wheel = no | ||
python-file-with-version = pelican/plugins/minification/__init__.py | ||
|
||
[aliases] | ||
test=pytest |
Oops, something went wrong.