diff --git a/blurry/__init__.py b/blurry/__init__.py index a3fedc6..7cbebe1 100644 --- a/blurry/__init__.py +++ b/blurry/__init__.py @@ -25,6 +25,7 @@ from blurry.markdown import convert_markdown_file_to_html from blurry.open_graph import open_graph_meta_tags from blurry.plugins import discovered_html_plugins +from blurry.plugins import discovered_jinja_extensions from blurry.plugins import discovered_jinja_filter_plugins from blurry.schema_validation import validate_front_matter_as_schema from blurry.settings import get_build_directory @@ -69,6 +70,9 @@ def get_jinja_env(): } ) ), + extensions=[ + jinja_extension.load() for jinja_extension in discovered_jinja_extensions + ], ) for filter_plugin in discovered_jinja_filter_plugins: try: diff --git a/blurry/cli.py b/blurry/cli.py index ef689b1..84e25c3 100644 --- a/blurry/cli.py +++ b/blurry/cli.py @@ -2,6 +2,7 @@ from rich.table import Table from blurry.plugins import discovered_html_plugins +from blurry.plugins import discovered_jinja_extensions from blurry.plugins import discovered_jinja_filter_plugins from blurry.plugins import discovered_markdown_plugins @@ -27,7 +28,10 @@ def print_plugin_table(): plugin_table.add_row( "\n".join([p.name for p in discovered_markdown_plugins]), "\n".join([p.name for p in discovered_html_plugins]), - "\n".join([p.name for p in discovered_jinja_filter_plugins]), + "\n".join( + [p.name for p in discovered_jinja_filter_plugins] + + [p.name for p in discovered_jinja_extensions] + ), ) console.print(plugin_table) diff --git a/blurry/plugins/__init__.py b/blurry/plugins/__init__.py index d17b6b1..cb26555 100644 --- a/blurry/plugins/__init__.py +++ b/blurry/plugins/__init__.py @@ -3,3 +3,4 @@ discovered_markdown_plugins = entry_points(group="blurry.markdown_plugins") discovered_html_plugins = entry_points(group="blurry.html_plugins") discovered_jinja_filter_plugins = entry_points(group="blurry.jinja_filter_plugins") +discovered_jinja_extensions = entry_points(group="blurry.jinja_extensions") diff --git a/blurry/plugins/jinja_plugins/__init__.py b/blurry/plugins/jinja_plugins/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/blurry/plugins/jinja_plugins/blurry_image_extension.py b/blurry/plugins/jinja_plugins/blurry_image_extension.py new file mode 100644 index 0000000..b831d72 --- /dev/null +++ b/blurry/plugins/jinja_plugins/blurry_image_extension.py @@ -0,0 +1,53 @@ +import mimetypes +from pathlib import Path +from urllib.parse import urlparse + +from jinja2_simple_tags import StandaloneTag +from rich.console import Console +from wand.exceptions import BlobError +from wand.image import Image + +from blurry.images import add_image_width_to_path +from blurry.settings import get_build_directory +from blurry.utils import build_path_to_url + +warning_console = Console(stderr=True, style="bold yellow") + + +class BlurryImage(StandaloneTag): + safe_output = True + tags = {"blurry_image"} + + def render(self, *args, **kwargs): + (image_url,) = args + width = kwargs.get("width") + image_content_path: str = "." + urlparse(image_url).path + image_path = get_build_directory() / image_content_path + + try: + with Image(filename=str(image_path)) as image: + image_width = image.width + image_height = image.height + image_mimetype = image.mimetype + except BlobError: + warning_console.print(f"Could not find image: {image_path}") + return "" + + if width: + image_path = add_image_width_to_path(image_path, width) + + if image_mimetype in [ + mimetypes.types_map[".jpg"], + mimetypes.types_map[".png"], + ]: + image_path = Path(str(image_path).replace(image_path.suffix, ".avif")) + + if not image_path.exists(): + warning_console.print( + f"blurry_image: Could not find {image_path}. Skipping." + ) + return "" + + src = build_path_to_url(image_path) + + return f'' diff --git a/poetry.lock b/poetry.lock index 026aa76..7e22d43 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,9 +1,10 @@ -# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.4.2 and should not be changed by hand. [[package]] name = "annotated-types" version = "0.6.0" description = "Reusable constraint types to use with typing.Annotated" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -15,6 +16,7 @@ files = [ name = "argcomplete" version = "3.2.3" description = "Bash tab completion for argparse" +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -29,6 +31,7 @@ test = ["coverage", "mypy", "pexpect", "ruff", "wheel"] name = "atomicwrites" version = "1.4.1" description = "Atomic file writes." +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -39,6 +42,7 @@ files = [ name = "attrs" version = "23.2.0" description = "Classes Without Boilerplate" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -58,6 +62,7 @@ tests-no-zope = ["attrs[tests-mypy]", "cloudpickle", "hypothesis", "pympler", "p name = "black" version = "22.12.0" description = "The uncompromising code formatter." +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -92,6 +97,7 @@ uvloop = ["uvloop (>=0.15.2)"] name = "cachetools" version = "5.3.3" description = "Extensible memoizing collections and decorators" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -103,6 +109,7 @@ files = [ name = "click" version = "8.1.7" description = "Composable command line interface toolkit" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -117,6 +124,7 @@ colorama = {version = "*", markers = "platform_system == \"Windows\""} name = "colorama" version = "0.4.6" description = "Cross-platform colored terminal text." +category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" files = [ @@ -128,6 +136,7 @@ files = [ name = "colorlog" version = "6.8.2" description = "Add colours to the output of Python's logging module." +category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -145,6 +154,7 @@ development = ["black", "flake8", "mypy", "pytest", "types-colorama"] name = "coverage" version = "7.4.4" description = "Code coverage measurement for Python" +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -209,6 +219,7 @@ toml = ["tomli"] name = "distlib" version = "0.3.8" description = "Distribution utilities" +category = "dev" optional = false python-versions = "*" files = [ @@ -220,6 +231,7 @@ files = [ name = "docopt" version = "0.6.2" description = "Pythonic argument parser, that will make you smile" +category = "dev" optional = false python-versions = "*" files = [ @@ -230,6 +242,7 @@ files = [ name = "dpath" version = "2.1.6" description = "Filesystem-like pathing and searching for dictionaries" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -241,6 +254,7 @@ files = [ name = "ffmpeg-python" version = "0.2.0" description = "Python bindings for FFmpeg - with complex filtering support" +category = "main" optional = false python-versions = "*" files = [ @@ -258,6 +272,7 @@ dev = ["Sphinx (==2.1.0)", "future (==0.17.1)", "numpy (==1.16.4)", "pytest (==4 name = "filelock" version = "3.13.4" description = "A platform independent file lock." +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -274,6 +289,7 @@ typing = ["typing-extensions (>=4.8)"] name = "frozendict" version = "2.4.1" description = "A simple immutable dictionary" +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -319,6 +335,7 @@ files = [ name = "future" version = "1.0.0" description = "Clean single-source support for Python 3 and 2" +category = "main" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" files = [ @@ -330,6 +347,7 @@ files = [ name = "htmlmin2" version = "0.1.13" description = "An HTML Minifier" +category = "main" optional = false python-versions = "*" files = [ @@ -340,6 +358,7 @@ files = [ name = "iniconfig" version = "2.0.0" description = "brain-dead simple config-ini parsing" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -351,6 +370,7 @@ files = [ name = "jinja2" version = "3.1.3" description = "A very fast and expressive template engine." +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -364,10 +384,26 @@ MarkupSafe = ">=2.0" [package.extras] i18n = ["Babel (>=2.7)"] +[[package]] +name = "jinja2-simple-tags" +version = "0.6.1" +description = "Base classes for quick-and-easy template tag development" +category = "main" +optional = false +python-versions = ">=3.6" +files = [ + {file = "jinja2-simple-tags-0.6.1.tar.gz", hash = "sha256:54abf83883dcd13f8fd2ea2c42feeea8418df3640907bd5251dec5e25a6af0e3"}, + {file = "jinja2_simple_tags-0.6.1-py2.py3-none-any.whl", hash = "sha256:7b7cfa92f6813a1e0f0b61b9efcab60e6793674753e1f784ff270542e80ae20f"}, +] + +[package.dependencies] +Jinja2 = ">=2.10" + [[package]] name = "livereload" version = "2.6.3" description = "Python LiveReload is an awesome tool for web developers" +category = "main" optional = false python-versions = "*" files = [ @@ -383,6 +419,7 @@ tornado = {version = "*", markers = "python_version > \"2.7\""} name = "lxml" version = "5.2.1" description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API." +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -554,6 +591,7 @@ source = ["Cython (>=3.0.10)"] name = "markdown-it-py" version = "3.0.0" description = "Python port of markdown-it. Markdown parsing, done right!" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -578,6 +616,7 @@ testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] name = "markupsafe" version = "2.1.5" description = "Safely add untrusted strings to HTML/XML markup." +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -647,6 +686,7 @@ files = [ name = "mdurl" version = "0.1.2" description = "Markdown URL utilities" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -658,6 +698,7 @@ files = [ name = "mistune" version = "3.0.2" description = "A sane and fast Markdown parser with useful plugins and renderers" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -669,6 +710,7 @@ files = [ name = "mypy-extensions" version = "1.0.0" description = "Type system extensions for programs checked with the mypy type checker." +category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -680,6 +722,7 @@ files = [ name = "nodeenv" version = "1.8.0" description = "Node.js virtual environment builder" +category = "dev" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*" files = [ @@ -694,6 +737,7 @@ setuptools = "*" name = "nox" version = "2024.3.2" description = "Flexible test automation." +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -715,6 +759,7 @@ uv = ["uv"] name = "packaging" version = "24.0" description = "Core utilities for Python packages" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -726,6 +771,7 @@ files = [ name = "pathspec" version = "0.12.1" description = "Utility library for gitignore style pattern matching of file paths." +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -737,6 +783,7 @@ files = [ name = "platformdirs" version = "4.2.0" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -752,6 +799,7 @@ test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest- name = "pluggy" version = "1.4.0" description = "plugin and hook calling mechanisms for python" +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -767,6 +815,7 @@ testing = ["pytest", "pytest-benchmark"] name = "py" version = "1.11.0" description = "library with cross-python path, ini-parsing, io, code, log facilities" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ @@ -778,6 +827,7 @@ files = [ name = "pydantic" version = "2.7.0" description = "Data validation using Python type hints" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -797,6 +847,7 @@ email = ["email-validator (>=2.0.0)"] name = "pydantic-core" version = "2.18.1" description = "Core functionality for Pydantic validation and serialization" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -888,6 +939,7 @@ typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" name = "pydantic2-schemaorg" version = "0.1.1" description = "Pydantic classes for Schema.org" +category = "main" optional = false python-versions = "<4.0,>=3.10" files = [ @@ -902,6 +954,7 @@ pydantic = ">=2.6.1,<3.0.0" name = "pygments" version = "2.17.2" description = "Pygments is a syntax highlighting package written in Python." +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -917,6 +970,7 @@ windows-terminal = ["colorama (>=0.4.6)"] name = "pyld" version = "2.0.4" description = "Python implementation of the JSON-LD API" +category = "main" optional = false python-versions = "*" files = [ @@ -939,6 +993,7 @@ requests = ["requests"] name = "pyright" version = "1.1.358" description = "Command line wrapper for pyright" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -957,6 +1012,7 @@ dev = ["twine (>=3.4.1)"] name = "pytest" version = "6.2.5" description = "pytest: simple powerful testing with Python" +category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -981,6 +1037,7 @@ testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xm name = "pytest-cov" version = "2.12.1" description = "Pytest plugin for measuring coverage." +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ @@ -1000,6 +1057,7 @@ testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtuale name = "pytest-watch" version = "4.2.0" description = "Local continuous test runner with pytest and watchdog." +category = "dev" optional = false python-versions = "*" files = [ @@ -1016,6 +1074,7 @@ watchdog = ">=0.6.0" name = "rich" version = "13.7.1" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" +category = "main" optional = false python-versions = ">=3.7.0" files = [ @@ -1034,6 +1093,7 @@ jupyter = ["ipywidgets (>=7.5.1,<9)"] name = "ruff" version = "0.1.15" description = "An extremely fast Python linter and code formatter, written in Rust." +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1060,6 +1120,7 @@ files = [ name = "selectolax" version = "0.3.21" description = "Fast HTML5 parser with CSS selectors." +category = "main" optional = false python-versions = "*" files = [ @@ -1128,6 +1189,7 @@ cython = ["Cython (==0.29.36)"] name = "setuptools" version = "69.5.0" description = "Easily download, build, install, upgrade, and uninstall Python packages" +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -1144,6 +1206,7 @@ testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jar name = "six" version = "1.16.0" description = "Python 2 and 3 compatibility utilities" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" files = [ @@ -1155,6 +1218,7 @@ files = [ name = "toml" version = "0.10.2" description = "Python Library for Tom's Obvious, Minimal Language" +category = "main" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" files = [ @@ -1166,6 +1230,7 @@ files = [ name = "tomli" version = "2.0.1" description = "A lil' TOML parser" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1177,6 +1242,7 @@ files = [ name = "tornado" version = "6.4" description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed." +category = "main" optional = false python-versions = ">= 3.8" files = [ @@ -1197,6 +1263,7 @@ files = [ name = "typer" version = "0.6.1" description = "Typer, build great CLIs. Easy to code. Based on Python type hints." +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -1217,6 +1284,7 @@ test = ["black (>=22.3.0,<23.0.0)", "coverage (>=5.2,<6.0)", "isort (>=5.0.6,<6. name = "typing-extensions" version = "4.11.0" description = "Backported and Experimental Type Hints for Python 3.8+" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1228,6 +1296,7 @@ files = [ name = "virtualenv" version = "20.25.1" description = "Virtual Python Environment builder" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1248,6 +1317,7 @@ test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess name = "wand" version = "0.6.13" description = "Ctypes-based simple MagickWand API binding for Python" +category = "main" optional = false python-versions = "*" files = [ @@ -1263,6 +1333,7 @@ test = ["pytest (>=7.2.0)"] name = "watchdog" version = "4.0.0" description = "Filesystem events monitoring" +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -1303,4 +1374,4 @@ watchmedo = ["PyYAML (>=3.10)"] [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "97f3dca65d2ff7f20db8edd47b2346631f1eb307eab78f9002eb08909bbba90e" +content-hash = "c6aa303093c8a09f022adead8d88cd15aeb433ce4d303b89a7a5eaaed4dbe849" diff --git a/pyproject.toml b/pyproject.toml index d0a226c..38e6094 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,6 +37,7 @@ typer = "^0.6.1" htmlmin2 = "^0.1.13" pydantic2-schemaorg = "^0.1.1" dpath = "^2.1.6" +jinja2-simple-tags = "^0.6.1" [tool.poetry.scripts] blurry = 'blurry:main' @@ -70,3 +71,6 @@ python_code = 'blurry.plugins.markdown_plugins.python_code_plugin:python_code' python_code_in_list = 'blurry.plugins.markdown_plugins.python_code_plugin:python_code_in_list' punctuation = 'blurry.plugins.markdown_plugins.punctuation_plugin:punctuation' container = 'blurry.plugins.markdown_plugins.container_plugin:container' + +[tool.poetry.plugins."blurry.jinja_extensions"] +blurry_image = "blurry.plugins.jinja_plugins.blurry_image_extension:BlurryImage"