diff --git a/.github/workflows/run_tests.yml b/.github/workflows/run_tests.yml index e961490..0f18b94 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: @@ -19,7 +23,9 @@ jobs: run: | python -m pip install --upgrade pip pip install poetry pytest - poetry install + poetry install --no-root --no-interaction + - name: Remove Poetry cache + run: poetry cache clear . --all -n - name: Lint with Ruff run: | pip install ruff @@ -28,6 +34,3 @@ jobs: - name: Test with pytest run: | poetry run pytest - #- name: Generate Coverage Report - # run: | - # coverage report -m diff --git a/examples/basic_example.py b/examples/basic_example.py new file mode 100644 index 0000000..e794772 --- /dev/null +++ b/examples/basic_example.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 basic_example(): + tmpl = TrimTemplate('examples/main.html.trim', vars=template_vars, pretty=True) + output = tmpl.render() + print("=== Basic Example Output ===\n") + print(output) + +if __name__ == '__main__': + basic_example() \ No newline at end of file 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/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/poetry.lock b/poetry.lock index 3f17609..b3563ae 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,18 +1,20 @@ -# This file is automatically @generated by Poetry 1.8.1 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" -version = "4.12.3" +version = "4.13.4" description = "Screen-scraping library" optional = false -python-versions = ">=3.6.0" +python-versions = "^3.12" +groups = ["main"] 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"] @@ -27,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"}, @@ -41,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"}, @@ -48,60 +53,80 @@ 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" +groups = ["main"] 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" +groups = ["main"] 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" +groups = ["main"] 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" +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"}, +] + +[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" +groups = ["main"] 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" @@ -109,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"}, @@ -131,16 +157,29 @@ 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" +groups = ["main"] +files = [ + {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" +groups = ["main"] files = [ - {file = "soupsieve-2.5-py3-none-any.whl", hash = "sha256:eaa337ff55a1579b6549dc679565eac1e3d000563bcb1c8ab0d0fefbc0c2cdc7"}, - {file = "soupsieve-2.5.tar.gz", hash = "sha256:5663d5a7b3bfaeee0bc4372e7fc48f9cff4940b3eec54a6451cc5299f1097690"}, + {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" 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" 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',