From 630abbf28671a27e89b0046b8216d5c97715bff5 Mon Sep 17 00:00:00 2001 From: Juan-Pablo Scaletti Date: Fri, 16 Aug 2024 21:02:57 -0500 Subject: [PATCH 1/3] Add experimental slots support --- src/jinjax/catalog.py | 25 +++++- src/jinjax/jinjax.py | 2 +- tests/test_render.py | 199 ++++++++++++++++++++---------------------- 3 files changed, 118 insertions(+), 108 deletions(-) diff --git a/src/jinjax/catalog.py b/src/jinjax/catalog.py index 2a877be..46eebf9 100644 --- a/src/jinjax/catalog.py +++ b/src/jinjax/catalog.py @@ -1,5 +1,6 @@ import os import typing as t +from collections import UserString from hashlib import sha256 from pathlib import Path @@ -22,6 +23,26 @@ ARGS_CONTENT = "content" +class CallerWrapper(UserString): + def __init__(self, caller: t.Callable | None, content: str = "") -> None: + self._caller = caller + # Pre-calculate the non-slotted content so the assets are loaded + self._content = caller("") if caller else Markup(content) + + def __call__(self, slot: str = "") -> str: + if slot and self._caller: + return self._caller(slot) + return self._content + + def __html__(self) -> str: + return self() + + @property + def data(self) -> str: # type: ignore + return self() + + + class Catalog: """ The object that manages the components and their global settings. @@ -337,9 +358,7 @@ def irender( f"were parsed incorrectly as:\n {str(kw)}" ) from exc - args[ARGS_CONTENT] = Markup( - content if content or not caller else caller().strip() - ) + args[ARGS_CONTENT] = CallerWrapper(caller=caller, content=content) return component.render(**args) def get_middleware( diff --git a/src/jinjax/jinjax.py b/src/jinjax/jinjax.py index d0a3b05..77bc6af 100644 --- a/src/jinjax/jinjax.py +++ b/src/jinjax/jinjax.py @@ -10,7 +10,7 @@ RENDER_CMD = "catalog.irender" -BLOCK_CALL = '{% call [CMD]("[TAG]"[ATTRS]) -%}[CONTENT]{%- endcall %}' +BLOCK_CALL = '{% call(slot) [CMD]("[TAG]"[ATTRS]) -%}[CONTENT]{%- endcall %}' BLOCK_CALL = BLOCK_CALL.replace("[CMD]", RENDER_CMD) INLINE_CALL = '{{ [CMD]("[TAG]"[ATTRS]) }}' INLINE_CALL = INLINE_CALL.replace("[CMD]", RENDER_CMD) diff --git a/tests/test_render.py b/tests/test_render.py index 02d999e..bc42351 100644 --- a/tests/test_render.py +++ b/tests/test_render.py @@ -51,12 +51,11 @@ def test_render_content(catalog, folder, autoescape): content = '' html = catalog.render("Card", __content=content) print(html) - assert ( - html - == Markup(f""" + assert html == Markup( + f"""
{content} -
""".strip()) +""".strip() ) @@ -71,7 +70,11 @@ def test_render_content(catalog, folder, autoescape): ], ) def test_render_mix_of_contentful_and_contentless_components( - catalog, folder, source, expected, autoescape, + catalog, + folder, + source, + expected, + autoescape, ): catalog.jinja_env.autoescape = autoescape @@ -324,25 +327,21 @@ def test_default_attr(catalog, folder, autoescape): def test_raw_content(catalog, folder, autoescape): catalog.jinja_env.autoescape = autoescape - (folder / "Code.jinja").write_text( - """ + (folder / "Code.jinja").write_text("""
 {{ content|e }}
 
-""" - ) +""") - (folder / "Page.jinja").write_text( - """ + (folder / "Page.jinja").write_text(""" -{% raw %} +{% raw -%} {#def message="Hello", world=False #}
{{ message }}{% if world %} World{% endif %}
-{% endraw %} +{%- endraw %}
-""" - ) +""") html = catalog.render("Page") print(html) @@ -362,14 +361,11 @@ def test_raw_content(catalog, folder, autoescape): def test_multiple_raw(catalog, folder, autoescape): catalog.jinja_env.autoescape = autoescape - (folder / "C.jinja").write_text( - """ + (folder / "C.jinja").write_text("""
-""" - ) +""") - (folder / "Page.jinja").write_text( - """ + (folder / "Page.jinja").write_text(""" {% raw -%} @@ -377,8 +373,7 @@ def test_multiple_raw(catalog, folder, autoescape): {% raw %}{% endraw %} -""" - ) +""") html = catalog.render("Page", message="Hello") print(html) @@ -398,20 +393,17 @@ def test_multiple_raw(catalog, folder, autoescape): def test_check_for_unclosed(catalog, folder, autoescape): catalog.jinja_env.autoescape = autoescape - (folder / "Lorem.jinja").write_text( - """ + (folder / "Lorem.jinja").write_text(""" {#def ipsum=False #}

lorem {{ "ipsum" if ipsum else "lorem" }}

-""" - ) +""") - (folder / "Page.jinja").write_text( - """ + (folder / "Page.jinja").write_text("""
-""" - ) +""") + with pytest.raises(TemplateSyntaxError): try: catalog.render("Page") @@ -424,23 +416,19 @@ def test_check_for_unclosed(catalog, folder, autoescape): def test_dict_as_attr(catalog, folder, autoescape): catalog.jinja_env.autoescape = autoescape - (folder / "CitiesList.jinja").write_text( - """ + (folder / "CitiesList.jinja").write_text(""" {#def cities #} {% for city, country in cities.items() -%}

{{ city }}, {{ country }}

{%- endfor %} -""" - ) +""") - (folder / "Page.jinja").write_text( - """ + (folder / "Page.jinja").write_text(""" -""" - ) +""") html = catalog.render("Page") assert html == Markup("

Lima, Peru

New York, USA

") @@ -450,32 +438,26 @@ def test_dict_as_attr(catalog, folder, autoescape): def test_cleanup_assets(catalog, folder, autoescape): catalog.jinja_env.autoescape = autoescape - (folder / "Layout.jinja").write_text( - """ + (folder / "Layout.jinja").write_text(""" {{ catalog.render_assets() }} {{ content }} -""" - ) +""") - (folder / "Foo.jinja").write_text( - """ + (folder / "Foo.jinja").write_text(""" {#js foo.js #}

Foo

-""" - ) +""") - (folder / "Bar.jinja").write_text( - """ + (folder / "Bar.jinja").write_text(""" {#js bar.js #}

Bar

-""" - ) +""") html = catalog.render("Foo") print(html, "\n") @@ -504,7 +486,6 @@ def test_cleanup_assets(catalog, folder, autoescape): @pytest.mark.parametrize("autoescape", [True, False]) def test_do_not_mess_with_external_jinja_env(folder_t, folder, autoescape): - """https://github.com/jpsca/jinjax/issues/19""" (folder_t / "greeting.html").write_text("Jinja still works") (folder / "Greeting.jinja").write_text("JinjaX works") @@ -559,22 +540,18 @@ def test_do_not_mess_with_external_jinja_env(folder_t, folder, autoescape): def test_auto_reload(catalog, folder, autoescape): catalog.jinja_env.autoescape = autoescape - (folder / "Layout.jinja").write_text( - """ + (folder / "Layout.jinja").write_text(""" {{ content }} -""" - ) +""") - (folder / "Foo.jinja").write_text( - """ + (folder / "Foo.jinja").write_text("""

Foo

-""" - ) +""") bar_file = folder / "Bar.jinja" bar_file.write_text("

Bar

") @@ -624,22 +601,18 @@ def test_subcomponents(catalog, folder, autoescape): catalog.jinja_env.autoescape = autoescape """Issue https://github.com/jpsca/jinjax/issues/32""" - (folder / "Page.jinja").write_text( - """ + (folder / "Page.jinja").write_text(""" {#def message #}

lorem ipsum

{{ message }} -""" - ) +""") - (folder / "Subcomponent.jinja").write_text( - """ + (folder / "Subcomponent.jinja").write_text("""

foo bar

-""" - ) +""") html = catalog.render("Page", message="<3") @@ -665,22 +638,18 @@ def test_subcomponents(catalog, folder, autoescape): def test_fingerprint_assets(catalog, folder: Path, autoescape): catalog.jinja_env.autoescape = autoescape - (folder / "Layout.jinja").write_text( - """ + (folder / "Layout.jinja").write_text(""" {{ catalog.render_assets() }} {{ content }} -""" - ) +""") - (folder / "Page.jinja").write_text( - """ + (folder / "Page.jinja").write_text(""" {#css app.css, http://example.com/super.css #} {#js app.js #} Hi -""" - ) +""") (folder / "app.css").write_text("...") @@ -697,17 +666,13 @@ def test_fingerprint_assets(catalog, folder: Path, autoescape): def test_colon_in_attrs(catalog, folder, autoescape): catalog.jinja_env.autoescape = autoescape - (folder / "C.jinja").write_text( - """ + (folder / "C.jinja").write_text("""
-""" - ) +""") - (folder / "Page.jinja").write_text( - """ + (folder / "Page.jinja").write_text(""" -""" - ) +""") html = catalog.render("Page", message="Hello") print(html) @@ -718,28 +683,22 @@ def test_colon_in_attrs(catalog, folder, autoescape): def test_template_globals(catalog, folder, autoescape): catalog.jinja_env.autoescape = autoescape - (folder / "Input.jinja").write_text( - """ + (folder / "Input.jinja").write_text(""" {# def name, value #} -""" - ) - (folder / "CsrfToken.jinja").write_text( - """ +""") + + (folder / "CsrfToken.jinja").write_text(""" -""" - ) - (folder / "Form.jinja").write_text( - """ +""") + + (folder / "Form.jinja").write_text("""
{{content}} -""" - ) +""") - (folder / "Page.jinja").write_text( - """ + (folder / "Page.jinja").write_text(""" {# def value #}
-""" - ) +""") html = catalog.render("Page", value="bar", __globals={"csrf_token": "abc"}) print(html) @@ -751,9 +710,7 @@ def test_template_globals_update_cache(catalog, folder, autoescape): catalog.jinja_env.autoescape = autoescape (folder / "CsrfToken.jinja").write_text( - """ - -""" + """""" ) (folder / "Page.jinja").write_text("""""") @@ -942,7 +899,7 @@ def test_auto_load_assets_with_same_name(catalog, folder, autoescape): html = catalog.render("Page") print(html) - expected = """ + expected = """ @@ -994,3 +951,37 @@ def test_mixed_syntax(catalog, folder): print(html) expected = """4 {{2+2}} {'lorem': 'ipsum'} False""".strip() assert html == Markup(expected) + + +@pytest.mark.parametrize("autoescape", [True, False]) +def test_slots(catalog, folder, autoescape): + catalog.jinja_env.autoescape = autoescape + + (folder / "Component.jinja").write_text( + """ +

{{ content }}

+

{{ content("first") }}

+

{{ content("second") }}

+

{{ content() }}

+""".strip() + ) + + (folder / "Messages.jinja").write_text( + """ + +{% if slot == "first" %}Hello World +{%- elif slot == "second" %}Lorem Ipsum +{%- else %}Default{% endif %} + +""".strip() + ) + + html = catalog.render("Messages") + print(html) + expected = """ +

Default

+

Hello World

+

Lorem Ipsum

+

Default

+""".strip() + assert html == Markup(expected) From f8c004793934e68581f295b1dec508b8054d973e Mon Sep 17 00:00:00 2001 From: Juan-Pablo Scaletti Date: Fri, 16 Aug 2024 22:08:52 -0500 Subject: [PATCH 2/3] fix docs --- benchmark/Real.jinja | 2 +- docs/components/Home.jinja | 4 ++-- docs/components/SocialCardIndex.jinja | 4 ++-- docs/content/ui/tabs.md | 12 ++++++------ docs/theme/ExampleTabs.jinja | 9 ++++----- docs/theme/Layout.jinja | 2 +- docs/theme/NavGlobal.jinja | 2 +- docs/theme/NavMobile.jinja | 8 ++++---- docs/theme/NavTop.jinja | 4 ++-- docs/theme/Page.jinja | 12 ++++++------ docs/theme/PageSingle.jinja | 6 +++--- docs/theme/SocialCard.jinja | 4 ++-- docs/theme/Test.jinja | 2 +- 13 files changed, 35 insertions(+), 36 deletions(-) diff --git a/benchmark/Real.jinja b/benchmark/Real.jinja index c128349..163b3af 100644 --- a/benchmark/Real.jinja +++ b/benchmark/Real.jinja @@ -2,7 +2,7 @@ - + diff --git a/docs/components/Home.jinja b/docs/components/Home.jinja index 2d067cb..045150b 100644 --- a/docs/components/Home.jinja +++ b/docs/components/Home.jinja @@ -52,12 +52,12 @@
{% for product in products %} - + {% endfor %}
- + ``` {% endraw %}{% endfilter %} diff --git a/docs/components/SocialCardIndex.jinja b/docs/components/SocialCardIndex.jinja index 3203224..54da150 100644 --- a/docs/components/SocialCardIndex.jinja +++ b/docs/components/SocialCardIndex.jinja @@ -1,7 +1,7 @@ {#def page #}