Skip to content

Commit 37cd62b

Browse files
committed
Add missing sections in README.md.
Also, run tests on the README.
1 parent c39dd64 commit 37cd62b

File tree

3 files changed

+172
-11
lines changed

3 files changed

+172
-11
lines changed

README.md

Lines changed: 169 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -353,24 +353,32 @@ position in the template.
353353
Among other things, this means you can return a `Template` directly from a
354354
component function:
355355

356+
<!-- invisible-code-block: python
357+
from string.templatelib import Template
358+
-->
359+
356360
```python
357361
def Greeting(name: str) -> Template:
358362
return t"<h1>Hello, {name}!</h1>"
359363

360364
result = html(t"<{Greeting} name='Alice' />")
361-
# <h1>Hello, Alice!</h1>
365+
assert str(result) == "<h1>Hello, Alice!</h1>"
362366
```
363367

364368
You may also return an iterable:
365369

370+
<!-- invisible-code-block: python
371+
from string.templatelib import Template
372+
-->
373+
366374
```python
367375
from typing import Iterable
368376

369377
def Items() -> Iterable[Template]:
370378
return [t"<li>first</li>", t"<li>second</li>"]
371379

372380
result = html(t"<ul><{Items} /></ul>")
373-
# <ul><li>first</li><li>second</li></ul>
381+
assert str(result) == "<ul><li>first</li><li>second</li></ul>"
374382
```
375383

376384
If you prefer, you can use **explicit fragment syntax** to wrap multiple
@@ -381,7 +389,7 @@ def Items() -> Node:
381389
return html(t'<><li>first</li><li>second</li></>')
382390

383391
result = html(t'<ul><{Items} /></ul>')
384-
# <ul><li>first</li><li>second</li></ul>
392+
assert str(result) == "<ul><li>first</li><li>second</li></ul>"
385393
```
386394

387395
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.
434442

435443
#### SVG Support
436444

437-
TODO: say more about SVG support
445+
SVG elements work seamlessly with `tdom` since they follow the same XML-like
446+
syntax as HTML. You can create inline SVG graphics by simply including SVG tags
447+
in your templates:
448+
449+
<!-- invisible-code-block: python
450+
from tdom import html, Node
451+
-->
452+
453+
```python
454+
icon = html(t"""
455+
<svg width="24" height="24" viewBox="0 0 24 24" fill="none">
456+
<circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="2"/>
457+
<path d="M12 6v6l4 2" stroke="currentColor" stroke-width="2"/>
458+
</svg>
459+
""")
460+
assert '<svg width="24" height="24"' in str(icon)
461+
assert '<circle cx="12" cy="12" r="10"' in str(icon)
462+
```
463+
464+
All the same interpolation, attribute handling, and component features work with
465+
SVG elements:
466+
467+
```python
468+
def Icon(*, size: int = 24, color: str = "currentColor") -> Node:
469+
return html(t"""
470+
<svg width="{size}" height="{size}" viewBox="0 0 24 24" fill="none">
471+
<circle cx="12" cy="12" r="10" stroke="{color}" stroke-width="2"/>
472+
</svg>
473+
""")
474+
475+
result = html(t'<{Icon} size={48} color="blue" />')
476+
assert 'width="48"' in str(result)
477+
assert 'stroke="blue"' in str(result)
478+
```
438479

439480
#### Context
440481

441-
TODO: implement context feature
482+
Unlike some template systems that provide implicit "context" objects for passing
483+
data through component hierarchies, `tdom` embraces Python's explicit approach.
484+
If you need to pass data to nested components, you have several Pythonic
485+
options:
486+
487+
1. **Pass data as explicit arguments**: The most straightforward approach.
488+
489+
2. **Use closures**: Components are just functions, so they can close over
490+
variables in their enclosing scope:
491+
492+
```python
493+
theme = {"primary": "blue", "spacing": "10px"}
494+
495+
def Button(text: str) -> Node:
496+
# Button has access to theme from enclosing scope
497+
return html(t'<button style="color: {theme["primary"]}; margin: {theme["spacing"]}">{text}</button>')
498+
499+
result = html(t'<{Button} text="Click me" />')
500+
assert 'color: blue' in str(result)
501+
assert 'margin: 10px' in str(result)
502+
assert '>Click me</button>' in str(result)
503+
```
504+
505+
3. **Use module-level or global state**: For truly application-wide
506+
configuration.
507+
508+
4. **Use a dedicated context library**: Libraries like `contextvars` can
509+
provide more sophisticated context management if needed.
510+
511+
This explicit approach makes it clear where data comes from and avoids the
512+
"magic" of implicit context passing.
442513

443514
### The `tdom` Module
444515

445516
#### Working with `Node` Objects
446517

447-
TODO: say more about working with them directly
518+
While `html()` is the primary way to create nodes, you can also construct them
519+
directly for programmatic HTML generation:
520+
521+
```python
522+
from tdom import Element, Text, Fragment, Comment, DocumentType
523+
524+
# Create elements directly
525+
div = Element("div", attrs={"class": "container"}, children=[
526+
Text("Hello, "),
527+
Element("strong", children=[Text("World")]),
528+
])
529+
assert str(div) == '<div class="container">Hello, <strong>World</strong></div>'
530+
531+
# Create fragments to group multiple nodes
532+
fragment = Fragment(children=[
533+
Element("h1", children=[Text("Title")]),
534+
Element("p", children=[Text("Paragraph")]),
535+
])
536+
assert str(fragment) == "<h1>Title</h1><p>Paragraph</p>"
537+
538+
# Add comments
539+
page = Element("body", children=[
540+
Comment("Navigation section"),
541+
Element("nav", children=[Text("Nav content")]),
542+
])
543+
assert str(page) == "<body><!--Navigation section--><nav>Nav content</nav></body>"
544+
```
545+
546+
All nodes implement the `__html__()` protocol, which means they can be used
547+
anywhere that expects an object with HTML representation. Converting a node to a
548+
string (via `str()` or `print()`) automatically renders it as HTML with proper
549+
escaping.
448550

449551
#### The `classnames()` Helper
450552

451-
TODO: say more about it
553+
The `classnames()` function provides a flexible way to build class name strings
554+
from various input types. It's particularly useful when you need to
555+
conditionally include classes:
556+
557+
```python
558+
from tdom import classnames
559+
560+
# Combine strings
561+
assert classnames("btn", "btn-primary") == "btn btn-primary"
562+
563+
# Use dictionaries for conditional classes
564+
is_active = True
565+
is_disabled = False
566+
assert classnames("btn", {
567+
"btn-active": is_active,
568+
"btn-disabled": is_disabled
569+
}) == "btn btn-active"
570+
571+
# Mix lists, dicts, and strings
572+
assert classnames(
573+
"btn",
574+
["btn-large", "rounded"],
575+
{"btn-primary": True, "btn-secondary": False},
576+
None, # Ignored
577+
False # Ignored
578+
) == "btn btn-large rounded btn-primary"
579+
580+
# Nested lists are flattened
581+
assert classnames(["btn", ["btn-primary", ["active"]]]) == "btn btn-primary active"
582+
```
583+
584+
This function is automatically used when processing `class` attributes in
585+
templates, so you can pass any of these input types directly in your t-strings.
452586

453587
#### Utilities
454588

455-
TODO: say more about them
589+
The `tdom` package includes several utility functions for working with
590+
interpolations:
591+
592+
**`format_interpolation()`**: This function handles the formatting of
593+
interpolated values according to their format specifiers and conversions. It's
594+
used internally by the `html()` function but can also be used independently:
595+
596+
```python
597+
from string.templatelib import Interpolation
598+
from tdom.utils import format_interpolation, convert
599+
600+
# Test convert function
601+
assert convert("hello", "s") == "hello"
602+
assert convert("hello", "r") == "'hello'"
603+
assert convert(42, None) == 42
604+
605+
# format_interpolation is used internally for custom format specifiers
606+
# The html() function uses this to implement :safe and :unsafe specifiers
607+
```
608+
609+
**`convert()`**: Applies conversion specifiers (`!a`, `!r`, `!s`) to values
610+
before formatting, following the same semantics as f-strings.
611+
612+
These utilities follow the patterns established by PEP 750 for t-string
613+
processing, allowing you to build custom template processors if needed.
456614

457-
## Supporters
615+
## Contributing
458616

459-
TODO: add supporters
617+
Contributions are welcome! Please feel free to submit issues or pull requests on
618+
[GitHub](https://github.com/t-strings/tdom).

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ dev = [
5050
]
5151

5252
[tool.pytest.ini_options]
53-
testpaths = ["tdom", "docs"]
53+
testpaths = ["tdom", "docs", "README.md"]
5454
python_files = ["*_test.py"]
5555
addopts = "-p no:doctest"
5656

tdom/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
from markupsafe import Markup, escape
22

3+
from .classnames import classnames
34
from .nodes import Comment, DocumentType, Element, Fragment, Node, Text
45
from .processor import html
56

67
# We consider `Markup` and `escape` to be part of this module's public API
78

89
__all__ = [
10+
"classnames",
911
"Comment",
1012
"DocumentType",
1113
"Element",

0 commit comments

Comments
 (0)