Coverage for tdom / placeholders.py: 100%

48 statements  

« prev     ^ index     » next       coverage.py v7.13.0, created at 2026-01-12 16:43 +0000

1from dataclasses import dataclass, field 

2import random 

3import re 

4import string 

5 

6from .template_utils import TemplateRef 

7 

8 

9def make_placeholder_config() -> PlaceholderConfig: 

10 prefix = f"t🐍{''.join(random.choices(string.ascii_lowercase, k=2))}-" 

11 suffix = f"-{''.join(random.choices(string.ascii_lowercase, k=2))}🐍t" 

12 return PlaceholderConfig( 

13 prefix=prefix, 

14 suffix=suffix, 

15 pattern=re.compile(re.escape(prefix) + r"(\d+)" + re.escape(suffix)), 

16 ) 

17 

18 

19@dataclass(frozen=True) 

20class PlaceholderConfig: 

21 """String operations for working with a placeholder pattern.""" 

22 

23 prefix: str 

24 suffix: str 

25 pattern: re.Pattern 

26 

27 def make_placeholder(self, i: int) -> str: 

28 """Generate a placeholder for the i-th interpolation.""" 

29 return f"{self.prefix}{i}{self.suffix}" 

30 

31 def match_placeholders(self, s: str) -> list[re.Match[str]]: 

32 """Find all placeholders in a string.""" 

33 return list(self.pattern.finditer(s)) 

34 

35 def find_placeholders(self, s: str) -> TemplateRef: 

36 """ 

37 Find all placeholders in a string and return a TemplateRef. 

38 

39 If no placeholders are found, returns a static TemplateRef. 

40 """ 

41 matches = self.match_placeholders(s) 

42 if not matches: 

43 return TemplateRef.literal(s) 

44 

45 strings: list[str] = [] 

46 i_indexes: list[int] = [] 

47 last_index = 0 

48 for match in matches: 

49 start, end = match.span() 

50 strings.append(s[last_index:start]) 

51 i_indexes.append(int(match[1])) 

52 last_index = end 

53 strings.append(s[last_index:]) 

54 

55 return TemplateRef(tuple(strings), tuple(i_indexes)) 

56 

57 

58@dataclass 

59class PlaceholderState: 

60 known: set[int] = field(default_factory=set) 

61 config: PlaceholderConfig = field(default_factory=make_placeholder_config) 

62 """Collection of currently 'known and active' placeholder indexes.""" 

63 

64 @property 

65 def is_empty(self) -> bool: 

66 return len(self.known) == 0 

67 

68 def add_placeholder(self, index: int) -> str: 

69 placeholder = self.config.make_placeholder(index) 

70 self.known.add(index) 

71 return placeholder 

72 

73 def remove_placeholders(self, text: str) -> TemplateRef: 

74 """ 

75 Find all known placeholders in the text and return their indices. 

76 

77 If unknown placeholders are found, raises ValueError. 

78 

79 If no placeholders are found, returns a static PlaceholderRef. 

80 """ 

81 pt = self.config.find_placeholders(text) 

82 for index in pt.i_indexes: 

83 if index not in self.known: 

84 raise ValueError(f"Unknown placeholder index {index} found in text.") 

85 self.known.remove(index) 

86 return pt