From 5c0359fc82e94476ef05c4b85ebba0855b1c390c Mon Sep 17 00:00:00 2001 From: David Kelly Date: Mon, 14 Jul 2025 18:57:02 +0200 Subject: [PATCH 01/13] examples of sub-template rendering --- examples/footer.html.trim | 8 ++++ examples/header.html.trim | 11 +++++ examples/main.html.trim | 21 +++++++++ examples/simple_test.py | 26 ++++++++++ examples/sub_template_example.py | 81 ++++++++++++++++++++++++++++++++ examples/user_card.html.trim | 13 +++++ pyproject.toml | 2 +- 7 files changed, 161 insertions(+), 1 deletion(-) create mode 100644 examples/footer.html.trim create mode 100644 examples/header.html.trim create mode 100644 examples/main.html.trim create mode 100644 examples/simple_test.py create mode 100644 examples/sub_template_example.py create mode 100644 examples/user_card.html.trim diff --git a/examples/footer.html.trim b/examples/footer.html.trim new file mode 100644 index 0000000..fa25577 --- /dev/null +++ b/examples/footer.html.trim @@ -0,0 +1,8 @@ +footer.site-footer + .footer-content + p © {current_year} {site_name}. All rights reserved. + + .footer-links + a href='/privacy' Privacy Policy + a href='/terms' Terms of Service + a href='/contact' Contact Us \ No newline at end of file diff --git a/examples/header.html.trim b/examples/header.html.trim new file mode 100644 index 0000000..6731987 --- /dev/null +++ b/examples/header.html.trim @@ -0,0 +1,11 @@ +header.navbar + .nav-brand + a href='/' {site_name} + + nav.nav-menu + - if user.logged_in + a href='/profile' Profile + a href='/logout' Logout + - else + a href='/login' Login + a href='/register' Register \ No newline at end of file diff --git a/examples/main.html.trim b/examples/main.html.trim new file mode 100644 index 0000000..74a7769 --- /dev/null +++ b/examples/main.html.trim @@ -0,0 +1,21 @@ +doctype html + +html + head + title {page_title} + stylesheet src='/styles.css' + meta charset='utf-8' + + body + - render header.html.trim + + main.container + h1 {main_heading} + p {content} + + .user-list + h2 Users + - for user in users + - render user_card.html.trim + + - render footer.html.trim \ No newline at end of file diff --git a/examples/simple_test.py b/examples/simple_test.py new file mode 100644 index 0000000..c5d4141 --- /dev/null +++ b/examples/simple_test.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python3 +""" +Simple test to verify basic template functionality. +""" + +from trim_template.trim import TrimTemplate + +# Simple test with basic variables +template_vars = { + 'page_title': 'Test Page', + 'site_name': 'TestApp', + 'main_heading': 'Hello World', + 'content': 'This is a test.', + 'current_year': 2025, + 'user': type('User', (), {'logged_in': False})(), + 'users': [], +} + +def test_simple(): + tmpl = TrimTemplate('examples/main.html.trim', vars=template_vars, pretty=True) + output = tmpl.render() + print("=== Simple Test Output ===\n") + print(output) + +if __name__ == '__main__': + test_simple() \ No newline at end of file diff --git a/examples/sub_template_example.py b/examples/sub_template_example.py new file mode 100644 index 0000000..bfa4564 --- /dev/null +++ b/examples/sub_template_example.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python3 +""" +Example demonstrating sub-template usage with TrimTemplate. +This shows how to include multiple sub-templates in a main template. +""" + +from trim_template.trim import TrimTemplate +from datetime import datetime + +# Simple class to represent a user object +class User: + def __init__(self, id, name, email, role, avatar_url, is_admin=False): + self.id = id + self.name = name + self.email = email + self.role = role + self.avatar_url = avatar_url + self.is_admin = is_admin + +# Simple class to represent the current user +class CurrentUser: + def __init__(self, logged_in=True, name="", email=""): + self.logged_in = logged_in + self.name = name + self.email = email + +# Sample data for the template +template_vars = { + 'page_title': 'User Dashboard', + 'site_name': 'MyAwesomeApp', + 'main_heading': 'Welcome to the Dashboard', + 'content': 'This page demonstrates sub-template usage with header, user cards, and footer.', + 'current_year': datetime.now().year, + 'user': CurrentUser( + logged_in=True, + name='John Doe', + email='john@example.com' + ), + 'users': [ + User( + id=1, + name='Alice Johnson', + email='alice@example.com', + role='Admin', + avatar_url='/avatars/alice.jpg', + is_admin=True + ), + User( + id=2, + name='Bob Smith', + email='bob@example.com', + role='User', + avatar_url='/avatars/bob.jpg', + is_admin=False + ), + User( + id=3, + name='Carol Davis', + email='carol@example.com', + role='Moderator', + avatar_url='/avatars/carol.jpg', + is_admin=True + ) + ] +} + +def main(): + # Create template instance with pretty formatting + tmpl = TrimTemplate('examples/main.html.trim', vars=template_vars, pretty=True) + + # Render the template + output = tmpl.render() + + print("=== Sub-Template Example Output ===\n") + print(output) + + print("\n=== Template Debug Info ===") + tmpl.debug() + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/examples/user_card.html.trim b/examples/user_card.html.trim new file mode 100644 index 0000000..822a42e --- /dev/null +++ b/examples/user_card.html.trim @@ -0,0 +1,13 @@ +.user-card + .user-avatar + img src={user.avatar_url} alt='{user.name} avatar' + + .user-info + h3 {user.name} + p.user-email {user.email} + p.user-role {user.role} + + .user-actions + a.btn.btn-primary href='/users/{user.id}' View Profile + - if user.is_admin + a.btn.btn-secondary href='/admin/users/{user.id}' Admin \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index c894ed1..fabe502 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "trim-template" -version = "0.1.3" +version = "1.0.0" description = "A templating engine inspired by Ruby's Slim template engine" authors = ["David Kelly"] license = "MIT License" From 123836c16dae77a3906a4fafa9fa10c97ea6d6c5 Mon Sep 17 00:00:00 2001 From: David Kelly Date: Mon, 14 Jul 2025 19:05:56 +0200 Subject: [PATCH 02/13] update poetry lock --- poetry.lock | 87 +++++++++++++++++++++++++++++++++++------------------ 1 file changed, 57 insertions(+), 30 deletions(-) diff --git a/poetry.lock b/poetry.lock index 3f17609..f5d036f 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,18 +1,19 @@ -# This file is automatically @generated by Poetry 1.8.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. [[package]] name = "beautifulsoup4" -version = "4.12.3" +version = "4.13.4" description = "Screen-scraping library" optional = false -python-versions = ">=3.6.0" +python-versions = ">=3.7.0" files = [ - {file = "beautifulsoup4-4.12.3-py3-none-any.whl", hash = "sha256:b80878c9f40111313e55da8ba20bdba06d8fa3969fc68304167741bbf9e082ed"}, - {file = "beautifulsoup4-4.12.3.tar.gz", hash = "sha256:74e3d1928edc070d21748185c46e3fb33490f22f52a3addee9aee0f4f7781051"}, + {file = "beautifulsoup4-4.13.4-py3-none-any.whl", hash = "sha256:9bbbb14bfde9d79f38b8cd5f8c7c85f4b8f2523190ebed90e950a8dea4cb1c4b"}, + {file = "beautifulsoup4-4.13.4.tar.gz", hash = "sha256:dbb3c4e1ceae6aefebdaf2423247260cd062430a410e38c66f2baa50a8437195"}, ] [package.dependencies] soupsieve = ">1.2" +typing-extensions = ">=4.0.0" [package.extras] cchardet = ["cchardet"] @@ -48,60 +49,75 @@ files = [ [[package]] name = "iniconfig" -version = "2.0.0" +version = "2.1.0" description = "brain-dead simple config-ini parsing" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, - {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, + {file = "iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760"}, + {file = "iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7"}, ] [[package]] name = "packaging" -version = "23.2" +version = "25.0" description = "Core utilities for Python packages" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"}, - {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, + {file = "packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484"}, + {file = "packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"}, ] [[package]] name = "pluggy" -version = "1.4.0" +version = "1.6.0" description = "plugin and hook calling mechanisms for python" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "pluggy-1.4.0-py3-none-any.whl", hash = "sha256:7db9f7b503d67d1c5b95f59773ebb58a8c1c288129a88665838012cfb07b8981"}, - {file = "pluggy-1.4.0.tar.gz", hash = "sha256:8c85c2876142a764e5b7548e7d9a0e0ddb46f5185161049a79b7e974454223be"}, + {file = "pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746"}, + {file = "pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3"}, ] [package.extras] dev = ["pre-commit", "tox"] -testing = ["pytest", "pytest-benchmark"] +testing = ["coverage", "pytest", "pytest-benchmark"] + +[[package]] +name = "pygments" +version = "2.19.2" +description = "Pygments is a syntax highlighting package written in Python." +optional = false +python-versions = ">=3.8" +files = [ + {file = "pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b"}, + {file = "pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887"}, +] + +[package.extras] +windows-terminal = ["colorama (>=0.4.6)"] [[package]] name = "pytest" -version = "8.0.2" +version = "8.4.1" description = "pytest: simple powerful testing with Python" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "pytest-8.0.2-py3-none-any.whl", hash = "sha256:edfaaef32ce5172d5466b5127b42e0d6d35ebbe4453f0e3505d96afd93f6b096"}, - {file = "pytest-8.0.2.tar.gz", hash = "sha256:d4051d623a2e0b7e51960ba963193b09ce6daeb9759a451844a21e4ddedfc1bd"}, + {file = "pytest-8.4.1-py3-none-any.whl", hash = "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7"}, + {file = "pytest-8.4.1.tar.gz", hash = "sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c"}, ] [package.dependencies] -colorama = {version = "*", markers = "sys_platform == \"win32\""} -iniconfig = "*" -packaging = "*" -pluggy = ">=1.3.0,<2.0" +colorama = {version = ">=0.4", markers = "sys_platform == \"win32\""} +iniconfig = ">=1" +packaging = ">=20" +pluggy = ">=1.5,<2" +pygments = ">=2.7.2" [package.extras] -testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] +dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "requests", "setuptools", "xmlschema"] [[package]] name = "ruff" @@ -131,13 +147,24 @@ files = [ [[package]] name = "soupsieve" -version = "2.5" +version = "2.7" description = "A modern CSS selector implementation for Beautiful Soup." optional = false python-versions = ">=3.8" files = [ - {file = "soupsieve-2.5-py3-none-any.whl", hash = "sha256:eaa337ff55a1579b6549dc679565eac1e3d000563bcb1c8ab0d0fefbc0c2cdc7"}, - {file = "soupsieve-2.5.tar.gz", hash = "sha256:5663d5a7b3bfaeee0bc4372e7fc48f9cff4940b3eec54a6451cc5299f1097690"}, + {file = "soupsieve-2.7-py3-none-any.whl", hash = "sha256:6e60cc5c1ffaf1cebcc12e8188320b72071e922c2e897f737cadce79ad5d30c4"}, + {file = "soupsieve-2.7.tar.gz", hash = "sha256:ad282f9b6926286d2ead4750552c8a6142bc4c783fd66b0293547c8fe6ae126a"}, +] + +[[package]] +name = "typing-extensions" +version = "4.14.1" +description = "Backported and Experimental Type Hints for Python 3.9+" +optional = false +python-versions = ">=3.9" +files = [ + {file = "typing_extensions-4.14.1-py3-none-any.whl", hash = "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76"}, + {file = "typing_extensions-4.14.1.tar.gz", hash = "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36"}, ] [metadata] From c6ad8c4bdb70e9803c4859ae0d51ecd5cf40c619 Mon Sep 17 00:00:00 2001 From: David Kelly Date: Mon, 14 Jul 2025 20:47:54 +0200 Subject: [PATCH 03/13] poetry install clean --- .github/workflows/run_tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/run_tests.yml b/.github/workflows/run_tests.yml index e961490..842b0c2 100644 --- a/.github/workflows/run_tests.yml +++ b/.github/workflows/run_tests.yml @@ -19,7 +19,7 @@ jobs: run: | python -m pip install --upgrade pip pip install poetry pytest - poetry install + poetry install --no-root --no-interaction - name: Lint with Ruff run: | pip install ruff From b7a668da4b94867c90ff0548666520cf8fb50686 Mon Sep 17 00:00:00 2001 From: David Kelly Date: Mon, 14 Jul 2025 20:51:48 +0200 Subject: [PATCH 04/13] clear poetry cache --- .github/workflows/run_tests.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/run_tests.yml b/.github/workflows/run_tests.yml index 842b0c2..c7f22d4 100644 --- a/.github/workflows/run_tests.yml +++ b/.github/workflows/run_tests.yml @@ -15,6 +15,8 @@ jobs: uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} + - name: Remove Poetry cache + run: poetry cache clear . --all - name: Install dependencies run: | python -m pip install --upgrade pip @@ -28,6 +30,3 @@ jobs: - name: Test with pytest run: | poetry run pytest - #- name: Generate Coverage Report - # run: | - # coverage report -m From 8c5c934383ac62c7e20da721c26a3147591bd2a6 Mon Sep 17 00:00:00 2001 From: David Kelly Date: Mon, 14 Jul 2025 20:53:16 +0200 Subject: [PATCH 05/13] fix run order --- .github/workflows/run_tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/run_tests.yml b/.github/workflows/run_tests.yml index c7f22d4..c807877 100644 --- a/.github/workflows/run_tests.yml +++ b/.github/workflows/run_tests.yml @@ -15,13 +15,13 @@ jobs: uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - - name: Remove Poetry cache - run: poetry cache clear . --all - name: Install dependencies run: | python -m pip install --upgrade pip pip install poetry pytest poetry install --no-root --no-interaction + - name: Remove Poetry cache + run: poetry cache clear . --all - name: Lint with Ruff run: | pip install ruff From 1a43cc7e0bfedfc94b6b2cd928c3fcc5eb427fc7 Mon Sep 17 00:00:00 2001 From: David Kelly Date: Mon, 14 Jul 2025 20:57:40 +0200 Subject: [PATCH 06/13] bust poetry cache --- .github/workflows/run_tests.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/run_tests.yml b/.github/workflows/run_tests.yml index c807877..f1ebd04 100644 --- a/.github/workflows/run_tests.yml +++ b/.github/workflows/run_tests.yml @@ -10,7 +10,11 @@ jobs: python-version: ["3.12"] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 + - uses: actions/cache@v3 + with: + path: ~/.cache/pypoetry + key: ${{ runner.os }}-poetry-${{ hashFiles('**/poetry.lock', '**/pyproject.toml') }} - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v4 with: From eac4e69fadb71a33167c1f2ccfce3b4930a4ccf4 Mon Sep 17 00:00:00 2001 From: David Kelly Date: Mon, 14 Jul 2025 21:01:42 +0200 Subject: [PATCH 07/13] Regenerate poetry.lock with Poetry 2.1.3 --- poetry.lock | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/poetry.lock b/poetry.lock index f5d036f..ff22b4d 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.1.3 and should not be changed by hand. [[package]] name = "beautifulsoup4" @@ -6,6 +6,7 @@ version = "4.13.4" description = "Screen-scraping library" optional = false python-versions = ">=3.7.0" +groups = ["main"] files = [ {file = "beautifulsoup4-4.13.4-py3-none-any.whl", hash = "sha256:9bbbb14bfde9d79f38b8cd5f8c7c85f4b8f2523190ebed90e950a8dea4cb1c4b"}, {file = "beautifulsoup4-4.13.4.tar.gz", hash = "sha256:dbb3c4e1ceae6aefebdaf2423247260cd062430a410e38c66f2baa50a8437195"}, @@ -28,6 +29,7 @@ version = "0.0.2" description = "Dummy package for Beautiful Soup (beautifulsoup4)" optional = false python-versions = "*" +groups = ["main"] files = [ {file = "bs4-0.0.2-py2.py3-none-any.whl", hash = "sha256:abf8742c0805ef7f662dce4b51cca104cffe52b835238afc169142ab9b3fbccc"}, {file = "bs4-0.0.2.tar.gz", hash = "sha256:a48685c58f50fe127722417bae83fe6badf500d54b55f7e39ffe43b798653925"}, @@ -42,6 +44,8 @@ version = "0.4.6" description = "Cross-platform colored terminal text." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +groups = ["main"] +markers = "sys_platform == \"win32\"" files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, @@ -53,6 +57,7 @@ version = "2.1.0" description = "brain-dead simple config-ini parsing" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760"}, {file = "iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7"}, @@ -64,6 +69,7 @@ version = "25.0" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484"}, {file = "packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"}, @@ -75,6 +81,7 @@ version = "1.6.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746"}, {file = "pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3"}, @@ -90,6 +97,7 @@ version = "2.19.2" description = "Pygments is a syntax highlighting package written in Python." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b"}, {file = "pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887"}, @@ -104,6 +112,7 @@ version = "8.4.1" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "pytest-8.4.1-py3-none-any.whl", hash = "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7"}, {file = "pytest-8.4.1.tar.gz", hash = "sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c"}, @@ -125,6 +134,7 @@ version = "0.2.2" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "ruff-0.2.2-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:0a9efb032855ffb3c21f6405751d5e147b0c6b631e3ca3f6b20f917572b97eb6"}, {file = "ruff-0.2.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:d450b7fbff85913f866a5384d8912710936e2b96da74541c82c1b458472ddb39"}, @@ -151,6 +161,7 @@ version = "2.7" description = "A modern CSS selector implementation for Beautiful Soup." optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "soupsieve-2.7-py3-none-any.whl", hash = "sha256:6e60cc5c1ffaf1cebcc12e8188320b72071e922c2e897f737cadce79ad5d30c4"}, {file = "soupsieve-2.7.tar.gz", hash = "sha256:ad282f9b6926286d2ead4750552c8a6142bc4c783fd66b0293547c8fe6ae126a"}, @@ -162,12 +173,13 @@ version = "4.14.1" description = "Backported and Experimental Type Hints for Python 3.9+" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "typing_extensions-4.14.1-py3-none-any.whl", hash = "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76"}, {file = "typing_extensions-4.14.1.tar.gz", hash = "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36"}, ] [metadata] -lock-version = "2.0" +lock-version = "2.1" python-versions = "^3.12" -content-hash = "1dbf8bd6d55e95f25b62a5f4f7fc30607516b757418d8d955fb9050dd1064741" +content-hash = "1c8af31b8cc0775e1c94fd59d2fc1c2d35b8c6071426969f7daf464d9333b3c4" From 14c945ed54ba8a064fa659b29f28c2c8ca114a73 Mon Sep 17 00:00:00 2001 From: David Kelly Date: Mon, 14 Jul 2025 21:07:18 +0200 Subject: [PATCH 08/13] fix python version --- poetry.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/poetry.lock b/poetry.lock index ff22b4d..b3563ae 100644 --- a/poetry.lock +++ b/poetry.lock @@ -5,7 +5,7 @@ name = "beautifulsoup4" version = "4.13.4" description = "Screen-scraping library" optional = false -python-versions = ">=3.7.0" +python-versions = "^3.12" groups = ["main"] files = [ {file = "beautifulsoup4-4.13.4-py3-none-any.whl", hash = "sha256:9bbbb14bfde9d79f38b8cd5f8c7c85f4b8f2523190ebed90e950a8dea4cb1c4b"}, From 33df68d20c194a3484573a3a1f01fab00a77618e Mon Sep 17 00:00:00 2001 From: David Kelly Date: Tue, 15 Jul 2025 14:07:45 +0200 Subject: [PATCH 09/13] clear poetry cache silently --- .github/workflows/run_tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/run_tests.yml b/.github/workflows/run_tests.yml index f1ebd04..0f18b94 100644 --- a/.github/workflows/run_tests.yml +++ b/.github/workflows/run_tests.yml @@ -25,7 +25,7 @@ jobs: pip install poetry pytest poetry install --no-root --no-interaction - name: Remove Poetry cache - run: poetry cache clear . --all + run: poetry cache clear . --all -n - name: Lint with Ruff run: | pip install ruff From 763310b45dda01527ff947d53d320911d9bd0091 Mon Sep 17 00:00:00 2001 From: David Kelly Date: Tue, 15 Jul 2025 14:14:43 +0200 Subject: [PATCH 10/13] rename simple test to basic example --- .github/workflows/run_tests.yml | 2 +- examples/{simple_test.py => basic_example.py} | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) rename examples/{simple_test.py => basic_example.py} (87%) diff --git a/.github/workflows/run_tests.yml b/.github/workflows/run_tests.yml index 0f18b94..046dd56 100644 --- a/.github/workflows/run_tests.yml +++ b/.github/workflows/run_tests.yml @@ -25,7 +25,7 @@ jobs: pip install poetry pytest poetry install --no-root --no-interaction - name: Remove Poetry cache - run: poetry cache clear . --all -n + run: poetry cache clear . --all -npo - name: Lint with Ruff run: | pip install ruff diff --git a/examples/simple_test.py b/examples/basic_example.py similarity index 87% rename from examples/simple_test.py rename to examples/basic_example.py index c5d4141..e794772 100644 --- a/examples/simple_test.py +++ b/examples/basic_example.py @@ -16,11 +16,11 @@ 'users': [], } -def test_simple(): +def basic_example(): tmpl = TrimTemplate('examples/main.html.trim', vars=template_vars, pretty=True) output = tmpl.render() - print("=== Simple Test Output ===\n") + print("=== Basic Example Output ===\n") print(output) if __name__ == '__main__': - test_simple() \ No newline at end of file + basic_example() \ No newline at end of file From 5cb4c6cc854b167fbeed6a128dad4ce4e3ae512d Mon Sep 17 00:00:00 2001 From: David Kelly Date: Tue, 15 Jul 2025 14:15:34 +0200 Subject: [PATCH 11/13] typo --- .github/workflows/run_tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/run_tests.yml b/.github/workflows/run_tests.yml index 046dd56..0f18b94 100644 --- a/.github/workflows/run_tests.yml +++ b/.github/workflows/run_tests.yml @@ -25,7 +25,7 @@ jobs: pip install poetry pytest poetry install --no-root --no-interaction - name: Remove Poetry cache - run: poetry cache clear . --all -npo + run: poetry cache clear . --all -n - name: Lint with Ruff run: | pip install ruff From 20e4f3bfad672f9f0e450d7fd70c4aea7729746c Mon Sep 17 00:00:00 2001 From: David Kelly Date: Tue, 15 Jul 2025 14:17:33 +0200 Subject: [PATCH 12/13] fix ruff --- trim_template/node_parser.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/trim_template/node_parser.py b/trim_template/node_parser.py index 32383ba..b434305 100644 --- a/trim_template/node_parser.py +++ b/trim_template/node_parser.py @@ -1,6 +1,3 @@ -# from trim import TrimTemplate -import os - SINGLE_TAGS = ["doctype", "img", "br", "hr", "input", "link", "meta"] BOOLEAN_ATTRIBUTES = [ 'checked', 'selected', 'disabled', 'readonly', 'multiple', 'ismap', 'defer', 'declare', 'noresize', 'nowrap', From 75be4e6bb0fd3950883840b29380873bf48529e2 Mon Sep 17 00:00:00 2001 From: David Kelly Date: Tue, 15 Jul 2025 16:30:27 +0200 Subject: [PATCH 13/13] fix for = in tag attributes --- CHANGELOG.md | 10 +++++++++- pyproject.toml | 4 ++-- setup.py | 2 +- tests/test_tag_attributes.py | 12 ++++++++++++ trim_template/node.py | 16 ++++++++++++---- 5 files changed, 36 insertions(+), 8 deletions(-) create mode 100644 tests/test_tag_attributes.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 03b1b67..3e66120 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,12 @@ -21-06-2024 +15-06-2025 - v1.0.1 + +* Fixed bug in attribute parsing for tags with multiple attributes +* Improved regex parsing to handle quoted attribute values correctly +* Added comprehensive test for multi-attribute tags (e.g., meta name='viewport' initial-scale='1') +* Renamed .skml files to .html.trim and updated all test references +* Added sub-template examples with user objects in examples directory + +21-06-2024 - v1.0.0 * make trim-template available on PyPI diff --git a/pyproject.toml b/pyproject.toml index fabe502..d6728d5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "trim-template" -version = "1.0.0" +version = "1.0.1" description = "A templating engine inspired by Ruby's Slim template engine" authors = ["David Kelly"] license = "MIT License" @@ -10,7 +10,7 @@ keywords = ["template", "trim", "engine", "html"] [project] name = "trim-template" -version = "0.1.0" +version = "1.0.1" description = "A templating engine inspired by Ruby's Slim template engine" authors = [ {name = "David Kelly", email = "info@futuresbright.com"} diff --git a/setup.py b/setup.py index 22e1e93..faa2cd6 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ setup( name='trim-template', - version='1.0', + version='1.0.1', packages=['trim_template'], entry_points={ 'console_scripts': [ diff --git a/tests/test_tag_attributes.py b/tests/test_tag_attributes.py new file mode 100644 index 0000000..44d43c7 --- /dev/null +++ b/tests/test_tag_attributes.py @@ -0,0 +1,12 @@ +from trim_template.trim import TrimTemplate + +template = """ +meta name='viewport' initial-scale='1' +meta name='description' content='A description of the page' +""" + +def test_tag_attributes(): + tmpl = TrimTemplate(template, pretty=False) + output = tmpl.render() + + assert output == '' \ No newline at end of file diff --git a/trim_template/node.py b/trim_template/node.py index 75e5c94..9a342d0 100644 --- a/trim_template/node.py +++ b/trim_template/node.py @@ -20,7 +20,8 @@ def __init__(self, indentation, line = None): return self.line = line.strip() - self.parts = self.line.split(" ") + # Use regex to split on key='value', key="value", or key=value, or tag + self.parts = re.findall(r"[^ \t]+='[^']*'|[^ \t]+=\"[^\"]*\"|[^ \t]+", self.line) if self.line == '': return @@ -109,8 +110,15 @@ def parse_attributes(self): for a in (self.parts[1:]): if '=' in a: - key, val = a.split('=') - self.attributes[key] = val.strip('"').strip("'") + equal_pos = a.find('=') + key = a[:equal_pos].strip('"\'') + val = a[equal_pos + 1:] + + # Only strip quotes if both ends are quoted + if (val.startswith('"') and val.endswith('"')) or (val.startswith("'") and val.endswith("'")): + val = val[1:-1] + + self.attributes[key] = val else: self.text += a + " " @@ -133,4 +141,4 @@ def has_attributes(self): return self.attributes != [] def attributes(self): - return ' '.join(self.attributes) + return ' '.join(f'{k}="{v}"' for k, v in self.attributes.items())