Skip to content

Commit

Permalink
feat: add blurry_image Jinja extension (#75)
Browse files Browse the repository at this point in the history
Adds blurry_image extension to insert an <img> tag with width &
height into a Jinja template. Optionally inserts an image of a
specific size.

Usage:

```
{% blurry_image page.image, width=250 %}
```
  • Loading branch information
johnfraney authored Apr 28, 2024
1 parent 73154f2 commit dc2ac7b
Show file tree
Hide file tree
Showing 11 changed files with 270 additions and 5 deletions.
4 changes: 4 additions & 0 deletions blurry/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand Down
6 changes: 5 additions & 1 deletion blurry/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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)
1 change: 1 addition & 0 deletions blurry/plugins/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Empty file.
75 changes: 75 additions & 0 deletions blurry/plugins/jinja_plugins/blurry_image_extension.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
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, width) = args
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 ""

attributes = {
"width": image_width,
"height": image_height,
}
for attribute_key in kwargs:
if attribute_key in ["width", "height"]:
warning_console.print(
f"blurry_image: Received {attribute_key} in template {self.template} but this attribute is dynamic. Skipping."
)
continue
attributes[attribute_key] = kwargs.get(attribute_key)

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 ""

attributes["src"] = build_path_to_url(image_path)

if "alt" not in attributes:
warning_console.print(
f"blurry_image: alt attribute missing for image in {self.template}. "
"This can negatively affect accessibility. "
"Use an empty alt tag if an image is only for show."
)

attributes_str = " ".join(
f'{name}="{value}"' for name, value in attributes.items()
)

return f"<img {attributes_str}/>"
30 changes: 30 additions & 0 deletions docs/content/plugins/write-a-jinja-extension-plugin.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
+++
"@type" = "WebPage"
name = "Plugins: write a Jinja extension plugin"
abstract = "Documentation for Blurry's Jinja extension plugins"
datePublished = 2024-04-28
+++

# Plugins: write an Jinja extension plugin

Blurry makes it easy to add [custom Jinja extensions](https://jinja.palletsprojects.com/en/3.1.x/extensions/) to your site.
What is a Jinja extension?
From the Jinja docs:

> Jinja supports extensions that can add extra filters, tests, globals or even extend the parser. The main motivation of extensions is to move often used code into a reusable class like adding support for internationalization.
With [custom extensions](https://jinja.palletsprojects.com/en/3.1.x/extensions/#module-jinja2.ext) you can add custom tags to Jinja, like Blurry's `{% blurry_image %}` tag.

## Example: `{% blurry_image %}`

This tag finds the optimized version of an image at the specified URL, and optionally of the specified size.
You can find it in Blurry's source code in `blurry/plugins/jinja_plugins/blurry_image_extension.py`.

Under the hood the extension uses [`jinja2-simple-tags`](https://github.com/dldevinc/jinja2-simple-tags) to simplify the process of writing a custom extension.

To use a custom Jinja extension you've developed, add the appropriate plugin syntax to your project's `pyproject.toml` file:

```toml
[tool.poetry.plugins."blurry.jinja_extensions"]
stars = "{{ yourproject.your_extension_file }}:YourExtension"
```
28 changes: 28 additions & 0 deletions docs/content/templates/syntax.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
name = "Templates: syntax"
abstract = "Documentation for Blurry's template files and Jinja syntax"
datePublished = 2023-04-09
dateModified = 2023-04-28
image = {contentUrl = "../images/schema.org-logo.png"}
+++

Expand Down Expand Up @@ -42,3 +43,30 @@ If your templates require more granularity than the Schema.org types, you can wr
[blurry.template_schema_types]
ContextWebPage = 'WebPage'
```

## Blurry-included plugins

Blurry ships with some plugins to simplify writing templates.

### `{% blurry_image %}`

This extension adds the `{% blurry_image %}` tag to simplify including images reference in [Markdown front matter](../content/markdown.md) in your templates.
It does a few things:

- Finds the image in your build directory
- Extracts the images width & height
- Builds an `<img>` tag with width, height, and the othwer attributes specified in the tag

#### Examples

Basic example:

```jinja
{% blurry_image page.thumbnailUrl, alt="Image description" %}
```

Example with explicit width (image with this width must be present in the build folder):

```jinja
{% blurry_image page.thumbnailUrl, 250, id="image-id", class="responsive-image", loading="lazy" %}
```
Loading

0 comments on commit dc2ac7b

Please sign in to comment.