blurry-cli 0.6.1__tar.gz → 0.7.0__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.
- {blurry_cli-0.6.1 → blurry_cli-0.7.0}/PKG-INFO +3 -2
- {blurry_cli-0.6.1 → blurry_cli-0.7.0}/blurry/__init__.py +17 -5
- {blurry_cli-0.6.1 → blurry_cli-0.7.0}/blurry/images.py +17 -15
- {blurry_cli-0.6.1 → blurry_cli-0.7.0}/blurry/markdown/__init__.py +13 -15
- blurry_cli-0.7.0/blurry/schema_validation.py +61 -0
- {blurry_cli-0.6.1 → blurry_cli-0.7.0}/pyproject.toml +5 -3
- {blurry_cli-0.6.1 → blurry_cli-0.7.0}/LICENSE +0 -0
- {blurry_cli-0.6.1 → blurry_cli-0.7.0}/README.md +0 -0
- {blurry_cli-0.6.1 → blurry_cli-0.7.0}/blurry/__main__.py +0 -0
- {blurry_cli-0.6.1 → blurry_cli-0.7.0}/blurry/async_typer.py +0 -0
- {blurry_cli-0.6.1 → blurry_cli-0.7.0}/blurry/cli.py +0 -0
- {blurry_cli-0.6.1 → blurry_cli-0.7.0}/blurry/constants.py +0 -0
- {blurry_cli-0.6.1 → blurry_cli-0.7.0}/blurry/markdown/front_matter.py +0 -0
- {blurry_cli-0.6.1 → blurry_cli-0.7.0}/blurry/markdown/renderer_functions/__init__.py +0 -0
- {blurry_cli-0.6.1 → blurry_cli-0.7.0}/blurry/markdown/renderer_functions/render_video.py +0 -0
- {blurry_cli-0.6.1 → blurry_cli-0.7.0}/blurry/open_graph.py +0 -0
- {blurry_cli-0.6.1 → blurry_cli-0.7.0}/blurry/plugins/__init__.py +0 -0
- {blurry_cli-0.6.1 → blurry_cli-0.7.0}/blurry/plugins/html_plugins/__init__.py +0 -0
- {blurry_cli-0.6.1 → blurry_cli-0.7.0}/blurry/plugins/html_plugins/minify_html_plugin.py +0 -0
- {blurry_cli-0.6.1 → blurry_cli-0.7.0}/blurry/plugins/markdown_plugins/__init__.py +0 -0
- {blurry_cli-0.6.1 → blurry_cli-0.7.0}/blurry/plugins/markdown_plugins/container_plugin.py +0 -0
- {blurry_cli-0.6.1 → blurry_cli-0.7.0}/blurry/plugins/markdown_plugins/punctuation_plugin.py +0 -0
- {blurry_cli-0.6.1 → blurry_cli-0.7.0}/blurry/plugins/markdown_plugins/python_code_plugin.py +0 -0
- {blurry_cli-0.6.1 → blurry_cli-0.7.0}/blurry/py.typed +0 -0
- {blurry_cli-0.6.1 → blurry_cli-0.7.0}/blurry/settings.py +0 -0
- {blurry_cli-0.6.1 → blurry_cli-0.7.0}/blurry/sitemap.py +0 -0
- {blurry_cli-0.6.1 → blurry_cli-0.7.0}/blurry/types.py +0 -0
- {blurry_cli-0.6.1 → blurry_cli-0.7.0}/blurry/utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: blurry-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.0
|
|
4
4
|
Summary: A Mistune-based static site generator for Python
|
|
5
5
|
Home-page: https://github.com/blurry-dev/blurry
|
|
6
6
|
License: MIT
|
|
@@ -24,9 +24,10 @@ Requires-Dist: Jinja2 (>=3.0.0,<4.0.0)
|
|
|
24
24
|
Requires-Dist: PyLD (>=2.0.3,<3.0.0)
|
|
25
25
|
Requires-Dist: Wand (>=0.6.6,<0.7.0)
|
|
26
26
|
Requires-Dist: ffmpeg-python (>=0.2.0,<0.3.0)
|
|
27
|
-
Requires-Dist:
|
|
27
|
+
Requires-Dist: htmlmin2 (>=0.1.13,<0.2.0)
|
|
28
28
|
Requires-Dist: livereload (>=2.6.3,<3.0.0)
|
|
29
29
|
Requires-Dist: mistune (>=3.0.0rc5,<4.0.0)
|
|
30
|
+
Requires-Dist: pydantic2-schemaorg (>=0.1.1,<0.2.0)
|
|
30
31
|
Requires-Dist: rich (>=13.3.3,<14.0.0)
|
|
31
32
|
Requires-Dist: selectolax (>=0.3.6,<0.4.0)
|
|
32
33
|
Requires-Dist: toml (>=0.10.2,<0.11.0)
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import dataclasses
|
|
3
3
|
import json
|
|
4
|
+
import mimetypes
|
|
4
5
|
import os
|
|
5
6
|
import shutil
|
|
6
7
|
from collections.abc import Coroutine
|
|
7
8
|
from copy import deepcopy
|
|
8
9
|
from datetime import datetime
|
|
9
|
-
from mimetypes import guess_type
|
|
10
|
-
from mimetypes import types_map
|
|
11
10
|
from pathlib import Path
|
|
12
11
|
from typing import Any
|
|
13
12
|
|
|
@@ -16,6 +15,7 @@ from jinja2 import FileSystemLoader
|
|
|
16
15
|
from jinja2 import select_autoescape
|
|
17
16
|
from livereload import Server
|
|
18
17
|
from rich import print
|
|
18
|
+
from rich.console import Console
|
|
19
19
|
|
|
20
20
|
from blurry.async_typer import AsyncTyper
|
|
21
21
|
from blurry.cli import print_blurry_name
|
|
@@ -26,6 +26,7 @@ from blurry.markdown import convert_markdown_file_to_html
|
|
|
26
26
|
from blurry.open_graph import open_graph_meta_tags
|
|
27
27
|
from blurry.plugins import discovered_html_plugins
|
|
28
28
|
from blurry.plugins import discovered_jinja_filter_plugins
|
|
29
|
+
from blurry.schema_validation import validate_front_matter_as_schema
|
|
29
30
|
from blurry.settings import get_build_directory
|
|
30
31
|
from blurry.settings import get_content_directory
|
|
31
32
|
from blurry.settings import get_templates_directory
|
|
@@ -47,6 +48,8 @@ def json_converter_with_dates(item: Any) -> None | str:
|
|
|
47
48
|
return item.strftime("%Y-%M-%D")
|
|
48
49
|
|
|
49
50
|
|
|
51
|
+
warning_console = Console(stderr=True, style="bold yellow")
|
|
52
|
+
|
|
50
53
|
print_blurry_name()
|
|
51
54
|
print_plugin_table()
|
|
52
55
|
|
|
@@ -80,7 +83,7 @@ def get_jinja_env():
|
|
|
80
83
|
|
|
81
84
|
async def process_non_markdown_file(filepath: Path):
|
|
82
85
|
CONTENT_DIR = get_content_directory()
|
|
83
|
-
mimetype, _ = guess_type(filepath, strict=False)
|
|
86
|
+
mimetype, _ = mimetypes.guess_type(filepath, strict=False)
|
|
84
87
|
relative_filepath = filepath.relative_to(CONTENT_DIR)
|
|
85
88
|
build_filepath = get_build_directory() / relative_filepath
|
|
86
89
|
output_file = Path(build_filepath)
|
|
@@ -90,7 +93,11 @@ async def process_non_markdown_file(filepath: Path):
|
|
|
90
93
|
shutil.copyfile(filepath, build_filepath)
|
|
91
94
|
|
|
92
95
|
# Create srcset images
|
|
93
|
-
if mimetype in [
|
|
96
|
+
if mimetype in [
|
|
97
|
+
mimetypes.types_map[".jpg"],
|
|
98
|
+
mimetypes.types_map[".png"],
|
|
99
|
+
mimetypes.common_types[".webp"],
|
|
100
|
+
]:
|
|
94
101
|
await generate_images_for_srcset(filepath)
|
|
95
102
|
|
|
96
103
|
|
|
@@ -144,6 +151,9 @@ async def write_html_file(
|
|
|
144
151
|
format_schema_data(schema_variables),
|
|
145
152
|
default=json_converter_with_dates,
|
|
146
153
|
)
|
|
154
|
+
|
|
155
|
+
validate_front_matter_as_schema(file_data.path, front_matter, warning_console)
|
|
156
|
+
|
|
147
157
|
schema_type_tag = f'<script type="application/ld+json">{schema_data}</script>'
|
|
148
158
|
|
|
149
159
|
template_context = {
|
|
@@ -233,8 +243,10 @@ async def build(release=True):
|
|
|
233
243
|
print(f"Blurring {len(markdown_tasks)} Markdown files from: {content_dir_relative}")
|
|
234
244
|
|
|
235
245
|
await asyncio.gather(*markdown_tasks)
|
|
246
|
+
loop = asyncio.new_event_loop()
|
|
247
|
+
asyncio.set_event_loop(loop)
|
|
236
248
|
for non_markdown_task in non_markdown_tasks:
|
|
237
|
-
await asyncio.to_thread(lambda:
|
|
249
|
+
await asyncio.to_thread(lambda: loop.run_until_complete(non_markdown_task))
|
|
238
250
|
|
|
239
251
|
end = datetime.now()
|
|
240
252
|
|
|
@@ -6,18 +6,21 @@ from wand.image import Image
|
|
|
6
6
|
from blurry.settings import get_build_directory
|
|
7
7
|
from blurry.settings import get_content_directory
|
|
8
8
|
from blurry.settings import SETTINGS
|
|
9
|
+
from blurry.settings import update_settings
|
|
9
10
|
|
|
10
|
-
AVIF_COMPRESSION_QUALITY = SETTINGS["AVIF_COMPRESSION_QUALITY"]
|
|
11
|
-
IMAGE_WIDTHS = SETTINGS["IMAGE_WIDTHS"]
|
|
12
|
-
MAXIMUM_IMAGE_WIDTH = int(SETTINGS["MAXIMUM_IMAGE_WIDTH"])
|
|
13
|
-
THUMBNAIL_WIDTH = int(SETTINGS["THUMBNAIL_WIDTH"])
|
|
14
11
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
12
|
+
def get_target_image_widths():
|
|
13
|
+
update_settings()
|
|
14
|
+
IMAGE_WIDTHS = SETTINGS["IMAGE_WIDTHS"]
|
|
15
|
+
MAXIMUM_IMAGE_WIDTH = int(SETTINGS["MAXIMUM_IMAGE_WIDTH"])
|
|
16
|
+
THUMBNAIL_WIDTH = int(SETTINGS["THUMBNAIL_WIDTH"])
|
|
19
17
|
|
|
20
|
-
TARGET_IMAGE_WIDTHS
|
|
18
|
+
TARGET_IMAGE_WIDTHS = [w for w in IMAGE_WIDTHS if w < MAXIMUM_IMAGE_WIDTH]
|
|
19
|
+
TARGET_IMAGE_WIDTHS.append(MAXIMUM_IMAGE_WIDTH)
|
|
20
|
+
if THUMBNAIL_WIDTH not in TARGET_IMAGE_WIDTHS:
|
|
21
|
+
TARGET_IMAGE_WIDTHS.append(THUMBNAIL_WIDTH)
|
|
22
|
+
|
|
23
|
+
return sorted(TARGET_IMAGE_WIDTHS)
|
|
21
24
|
|
|
22
25
|
|
|
23
26
|
def add_image_width_to_path(image_path: Path, width: int) -> Path:
|
|
@@ -28,9 +31,8 @@ def add_image_width_to_path(image_path: Path, width: int) -> Path:
|
|
|
28
31
|
|
|
29
32
|
|
|
30
33
|
async def convert_image_to_avif(image_path: Path, target_path: Path | None = None):
|
|
34
|
+
AVIF_COMPRESSION_QUALITY = SETTINGS["AVIF_COMPRESSION_QUALITY"]
|
|
31
35
|
image_suffix = image_path.suffix
|
|
32
|
-
if image_suffix in [".webp", ".gif"]:
|
|
33
|
-
return
|
|
34
36
|
avif_filepath = str(target_path or image_path).replace(image_suffix, ".avif")
|
|
35
37
|
if Path(avif_filepath).exists():
|
|
36
38
|
return
|
|
@@ -43,8 +45,7 @@ async def convert_image_to_avif(image_path: Path, target_path: Path | None = Non
|
|
|
43
45
|
async def generate_images_for_srcset(image_path: Path):
|
|
44
46
|
BUILD_DIR = get_build_directory()
|
|
45
47
|
CONTENT_DIR = get_content_directory()
|
|
46
|
-
|
|
47
|
-
return
|
|
48
|
+
|
|
48
49
|
with Image(filename=str(image_path)) as img:
|
|
49
50
|
width = img.width
|
|
50
51
|
# Convert original image
|
|
@@ -69,8 +70,9 @@ async def generate_images_for_srcset(image_path: Path):
|
|
|
69
70
|
|
|
70
71
|
|
|
71
72
|
def get_widths_for_image_width(image_width: int) -> list[int]:
|
|
72
|
-
|
|
73
|
-
if
|
|
73
|
+
target_image_widths = get_target_image_widths()
|
|
74
|
+
widths = [tw for tw in target_image_widths if tw < image_width]
|
|
75
|
+
if image_width < target_image_widths[-1]:
|
|
74
76
|
widths.append(image_width)
|
|
75
77
|
return widths
|
|
76
78
|
|
|
@@ -74,22 +74,20 @@ class BlurryRenderer(mistune.HTMLRenderer):
|
|
|
74
74
|
# Tailor srcset and sizes to image width
|
|
75
75
|
with Image(filename=str(absolute_path)) as img:
|
|
76
76
|
image_width = img.width
|
|
77
|
+
image_height = img.height
|
|
77
78
|
attributes["width"] = image_width
|
|
78
|
-
attributes["height"] =
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
source_tag = '<source srcset="{}" sizes="{}" />'.format(
|
|
91
|
-
avif_srcset, attributes["sizes"]
|
|
92
|
-
)
|
|
79
|
+
attributes["height"] = image_height
|
|
80
|
+
|
|
81
|
+
image_widths = get_widths_for_image_width(image_width)
|
|
82
|
+
|
|
83
|
+
attributes["sizes"] = generate_sizes_string(image_widths)
|
|
84
|
+
attributes["srcset"] = generate_srcset_string(src, image_widths)
|
|
85
|
+
avif_srcset = generate_srcset_string(
|
|
86
|
+
src.replace(extension, "avif"), image_widths
|
|
87
|
+
)
|
|
88
|
+
source_tag = '<source srcset="{}" sizes="{}" type="image/avif" />'.format(
|
|
89
|
+
avif_srcset, attributes["sizes"]
|
|
90
|
+
)
|
|
93
91
|
|
|
94
92
|
attributes_str = " ".join(
|
|
95
93
|
f'{name}="{value}"' for name, value in attributes.items()
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import importlib
|
|
2
|
+
from collections.abc import MutableMapping
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from pydantic.v1 import ValidationError
|
|
6
|
+
from rich.console import Console
|
|
7
|
+
|
|
8
|
+
from blurry.settings import SETTINGS
|
|
9
|
+
from blurry.settings import update_settings
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Config:
|
|
13
|
+
extra = "forbid"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def validate_front_matter_as_schema(
|
|
17
|
+
path: Path, front_matter: MutableMapping, console: Console
|
|
18
|
+
):
|
|
19
|
+
"""
|
|
20
|
+
Validates schema data using pydantic_schemaorg, disallowing extra fields
|
|
21
|
+
"""
|
|
22
|
+
update_settings()
|
|
23
|
+
|
|
24
|
+
schema_type = front_matter["@type"]
|
|
25
|
+
|
|
26
|
+
if mapped_schema_type := SETTINGS["TEMPLATE_SCHEMA_TYPES"].get(schema_type):
|
|
27
|
+
schema_type = mapped_schema_type
|
|
28
|
+
|
|
29
|
+
# Import pydantic_schemaorg model
|
|
30
|
+
try:
|
|
31
|
+
pydantic_schemaorg_model_module = importlib.import_module(
|
|
32
|
+
f"pydantic2_schemaorg.{schema_type}"
|
|
33
|
+
)
|
|
34
|
+
except ModuleNotFoundError:
|
|
35
|
+
console.print(
|
|
36
|
+
f"{path}: Could not find Schema type for {schema_type}. Skipping."
|
|
37
|
+
)
|
|
38
|
+
return
|
|
39
|
+
|
|
40
|
+
schema_model = getattr(pydantic_schemaorg_model_module, schema_type)
|
|
41
|
+
|
|
42
|
+
# Create new Pydantic model that forbids extra fields
|
|
43
|
+
class NonExtraSchemaModel(schema_model, extra="forbid"): # type: ignore
|
|
44
|
+
pass
|
|
45
|
+
|
|
46
|
+
# Validate model and print errors
|
|
47
|
+
try:
|
|
48
|
+
non_schema_variable_prefix = SETTINGS["FRONTMATTER_NON_SCHEMA_VARIABLE_PREFIX"]
|
|
49
|
+
schema_front_matter = {
|
|
50
|
+
k: v
|
|
51
|
+
for k, v in front_matter.items()
|
|
52
|
+
if not k.startswith(non_schema_variable_prefix)
|
|
53
|
+
}
|
|
54
|
+
NonExtraSchemaModel(**schema_front_matter)
|
|
55
|
+
except ValidationError as e:
|
|
56
|
+
for error in e.errors():
|
|
57
|
+
msg = error["msg"]
|
|
58
|
+
loc = error["loc"]
|
|
59
|
+
console.print(
|
|
60
|
+
f"{path}: {schema_type} schema validation error: {msg}: {loc}"
|
|
61
|
+
)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "blurry-cli"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.7.0"
|
|
4
4
|
description = "A Mistune-based static site generator for Python"
|
|
5
5
|
authors = ["John Franey <franey@duck.com>"]
|
|
6
6
|
license = "MIT"
|
|
@@ -12,6 +12,7 @@ classifiers = [
|
|
|
12
12
|
"Operating System :: OS Independent",
|
|
13
13
|
"Programming Language :: Python :: 3.10",
|
|
14
14
|
"Programming Language :: Python :: 3.11",
|
|
15
|
+
"Programming Language :: Python :: 3.12",
|
|
15
16
|
"Topic :: Internet :: WWW/HTTP :: Dynamic Content :: Content Management System",
|
|
16
17
|
"Topic :: Internet :: WWW/HTTP :: Site Management ",
|
|
17
18
|
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
@@ -26,7 +27,6 @@ Jinja2 = "^3.0.0"
|
|
|
26
27
|
PyLD = "^2.0.3"
|
|
27
28
|
Wand = "^0.6.6"
|
|
28
29
|
ffmpeg-python = "^0.2.0"
|
|
29
|
-
htmlmin = "^0.1.12"
|
|
30
30
|
livereload = "^2.6.3"
|
|
31
31
|
mistune = "^3.0.0rc5"
|
|
32
32
|
python = "^3.10"
|
|
@@ -34,13 +34,15 @@ rich = "^13.3.3"
|
|
|
34
34
|
selectolax = "^0.3.6"
|
|
35
35
|
toml = "^0.10.2"
|
|
36
36
|
typer = "^0.6.1"
|
|
37
|
+
htmlmin2 = "^0.1.13"
|
|
38
|
+
pydantic2-schemaorg = "^0.1.1"
|
|
37
39
|
|
|
38
40
|
[tool.poetry.scripts]
|
|
39
41
|
blurry = 'blurry:main'
|
|
40
42
|
|
|
41
43
|
[tool.poetry.group.dev.dependencies]
|
|
42
44
|
black = "^22.10.0"
|
|
43
|
-
nox = "^
|
|
45
|
+
nox = "^2024.03.02"
|
|
44
46
|
pyright = "^1.1.311"
|
|
45
47
|
pytest = "^6.2.2"
|
|
46
48
|
pytest-cov = "^2.11.1"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|