|
| 1 | +from string.templatelib import Template, Interpolation |
| 2 | + |
| 3 | +print("=== Basic functionality ===") |
| 4 | +t = t"Hello World" |
| 5 | +print(type(t).__name__) |
| 6 | + |
| 7 | +name = "World" |
| 8 | +t2 = t"Hello {name}" |
| 9 | +print(f"Strings: {t2.strings}") |
| 10 | +print(f"Value: {t2.interpolations[0].value}") |
| 11 | +print(f"str(): {str(t2)}") |
| 12 | + |
| 13 | +t_raw = rt"Path: C:\test\{name}" |
| 14 | +print(f"Raw: '{t_raw.strings[0]}'") |
| 15 | + |
| 16 | +t_tr = tr"Path: C:\test\{name}" |
| 17 | +print(f"tr: '{t_tr.strings[0]}'") |
| 18 | + |
| 19 | +print("\n=== Parser tests ===") |
| 20 | +data = {"a": {"b": [1, 2, 3]}} |
| 21 | +t_complex = t"{data['a']['b'][0]}" |
| 22 | +print(f"Complex: {str(t_complex)}") |
| 23 | + |
| 24 | +print(f"None: {str(t'{None}')}") |
| 25 | +print(f"True: {str(t'{True}')}") |
| 26 | +print(f"False: {str(t'{False}')}") |
| 27 | +print(f"Ellipsis: {str(t'{...}')}") |
| 28 | + |
| 29 | +obj = type('Obj', (), { |
| 30 | + '__getattr__': lambda s, n: s, |
| 31 | + '__getitem__': lambda s, k: s, |
| 32 | + '__call__': lambda s, *a: 42, |
| 33 | + '__str__': lambda s: "42" |
| 34 | +})() |
| 35 | +print(f"Deep nest: {str(t'{obj.a.b[0].c()}')}") |
| 36 | + |
| 37 | +print("\n=== Conversions and formatting ===") |
| 38 | +val = {"key": "value"} |
| 39 | +print(f"repr: {str(t'{val!r}')}") |
| 40 | +print(f"str: {str(t'{val!s}')}") |
| 41 | +# print(f"ascii: {str(t'{val!a}')}") |
| 42 | + |
| 43 | +print(f"Width: '{str(t'{42:10d}')}'") |
| 44 | +print(f"Precision: {str(t'{3.14159:.2f}')}") |
| 45 | + |
| 46 | +x = 42 |
| 47 | +t_debug = t"{x=}" |
| 48 | +assert t_debug.strings == ("x=", "") |
| 49 | +assert t_debug.interpolations[0].expression == "x" |
| 50 | +assert t_debug.interpolations[0].conversion == "r" |
| 51 | +assert t_debug.interpolations[0].value == 42 |
| 52 | +print(f"Debug: {str(t_debug)}") |
| 53 | + |
| 54 | +y = 10 |
| 55 | +t_debug2 = t"{x + y=}" |
| 56 | +assert t_debug2.strings == ("x + y=", "") |
| 57 | +assert t_debug2.interpolations[0].expression == "x + y" |
| 58 | +assert t_debug2.interpolations[0].conversion == "r" |
| 59 | +assert t_debug2.interpolations[0].value == 52 |
| 60 | + |
| 61 | +pi = 3.14159 |
| 62 | +t_debug3 = t"{pi=:.2f}" |
| 63 | +assert t_debug3.strings == ("pi=", "") |
| 64 | +assert t_debug3.interpolations[0].expression == "pi" |
| 65 | +assert t_debug3.interpolations[0].conversion is None |
| 66 | +assert t_debug3.interpolations[0].format_spec == ".2f" |
| 67 | +assert t_debug3.interpolations[0].value == 3.14159 |
| 68 | + |
| 69 | +print("\n=== Constructor tests ===") |
| 70 | +t_empty = Template() |
| 71 | +print(f"Empty: {t_empty.strings}") |
| 72 | + |
| 73 | +t_single = Template("single") |
| 74 | +print(f"Single: {t_single.strings}") |
| 75 | + |
| 76 | +t_multi = Template("a", "b", "c") |
| 77 | +print(f"Multi: {t_multi.strings}") |
| 78 | + |
| 79 | +i1 = Interpolation(1, "x") |
| 80 | +i2 = Interpolation(2, "y") |
| 81 | +t_mixed = Template("start", i1, "middle", i2, "end") |
| 82 | +print(f"Mixed: strings={t_mixed.strings}, values={t_mixed.values}") |
| 83 | + |
| 84 | +print("\n=== Operations ===") |
| 85 | +t1 = t"Hello" |
| 86 | +t2 = t" World" |
| 87 | +t_concat = t1 + t2 |
| 88 | +print(f"Concat: '{str(t_concat)}'") |
| 89 | + |
| 90 | +items = list(t"a{1}b{2}c") |
| 91 | +print(f"Iterator: {[type(x).__name__ for x in items]}") |
| 92 | + |
| 93 | +t_attr = t"test{42}" |
| 94 | +print(f"strings attr: {t_attr.strings}") |
| 95 | +print(f"interpolations attr: {len(t_attr.interpolations)}") |
| 96 | +print(f"values attr: {t_attr.values}") |
| 97 | + |
| 98 | +print(f"repr: {repr(t2)[:50]}...") |
| 99 | + |
| 100 | +print("\n=== Escaped braces evaluation ===") |
| 101 | +t_escaped = Template("{", Interpolation(42, "x"), "}", Interpolation(42, "x"), "{{", Interpolation(42, "x"), "}}") |
| 102 | +print(f"Escaped eval: '{str(t_escaped)}'") |
| 103 | + |
| 104 | +t_braces = Template("{{hello}}", Interpolation(1, "a"), " {", Interpolation(2, "b"), "} ", Interpolation(3, "c"), "{{world}}") |
| 105 | +print(f"Braces in strings: '{str(t_braces)}'") |
| 106 | + |
| 107 | +print("\n=== Memory stress test ===") |
| 108 | +for n in [10, 20, 30]: |
| 109 | + args = [] |
| 110 | + for i in range(n): |
| 111 | + args.append("s") |
| 112 | + args.append(Interpolation(i, f"var{i}")) |
| 113 | + args.append("s") |
| 114 | + t_mem = Template(*args) |
| 115 | + result = str(t_mem) |
| 116 | + print(f"Memory test [{n}]: {len(result)} chars") |
| 117 | + |
| 118 | +large_args = [] |
| 119 | +for i in range(20): |
| 120 | + large_args.append("") |
| 121 | + large_args.append(Interpolation(i, f"v{i}")) |
| 122 | +large_args.append("") |
| 123 | +t_large = Template(*large_args) |
| 124 | +print(f"Large values: {len(t_large.values)} values") |
| 125 | + |
| 126 | +print("\n=== Nested quotes and braces tests ===") |
| 127 | +t_nested1 = t"{"{}"}" |
| 128 | +print(f"Nested quotes 1: {str(t_nested1)}") |
| 129 | +print(f" Value: {t_nested1.interpolations[0].value}") |
| 130 | +print(f" Expression: {t_nested1.interpolations[0].expression}") |
| 131 | + |
| 132 | +t_nested2 = t"{'hello'}" |
| 133 | +print(f"Nested quotes 2: {str(t_nested2)}") |
| 134 | +print(f" Value: {t_nested2.interpolations[0].value}") |
| 135 | + |
| 136 | +t_nested3 = t'{"world"}' |
| 137 | +print(f"Nested quotes 3: {str(t_nested3)}") |
| 138 | +print(f" Value: {t_nested3.interpolations[0].value}") |
| 139 | + |
| 140 | +t_nested4 = t"{['a', 'b', 'c'][1]}" |
| 141 | +print(f"Nested quotes 4: {str(t_nested4)}") |
| 142 | +print(f" Value: {t_nested4.interpolations[0].value}") |
| 143 | + |
| 144 | +d = {"key": "value", "x": 123} |
| 145 | +t_nested5 = t"{d['key']}" |
| 146 | +print(f"Nested quotes 5: {str(t_nested5)}") |
| 147 | +print(f" Value: {t_nested5.interpolations[0].value}") |
| 148 | + |
| 149 | +t_escaped1 = t'{{""}}' |
| 150 | +print(f"Escaped braces 1: {str(t_escaped1)}") |
| 151 | +print(f" Strings: {t_escaped1.strings}") |
| 152 | + |
| 153 | +t_escaped2 = t"{{}}}}" |
| 154 | +print(f"Escaped braces 2: {str(t_escaped2)}") |
| 155 | +print(f" Strings: {t_escaped2.strings}") |
| 156 | + |
| 157 | +x = "test" |
| 158 | +t_mixed = t"{{before}} {x} {{after}}" |
| 159 | +print(f"Mixed escaped: {str(t_mixed)}") |
| 160 | +print(f" Strings: {t_mixed.strings}") |
| 161 | +print(f" Value: {t_mixed.interpolations[0].value}") |
| 162 | + |
| 163 | +t_escape = t"{'\n\t'}" |
| 164 | +print(f"Escape sequences: {str(t_escape)}") |
| 165 | +print(f" Value repr: {repr(t_escape.interpolations[0].value)}") |
| 166 | + |
| 167 | +inner = "{}" |
| 168 | +t_nested_expr = t"{inner}" |
| 169 | +print(f"Nested expr: {str(t_nested_expr)}") |
| 170 | +print(f" Value: {t_nested_expr.interpolations[0].value}") |
| 171 | + |
| 172 | +print("\n=== Interpolation attribute tests ===") |
| 173 | +i_basic = Interpolation(42, "x") |
| 174 | +print(f"Basic conversion: {i_basic.conversion}") |
| 175 | +print(f"Basic format_spec: {i_basic.format_spec}") |
| 176 | + |
| 177 | +i_with_conv = Interpolation(42, "x", "s") |
| 178 | +print(f"With conversion: {i_with_conv.conversion}") |
| 179 | + |
| 180 | +i_with_fmt = Interpolation(42, "x", None, ":>10") |
| 181 | +print(f"With format_spec: {i_with_fmt.format_spec}") |
| 182 | + |
| 183 | +i_full = Interpolation(42, "x", "r", ":>10") |
| 184 | +print(f"Full conversion: {i_full.conversion}") |
| 185 | +print(f"Full format_spec: {i_full.format_spec}") |
| 186 | + |
| 187 | +t_conv = t"{42!s}" |
| 188 | +print(f"Template conversion: {t_conv.interpolations[0].conversion}") |
| 189 | + |
| 190 | +t_fmt = t"{42:>10}" |
| 191 | +print(f"Template format_spec: {t_fmt.interpolations[0].format_spec}") |
| 192 | + |
| 193 | +t_both = t"{42!r:>10}" |
| 194 | +print(f"Both conversion: {t_both.interpolations[0].conversion}") |
| 195 | +print(f"Both format_spec: {t_both.interpolations[0].format_spec}") |
| 196 | + |
| 197 | +print("\n=== Escape sequence tests ===") |
| 198 | +print(repr(t"Line1\nLine2")) |
| 199 | +print(repr(t"Path\\to\\file")) |
| 200 | +print(repr(t"It\'s working")) |
| 201 | +print(repr(t"She said \"Hello\"")) |
| 202 | +print(repr(t"Bell\a")) |
| 203 | +print(repr(t"Back\bspace")) |
| 204 | +print(repr(t"Tab\there")) |
| 205 | +print(repr(t"Vertical\vtab")) |
| 206 | +print(repr(t"Form\ffeed")) |
| 207 | +print(repr(t"Carriage\rreturn")) |
| 208 | + |
| 209 | +# Valid hex escapes |
| 210 | +print(repr(t"\x41")) |
| 211 | +print(repr(t"\x7F")) |
| 212 | + |
| 213 | +# Valid octal escapes |
| 214 | +print(repr(t"\0")) |
| 215 | +print(repr(t"\7")) |
| 216 | +print(repr(t"\77")) |
| 217 | +print(repr(t"\101")) |
| 218 | + |
| 219 | +print(repr(t"\1a")) |
| 220 | +print(repr(t"\123c")) |
| 221 | + |
| 222 | +# Line continuation |
| 223 | +print(repr(t"First \ |
| 224 | +second")) |
| 225 | +print(repr(t"One\ |
| 226 | +Two\ |
| 227 | +Three")) |
| 228 | + |
| 229 | +# Multiple escapes |
| 230 | +print(repr(t"\n\t\r")) |
| 231 | + |
| 232 | +# Raw strings |
| 233 | +print(repr(rt"\n\t\x41")) |
| 234 | +print(repr(rt"C:\new\test")) |
| 235 | + |
| 236 | +# Triple quoted strings |
| 237 | +print(repr(t"""\n\t\x41""")) |
| 238 | +print(repr(rt"""\n\t\x41""")) |
| 239 | + |
| 240 | +print("\n=== Triple quote edge cases ===") |
| 241 | +doc = '''This is a |
| 242 | +multi-line |
| 243 | +docstring''' |
| 244 | +result1 = t"""Documentation: |
| 245 | +{doc} |
| 246 | +End of doc""" |
| 247 | +print(f"Triple in interpolation: {str(result1)}") |
| 248 | +print(f" Value: {repr(result1.interpolations[0].value)}") |
| 249 | + |
| 250 | +data = { |
| 251 | + "users": [ |
| 252 | + {"name": "Alice", "msg": 'Say "Hello"'}, |
| 253 | + {"name": "Bob", "msg": "It's great!"} |
| 254 | + ] |
| 255 | +} |
| 256 | +complex_nested = t"""Users: |
| 257 | +{data['users'][0]['name']}: {data['users'][0]['msg']} |
| 258 | +{data['users'][1]['name']}: {data['users'][1]['msg']}""" |
| 259 | +print(f"Complex nested: {str(complex_nested)}") |
| 260 | +print(f" Values: {complex_nested.values}") |
| 261 | + |
| 262 | +import os |
| 263 | +path_sep = os.sep |
| 264 | +raw_with_interp = rt"""Path: C:\Users\{path_sep}Documents |
| 265 | +Raw newline: \n |
| 266 | +Raw tab: \t""" |
| 267 | +print(f"Raw with interpolation: {str(raw_with_interp)}") |
| 268 | +print(f" String 0 repr: {repr(raw_with_interp.strings[0])}") |
| 269 | + |
| 270 | +long_line = "x" * 50 |
| 271 | +long_triple = t"""Start |
| 272 | +{long_line} |
| 273 | +End""" |
| 274 | +print(f"Long line: strings={long_triple.strings}") |
| 275 | +print(f" Value length: {len(long_triple.values[0])}") |
| 276 | + |
| 277 | +inner = t"inner {42}" |
| 278 | +outer = t"""Outer template: |
| 279 | +{inner} |
| 280 | +End""" |
| 281 | +print(f"Nested templates: {str(outer)}") |
| 282 | +print(f" Inner value: {outer.values[0]}") |
| 283 | + |
| 284 | +formatted = t"""Math constants: |
| 285 | +Pi: {314:.2f} |
| 286 | +E: {271:.1e} |
| 287 | +Sqrt(2): {141:.0f}""" |
| 288 | +print(f"Formatted values: {repr(formatted)}") |
| 289 | +print(f" Interpolation count: {len(formatted.interpolations)}") |
| 290 | +print(f" Format specs: {[i.format_spec for i in formatted.interpolations]}") |
| 291 | + |
| 292 | +x, y, z = 1, 2, 3 |
| 293 | +debug_complex = t"""Debug info: |
| 294 | +{x + y=} {x * y * z=} {x < y < z=}""" |
| 295 | +print(f"Debug complex: {repr(debug_complex)}") |
| 296 | +print(f" Expressions: {[i.expression for i in debug_complex.interpolations]}") |
| 297 | + |
| 298 | +part1 = t"""Part 1 |
| 299 | +with newline""" |
| 300 | +part2 = t'''Part 2 |
| 301 | +also multiline''' |
| 302 | +concatenated = part1 + part2 |
| 303 | +print(f"Concatenated triple: {repr(concatenated)}") |
| 304 | +print(f" Strings: {concatenated.strings}") |
| 305 | + |
| 306 | +print("\n=== PEP 701 brace compliance ===") |
| 307 | +t_brace1 = t"{{" |
| 308 | +print(f"Escaped {{{{: {t_brace1.strings}") |
| 309 | +assert t_brace1.strings == ('{',) |
| 310 | + |
| 311 | +t_brace2 = t"}}" |
| 312 | +print(f"Escaped }}}}: {t_brace2.strings}") |
| 313 | +assert t_brace2.strings == ('}',) |
| 314 | + |
| 315 | +t_brace3 = t"test{{escape}}here" |
| 316 | +print(f"Mixed: {t_brace3.strings}") |
| 317 | +assert t_brace3.strings == ('test{escape}here',) |
| 318 | + |
| 319 | +print("\n=== Format spec scope resolution ===") |
| 320 | +def test_format_scope(): |
| 321 | + width = 10 |
| 322 | + x = 42 |
| 323 | + t_scope = t"{x:{width}}" |
| 324 | + print(f"Local vars: width={width}, x={x}") |
| 325 | + print(f"Format spec: {t_scope.interpolations[0].format_spec}") |
| 326 | + return t_scope.interpolations[0].format_spec == '10' |
| 327 | + |
| 328 | +result = test_format_scope() |
| 329 | +print(f"Scope test: {'PASS' if result else 'FAIL'}") |
| 330 | +assert result, "Format spec scope resolution failed" |
| 331 | + |
| 332 | +precision = 2 |
| 333 | +value = 3.14159 |
| 334 | +t_prec = t"{value:.{precision}f}" |
| 335 | +print(f"Precision: format_spec={t_prec.interpolations[0].format_spec}") |
| 336 | +assert t_prec.interpolations[0].format_spec == '.2f' |
| 337 | + |
| 338 | +print("\nBasic tests completed!") |
0 commit comments