Coverage for src/minihtml/_template.py: 100%
56 statements
« prev ^ index » next coverage.py v7.6.12, created at 2025-02-22 21:44 +0100
« prev ^ index » next coverage.py v7.6.12, created at 2025-02-22 21:44 +0100
1import io
2from collections.abc import Iterable
3from functools import wraps
4from typing import Callable, Concatenate, ParamSpec, TextIO, TypeAlias, overload
6from ._component import Component
7from ._core import HasNodes, Node, iter_nodes, register_with_context
8from ._template_context import get_template_context, template_context
10P = ParamSpec("P")
12TemplateImpl: TypeAlias = Callable[P, Node | HasNodes]
13TemplateImplLayout: TypeAlias = Callable[Concatenate[Component, P], None]
16def _render(result: Node | HasNodes, doctype: bool) -> str:
17 buf = io.StringIO()
18 if doctype:
19 buf.write("<!doctype html>\n")
20 Node.render_list(buf, iter_nodes([result]))
21 buf.write("\n")
22 return buf.getvalue()
25@overload
26def template(
27 *, doctype: bool = ...
28) -> Callable[[TemplateImpl[P]], Callable[P, str]]: ...
31@overload
32def template(
33 layout: Component, *, doctype: bool = ...
34) -> Callable[[TemplateImplLayout[P]], Callable[P, str]]: ...
37def template(
38 layout: Component | None = None,
39 *,
40 doctype: bool = True,
41) -> (
42 Callable[[TemplateImpl[P]], Callable[P, str]]
43 | Callable[[TemplateImplLayout[P]], Callable[P, str]]
44):
45 if layout is None:
47 def plain_decorator(fn: TemplateImpl[P]) -> Callable[P, str]:
48 @wraps(fn)
49 def wrapper(*args: P.args, **kwargs: P.kwargs) -> str:
50 with template_context():
51 result = fn(*args, **kwargs)
52 return _render(result, doctype=doctype)
54 return wrapper
56 return plain_decorator
58 else:
60 def layout_decorator(fn: TemplateImplLayout[P]) -> Callable[P, str]:
61 @wraps(fn)
62 def wrapper(*args: P.args, **kwargs: P.kwargs) -> str:
63 with template_context():
64 with layout as result:
65 fn(layout, *args, **kwargs)
66 return _render(result, doctype=doctype)
68 return wrapper
70 return layout_decorator
73class ResourceWrapper(Node):
74 def __init__(self, callback: Callable[[], Iterable[Node]]):
75 self._callback = callback
76 self._inline = False
78 def write(self, f: TextIO, indent: int = 0) -> None:
79 nodes = list(self._callback())
80 n = len(nodes)
81 for i, node in enumerate(nodes):
82 node.write(f, indent)
83 if i < n - 1:
84 f.write("\n")
85 f.write(" " * indent)
88def component_styles() -> ResourceWrapper:
89 wrapper = ResourceWrapper(lambda: get_template_context().styles)
90 register_with_context(wrapper)
91 return wrapper
94def component_scripts() -> ResourceWrapper:
95 wrapper = ResourceWrapper(lambda: get_template_context().scripts)
96 register_with_context(wrapper)
97 return wrapper