Coverage for tdom / template_utils.py: 100%
53 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-05-03 21:23 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-05-03 21:23 +0000
1import typing as t
2from collections.abc import Sequence
3from dataclasses import dataclass
4from string.templatelib import Interpolation, Template
7def template_from_parts(
8 strings: Sequence[str], interpolations: Sequence[Interpolation]
9) -> Template:
10 """Construct a template string from the given strings and parts."""
11 assert len(strings) == len(interpolations) + 1, (
12 "TemplateRef must have one more string than interpolation references."
13 )
14 flat = [x for pair in zip(strings, interpolations) for x in pair] + [strings[-1]]
15 return Template(*flat)
18def combine_template_refs(*template_refs: TemplateRef) -> TemplateRef:
19 return TemplateRef.from_naive_template(
20 sum((tr.to_naive_template() for tr in template_refs), t"")
21 )
24@dataclass(slots=True, frozen=True)
25class TemplateRef:
26 """Reference to a template with indexes for its original interpolations."""
28 strings: tuple[str, ...]
29 """Static string parts of the original string.templatelib.Template"""
31 i_indexes: tuple[int, ...]
32 """Indexes of the interpolations in the original string.templatelib.Template"""
34 @property
35 def is_literal(self) -> bool:
36 """Return True if there are no interpolations."""
37 return not self.i_indexes
39 @property
40 def is_empty(self) -> bool:
41 """Return True if the template is empty."""
42 return self.is_literal and self.strings[0] == ""
44 @property
45 def is_singleton(self) -> bool:
46 """Return True if there is exactly one interpolation and no other content."""
47 return self.strings == ("", "")
49 def to_naive_template(self) -> Template:
50 return template_from_parts(
51 self.strings, [Interpolation(i, "", None, "") for i in self.i_indexes]
52 )
54 @classmethod
55 def literal(cls, s: str) -> t.Self:
56 return cls((s,), ())
58 @classmethod
59 def empty(cls) -> t.Self:
60 return cls.literal("")
62 @classmethod
63 def singleton(cls, i_index: int) -> t.Self:
64 return cls(("", ""), (i_index,))
66 @classmethod
67 def from_naive_template(cls, t: Template) -> TemplateRef:
68 return cls(
69 strings=t.strings,
70 i_indexes=tuple(int(ip.value) for ip in t.interpolations),
71 )
73 def __post_init__(self):
74 if len(self.strings) != len(self.i_indexes) + 1:
75 raise ValueError(
76 "TemplateRef must have one more string than interpolation indexes."
77 )
79 def __iter__(self):
80 index = 0
81 last_s_index = len(self.strings) - 1
82 while index <= last_s_index:
83 s = self.strings[index]
84 if s:
85 yield s
86 if index < last_s_index:
87 yield self.i_indexes[index]
88 index += 1
90 def resolve(self, interpolations: tuple[Interpolation, ...]) -> Template:
91 """Use the given interpolations to resolve this reference template into a Template."""
92 resolved = [interpolations[i_index] for i_index in self.i_indexes]
93 return template_from_parts(self.strings, resolved)