From 37cd62bf0edd088faa44d41d3abc77209857775d Mon Sep 17 00:00:00 2001 From: Stefane Fermigier Date: Wed, 15 Oct 2025 19:44:59 +0200 Subject: [PATCH] Add missing sections in README.md. Also, run tests on the README. --- README.md | 179 ++++++++++++++++++++++++++++++++++++++++++++--- pyproject.toml | 2 +- tdom/__init__.py | 2 + 3 files changed, 172 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index f639466..aa14613 100644 --- a/README.md +++ b/README.md @@ -353,16 +353,24 @@ position in the template. Among other things, this means you can return a `Template` directly from a component function: + + ```python def Greeting(name: str) -> Template: return t"

Hello, {name}!

" result = html(t"<{Greeting} name='Alice' />") -#

Hello, Alice!

+assert str(result) == "

Hello, Alice!

" ``` You may also return an iterable: + + ```python from typing import Iterable @@ -370,7 +378,7 @@ def Items() -> Iterable[Template]: return [t"
  • first
  • ", t"
  • second
  • "] result = html(t"") -# +assert str(result) == "" ``` If you prefer, you can use **explicit fragment syntax** to wrap multiple @@ -381,7 +389,7 @@ def Items() -> Node: return html(t'<>
  • first
  • second
  • ') result = html(t'') -# +assert str(result) == "" ``` This is not required, but it can make your intent clearer. @@ -434,26 +442,177 @@ ask for children, but they are provided, then they are silently ignored. #### SVG Support -TODO: say more about SVG support +SVG elements work seamlessly with `tdom` since they follow the same XML-like +syntax as HTML. You can create inline SVG graphics by simply including SVG tags +in your templates: + + + +```python +icon = html(t""" + + + + +""") +assert ' Node: + return html(t""" + + + + """) + +result = html(t'<{Icon} size={48} color="blue" />') +assert 'width="48"' in str(result) +assert 'stroke="blue"' in str(result) +``` #### Context -TODO: implement context feature +Unlike some template systems that provide implicit "context" objects for passing +data through component hierarchies, `tdom` embraces Python's explicit approach. +If you need to pass data to nested components, you have several Pythonic +options: + +1. **Pass data as explicit arguments**: The most straightforward approach. + +2. **Use closures**: Components are just functions, so they can close over + variables in their enclosing scope: + +```python +theme = {"primary": "blue", "spacing": "10px"} + +def Button(text: str) -> Node: + # Button has access to theme from enclosing scope + return html(t'') + +result = html(t'<{Button} text="Click me" />') +assert 'color: blue' in str(result) +assert 'margin: 10px' in str(result) +assert '>Click me' in str(result) +``` + +3. **Use module-level or global state**: For truly application-wide + configuration. + +4. **Use a dedicated context library**: Libraries like `contextvars` can + provide more sophisticated context management if needed. + +This explicit approach makes it clear where data comes from and avoids the +"magic" of implicit context passing. ### The `tdom` Module #### Working with `Node` Objects -TODO: say more about working with them directly +While `html()` is the primary way to create nodes, you can also construct them +directly for programmatic HTML generation: + +```python +from tdom import Element, Text, Fragment, Comment, DocumentType + +# Create elements directly +div = Element("div", attrs={"class": "container"}, children=[ + Text("Hello, "), + Element("strong", children=[Text("World")]), +]) +assert str(div) == '
    Hello, World
    ' + +# Create fragments to group multiple nodes +fragment = Fragment(children=[ + Element("h1", children=[Text("Title")]), + Element("p", children=[Text("Paragraph")]), +]) +assert str(fragment) == "

    Title

    Paragraph

    " + +# Add comments +page = Element("body", children=[ + Comment("Navigation section"), + Element("nav", children=[Text("Nav content")]), +]) +assert str(page) == "" +``` + +All nodes implement the `__html__()` protocol, which means they can be used +anywhere that expects an object with HTML representation. Converting a node to a +string (via `str()` or `print()`) automatically renders it as HTML with proper +escaping. #### The `classnames()` Helper -TODO: say more about it +The `classnames()` function provides a flexible way to build class name strings +from various input types. It's particularly useful when you need to +conditionally include classes: + +```python +from tdom import classnames + +# Combine strings +assert classnames("btn", "btn-primary") == "btn btn-primary" + +# Use dictionaries for conditional classes +is_active = True +is_disabled = False +assert classnames("btn", { + "btn-active": is_active, + "btn-disabled": is_disabled +}) == "btn btn-active" + +# Mix lists, dicts, and strings +assert classnames( + "btn", + ["btn-large", "rounded"], + {"btn-primary": True, "btn-secondary": False}, + None, # Ignored + False # Ignored +) == "btn btn-large rounded btn-primary" + +# Nested lists are flattened +assert classnames(["btn", ["btn-primary", ["active"]]]) == "btn btn-primary active" +``` + +This function is automatically used when processing `class` attributes in +templates, so you can pass any of these input types directly in your t-strings. #### Utilities -TODO: say more about them +The `tdom` package includes several utility functions for working with +interpolations: + +**`format_interpolation()`**: This function handles the formatting of +interpolated values according to their format specifiers and conversions. It's +used internally by the `html()` function but can also be used independently: + +```python +from string.templatelib import Interpolation +from tdom.utils import format_interpolation, convert + +# Test convert function +assert convert("hello", "s") == "hello" +assert convert("hello", "r") == "'hello'" +assert convert(42, None) == 42 + +# format_interpolation is used internally for custom format specifiers +# The html() function uses this to implement :safe and :unsafe specifiers +``` + +**`convert()`**: Applies conversion specifiers (`!a`, `!r`, `!s`) to values +before formatting, following the same semantics as f-strings. + +These utilities follow the patterns established by PEP 750 for t-string +processing, allowing you to build custom template processors if needed. -## Supporters +## Contributing -TODO: add supporters +Contributions are welcome! Please feel free to submit issues or pull requests on +[GitHub](https://github.com/t-strings/tdom). diff --git a/pyproject.toml b/pyproject.toml index 1fc9a2b..c9b6d6d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -50,7 +50,7 @@ dev = [ ] [tool.pytest.ini_options] -testpaths = ["tdom", "docs"] +testpaths = ["tdom", "docs", "README.md"] python_files = ["*_test.py"] addopts = "-p no:doctest" diff --git a/tdom/__init__.py b/tdom/__init__.py index 4503582..d5fa015 100644 --- a/tdom/__init__.py +++ b/tdom/__init__.py @@ -1,11 +1,13 @@ from markupsafe import Markup, escape +from .classnames import classnames from .nodes import Comment, DocumentType, Element, Fragment, Node, Text from .processor import html # We consider `Markup` and `escape` to be part of this module's public API __all__ = [ + "classnames", "Comment", "DocumentType", "Element",