diff --git a/kjvstudy_org/jinja_filters.py b/kjvstudy_org/jinja_filters.py index 2ca1fe0..c31d9b0 100644 --- a/kjvstudy_org/jinja_filters.py +++ b/kjvstudy_org/jinja_filters.py @@ -1,8 +1,8 @@ """ -Custom template filters for KJV Study. +Custom Jinja2 template filters for KJV Study. All filters are registered in register_filters() which should be called with -the MiniJinja environment after templates are initialized. +the Jinja2 environment after templates are initialized. Available Filters: slugify - Create URL-safe slugs from text @@ -20,7 +20,6 @@ Available Filters: import re import mistune -import minijinja from functools import lru_cache @@ -381,35 +380,17 @@ def strip_links(text): return re.sub(r']*>([^<]*)', r'\1', text) -def _safe_html(func): - """Wrap a filter function so its return value is marked as safe HTML.""" - def wrapper(*args, **kwargs): - result = func(*args, **kwargs) - if result is None: - return result - return minijinja.safe(str(result)) - return wrapper - - -def rsplit(text, sep=' ', maxsplit=-1): - """Python rsplit as a template filter, since MiniJinja lacks it.""" - if not text: - return [] - return text.rsplit(sep, maxsplit) - - def register_filters(env): - """Register all custom filters with a MiniJinja environment.""" - env.add_filter('slugify', create_slug) - env.add_filter('md', _safe_html(markdown_to_html)) - env.add_filter('mdi', _safe_html(markdown_inline)) - env.add_filter('link_names', _safe_html(link_person_names_in_text)) - env.add_filter('link_verses', _safe_html(link_verse_references_in_text)) - env.add_filter('inject_word_markers', _safe_html(inject_word_markers)) - env.add_filter('red_letter', _safe_html(red_letter)) - env.add_filter('format_lists', _safe_html(format_numbered_lists)) - env.add_filter('split_paragraphs', _safe_html(split_paragraphs)) - env.add_filter('number_format', number_format) - env.add_filter('linkify_strongs', _safe_html(linkify_strongs)) - env.add_filter('strip_links', strip_links) - env.add_filter('rsplit', rsplit) + """Register all custom filters with a Jinja2 environment.""" + env.filters['slugify'] = create_slug + env.filters['md'] = markdown_to_html + env.filters['mdi'] = markdown_inline + env.filters['link_names'] = link_person_names_in_text + env.filters['link_verses'] = link_verse_references_in_text + env.filters['inject_word_markers'] = inject_word_markers + env.filters['red_letter'] = red_letter + env.filters['format_lists'] = format_numbered_lists + env.filters['split_paragraphs'] = split_paragraphs + env.filters['number_format'] = number_format + env.filters['linkify_strongs'] = linkify_strongs + env.filters['strip_links'] = strip_links diff --git a/kjvstudy_org/minijinja_templates.py b/kjvstudy_org/minijinja_templates.py deleted file mode 100644 index dc99bcb..0000000 --- a/kjvstudy_org/minijinja_templates.py +++ /dev/null @@ -1,65 +0,0 @@ -"""MiniJinja integration for FastAPI. - -Drop-in replacement for fastapi.templating.Jinja2Templates using MiniJinja -(a fast Rust-based Jinja2-compatible template engine). -""" - -import minijinja -from markupsafe import Markup, escape as markupsafe_escape -from starlette.responses import HTMLResponse - - -def _jinja2_compatible_finalizer(value): - """Use markupsafe's escaping (same as Jinja2) instead of MiniJinja's more aggressive escaping.""" - if isinstance(value, Markup): - return value - return Markup(markupsafe_escape(str(value))) - - -class _TemplateWrapper: - """Wraps a MiniJinja environment + template name to support .render() calls.""" - - def __init__(self, env, name): - self._env = env - self._name = name - - def render(self, *args, **kwargs): - if args and isinstance(args[0], dict): - kwargs.update(args[0]) - return self._env.render_template(self._name, **kwargs) - - -class MiniJinjaTemplates: - """FastAPI-compatible template renderer backed by MiniJinja.""" - - def __init__(self, directory: str): - self.directory = directory - self.env = minijinja.Environment( - loader=minijinja.load_from_path(directory), - ) - self.env.auto_escape_callback = lambda name: name.endswith((".html", ".xml")) - self.env.finalizer = _jinja2_compatible_finalizer - self.env.reload_before_render = False - - def get_template(self, name: str): - """Return a template wrapper with a .render() method, matching Jinja2 API.""" - return _TemplateWrapper(self.env, name) - - def TemplateResponse(self, request_or_name, name_or_context=None, context=None, status_code=200, **kwargs): - """Render a template and return an HTMLResponse. - - Supports both calling conventions: - TemplateResponse(request, "name.html", {...}) # new Starlette style - TemplateResponse("name.html", {"request": req, ...}) # old Starlette style - """ - if isinstance(request_or_name, str): - # Old style: TemplateResponse("name.html", {"request": req, ...}) - name = request_or_name - ctx = dict(name_or_context) if name_or_context else {} - else: - # New style: TemplateResponse(request, "name.html", {...}) - name = name_or_context - ctx = dict(context) if context else {} - ctx["request"] = request_or_name - html = self.env.render_template(name, **ctx) - return HTMLResponse(content=html, status_code=status_code, **kwargs) diff --git a/kjvstudy_org/routes/resources.py b/kjvstudy_org/routes/resources.py index ed95409..3176c12 100644 --- a/kjvstudy_org/routes/resources.py +++ b/kjvstudy_org/routes/resources.py @@ -63,7 +63,7 @@ def init_templates(app_templates): """Initialize templates from the main app.""" global templates templates = app_templates - templates.env.add_global('resource_pdf_available', WEASYPRINT_AVAILABLE) + templates.env.globals['resource_pdf_available'] = WEASYPRINT_AVAILABLE def get_books(): diff --git a/kjvstudy_org/server.py b/kjvstudy_org/server.py index 3637773..14d71ad 100644 --- a/kjvstudy_org/server.py +++ b/kjvstudy_org/server.py @@ -12,7 +12,7 @@ from typing import List, Dict, Optional from fastapi import FastAPI, HTTPException, Request, Query, Path from fastapi.exception_handlers import http_exception_handler from fastapi.responses import HTMLResponse, Response, RedirectResponse, JSONResponse, StreamingResponse -from .minijinja_templates import MiniJinjaTemplates +from fastapi.templating import Jinja2Templates from fastapi.middleware.gzip import GZipMiddleware from fastapi.openapi.utils import get_openapi from starlette.exceptions import HTTPException as StarletteHTTPException @@ -327,14 +327,14 @@ current_dir = PathLib(__file__).parent static_dir = current_dir / "static" templates_dir = current_dir / "templates" -templates = MiniJinjaTemplates(directory=str(templates_dir)) +templates = Jinja2Templates(directory=str(templates_dir)) -# Register custom filters +# Register custom Jinja2 filters from .jinja_filters import register_filters register_filters(templates.env) # Add global template variables -templates.env.add_global('disable_analytics', os.getenv("DISABLE_ANALYTICS", "false").lower() == "true") +templates.env.globals['disable_analytics'] = os.getenv("DISABLE_ANALYTICS", "false").lower() == "true" # Cache-busting for static files using file modification time import hashlib @@ -351,7 +351,7 @@ def static_hash(filename): _static_hashes[filename] = "0" return _static_hashes[filename] -templates.env.add_function('static_hash', static_hash) +templates.env.globals['static_hash'] = static_hash # Initialize templates for route modules init_api_templates(templates) diff --git a/kjvstudy_org/templates/family_tree_generation.html b/kjvstudy_org/templates/family_tree_generation.html index 86bc240..c25ad93 100644 --- a/kjvstudy_org/templates/family_tree_generation.html +++ b/kjvstudy_org/templates/family_tree_generation.html @@ -275,15 +275,19 @@ {% endif %} -{% set ns = namespace(kekule_count=0, with_children=0, total_children=0) %} +{% set kekule_count = [] %} +{% set with_children = [] %} +{% set total_children = [] %} {% for person_id in generation_people %} {% set person = family_tree_data[person_id] %} {% if person.kekule_number is not none %} - {% set ns.kekule_count = ns.kekule_count + 1 %} + {% set _ = kekule_count.append(1) %} {% endif %} {% if person.children|length > 0 %} - {% set ns.with_children = ns.with_children + 1 %} - {% set ns.total_children = ns.total_children + person.children|length %} + {% set _ = with_children.append(1) %} + {% for c in person.children %} + {% set _ = total_children.append(1) %} + {% endfor %} {% endif %} {% endfor %} @@ -292,22 +296,22 @@ People: {{ generation_people|length }} - {% if ns.kekule_count > 0 %} + {% if kekule_count|length > 0 %}
In Christ's line: - {{ ns.kekule_count }} + {{ kekule_count|length }}
{% endif %} - {% if ns.with_children > 0 %} + {% if with_children|length > 0 %}
With children: - {{ ns.with_children }} + {{ with_children|length }}
{% endif %} - {% if ns.total_children > 0 %} + {% if total_children|length > 0 %}
Total offspring: - {{ ns.total_children }} + {{ total_children|length }}
{% endif %} diff --git a/kjvstudy_org/templates/parable_detail.html b/kjvstudy_org/templates/parable_detail.html index 9e89ea1..327417c 100644 --- a/kjvstudy_org/templates/parable_detail.html +++ b/kjvstudy_org/templates/parable_detail.html @@ -95,7 +95,7 @@ {% for verse in parable.verses %}
- {% set ref_parts = verse.reference|rsplit(' ', 1) %} + {% set ref_parts = verse.reference.rsplit(' ', 1) %} {% if ref_parts|length == 2 %} {% set book_name = ref_parts[0] %} {% set chapter_verse = ref_parts[1] %} diff --git a/kjvstudy_org/templates/resource_detail.html b/kjvstudy_org/templates/resource_detail.html index a172eab..9ef37df 100644 --- a/kjvstudy_org/templates/resource_detail.html +++ b/kjvstudy_org/templates/resource_detail.html @@ -174,7 +174,7 @@ article { {% for verse in item.verses %}
- {% set ref_parts = verse.reference|rsplit(' ', 1) %} + {% set ref_parts = verse.reference.rsplit(' ', 1) %} {% if ref_parts|length == 2 %} {% set book_name = ref_parts[0] %} {% set chapter_verse = ref_parts[1] %} diff --git a/kjvstudy_org/templates/resource_index.html b/kjvstudy_org/templates/resource_index.html index 320ad30..0e9ee65 100644 --- a/kjvstudy_org/templates/resource_index.html +++ b/kjvstudy_org/templates/resource_index.html @@ -289,7 +289,7 @@ document.body.dataset.resourceReader = 'true'; {% for verse in item.verses %}
- {% set ref_parts = verse.reference|rsplit(' ', 1) %} + {% set ref_parts = verse.reference.rsplit(' ', 1) %} {% if ref_parts|length == 2 %} {% set book_name = ref_parts[0] %} {% set chapter_verse = ref_parts[1] %} diff --git a/kjvstudy_org/templates/tetragrammaton.html b/kjvstudy_org/templates/tetragrammaton.html index 3315d69..858c12a 100644 --- a/kjvstudy_org/templates/tetragrammaton.html +++ b/kjvstudy_org/templates/tetragrammaton.html @@ -138,7 +138,7 @@

Key Scripture

{% for verse in section.verses %}

- {% set _rp = verse.reference|rsplit(' ', 1) %}{{ verse.reference }}
+ {{ verse.reference }}
{{ verse.text }}

{% endfor %} diff --git a/kjvstudy_org/templates/topic_detail.html b/kjvstudy_org/templates/topic_detail.html index 59720a7..39f7cc2 100644 --- a/kjvstudy_org/templates/topic_detail.html +++ b/kjvstudy_org/templates/topic_detail.html @@ -229,7 +229,7 @@ {% endif %} {% set reference = reference.strip() %} {% if reference %} - {% set ref_parts = reference|rsplit(' ', 1) %} + {% set ref_parts = reference.rsplit(' ', 1) %} {% if ref_parts|length == 2 %} {% set book_name = ref_parts[0] %} {% set chapter_verse = ref_parts[1] %} diff --git a/kjvstudy_org/templates/verse.html b/kjvstudy_org/templates/verse.html index af0a762..dee8198 100644 --- a/kjvstudy_org/templates/verse.html +++ b/kjvstudy_org/templates/verse.html @@ -1101,7 +1101,7 @@ a.crossref-pill:hover {

Cross References

{% for ref in cross_references %} - {%- set ref_parts = ref.ref|rsplit(' ', 1) -%} + {%- set ref_parts = ref.ref.rsplit(' ', 1) -%} {%- if ref_parts|length == 2 -%} {%- set book_name = ref_parts[0] -%} {%- set chapter_verse = ref_parts[1] -%} diff --git a/pyproject.toml b/pyproject.toml index 5090dc8..a716c23 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,6 @@ dependencies = [ "fastapi[standard]>=0.115.12", "ged4py>=0.5.2", "granian>=2.0", - "minijinja>=2.19.0", "mistune>=3.0.2", "parse>=1.20.2", "python-gedcom>=1.0.0", diff --git a/uv.lock b/uv.lock index 4de8922..2d938e9 100644 --- a/uv.lock +++ b/uv.lock @@ -533,7 +533,6 @@ dependencies = [ { name = "fastapi", extra = ["standard"] }, { name = "ged4py" }, { name = "granian" }, - { name = "minijinja" }, { name = "mistune" }, { name = "parse" }, { name = "python-gedcom" }, @@ -560,7 +559,6 @@ requires-dist = [ { name = "fastapi", extras = ["standard"], specifier = ">=0.115.12" }, { name = "ged4py", specifier = ">=0.5.2" }, { name = "granian", specifier = ">=2.0" }, - { name = "minijinja", specifier = ">=2.19.0" }, { name = "mistune", specifier = ">=3.0.2" }, { name = "parse", specifier = ">=1.20.2" }, { name = "pytest", marker = "extra == 'dev'", specifier = ">=8.3.5" }, @@ -628,25 +626,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, ] -[[package]] -name = "minijinja" -version = "2.19.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c2/4f/c06a20fa16a63dacc131aa82cf0cbf95239cfb851fb60330c8016e9862b7/minijinja-2.19.0.tar.gz", hash = "sha256:c2e95fd56a8ab9419403ea7f25273a6b570495116bc1af21fb4c178f4d9c5ff7", size = 290703, upload-time = "2026-04-01T21:31:41.983Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f0/e8/45957b91756603f6e60241a1d3919fbdb94a5e11bd1b9570a13df1af207f/minijinja-2.19.0-cp38-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:487d2def6d16d30ad0cb7b0e949afb06815e144e502d0cf4465cd34fa429361e", size = 2098108, upload-time = "2026-04-01T21:31:25.819Z" }, - { url = "https://files.pythonhosted.org/packages/67/89/8fd3e725297e9e1c8751f29535654b06af5770c36e7cea75ed62d4b21024/minijinja-2.19.0-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2725643b1d19f4f60614153bee6ea1a291c6aa410071b61299113cfca1c23e20", size = 1096108, upload-time = "2026-04-01T21:31:27.868Z" }, - { url = "https://files.pythonhosted.org/packages/b7/12/b48b6a2d60fe7c211773ede48bcefb1c6fe1c973509381f8a21b9841a71a/minijinja-2.19.0-cp38-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a59e1d23b5e62aca1c527869a0ba09126fdc9c69d43beeccb3cf07c36fc8fbe5", size = 1072471, upload-time = "2026-04-01T21:31:29.381Z" }, - { url = "https://files.pythonhosted.org/packages/8a/92/86d8c40ff6035ef19bf8f35b25b13fcd763c7624e3763f6b2e2ce5ccd959/minijinja-2.19.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:86693fff42fdf9e6c6684b5b44e79ee708b9419e6331ffe217bbcbe7c2de6016", size = 1126643, upload-time = "2026-04-01T21:31:31.174Z" }, - { url = "https://files.pythonhosted.org/packages/26/ac/014e16acde50f6542877b02ff2f80e610193edc8899ee11363271e4373fb/minijinja-2.19.0-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cc7ef1f9dbdaa9d47c75d7f903d55cbd766c55b38ec195c0ba11e880566bbcf3", size = 1260960, upload-time = "2026-04-01T21:31:32.357Z" }, - { url = "https://files.pythonhosted.org/packages/d3/d9/3d4ef6c327acd40f6efafc8d834c6bd288a7271b4b342cbac07726eba72e/minijinja-2.19.0-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:3defd79efd5af849dd48e3ab03b67772dc30fe53935316c953f77868938da77d", size = 1272078, upload-time = "2026-04-01T21:31:33.782Z" }, - { url = "https://files.pythonhosted.org/packages/0b/34/d0a4f228510b3784717063f63d6784ebd4021afe77df8c868ef06e2140c2/minijinja-2.19.0-cp38-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:c2bacdeca8ca5d30035e8e78d119faca248b5ba0597200937a56980880d7d11d", size = 1347195, upload-time = "2026-04-01T21:31:35.334Z" }, - { url = "https://files.pythonhosted.org/packages/49/bd/4b0b92360bf44a9c0ec4db57537cecb3774dead6e5bed8e88c5079541f4e/minijinja-2.19.0-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:38ed4fef67f550ae8552b261f3a40d389bab782f1dc19e8a015b3ad25da03bc1", size = 1425814, upload-time = "2026-04-01T21:31:36.66Z" }, - { url = "https://files.pythonhosted.org/packages/98/29/ac1ac223755881885ccbcbdaad164a4a323606d9d0abdeedcc262d51a1d9/minijinja-2.19.0-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:695ce4264dbe6422ef45a344dc2c1044eb3a7543606e1b4a255df5b443074462", size = 1349051, upload-time = "2026-04-01T21:31:38.126Z" }, - { url = "https://files.pythonhosted.org/packages/c6/48/b78f84441a81be2cd528dcc54f8726f6e10ecb613357cfadb7479e832857/minijinja-2.19.0-cp38-abi3-win32.whl", hash = "sha256:fa11ceea225e0458ff8126de18c3baad936106cefe95ac9c9b65795bd71b018f", size = 1025114, upload-time = "2026-04-01T21:31:39.48Z" }, - { url = "https://files.pythonhosted.org/packages/0e/28/5b7eabd21a7e3b0c7f2b33d11305e6ae6fadbc022d09ed2730adae4cb479/minijinja-2.19.0-cp38-abi3-win_amd64.whl", hash = "sha256:0cf0ee1abf477c91caf8730cd19ab01960c6be124af9cd1fcbd546a4bfde36bb", size = 1078381, upload-time = "2026-04-01T21:31:40.726Z" }, -] - [[package]] name = "mistune" version = "3.1.4"