From 5e78f708e8ce36de4b4b52965b85f94b281b8c1d Mon Sep 17 00:00:00 2001 From: Mohamed Feddad Date: Sun, 5 Dec 2021 16:27:10 +0400 Subject: [PATCH 1/3] Refactor runtime_options and drop parser_precedence, hashing --- README.md | 7 +++--- flask_minify/decorators.py | 29 ++++++----------------- flask_minify/main.py | 28 +++++------------------ flask_minify/parsers.py | 47 ++++++++++++++++++++++++++++---------- flask_minify/utils.py | 8 +++++++ 5 files changed, 59 insertions(+), 60 deletions(-) diff --git a/README.md b/README.md index d97559d..5b8d8ea 100644 --- a/README.md +++ b/README.md @@ -75,7 +75,6 @@ Option | type | Description static | `bool` | enable minifying static files css, less and js (default: `True`) script_types | `list` | script types to limit js minification to (default: `[]`) parsers | `dict` | parsers to handle minifying specific tags, mainly for advanced customization (default: `{}`) - parser_precedence | `bool` | allow parser specific options to take precedence over the extension (default: `False`) #### - `bypass` and `bypass_caching` @@ -107,9 +106,9 @@ when using the option include `''` (empty string) in the list to include script #### - `parsers` -using `parser` allows to pass tag specific options to the module responsible for the minification, as well as replacing the default -parser with another included option or your own custom one. In the following example will replace the default `style` (handles CSS) -parser `rcssmin` with `lesscpy`: +allows passing tag specific options to the module responsible for the minification, as well as replacing the default parser with another included option or your own custom one. + +In the following example will replace the default `style` (handles CSS) parser `rcssmin` with `lesscpy`: ```python from flask_minify import minify diff --git a/flask_minify/decorators.py b/flask_minify/decorators.py index e5d22d3..54675a6 100644 --- a/flask_minify/decorators.py +++ b/flask_minify/decorators.py @@ -1,5 +1,8 @@ from functools import wraps +from flask_minify.parsers import Parser +from flask_minify.utils import get_optimized_hashing + def minify( html=False, @@ -8,7 +11,6 @@ def minify( cache=True, fail_safe=True, parsers={}, - parser_precedence=False, ): """Decorator to minify endpoint HTML output. @@ -26,51 +28,34 @@ def minify( silence encountered exceptions. parsers: dict parsers to handle minifying specific tags. - parser_options_take_precedence: bool - allow parser specific options to override the extension. Returns ------- String of minified HTML content. """ + hashing = get_optimized_hashing() + parser = Parser(parsers, fail_safe) + parser.update_runtime_options(html, js, cssless) def decorator(function): @wraps(function) def wrapper(*args, **kwargs): - from .main import hashing - from .parsers import Parser - text = function(*args, **kwargs) key = None cache_key, cached = function.__dict__.get("minify", (None, None)) should_minify = isinstance(text, str) and any([html, js, cssless]) - runtime_options = { - "html": { - "only_html_content": not html, - "minify_inline": { - "script": js, - "style": cssless, - }, - }, - } if should_minify: if cache: key = hashing(text).hexdigest() if cache_key != key or not cache: - parser = Parser( - runtime_options=runtime_options, - fail_safe=fail_safe, - parsers=parsers, - parser_precedence=parser_precedence, - ) text = parser.minify(text, "html") if cache: function.__dict__["minify"] = (key, text) - should_return_cached = all([cache_key == key, cache, should_minify]) + should_return_cached = cache_key == key and cache and should_minify return cached if should_return_cached else text return wrapper diff --git a/flask_minify/main.py b/flask_minify/main.py index aab51d7..da41474 100644 --- a/flask_minify/main.py +++ b/flask_minify/main.py @@ -1,15 +1,10 @@ from itertools import tee from re import compile as compile_re -from sys import maxsize from flask import _app_ctx_stack, request -from xxhash import xxh32, xxh64 -from flask_minify.parsers import Html, Jsmin, Lesscpy, Parser -from flask_minify.utils import is_cssless, is_html, is_js - -# optimized hashing speed based on cpu architecture -hashing = xxh64 if maxsize > 2 ** 32 else xxh32 +from flask_minify.parsers import Parser +from flask_minify.utils import get_optimized_hashing, is_cssless, is_html, is_js class Minify: @@ -29,7 +24,6 @@ def __init__( static=True, script_types=[], parsers={}, - parser_precedence=False, ): """Extension to minify flask response for html, javascript, css and less. @@ -59,8 +53,6 @@ def __init__( list of script types to limit js minification to. parsers: dict parsers to handle minifying specific tags. - parser_precedence: bool - allow parser specific options to take precedence over the extension. Notes ----- @@ -97,17 +89,9 @@ def root(id): self._app = app self.passive = passive self.static = static - runtime_options = { - "html": { - "only_html_content": not html, - "script_types": script_types, - "minify_inline": { - "script": js, - "style": cssless, - }, - }, - } - self.parser = Parser(parsers, runtime_options, fail_safe, parser_precedence) + self.hashing = get_optimized_hashing() + self.parser = Parser(parsers, fail_safe) + self.parser.update_runtime_options(html, js, cssless, script_types) app and self.init_app(app) @@ -192,7 +176,7 @@ def get_minified_or_cached(self, content, tag): def _cache_dict(): return self.cache.get(self.endpoint, {}) - key = hashing(content.encode("utf-8")).hexdigest() + key = self.hashing(content.encode("utf-8")).hexdigest() limit_reached = len(_cache_dict()) >= self.caching_limit _, bypassed = self.get_endpoint_matches(self.endpoint, self.bypass_caching) diff --git a/flask_minify/parsers.py b/flask_minify/parsers.py index 238a8e3..cce6050 100644 --- a/flask_minify/parsers.py +++ b/flask_minify/parsers.py @@ -8,29 +8,38 @@ from flask_minify.utils import get_tag_contents -class Jsmin: - runtime_options = {"quote_chars": "'\"`"} +class ParserMixin: + # parser specific runtime option will take precedence over global + takes_precedence = False + + @property + def options_changed(self): + return self._o != self.runtime_options + + +class Jsmin(ParserMixin): + runtime_options = _o = {"quote_chars": "'\"`"} executer = staticmethod(jsmin) -class Rcssmin: - runtime_options = {"keep_bang_comments": False} +class Rcssmin(ParserMixin): + runtime_options = _o = {"keep_bang_comments": False} executer = staticmethod(cssmin) -class Lesscpy: - runtime_options = {"minify": True, "xminify": True} +class Lesscpy(ParserMixin): + runtime_options = _o = {"minify": True, "xminify": True} def executer(self, content, **options): return compile_less(StringIO(content), **options) -class Html: +class Html(ParserMixin): _default_tags = { "script": False, "style": False, } - runtime_options = { + runtime_options = _o = { "remove_comments": True, "remove_optional_attribute_quotes": False, "only_html_content": False, @@ -58,14 +67,26 @@ class Parser: def __init__( self, parsers={}, - runtime_options={}, fail_safe=False, - parser_precedence=False, + runtime_options={}, ): self.parsers = {**self._default_parsers, **parsers} self.runtime_options = {**runtime_options} self.fail_safe = fail_safe - self.parser_precedence = parser_precedence + + def update_runtime_options( + self, html=False, js=False, cssless=False, script_types=[] + ): + self.runtime_options.setdefault("html", {}).update( + { + "only_html_content": not html, + "script_types": script_types, + "minify_inline": { + "script": js, + "style": cssless, + }, + } + ) def minify(self, content, tag): if tag not in self.parsers: @@ -74,7 +95,9 @@ def minify(self, content, tag): parser = self.parsers[tag]() parser.parser = self - if self.parser_precedence: + if parser.options_changed: + runtime_options = parser.runtime_options + elif parser.takes_precedence: runtime_options = { **self.runtime_options.get(tag, {}), **parser.runtime_options, diff --git a/flask_minify/utils.py b/flask_minify/utils.py index 0b91844..db705eb 100644 --- a/flask_minify/utils.py +++ b/flask_minify/utils.py @@ -1,6 +1,9 @@ from re import DOTALL from re import compile as compile_re from re import sub +from sys import maxsize + +from xxhash import xxh32, xxh64 def is_empty(content): @@ -129,3 +132,8 @@ def is_cssless(response): content_type = getattr(response, "content_type", "") return "css" in content_type.lower() or "less" in content_type.lower() + + +def get_optimized_hashing(): + """Gets optimized hashing module based on cpu architecture""" + return xxh64 if maxsize > 2 ** 32 else xxh32 From 8b11e19ad43073b4f6a7e89166252fef9d950cdd Mon Sep 17 00:00:00 2001 From: Mohamed Feddad Date: Sun, 5 Dec 2021 16:28:17 +0400 Subject: [PATCH 2/3] Test changing parser options precedence and cleanups --- tests/constants.py | 2 ++ tests/units.py | 20 +++++++++++++++++--- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/tests/constants.py b/tests/constants.py index b459ddb..7bfb84f 100644 --- a/tests/constants.py +++ b/tests/constants.py @@ -117,3 +117,5 @@ ] ).encode("utf-8") ) + +COMPILED_LESS_RAW = "body {\n color: red;\n}" diff --git a/tests/units.py b/tests/units.py index 841bbb8..7c50198 100644 --- a/tests/units.py +++ b/tests/units.py @@ -1,11 +1,14 @@ -from os import path -from sys import path as sys_path from unittest import mock from flask_minify import minify, parsers from flask_minify.utils import is_cssless, is_empty, is_html, is_js -from .constants import CSS_EDGE_CASES, MINIFIED_CSS_EDGE_CASES +from .constants import ( + COMPILED_LESS_RAW, + CSS_EDGE_CASES, + LESS_RAW, + MINIFIED_CSS_EDGE_CASES, +) class TestUtils: @@ -81,3 +84,14 @@ def test_css_edge_cases_with_rcssmin(self): minified = parser.minify(CSS_EDGE_CASES, "style") assert minified == MINIFIED_CSS_EDGE_CASES + + def test_overriding_parser_options(self): + new_options = {"minify": False} + + class CustomParser(parsers.Lesscpy): + runtime_options = new_options + + parser = parsers.Parser({"style": CustomParser}) + minified = parser.minify(LESS_RAW, "style") + + assert minified == COMPILED_LESS_RAW From ca4e86a89a47674de7451ab67bd7f9d3b034781c Mon Sep 17 00:00:00 2001 From: Mohamed Feddad Date: Sun, 5 Dec 2021 16:37:55 +0400 Subject: [PATCH 3/3] Bump version to 0.34 --- flask_minify/about.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flask_minify/about.py b/flask_minify/about.py index 41986df..11d0cb0 100644 --- a/flask_minify/about.py +++ b/flask_minify/about.py @@ -1,4 +1,4 @@ -__version__ = "0.33" +__version__ = "0.34" __doc__ = "Flask extension to minify html, css, js and less." __license__ = "MIT" __author__ = "Mohamed Feddad"