html-tstring 0.1.1__tar.gz → 0.1.2__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.
- {html_tstring-0.1.1 → html_tstring-0.1.2}/.gitignore +3 -0
- {html_tstring-0.1.1 → html_tstring-0.1.2}/PKG-INFO +7 -6
- {html_tstring-0.1.1 → html_tstring-0.1.2}/README.md +6 -5
- {html_tstring-0.1.1 → html_tstring-0.1.2}/html_tstring/parser.py +1 -0
- {html_tstring-0.1.1 → html_tstring-0.1.2}/html_tstring/processor.py +31 -17
- {html_tstring-0.1.1 → html_tstring-0.1.2}/pyproject.toml +2 -1
- {html_tstring-0.1.1 → html_tstring-0.1.2}/.python-version +0 -0
- {html_tstring-0.1.1 → html_tstring-0.1.2}/LICENSE +0 -0
- {html_tstring-0.1.1 → html_tstring-0.1.2}/html_tstring/__init__.py +0 -0
- {html_tstring-0.1.1 → html_tstring-0.1.2}/html_tstring/classnames.py +0 -0
- {html_tstring-0.1.1 → html_tstring-0.1.2}/html_tstring/nodes.py +0 -0
- {html_tstring-0.1.1 → html_tstring-0.1.2}/html_tstring/utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: html-tstring
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.2
|
|
4
4
|
Summary: A 🤘 rockin' t-string HTML templating system for Python 3.14.
|
|
5
5
|
Project-URL: Homepage, https://github.com/t-strings/html-tstring
|
|
6
6
|
Project-URL: Changelog, https://github.com/t-strings/html-tstring/releases
|
|
@@ -110,7 +110,6 @@ Boolean attributes are supported too. Just use a boolean value in the attribute
|
|
|
110
110
|
|
|
111
111
|
```python
|
|
112
112
|
form_button = html(t"<button disabled={True} hidden={False}>Submit</button>")
|
|
113
|
-
print(form_button)
|
|
114
113
|
# <button disabled>Submit</button>
|
|
115
114
|
```
|
|
116
115
|
|
|
@@ -310,13 +309,15 @@ Because attributes are passed as keyword arguments, you can explicitly provide t
|
|
|
310
309
|
```python
|
|
311
310
|
from typing import Any
|
|
312
311
|
|
|
313
|
-
def Link(*, href: str, text: str, **
|
|
314
|
-
return t'<a href="{href}" {
|
|
312
|
+
def Link(*, href: str, text: str, data_value: int, **attrs: Any) -> Template:
|
|
313
|
+
return t'<a href="{href}" {attrs}>{text}: {data_value}</a>'
|
|
315
314
|
|
|
316
|
-
result = html(t'<{Link} href="https://example.com" text="Example" target="_blank" />')
|
|
317
|
-
# <a href="https://example.com" target="_blank">Example</a>
|
|
315
|
+
result = html(t'<{Link} href="https://example.com" text="Example" data-value={42} target="_blank" />')
|
|
316
|
+
# <a href="https://example.com" target="_blank">Example: 42</a>
|
|
318
317
|
```
|
|
319
318
|
|
|
319
|
+
Note that attributes with hyphens (like `data-value`) are converted to underscores (`data_value`) in the function signature.
|
|
320
|
+
|
|
320
321
|
In addition to returning a `Template` directly, component functions may also return any `Node` type found in [`html_tstring.nodes`](https://github.com/t-strings/html-tstring/blob/main/html_tstring/nodes.py). This allows you to build more complex components that manipulate the HTML structure programmatically.
|
|
321
322
|
|
|
322
323
|
#### SVG Support
|
|
@@ -85,7 +85,6 @@ Boolean attributes are supported too. Just use a boolean value in the attribute
|
|
|
85
85
|
|
|
86
86
|
```python
|
|
87
87
|
form_button = html(t"<button disabled={True} hidden={False}>Submit</button>")
|
|
88
|
-
print(form_button)
|
|
89
88
|
# <button disabled>Submit</button>
|
|
90
89
|
```
|
|
91
90
|
|
|
@@ -285,13 +284,15 @@ Because attributes are passed as keyword arguments, you can explicitly provide t
|
|
|
285
284
|
```python
|
|
286
285
|
from typing import Any
|
|
287
286
|
|
|
288
|
-
def Link(*, href: str, text: str, **
|
|
289
|
-
return t'<a href="{href}" {
|
|
287
|
+
def Link(*, href: str, text: str, data_value: int, **attrs: Any) -> Template:
|
|
288
|
+
return t'<a href="{href}" {attrs}>{text}: {data_value}</a>'
|
|
290
289
|
|
|
291
|
-
result = html(t'<{Link} href="https://example.com" text="Example" target="_blank" />')
|
|
292
|
-
# <a href="https://example.com" target="_blank">Example</a>
|
|
290
|
+
result = html(t'<{Link} href="https://example.com" text="Example" data-value={42} target="_blank" />')
|
|
291
|
+
# <a href="https://example.com" target="_blank">Example: 42</a>
|
|
293
292
|
```
|
|
294
293
|
|
|
294
|
+
Note that attributes with hyphens (like `data-value`) are converted to underscores (`data_value`) in the function signature.
|
|
295
|
+
|
|
295
296
|
In addition to returning a `Template` directly, component functions may also return any `Node` type found in [`html_tstring.nodes`](https://github.com/t-strings/html-tstring/blob/main/html_tstring/nodes.py). This allows you to build more complex components that manipulate the HTML structure programmatically.
|
|
296
297
|
|
|
297
298
|
#### SVG Support
|
|
@@ -95,6 +95,7 @@ class NodeParser(HTMLParser):
|
|
|
95
95
|
|
|
96
96
|
def get_node(self) -> Node:
|
|
97
97
|
"""Get the Node tree parsed from the input HTML."""
|
|
98
|
+
# CONSIDER: Should we invert things and offer streaming parsing?
|
|
98
99
|
assert not self.stack, "Did you forget to call close()?"
|
|
99
100
|
if len(self.root.children) > 1:
|
|
100
101
|
# The parse structure results in multiple root elements, so we
|
|
@@ -83,18 +83,16 @@ def _instrument(
|
|
|
83
83
|
yield s
|
|
84
84
|
# There are always count-1 placeholders between count strings.
|
|
85
85
|
if i < count - 1:
|
|
86
|
+
placeholder = _placeholder(i)
|
|
87
|
+
|
|
86
88
|
# Special case for component callables: if the interpolation
|
|
87
89
|
# is a callable, we need to make sure that any matching closing
|
|
88
90
|
# tag uses the same placeholder.
|
|
89
91
|
callable_id = callable_ids[i]
|
|
90
|
-
if callable_id
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
callable_placeholders[callable_id] = _placeholder(i)
|
|
95
|
-
yield callable_placeholders[callable_id]
|
|
96
|
-
else:
|
|
97
|
-
yield _placeholder(i)
|
|
92
|
+
if callable_id:
|
|
93
|
+
placeholder = callable_placeholders.setdefault(callable_id, placeholder)
|
|
94
|
+
|
|
95
|
+
yield placeholder
|
|
98
96
|
|
|
99
97
|
|
|
100
98
|
@lru_cache()
|
|
@@ -187,7 +185,9 @@ def _substitute_style_attr(value: object) -> t.Iterable[tuple[str, str | None]]:
|
|
|
187
185
|
yield ("style", str(value))
|
|
188
186
|
|
|
189
187
|
|
|
190
|
-
def _substitute_spread_attrs(
|
|
188
|
+
def _substitute_spread_attrs(
|
|
189
|
+
value: object,
|
|
190
|
+
) -> t.Iterable[tuple[str, object | None]]:
|
|
191
191
|
"""
|
|
192
192
|
Substitute a spread attribute based on the interpolated value.
|
|
193
193
|
|
|
@@ -214,7 +214,7 @@ CUSTOM_ATTR_HANDLERS = {
|
|
|
214
214
|
def _substitute_attr(
|
|
215
215
|
key: str,
|
|
216
216
|
value: object,
|
|
217
|
-
) -> t.Iterable[tuple[str,
|
|
217
|
+
) -> t.Iterable[tuple[str, object | None]]:
|
|
218
218
|
"""
|
|
219
219
|
Substitute a single attribute based on its key and the interpolated value.
|
|
220
220
|
|
|
@@ -230,21 +230,19 @@ def _substitute_attr(
|
|
|
230
230
|
|
|
231
231
|
# General handling for all other attributes:
|
|
232
232
|
match value:
|
|
233
|
-
case str():
|
|
234
|
-
yield (key, value)
|
|
235
233
|
case True:
|
|
236
234
|
yield (key, None)
|
|
237
235
|
case False | None:
|
|
238
236
|
pass
|
|
239
237
|
case _:
|
|
240
|
-
yield (key,
|
|
238
|
+
yield (key, value)
|
|
241
239
|
|
|
242
240
|
|
|
243
241
|
def _substitute_attrs(
|
|
244
242
|
attrs: dict[str, str | None], interpolations: tuple[Interpolation, ...]
|
|
245
|
-
) -> dict[str,
|
|
243
|
+
) -> dict[str, object | None]:
|
|
246
244
|
"""Substitute placeholders in attributes based on the corresponding interpolations."""
|
|
247
|
-
new_attrs: dict[str,
|
|
245
|
+
new_attrs: dict[str, object | None] = {}
|
|
248
246
|
for key, value in attrs.items():
|
|
249
247
|
if value and value.startswith(_PLACEHOLDER_PREFIX):
|
|
250
248
|
index = _placholder_index(value)
|
|
@@ -299,14 +297,20 @@ def _node_from_value(value: object) -> Node:
|
|
|
299
297
|
children = [_node_from_value(v) for v in value]
|
|
300
298
|
return Fragment(children=children)
|
|
301
299
|
case HasHTMLDunder():
|
|
300
|
+
# CONSIDER: could we return a lazy Text?
|
|
302
301
|
return Text(Markup(value.__html__()))
|
|
303
302
|
case _:
|
|
303
|
+
# CONSIDER: could we return a lazy Text?
|
|
304
304
|
return Text(str(value))
|
|
305
305
|
|
|
306
306
|
|
|
307
|
+
type ComponentReturn = Node | Template | str | HasHTMLDunder
|
|
308
|
+
type ComponentCallable = t.Callable[..., ComponentReturn | t.Iterable[ComponentReturn]]
|
|
309
|
+
|
|
310
|
+
|
|
307
311
|
def _invoke_component(
|
|
308
312
|
tag: str,
|
|
309
|
-
new_attrs: dict[str,
|
|
313
|
+
new_attrs: dict[str, object | None],
|
|
310
314
|
new_children: list[Node],
|
|
311
315
|
interpolations: tuple[Interpolation, ...],
|
|
312
316
|
) -> Node:
|
|
@@ -314,12 +318,16 @@ def _invoke_component(
|
|
|
314
318
|
index = _placholder_index(tag)
|
|
315
319
|
interpolation = interpolations[index]
|
|
316
320
|
value = format_interpolation(interpolation)
|
|
321
|
+
# TODO: consider use of signature() or other approaches to validation.
|
|
317
322
|
if not callable(value):
|
|
318
323
|
raise TypeError(
|
|
319
324
|
f"Expected a callable for component invocation, got {type(value).__name__}"
|
|
320
325
|
)
|
|
326
|
+
# Replace attr names hyphens with underscores for Python kwargs
|
|
327
|
+
kwargs = {k.replace("-", "_"): v for k, v in new_attrs.items()}
|
|
328
|
+
|
|
321
329
|
# Call the component and return the resulting node
|
|
322
|
-
result = value(*new_children, **
|
|
330
|
+
result = value(*new_children, **kwargs)
|
|
323
331
|
match result:
|
|
324
332
|
case Node():
|
|
325
333
|
return result
|
|
@@ -336,6 +344,11 @@ def _invoke_component(
|
|
|
336
344
|
)
|
|
337
345
|
|
|
338
346
|
|
|
347
|
+
def _stringify_attrs(attrs: dict[str, object | None]) -> dict[str, str | None]:
|
|
348
|
+
"""Convert all attribute values to strings, preserving None values."""
|
|
349
|
+
return {k: str(v) if v is not None else None for k, v in attrs.items()}
|
|
350
|
+
|
|
351
|
+
|
|
339
352
|
def _substitute_node(p_node: Node, interpolations: tuple[Interpolation, ...]) -> Node:
|
|
340
353
|
"""Substitute placeholders in a node based on the corresponding interpolations."""
|
|
341
354
|
match p_node:
|
|
@@ -350,6 +363,7 @@ def _substitute_node(p_node: Node, interpolations: tuple[Interpolation, ...]) ->
|
|
|
350
363
|
if tag.startswith(_PLACEHOLDER_PREFIX):
|
|
351
364
|
return _invoke_component(tag, new_attrs, new_children, interpolations)
|
|
352
365
|
else:
|
|
366
|
+
new_attrs = _stringify_attrs(new_attrs)
|
|
353
367
|
return Element(tag=tag, attrs=new_attrs, children=new_children)
|
|
354
368
|
case Fragment(children=children):
|
|
355
369
|
new_children = _substitute_and_flatten_children(children, interpolations)
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "html-tstring"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.2"
|
|
8
8
|
description = "A 🤘 rockin' t-string HTML templating system for Python 3.14."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.14"
|
|
@@ -34,6 +34,7 @@ CI = "https://github.com/t-strings/html-tstring/actions"
|
|
|
34
34
|
dev = [
|
|
35
35
|
"pyright>=1.1.404",
|
|
36
36
|
"pytest>=8.4.1",
|
|
37
|
+
"pytest-cov>=6.3.0",
|
|
37
38
|
"pytest-watcher>=0.4.3",
|
|
38
39
|
"ruff>=0.12.11",
|
|
39
40
|
]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|