Coverage for tdom/utils.py: 100%
28 statements
« prev ^ index » next coverage.py v7.10.6, created at 2025-09-17 19:54 +0000
« 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
5@t.overload
6def convert[T](value: T, conversion: None) -> T: ...
9@t.overload
10def convert(value: object, conversion: t.Literal["a", "r", "s"]) -> str: ...
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.
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
29type FormatMatcher = t.Callable[[str], bool]
30"""A predicate function that returns True if a given format specifier matches its criteria."""
32type CustomFormatter = t.Callable[[object, str], str]
33"""A function that takes a value and a format specifier and returns a formatted string."""
35type MatcherAndFormatter = tuple[str | FormatMatcher, CustomFormatter]
36"""
37A pair of a matcher and its corresponding formatter.
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"""
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)
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
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.
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.
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 )