html-tstring 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,10 @@
1
+ # Python-generated files
2
+ __pycache__/
3
+ *.py[oc]
4
+ build/
5
+ dist/
6
+ wheels/
7
+ *.egg-info
8
+
9
+ # Virtual environments
10
+ .venv
@@ -0,0 +1 @@
1
+ 3.14
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Dave Peck <davepeck@davepeck.org>
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,316 @@
1
+ Metadata-Version: 2.4
2
+ Name: html-tstring
3
+ Version: 0.1.0
4
+ Summary: A 🤘 rockin' t-string HTML templating system for Python 3.14.
5
+ Project-URL: Homepage, https://github.com/t-strings/html-tstring
6
+ Project-URL: Changelog, https://github.com/t-strings/html-tstring/releases
7
+ Project-URL: Issues, https://github.com/t-strings/html-tstring/issues
8
+ Project-URL: CI, https://github.com/t-strings/html-tstring/actions
9
+ Author-email: Dave Peck <davepeck@davepeck.org>
10
+ License: MIT
11
+ License-File: LICENSE
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Programming Language :: Python :: 3 :: Only
14
+ Classifier: Programming Language :: Python :: 3.14
15
+ Requires-Python: >=3.14
16
+ Requires-Dist: markupsafe>=3.0.2
17
+ Description-Content-Type: text/markdown
18
+
19
+ # html-tstring
20
+
21
+ A 🤘 rockin' t-string HTML templating system for Python 3.14.
22
+
23
+ [![PyPI](https://img.shields.io/pypi/v/html-tstring.svg)](https://pypi.org/project/html-tstring/)
24
+ [![Tests](https://github.com/t-strings/html-tstring/actions/workflows/ci.yml/badge.svg)](https://github.com/t-strings/tdom/actions/workflows/pytest.yml)
25
+ [![Changelog](https://img.shields.io/github/v/release/t-strings/html-tstring?include_prereleases&label=changelog)](https://github.com/t-strings/html-tstring/releases)
26
+ [![License](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/t-strings/html-tstring/blob/main/LICENSE)
27
+
28
+ ## Installation
29
+
30
+ Just run:
31
+
32
+ ```bash
33
+ pip install html-tstring
34
+ ```
35
+
36
+ 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:
37
+
38
+ ```bash
39
+ uv run --with html-tstring --python 3.14 python
40
+ ```
41
+
42
+ ## Usage
43
+
44
+ `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.
45
+
46
+ 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.
47
+
48
+ 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.
49
+
50
+ ### Getting Started
51
+
52
+ Import the `html` function and start creating templates:
53
+
54
+ ```python
55
+ from html_tstring import html
56
+ greeting = html(t"<h1>Hello, World!</h1>")
57
+ print(type(greeting)) # <class 'html_tstring.nodes.Element'>
58
+ print(greeting) # <h1>Hello, World!</h1>
59
+ ```
60
+
61
+ ### Variable Interpolation
62
+
63
+ Just like f-strings, you can interpolate (substitute) variables directly into your templates:
64
+
65
+ ```python
66
+ name = "Alice"
67
+ age = 30
68
+ user_info = html(t"<p>Hello, {name}! You are {age} years old.</p>")
69
+ print(user_info) # <p>Hello, Alice! You are 30 years old.</p>
70
+ ```
71
+
72
+ The `html()` function ensures that interpolated values are automatically escaped to prevent XSS attacks:
73
+
74
+ ```python
75
+ user_name = "<script>alert('owned')</script>"
76
+ safe_output = html(t"<p>Hello, {user_name}!</p>")
77
+ print(safe_output) # <p>Hello, &lt;script&gt;alert('owned')&lt;/script&gt;!</p>
78
+ ```
79
+
80
+ ### Attribute Substitution
81
+
82
+ The `html()` function provides a number of convenient ways to define HTML attributes.
83
+
84
+ #### Direct Attribute Values
85
+
86
+ You can place values directly in attribute positions:
87
+
88
+ ```python
89
+ url = "https://example.com"
90
+ link = html(t'<a href="{url}">Visit our site</a>')
91
+ # <a href="https://example.com">Visit our site</a>
92
+ ```
93
+
94
+ You don't _have_ to wrap your attribute values in quotes:
95
+
96
+ ```python
97
+ element_id = "my-button"
98
+ button = html(t"<button id={element_id}>Click me</button>")
99
+ # <button id="my-button">Click me</button>
100
+ ```
101
+
102
+ Boolean attributes are supported too. Just use a boolean value in the attribute position:
103
+
104
+ ```python
105
+ form_button = html(t"<button disabled={True} hidden={False}>Submit</button>")
106
+ print(form_button)
107
+ # <button disabled>Submit</button>
108
+ ```
109
+
110
+ #### The `class` Attribute
111
+
112
+ 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:
113
+
114
+ ```python
115
+ classes = ["btn", "btn-primary", "active"]
116
+ button = html(t'<button class="{classes}">Click me</button>')
117
+ # <button class="btn btn-primary active">Click me</button>
118
+ ```
119
+
120
+ For flexibility, you can also provide a list of strings, dictionaries, or a mix of both:
121
+
122
+ ```python
123
+ classes = ["btn", "btn-primary", {"active": True}, None, False and "disabled"]
124
+ button = html(t'<button class="{classes}">Click me</button>')
125
+ # <button class="btn btn-primary active">Click me</button>
126
+ ```
127
+
128
+ See the [`classnames()`](./html_tstring/classnames_test.py) helper function for more information on how class names are combined.
129
+
130
+ #### The `style` Attribute
131
+
132
+ In addition to strings, you can also provide a dictionary of CSS properties and values for the `style` attribute:
133
+
134
+ ```python
135
+ # Style attributes from dictionaries
136
+ styles = {"color": "red", "font-weight": "bold", "margin": "10px"}
137
+ styled = html(t"<p style={styles}>Important text</p>")
138
+ # <p style="color: red; font-weight: bold; margin: 10px">Important text</p>
139
+ ```
140
+
141
+ #### The `data` and `aria` Attributes
142
+
143
+ The `data` and `aria` attributes also have special handling to convert dictionary keys to the appropriate attribute names:
144
+
145
+ ```python
146
+ data_attrs = {"user-id": 123, "role": "admin"}
147
+ aria_attrs = {"label": "Close dialog", "hidden": True}
148
+ element = html(t"<div data={data_attrs} aria={aria_attrs}>Content</div>")
149
+ # <div data-user-id="123" data-role="admin" aria-label="Close dialog"
150
+ # aria-hidden="true">Content</div>
151
+ ```
152
+
153
+ 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/).
154
+
155
+ #### Attribute Spreading
156
+
157
+ It's possible to specify multiple attributes at once by using a dictionary and spreading it into an element using curly braces:
158
+
159
+ ```python
160
+ attrs = {"href": "https://example.com", "target": "_blank"}
161
+ link = html(t"<a {attrs}>External link</a>")
162
+ # <a href="https://example.com" target="_blank">External link</a>
163
+ ```
164
+
165
+ You can also combine spreading with individual attributes:
166
+
167
+ ```python
168
+ base_attrs = {"id": "my-link"}
169
+ target = "_blank"
170
+ link = html(t'<a {base_attrs} target="{target}">Link</a>')
171
+ # <a id="my-link" target="_blank">Link</a>
172
+ ```
173
+
174
+ Special attributes likes `class` behave as expected when combined with spreading:
175
+
176
+ ```python
177
+ classes = ["btn", {"active": True}]
178
+ attrs = {"class": classes, "id": "act_now", "data": {"wow": "such-attr"}}
179
+ button = html(t'<button {attrs}>Click me</button>')
180
+ # <button class="btn active" id="act_now" data-wow="such-attr">Click me</button>
181
+ ```
182
+
183
+ ### Conditional Rendering
184
+
185
+ You can use Python's conditional expressions for dynamic content:
186
+
187
+ ```python
188
+ is_logged_in = True
189
+ user_content = t"<span>Welcome back!</span>"
190
+ guest_content = t"<a href='/login'>Please log in</a>"
191
+ header = html(t"<div>{user_content if is_logged_in else guest_content}</div>")
192
+ # <div><span>Welcome back!</span></div>
193
+ ```
194
+
195
+ Short-circuit evaluation is also supported for conditionally including elements:
196
+
197
+ ```python
198
+ show_warning = False
199
+ warning = t'<div class="alert">Warning message</div>'
200
+ page = html(t"<main>{show_warning and warning}</main>")
201
+ # <main></main>
202
+ ```
203
+
204
+ ### Lists and Iteration
205
+
206
+ Generate repeated elements using list comprehensions:
207
+
208
+ ```python
209
+ fruits = ["Apple", "Banana", "Cherry"]
210
+ fruit_list = html(t"<ul>{[t'<li>{fruit}</li>' for fruit in fruits]}</ul>")
211
+ # <ul><li>Apple</li><li>Banana</li><li>Cherry</li></ul>
212
+ ```
213
+
214
+ ### Raw HTML Injection
215
+
216
+ 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.
217
+
218
+ 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.
219
+
220
+ The `Markup` class from MarkupSafe is available for use:
221
+
222
+ ```python
223
+ from html_tstring import html, Markup
224
+
225
+ trusted_html = Markup("<strong>This is safe HTML</strong>")
226
+ content = html(t"<div>{trusted_html}</div>")
227
+ # <div><strong>This is safe HTML</strong></div>
228
+ ```
229
+
230
+ As a convenience, `html-tstring` also supports a `:safe` format specifier that marks a string as safe HTML:
231
+
232
+ ```python
233
+ trusted_html = "<em>Emphasized text</em>"
234
+ page = html(t"<p>Here is some {trusted_html:safe} content.</p>")
235
+ # <p>Here is some <em>Emphasized text</em> content.</p>
236
+ ```
237
+
238
+ 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:
239
+
240
+ ```python
241
+ class SafeWidget:
242
+ def __html__(self):
243
+ return "<button>Custom Widget</button>"
244
+
245
+ page = html(t"<div>My widget: {SafeWidget()}</div>")
246
+ # <div>My widget: <button>Custom Widget</button></div>
247
+ ```
248
+
249
+ TODO: support explicitly marking content as `unsafe` with a format specifier, too.
250
+
251
+ ### Template Composition
252
+
253
+ You can easily combine multiple templates and create reusable components.
254
+
255
+ Template nesting is straightforward:
256
+
257
+ ```python
258
+ content = t"<h1>My Site</h1>"
259
+ page = html(t"<div>{content}</div>")
260
+ # <div><h1>My Site</h1></div>
261
+ ```
262
+
263
+ 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:
264
+
265
+ ```python
266
+ content = html(t"<h1>My Site</h1>")
267
+ page = html(t"<div>{content}</div>")
268
+ # <div><h1>My Site</h1></div>
269
+ ```
270
+
271
+ The result is the same either way.
272
+
273
+ ### Advanced Features
274
+
275
+ #### Component Functions
276
+
277
+ You can create reusable component functions that generate templates with dynamic content and attributes. Use these like custom HTML elements in your templates.
278
+
279
+ The basic form of all component functions is:
280
+
281
+ ```python
282
+ from typing import Any
283
+
284
+ def MyComponent(*children: Node, **attrs: Any) -> Template:
285
+ # Build your template using the provided props
286
+ return t"<div {attrs}>{children}</div>"
287
+ ```
288
+
289
+ To _invoke_ your component within an HTML template, use the special `<{ComponentName} ... />` syntax:
290
+
291
+ ```python
292
+ result = html(t"<{MyComponent} id='comp1'>Hello, Component!</{MyComponent}>")
293
+ # <div id="comp1">Hello, Component!</div>
294
+ ```
295
+
296
+ Because attributes are passed as keyword arguments, you can explicitly provide type hints for better editor support:
297
+
298
+ ```python
299
+ from typing import Any
300
+
301
+ def Link(*, href: str, text: str, **props: Any) -> Template:
302
+ return t'<a href="{href}" {props}>{text}</a>'
303
+
304
+ result = html(t'<{Link} href="https://example.com" text="Example" target="_blank" />')
305
+ # <a href="https://example.com" target="_blank">Example</a>
306
+ ```
307
+
308
+ In addition to returning a `Template` directly, component functions may also return any `Node` type found in [`html_tstring.nodes`](./html_tstring/nodes.py). This allows you to build more complex components that manipulate the HTML structure programmatically.
309
+
310
+ #### Context
311
+
312
+ TODO: implement context feature
313
+
314
+ #### Working with `Node` Objects
315
+
316
+ TODO: say more about working with them directly
@@ -0,0 +1,298 @@
1
+ # html-tstring
2
+
3
+ A 🤘 rockin' t-string HTML templating system for Python 3.14.
4
+
5
+ [![PyPI](https://img.shields.io/pypi/v/html-tstring.svg)](https://pypi.org/project/html-tstring/)
6
+ [![Tests](https://github.com/t-strings/html-tstring/actions/workflows/ci.yml/badge.svg)](https://github.com/t-strings/tdom/actions/workflows/pytest.yml)
7
+ [![Changelog](https://img.shields.io/github/v/release/t-strings/html-tstring?include_prereleases&label=changelog)](https://github.com/t-strings/html-tstring/releases)
8
+ [![License](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/t-strings/html-tstring/blob/main/LICENSE)
9
+
10
+ ## Installation
11
+
12
+ Just run:
13
+
14
+ ```bash
15
+ pip install html-tstring
16
+ ```
17
+
18
+ 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:
19
+
20
+ ```bash
21
+ uv run --with html-tstring --python 3.14 python
22
+ ```
23
+
24
+ ## Usage
25
+
26
+ `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.
27
+
28
+ 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.
29
+
30
+ 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.
31
+
32
+ ### Getting Started
33
+
34
+ Import the `html` function and start creating templates:
35
+
36
+ ```python
37
+ from html_tstring import html
38
+ greeting = html(t"<h1>Hello, World!</h1>")
39
+ print(type(greeting)) # <class 'html_tstring.nodes.Element'>
40
+ print(greeting) # <h1>Hello, World!</h1>
41
+ ```
42
+
43
+ ### Variable Interpolation
44
+
45
+ Just like f-strings, you can interpolate (substitute) variables directly into your templates:
46
+
47
+ ```python
48
+ name = "Alice"
49
+ age = 30
50
+ user_info = html(t"<p>Hello, {name}! You are {age} years old.</p>")
51
+ print(user_info) # <p>Hello, Alice! You are 30 years old.</p>
52
+ ```
53
+
54
+ The `html()` function ensures that interpolated values are automatically escaped to prevent XSS attacks:
55
+
56
+ ```python
57
+ user_name = "<script>alert('owned')</script>"
58
+ safe_output = html(t"<p>Hello, {user_name}!</p>")
59
+ print(safe_output) # <p>Hello, &lt;script&gt;alert('owned')&lt;/script&gt;!</p>
60
+ ```
61
+
62
+ ### Attribute Substitution
63
+
64
+ The `html()` function provides a number of convenient ways to define HTML attributes.
65
+
66
+ #### Direct Attribute Values
67
+
68
+ You can place values directly in attribute positions:
69
+
70
+ ```python
71
+ url = "https://example.com"
72
+ link = html(t'<a href="{url}">Visit our site</a>')
73
+ # <a href="https://example.com">Visit our site</a>
74
+ ```
75
+
76
+ You don't _have_ to wrap your attribute values in quotes:
77
+
78
+ ```python
79
+ element_id = "my-button"
80
+ button = html(t"<button id={element_id}>Click me</button>")
81
+ # <button id="my-button">Click me</button>
82
+ ```
83
+
84
+ Boolean attributes are supported too. Just use a boolean value in the attribute position:
85
+
86
+ ```python
87
+ form_button = html(t"<button disabled={True} hidden={False}>Submit</button>")
88
+ print(form_button)
89
+ # <button disabled>Submit</button>
90
+ ```
91
+
92
+ #### The `class` Attribute
93
+
94
+ 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:
95
+
96
+ ```python
97
+ classes = ["btn", "btn-primary", "active"]
98
+ button = html(t'<button class="{classes}">Click me</button>')
99
+ # <button class="btn btn-primary active">Click me</button>
100
+ ```
101
+
102
+ For flexibility, you can also provide a list of strings, dictionaries, or a mix of both:
103
+
104
+ ```python
105
+ classes = ["btn", "btn-primary", {"active": True}, None, False and "disabled"]
106
+ button = html(t'<button class="{classes}">Click me</button>')
107
+ # <button class="btn btn-primary active">Click me</button>
108
+ ```
109
+
110
+ See the [`classnames()`](./html_tstring/classnames_test.py) helper function for more information on how class names are combined.
111
+
112
+ #### The `style` Attribute
113
+
114
+ In addition to strings, you can also provide a dictionary of CSS properties and values for the `style` attribute:
115
+
116
+ ```python
117
+ # Style attributes from dictionaries
118
+ styles = {"color": "red", "font-weight": "bold", "margin": "10px"}
119
+ styled = html(t"<p style={styles}>Important text</p>")
120
+ # <p style="color: red; font-weight: bold; margin: 10px">Important text</p>
121
+ ```
122
+
123
+ #### The `data` and `aria` Attributes
124
+
125
+ The `data` and `aria` attributes also have special handling to convert dictionary keys to the appropriate attribute names:
126
+
127
+ ```python
128
+ data_attrs = {"user-id": 123, "role": "admin"}
129
+ aria_attrs = {"label": "Close dialog", "hidden": True}
130
+ element = html(t"<div data={data_attrs} aria={aria_attrs}>Content</div>")
131
+ # <div data-user-id="123" data-role="admin" aria-label="Close dialog"
132
+ # aria-hidden="true">Content</div>
133
+ ```
134
+
135
+ 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/).
136
+
137
+ #### Attribute Spreading
138
+
139
+ It's possible to specify multiple attributes at once by using a dictionary and spreading it into an element using curly braces:
140
+
141
+ ```python
142
+ attrs = {"href": "https://example.com", "target": "_blank"}
143
+ link = html(t"<a {attrs}>External link</a>")
144
+ # <a href="https://example.com" target="_blank">External link</a>
145
+ ```
146
+
147
+ You can also combine spreading with individual attributes:
148
+
149
+ ```python
150
+ base_attrs = {"id": "my-link"}
151
+ target = "_blank"
152
+ link = html(t'<a {base_attrs} target="{target}">Link</a>')
153
+ # <a id="my-link" target="_blank">Link</a>
154
+ ```
155
+
156
+ Special attributes likes `class` behave as expected when combined with spreading:
157
+
158
+ ```python
159
+ classes = ["btn", {"active": True}]
160
+ attrs = {"class": classes, "id": "act_now", "data": {"wow": "such-attr"}}
161
+ button = html(t'<button {attrs}>Click me</button>')
162
+ # <button class="btn active" id="act_now" data-wow="such-attr">Click me</button>
163
+ ```
164
+
165
+ ### Conditional Rendering
166
+
167
+ You can use Python's conditional expressions for dynamic content:
168
+
169
+ ```python
170
+ is_logged_in = True
171
+ user_content = t"<span>Welcome back!</span>"
172
+ guest_content = t"<a href='/login'>Please log in</a>"
173
+ header = html(t"<div>{user_content if is_logged_in else guest_content}</div>")
174
+ # <div><span>Welcome back!</span></div>
175
+ ```
176
+
177
+ Short-circuit evaluation is also supported for conditionally including elements:
178
+
179
+ ```python
180
+ show_warning = False
181
+ warning = t'<div class="alert">Warning message</div>'
182
+ page = html(t"<main>{show_warning and warning}</main>")
183
+ # <main></main>
184
+ ```
185
+
186
+ ### Lists and Iteration
187
+
188
+ Generate repeated elements using list comprehensions:
189
+
190
+ ```python
191
+ fruits = ["Apple", "Banana", "Cherry"]
192
+ fruit_list = html(t"<ul>{[t'<li>{fruit}</li>' for fruit in fruits]}</ul>")
193
+ # <ul><li>Apple</li><li>Banana</li><li>Cherry</li></ul>
194
+ ```
195
+
196
+ ### Raw HTML Injection
197
+
198
+ 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.
199
+
200
+ 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.
201
+
202
+ The `Markup` class from MarkupSafe is available for use:
203
+
204
+ ```python
205
+ from html_tstring import html, Markup
206
+
207
+ trusted_html = Markup("<strong>This is safe HTML</strong>")
208
+ content = html(t"<div>{trusted_html}</div>")
209
+ # <div><strong>This is safe HTML</strong></div>
210
+ ```
211
+
212
+ As a convenience, `html-tstring` also supports a `:safe` format specifier that marks a string as safe HTML:
213
+
214
+ ```python
215
+ trusted_html = "<em>Emphasized text</em>"
216
+ page = html(t"<p>Here is some {trusted_html:safe} content.</p>")
217
+ # <p>Here is some <em>Emphasized text</em> content.</p>
218
+ ```
219
+
220
+ 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:
221
+
222
+ ```python
223
+ class SafeWidget:
224
+ def __html__(self):
225
+ return "<button>Custom Widget</button>"
226
+
227
+ page = html(t"<div>My widget: {SafeWidget()}</div>")
228
+ # <div>My widget: <button>Custom Widget</button></div>
229
+ ```
230
+
231
+ TODO: support explicitly marking content as `unsafe` with a format specifier, too.
232
+
233
+ ### Template Composition
234
+
235
+ You can easily combine multiple templates and create reusable components.
236
+
237
+ Template nesting is straightforward:
238
+
239
+ ```python
240
+ content = t"<h1>My Site</h1>"
241
+ page = html(t"<div>{content}</div>")
242
+ # <div><h1>My Site</h1></div>
243
+ ```
244
+
245
+ 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:
246
+
247
+ ```python
248
+ content = html(t"<h1>My Site</h1>")
249
+ page = html(t"<div>{content}</div>")
250
+ # <div><h1>My Site</h1></div>
251
+ ```
252
+
253
+ The result is the same either way.
254
+
255
+ ### Advanced Features
256
+
257
+ #### Component Functions
258
+
259
+ You can create reusable component functions that generate templates with dynamic content and attributes. Use these like custom HTML elements in your templates.
260
+
261
+ The basic form of all component functions is:
262
+
263
+ ```python
264
+ from typing import Any
265
+
266
+ def MyComponent(*children: Node, **attrs: Any) -> Template:
267
+ # Build your template using the provided props
268
+ return t"<div {attrs}>{children}</div>"
269
+ ```
270
+
271
+ To _invoke_ your component within an HTML template, use the special `<{ComponentName} ... />` syntax:
272
+
273
+ ```python
274
+ result = html(t"<{MyComponent} id='comp1'>Hello, Component!</{MyComponent}>")
275
+ # <div id="comp1">Hello, Component!</div>
276
+ ```
277
+
278
+ Because attributes are passed as keyword arguments, you can explicitly provide type hints for better editor support:
279
+
280
+ ```python
281
+ from typing import Any
282
+
283
+ def Link(*, href: str, text: str, **props: Any) -> Template:
284
+ return t'<a href="{href}" {props}>{text}</a>'
285
+
286
+ result = html(t'<{Link} href="https://example.com" text="Example" target="_blank" />')
287
+ # <a href="https://example.com" target="_blank">Example</a>
288
+ ```
289
+
290
+ In addition to returning a `Template` directly, component functions may also return any `Node` type found in [`html_tstring.nodes`](./html_tstring/nodes.py). This allows you to build more complex components that manipulate the HTML structure programmatically.
291
+
292
+ #### Context
293
+
294
+ TODO: implement context feature
295
+
296
+ #### Working with `Node` Objects
297
+
298
+ TODO: say more about working with them directly
@@ -0,0 +1,17 @@
1
+ from markupsafe import Markup, escape
2
+
3
+ from .nodes import Comment, DocumentType, Element, Fragment, Text
4
+ from .processor import html
5
+
6
+ # We consider `Markup` and `escape` to be part of this module's public API
7
+
8
+ __all__ = [
9
+ "Comment",
10
+ "DocumentType",
11
+ "Element",
12
+ "escape",
13
+ "Fragment",
14
+ "html",
15
+ "Markup",
16
+ "Text",
17
+ ]