Skip to content
This repository was archived by the owner on Sep 15, 2025. It is now read-only.

Commit 6d4e685

Browse files
committed
Update README with initial usage examples.
1 parent 88b1d8b commit 6d4e685

File tree

2 files changed

+257
-34
lines changed

2 files changed

+257
-34
lines changed

README.md

Lines changed: 255 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,281 @@
11
# html-tstring
22

3-
Tools to manipulate and render HTML using Python 3.14's t-strings.
3+
A 🤘 rockin' t-string HTML templating system for Python 3.14.
44

5-
Real documentation is forthcoming!
5+
## Installation
66

7-
What can you do today?
7+
Just run:
88

9-
1. Render HTML to an `Element` tree:
9+
```bash
10+
pip install html-tstring
11+
```
12+
13+
Python 3.14 isn't out yet, but you can use [Astral's `uv`](https://docs.astral.sh/uv/) to easily try `html-tstring` in a Python 3.14 environment:
14+
15+
```bash
16+
uv run --with html-tstring --python 3.14 python
17+
```
18+
19+
## Usage
20+
21+
`html-tstring` leverages Python 3.14's [new t-strings feature](https://t-strings.help/introduction.html) to provide a powerful HTML templating system that feels familiar if you've used JSX, Jinja2, or Django templates.
22+
23+
T-strings work just like f-strings but use a `t` prefix and [create `Template` objects](https://docs.python.org/3.14/library/string.templatelib.html#template-strings) instead of strings.
24+
25+
Once you have a `Template`, you can call this package's `html()` function to convert it into a tree of `Node` objects that represent your HTML structure. From there, you can render it to a string, manipulate it programmatically, or compose it with other templates for maximum flexibility.
26+
27+
### Getting Started
28+
29+
Import the `html` function and start creating templates:
1030

1131
```python
1232
from html_tstring import html
33+
greeting = html(t"<h1>Hello, World!</h1>")
34+
print(type(greeting)) # <class 'html_tstring.nodes.Element'>
35+
print(greeting) # <h1>Hello, World!</h1>
36+
```
37+
38+
### Variable Interpolation
1339

14-
template = t"<div><h1>Hello, world!</h1></div>"
15-
element = html(template)
16-
print(str(element))
17-
# <div><h1>Hello, world!</h1></div>
40+
Just like f-strings, you can interpolate (substitute) variables directly into your templates:
41+
42+
```python
43+
name = "Alice"
44+
age = 30
45+
user_info = html(t"<p>Hello, {name}! You are {age} years old.</p>")
46+
print(user_info) # <p>Hello, Alice! You are 30 years old.</p>
1847
```
1948

20-
2. Get automatic escaping:
49+
The `html()` function ensures that interpolated values are automatically escaped to prevent XSS attacks:
2150

2251
```python
23-
from html_tstring import html
52+
user_name = "<script>alert('owned')</script>"
53+
safe_output = html(t"<p>Hello, {user_name}!</p>")
54+
print(safe_output) # <p>Hello, &lt;script&gt;alert('owned')&lt;/script&gt;!</p>
55+
```
56+
57+
### Attribute Substitution
58+
59+
The `html()` function provides a number of convenient ways to define HTML attributes.
60+
61+
#### Direct Attribute Values
2462

25-
evil = "<script>alert('Hacked!');</script>"
26-
template = t"<div>{evil}</div>"
27-
element = html(template)
28-
print(str(element))
29-
# <div>&lt;script&gt;alert('Hacked!');&lt;/script&gt;</div>
63+
You can place values directly in attribute positions:
64+
65+
```python
66+
url = "https://example.com"
67+
link = html(t'<a href="{url}">Visit our site</a>')
68+
# <a href="https://example.com">Visit our site</a>
3069
```
3170

32-
3. Safely nest HTML elements:
71+
You don't _have_ to wrap your attribute values in quotes:
3372

3473
```python
35-
from html_tstring import html
74+
element_id = "my-button"
75+
button = html(t"<button id={element_id}>Click me</button>")
76+
# <button id="my-button">Click me</button>
77+
```
78+
79+
Boolean attributes are supported too. Just use a boolean value in the attribute position:
3680

37-
header = html(t"<header><h1>Welcome</h1></header>")
38-
template = t"<div>{header}<p>This is the main content.</p></div>"
39-
element = html(template)
40-
print(str(element))
41-
# <div><header><h1>Welcome</h1></header><p>This is the main content.</p></div>
81+
```python
82+
form_button = html(t"<button disabled={True} hidden={False}>Submit</button>")
83+
print(form_button)
84+
# <button disabled>Submit</button>
4285
```
4386

44-
4. Or, safely nest HTML templates:
87+
#### The `class` Attribute
88+
89+
The `class` attribute has special handling to make it easy to combine multiple classes from different sources. The simplest way is to provide a list of class names:
4590

4691
```python
47-
from html_tstring import html
92+
classes = ["btn", "btn-primary", "active"]
93+
button = html(t'<button class="{classes}">Click me</button>')
94+
# <button class="btn btn-primary active">Click me</button>
95+
```
96+
97+
For flexibility, you can also provide a list of strings, dictionaries, or a mix of both:
4898

49-
header_template = t"<header><h1>Welcome</h1></header>"
50-
template = t"<div>{header_template}<p>This is the main content.</p></div>"
51-
element = html(template)
52-
print(str(element))
53-
# <div><header><h1>Welcome</h1></header><p>This is the main content.</p></div>
99+
```python
100+
classes = ["btn", "btn-primary", {"active": True}, None, False and "disabled"]
101+
button = html(t'<button class="{classes}">Click me</button>')
102+
# <button class="btn btn-primary active">Click me</button>
103+
```
104+
105+
See the [`classnames()`](./html_tstring/classnames_test.py) helper function for more information on how class names are combined.
106+
107+
#### The `style` Attribute
108+
109+
In addition to strings, you can also provide a dictionary of CSS properties and values for the `style` attribute:
110+
111+
```python
112+
# Style attributes from dictionaries
113+
styles = {"color": "red", "font-weight": "bold", "margin": "10px"}
114+
styled = html(t"<p style={styles}>Important text</p>")
115+
# <p style="color: red; font-weight: bold; margin: 10px">Important text</p>
116+
```
117+
118+
#### The `data` and `aria` Attributes
119+
120+
The `data` and `aria` attributes also have special handling to convert dictionary keys to the appropriate attribute names:
121+
122+
```python
123+
data_attrs = {"user-id": 123, "role": "admin"}
124+
aria_attrs = {"label": "Close dialog", "hidden": True}
125+
element = html(t"<div data={data_attrs} aria={aria_attrs}>Content</div>")
126+
# <div data-user-id="123" data-role="admin" aria-label="Close dialog"
127+
# aria-hidden="true">Content</div>
128+
```
129+
130+
Note that boolean values in `aria` attributes are converted to `"true"` or `"false"` as per [the ARIA specification](https://www.w3.org/TR/wai-aria-1.2/).
131+
132+
#### Attribute Spreading
133+
134+
It's possible to specify multiple attributes at once by using a dictionary and spreading it into an element using curly braces:
135+
136+
```python
137+
attrs = {"href": "https://example.com", "target": "_blank"}
138+
link = html(t"<a {attrs}>External link</a>")
139+
# <a href="https://example.com" target="_blank">External link</a>
140+
```
141+
142+
You can also combine spreading with individual attributes:
143+
144+
```python
145+
base_attrs = {"id": "my-link"}
146+
target = "_blank"
147+
link = html(t'<a {base_attrs} target="{target}">Link</a>')
148+
# <a id="my-link" target="_blank">Link</a>
54149
```
55150

56-
TODO: write more examples, convert them into tests.
151+
Special attributes likes `class` behave as expected when combined with spreading:
152+
153+
```python
154+
classes = ["btn", {"active": True}]
155+
attrs = {"class": classes, "id": "act_now", "data": {"wow": "such-attr"}}
156+
button = html(t'<button {attrs}>Click me</button>')
157+
# <button class="btn active" id="act_now" data-wow="such-attr">Click me</button>
158+
```
159+
160+
### Conditional Rendering
161+
162+
You can use Python's conditional expressions for dynamic content:
163+
164+
```python
165+
is_logged_in = True
166+
user_content = t"<span>Welcome back!</span>"
167+
guest_content = t"<a href='/login'>Please log in</a>"
168+
header = html(t"<div>{user_content if is_logged_in else guest_content}</div>")
169+
# <div><span>Welcome back!</span></div>
170+
```
171+
172+
Short-circuit evaluation is also supported for conditionally including elements:
173+
174+
```python
175+
show_warning = False
176+
warning = t'<div class="alert">Warning message</div>'
177+
page = html(t"<main>{show_warning and warning}</main>")
178+
# <main></main>
179+
```
180+
181+
### Lists and Iteration
182+
183+
Generate repeated elements using list comprehensions:
184+
185+
```python
186+
fruits = ["Apple", "Banana", "Cherry"]
187+
fruit_list = html(t"<ul>{[t'<li>{fruit}</li>' for fruit in fruits]}</ul>")
188+
# <ul><li>Apple</li><li>Banana</li><li>Cherry</li></ul>
189+
```
190+
191+
### Raw HTML Injection
192+
193+
The `html-tstring` package provides several ways to include trusted raw HTML content in your templates. This is useful when you have HTML content that you _know_ is safe and do not wish to escape.
194+
195+
Under the hood, `html-tstring` builds on top of the familiar [MarkupSafe](https://pypi.org/project/MarkupSafe/) library to handle trusted HTML content. If you've used Flask, Jinja2, or similar libraries, this will feel very familiar.
196+
197+
The `Markup` class from MarkupSafe is available for use:
198+
199+
```python
200+
from html_tstring import html, Markup
201+
202+
trusted_html = Markup("<strong>This is safe HTML</strong>")
203+
content = html(t"<div>{trusted_html}</div>")
204+
# <div><strong>This is safe HTML</strong></div>
205+
```
206+
207+
As a convenience, `html-tstring` also supports a `:safe` format specifier that marks a string as safe HTML:
208+
209+
```python
210+
trusted_html = "<em>Emphasized text</em>"
211+
page = html(t"<p>Here is some {trusted_html:safe} content.</p>")
212+
# <p>Here is some <em>Emphasized text</em> content.</p>
213+
```
214+
215+
For interoperability with other templating libraries, any object that implements a `__html__` method will be treated as safe HTML. Many popular libraries (including MarkupSafe and Django) use this convention:
216+
217+
```python
218+
class SafeWidget:
219+
def __html__(self):
220+
return "<button>Custom Widget</button>"
221+
222+
page = html(t"<div>My widget: {SafeWidget()}</div>")
223+
# <div>My widget: <button>Custom Widget</button></div>
224+
```
225+
226+
TODO: support explicitly marking content as `unsafe` with a format specifier, too.
227+
228+
### Template Composition
229+
230+
You can easily combine multiple templates and create reusable components.
231+
232+
Template nesting is straightforward:
233+
234+
```python
235+
content = t"<h1>My Site</h1>"
236+
page = html(t"<div>{content}</div>")
237+
# <div><h1>My Site</h1></div>
238+
```
239+
240+
In the example above, `content` is a `Template` object that gets correctly parsed and embedded within the outer template. You can also explicitly call `html()` on nested templates if you prefer:
241+
242+
```python
243+
content = html(t"<h1>My Site</h1>")
244+
page = html(t"<div>{content}</div>")
245+
# <div><h1>My Site</h1></div>
246+
```
247+
248+
The result is the same either way.
249+
250+
### Advanced Features
251+
252+
#### Component Functions
253+
254+
Create reusable template functions that work like custom HTML elements:
255+
256+
```python
257+
def Alert(message: str, type: str = "info", **props) -> Template:
258+
alert_class = f"alert alert-{type}"
259+
attrs = {"class": alert_class, **props}
260+
return t'<div {attrs}>{message}</div>'
261+
262+
# Use your component
263+
warning = html(t'<{Alert} message="Be careful!" type="warning" id="main-alert" />')
264+
```
265+
266+
#### Fragment Rendering
267+
268+
Templates can return multiple root elements:
269+
270+
```python
271+
def TableColumns() -> Template:
272+
return t"<td>Column 1</td><td>Column 2</td>"
273+
274+
table = html(t"<table><tr><{TableColumns} /></tr></table>")
275+
```
276+
277+
The `html()` function returns a tree of nodes that can be converted to strings, manipulated programmatically, or composed with other templates for maximum flexibility.
278+
279+
#### Context
280+
281+
TODO: implement context feature

pyproject.toml

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,10 @@ build-backend = "hatchling.build"
55
[project]
66
name = "html-tstring"
77
version = "0.1.0"
8-
description = "Tools to manipulate and render HTML using Python 3.14's t-strings."
8+
description = "A 🤘 rockin' t-string HTML templating system for Python 3.14."
99
readme = "README.md"
1010
requires-python = ">=3.14"
11-
dependencies = [
12-
"markupsafe>=3.0.2",
13-
]
11+
dependencies = ["markupsafe>=3.0.2"]
1412
authors = [{ name = "Dave Peck", email = "davepeck@davepeck.org" }]
1513
license = { text = "MIT" }
1614
classifiers = [

0 commit comments

Comments
 (0)