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.
Files changed (28) hide show
  1. {blurry_cli-0.6.1 → blurry_cli-0.7.0}/PKG-INFO +3 -2
  2. {blurry_cli-0.6.1 → blurry_cli-0.7.0}/blurry/__init__.py +17 -5
  3. {blurry_cli-0.6.1 → blurry_cli-0.7.0}/blurry/images.py +17 -15
  4. {blurry_cli-0.6.1 → blurry_cli-0.7.0}/blurry/markdown/__init__.py +13 -15
  5. blurry_cli-0.7.0/blurry/schema_validation.py +61 -0
  6. {blurry_cli-0.6.1 → blurry_cli-0.7.0}/pyproject.toml +5 -3
  7. {blurry_cli-0.6.1 → blurry_cli-0.7.0}/LICENSE +0 -0
  8. {blurry_cli-0.6.1 → blurry_cli-0.7.0}/README.md +0 -0
  9. {blurry_cli-0.6.1 → blurry_cli-0.7.0}/blurry/__main__.py +0 -0
  10. {blurry_cli-0.6.1 → blurry_cli-0.7.0}/blurry/async_typer.py +0 -0
  11. {blurry_cli-0.6.1 → blurry_cli-0.7.0}/blurry/cli.py +0 -0
  12. {blurry_cli-0.6.1 → blurry_cli-0.7.0}/blurry/constants.py +0 -0
  13. {blurry_cli-0.6.1 → blurry_cli-0.7.0}/blurry/markdown/front_matter.py +0 -0
  14. {blurry_cli-0.6.1 → blurry_cli-0.7.0}/blurry/markdown/renderer_functions/__init__.py +0 -0
  15. {blurry_cli-0.6.1 → blurry_cli-0.7.0}/blurry/markdown/renderer_functions/render_video.py +0 -0
  16. {blurry_cli-0.6.1 → blurry_cli-0.7.0}/blurry/open_graph.py +0 -0
  17. {blurry_cli-0.6.1 → blurry_cli-0.7.0}/blurry/plugins/__init__.py +0 -0
  18. {blurry_cli-0.6.1 → blurry_cli-0.7.0}/blurry/plugins/html_plugins/__init__.py +0 -0
  19. {blurry_cli-0.6.1 → blurry_cli-0.7.0}/blurry/plugins/html_plugins/minify_html_plugin.py +0 -0
  20. {blurry_cli-0.6.1 → blurry_cli-0.7.0}/blurry/plugins/markdown_plugins/__init__.py +0 -0
  21. {blurry_cli-0.6.1 → blurry_cli-0.7.0}/blurry/plugins/markdown_plugins/container_plugin.py +0 -0
  22. {blurry_cli-0.6.1 → blurry_cli-0.7.0}/blurry/plugins/markdown_plugins/punctuation_plugin.py +0 -0
  23. {blurry_cli-0.6.1 → blurry_cli-0.7.0}/blurry/plugins/markdown_plugins/python_code_plugin.py +0 -0
  24. {blurry_cli-0.6.1 → blurry_cli-0.7.0}/blurry/py.typed +0 -0
  25. {blurry_cli-0.6.1 → blurry_cli-0.7.0}/blurry/settings.py +0 -0
  26. {blurry_cli-0.6.1 → blurry_cli-0.7.0}/blurry/sitemap.py +0 -0
  27. {blurry_cli-0.6.1 → blurry_cli-0.7.0}/blurry/types.py +0 -0
  28. {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.6.1
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: htmlmin (>=0.1.12,<0.2.0)
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 [types_map[".jpg"], types_map[".png"]]:
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: asyncio.run(non_markdown_task))
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
- TARGET_IMAGE_WIDTHS = [w for w in IMAGE_WIDTHS if w < MAXIMUM_IMAGE_WIDTH]
16
- TARGET_IMAGE_WIDTHS.append(MAXIMUM_IMAGE_WIDTH)
17
- if THUMBNAIL_WIDTH not in TARGET_IMAGE_WIDTHS:
18
- TARGET_IMAGE_WIDTHS.append(THUMBNAIL_WIDTH)
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.sort()
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
- if image_path.suffix in [".webp", ".gif"]:
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
- widths = [tw for tw in TARGET_IMAGE_WIDTHS if tw < image_width]
73
- if image_width < TARGET_IMAGE_WIDTHS[-1]:
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"] = img.height
79
-
80
- if extension in ["webp", "gif"]:
81
- source_tag = ""
82
- else:
83
- image_widths = get_widths_for_image_width(image_width)
84
-
85
- attributes["sizes"] = generate_sizes_string(image_widths)
86
- attributes["srcset"] = generate_srcset_string(src, image_widths)
87
- avif_srcset = generate_srcset_string(
88
- src.replace(extension, "avif"), image_widths
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.6.1"
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 = "^2023.4.22"
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