Coverage for tdom/utils.py: 100%

28 statements  

« prev     ^ index     » next       coverage.py v7.10.6, created at 2025-09-17 19:54 +0000

1import typing as t 

2from string.templatelib import Interpolation 

3 

4 

5@t.overload 

6def convert[T](value: T, conversion: None) -> T: ... 

7 

8 

9@t.overload 

10def convert(value: object, conversion: t.Literal["a", "r", "s"]) -> str: ... 

11 

12 

13def convert[T](value: T, conversion: t.Literal["a", "r", "s"] | None) -> T | str: 

14 """ 

15 Convert a value according to the given conversion specifier. 

16 

17 In the future, something like this should probably ship with Python itself. 

18 """ 

19 if conversion == "a": 

20 return ascii(value) 

21 elif conversion == "r": 

22 return repr(value) 

23 elif conversion == "s": 

24 return str(value) 

25 else: 

26 return value 

27 

28 

29type FormatMatcher = t.Callable[[str], bool] 

30"""A predicate function that returns True if a given format specifier matches its criteria.""" 

31 

32type CustomFormatter = t.Callable[[object, str], str] 

33"""A function that takes a value and a format specifier and returns a formatted string.""" 

34 

35type MatcherAndFormatter = tuple[str | FormatMatcher, CustomFormatter] 

36""" 

37A pair of a matcher and its corresponding formatter. 

38 

39The matcher is used to determine if the formatter should be applied to a given  

40format specifier. If the matcher is a string, it must exactly match the format 

41specifier. If it is a FormatMatcher, it is called with the format specifier and 

42should return True if the formatter should be used. 

43""" 

44 

45 

46def _matcher_matches(matcher: str | FormatMatcher, format_spec: str) -> bool: 

47 """Check if a matcher matches a given format specifier.""" 

48 return matcher == format_spec if isinstance(matcher, str) else matcher(format_spec) 

49 

50 

51def _format_interpolation( 

52 value: object, 

53 format_spec: str, 

54 conversion: t.Literal["a", "r", "s"] | None, 

55 *, 

56 formatters: t.Sequence[MatcherAndFormatter], 

57) -> object: 

58 converted = convert(value, conversion) 

59 if format_spec: 

60 for matcher, formatter in formatters: 

61 if _matcher_matches(matcher, format_spec): 

62 return formatter(converted, format_spec) 

63 return format(converted, format_spec) 

64 return converted 

65 

66 

67def format_interpolation( 

68 interpolation: Interpolation, 

69 *, 

70 formatters: t.Sequence[MatcherAndFormatter] = tuple(), 

71) -> object: 

72 """ 

73 Format an Interpolation's value according to its format spec and conversion. 

74 

75 PEP 750 allows t-string processing code to decide whether, and how, to 

76 interpret format specifiers. This function takes an optional sequence of 

77 (matcher, formatter) pairs. If a matcher returns True for the given format 

78 spec, the corresponding formatter is used to format the value. If no 

79 matchers match, the default formatting behavior is used. 

80 

81 Conversions are always applied before formatting. 

82 """ 

83 return _format_interpolation( 

84 interpolation.value, 

85 interpolation.format_spec, 

86 interpolation.conversion, 

87 formatters=formatters, 

88 )