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

1import typing as t 

2from collections.abc import Sequence 

3from dataclasses import dataclass 

4from string.templatelib import Interpolation, Template 

5 

6 

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) 

16 

17 

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 ) 

22 

23 

24@dataclass(slots=True, frozen=True) 

25class TemplateRef: 

26 """Reference to a template with indexes for its original interpolations.""" 

27 

28 strings: tuple[str, ...] 

29 """Static string parts of the original string.templatelib.Template""" 

30 

31 i_indexes: tuple[int, ...] 

32 """Indexes of the interpolations in the original string.templatelib.Template""" 

33 

34 @property 

35 def is_literal(self) -> bool: 

36 """Return True if there are no interpolations.""" 

37 return not self.i_indexes 

38 

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] == "" 

43 

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 == ("", "") 

48 

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 ) 

53 

54 @classmethod 

55 def literal(cls, s: str) -> t.Self: 

56 return cls((s,), ()) 

57 

58 @classmethod 

59 def empty(cls) -> t.Self: 

60 return cls.literal("") 

61 

62 @classmethod 

63 def singleton(cls, i_index: int) -> t.Self: 

64 return cls(("", ""), (i_index,)) 

65 

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 ) 

72 

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 ) 

78 

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 

89 

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)