Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
mikf
GitHub Repository: mikf/gallery-dl
Path: blob/master/test/test_formatter.py
5457 views
1
#!/usr/bin/env python3
2
# -*- coding: utf-8 -*-
3
4
# Copyright 2021-2025 Mike Fährmann
5
#
6
# This program is free software; you can redistribute it and/or modify
7
# it under the terms of the GNU General Public License version 2 as
8
# published by the Free Software Foundation.
9
10
import os
11
import sys
12
import time
13
import unittest
14
import datetime
15
import tempfile
16
17
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
18
from gallery_dl import formatter, text, util, config # noqa E402
19
20
try:
21
import jinja2
22
except ImportError:
23
jinja2 = None
24
25
26
class TestFormatter(unittest.TestCase):
27
28
def tearDown(self):
29
config.clear()
30
31
kwdict = {
32
"a": "hElLo wOrLd",
33
"b": "äöü",
34
"j": "げんそうきょう",
35
"d": {"a": "foo", "b": 0, "c": None},
36
"i": 2,
37
"l": ["a", "b", "c"],
38
"L": [
39
{"name": "John Doe" , "age": 42, "email": "[email protected]"},
40
{"name": "Jane Smith" , "age": 24, "email": None},
41
{"name": "Max Mustermann", "age": False},
42
],
43
"n": None,
44
"s": " \n\r\tSPACE ",
45
"S": " \n\r\tS P A\tC\nE ",
46
"h": "<p>foo </p> &amp; bar <p> </p>",
47
"H": """<p>
48
<a href="http://www.example.com">Lorem ipsum dolor sit amet</a>.
49
Duis aute irure <a href="http://blog.example.org">dolor</a>.
50
</p>""",
51
"u": "&#x27;&lt; / &gt;&#x27;",
52
"t": 1262304000,
53
"ds": "2010-01-01T01:00:00+01:00",
54
"dt": datetime.datetime(2010, 1, 1),
55
"dt_dst": datetime.datetime(2010, 6, 1),
56
"i_str": "12345",
57
"f_str": "12.45",
58
"lang": "en",
59
"name": "Name",
60
"title1": "Title",
61
"title2": "",
62
"title3": None,
63
"title4": 0,
64
}
65
66
def test_conversions(self):
67
self._run_test("{a!l}", "hello world")
68
self._run_test("{a!u}", "HELLO WORLD")
69
self._run_test("{a!c}", "Hello world")
70
self._run_test("{a!C}", "Hello World")
71
self._run_test("{s!t}", "SPACE")
72
self._run_test("{S!t}", "S P A\tC\nE")
73
self._run_test("{a!U}", self.kwdict["a"])
74
self._run_test("{u!U}", "'< / >'")
75
self._run_test("{a!H}", self.kwdict["a"])
76
self._run_test("{h!H}", "foo & bar")
77
self._run_test("{u!H}", "'< / >'")
78
self._run_test("{n!H}", "")
79
self._run_test("{h!R}", [])
80
self._run_test("{H!R}", ["http://www.example.com",
81
"http://blog.example.org"])
82
self._run_test("{a!s}", self.kwdict["a"])
83
self._run_test("{a!r}", f"'{self.kwdict['a']}'")
84
self._run_test("{a!a}", f"'{self.kwdict['a']}'")
85
self._run_test("{b!a}", "'\\xe4\\xf6\\xfc'")
86
self._run_test("{a!S}", self.kwdict["a"])
87
self._run_test("{l!S}", "a, b, c")
88
self._run_test("{n!S}", "")
89
self._run_test("{t!d}", datetime.datetime(2010, 1, 1))
90
self._run_test("{t!d:%Y-%m-%d}", "2010-01-01")
91
self._run_test("{t!D}" , datetime.datetime(2010, 1, 1))
92
self._run_test("{ds!D}", datetime.datetime(2010, 1, 1))
93
self._run_test("{dt!D}", datetime.datetime(2010, 1, 1))
94
self._run_test("{t!D:%Y-%m-%d}", "2010-01-01")
95
self._run_test("{dt!T}", "1262304000")
96
self._run_test("{l!j}", '["a","b","c"]')
97
self._run_test("{dt!j}", '"2010-01-01 00:00:00"')
98
self._run_test("{a!g}", "hello-world")
99
self._run_test("{lang!L}", "English")
100
self._run_test("{'fr'!L}", "French")
101
self._run_test("{a!L}", None)
102
self._run_test("{a!n}", 11)
103
self._run_test("{l!n}", 3)
104
self._run_test("{d!n}", 3)
105
self._run_test("{s!W}", "SPACE")
106
self._run_test("{S!W}", "S P A C E")
107
self._run_test("{i_str!i}", 12345)
108
self._run_test("{i_str!f}", 12345.0)
109
self._run_test("{f_str!f}", 12.45)
110
111
with self.assertRaises(KeyError):
112
self._run_test("{a!q}", "hello world")
113
114
def test_optional(self):
115
self._run_test("{name}{title1}", "NameTitle")
116
self._run_test("{name}{title1:?//}", "NameTitle")
117
self._run_test("{name}{title1:? **/''/}", "Name **Title''")
118
119
self._run_test("{name}{title2}", "Name")
120
self._run_test("{name}{title2:?//}", "Name")
121
self._run_test("{name}{title2:? **/''/}", "Name")
122
123
self._run_test("{name}{title3}", "NameNone")
124
self._run_test("{name}{title3:?//}", "Name")
125
self._run_test("{name}{title3:? **/''/}", "Name")
126
127
self._run_test("{name}{title4}", "Name0")
128
self._run_test("{name}{title4:?//}", "Name")
129
self._run_test("{name}{title4:? **/''/}", "Name")
130
131
def test_missing(self):
132
replacement = "None"
133
134
self._run_test("{missing}", replacement)
135
self._run_test("{missing.attr}", replacement)
136
self._run_test("{missing[key]}", replacement)
137
self._run_test("{missing:?a//}", "")
138
139
self._run_test("{name[missing]}", replacement)
140
self._run_test("{name[missing].attr}", replacement)
141
self._run_test("{name[missing][key]}", replacement)
142
self._run_test("{name[missing]:?a//}", "")
143
144
def test_missing_custom_default(self):
145
replacement = default = "foobar"
146
self._run_test("{missing}" , replacement, default)
147
self._run_test("{missing.attr}", replacement, default)
148
self._run_test("{missing[key]}", replacement, default)
149
self._run_test("{missing:?a//}", f"a{default}", default)
150
151
def test_fmt_func(self):
152
self._run_test("{t}" , self.kwdict["t"] , None, int)
153
self._run_test("{t}" , self.kwdict["t"] , None, util.identity)
154
self._run_test("{dt}", self.kwdict["dt"], None, util.identity)
155
self._run_test("{ds}", self.kwdict["dt"], None, text.parse_datetime)
156
self._run_test("{ds:D%Y-%m-%dT%H:%M:%S%z}", self.kwdict["dt"],
157
None, util.identity)
158
159
def test_alternative(self):
160
self._run_test("{a|z}" , "hElLo wOrLd")
161
self._run_test("{z|a}" , "hElLo wOrLd")
162
self._run_test("{z|y|a}" , "hElLo wOrLd")
163
self._run_test("{z|y|x|a}", "hElLo wOrLd")
164
self._run_test("{z|n|a|y}", "hElLo wOrLd")
165
166
self._run_test("{z|a!C}" , "Hello World")
167
self._run_test("{z|a:Rh/C/}" , "CElLo wOrLd")
168
self._run_test("{z|a!C:RH/C/}", "Cello World")
169
self._run_test("{z|y|x:?</>/}", "")
170
171
self._run_test("{d[c]|d[b]|d[a]}", "foo")
172
self._run_test("{d[a]|d[b]|d[c]}", "foo")
173
self._run_test("{d[z]|d[y]|d[x]}", "None")
174
175
def test_indexing(self):
176
self._run_test("{l[0]}" , "a")
177
self._run_test("{a[6]}" , "w")
178
179
def test_dict_access(self):
180
self._run_test("{d[a]}" , "foo")
181
self._run_test("{d['a']}", "foo")
182
self._run_test('{d["a"]}', "foo")
183
184
def test_slice_str(self):
185
v = self.kwdict["a"]
186
self._run_test("{a[1:10]}" , v[1:10])
187
self._run_test("{a[-10:-1]}", v[-10:-1])
188
self._run_test("{a[5:]}" , v[5:])
189
self._run_test("{a[50:]}", v[50:])
190
self._run_test("{a[:5]}" , v[:5])
191
self._run_test("{a[:50]}", v[:50])
192
self._run_test("{a[:]}" , v)
193
self._run_test("{a[1:10:2]}" , v[1:10:2])
194
self._run_test("{a[-10:-1:2]}", v[-10:-1:2])
195
self._run_test("{a[5::2]}" , v[5::2])
196
self._run_test("{a[50::2]}", v[50::2])
197
self._run_test("{a[:5:2]}" , v[:5:2])
198
self._run_test("{a[:50:2]}", v[:50:2])
199
self._run_test("{a[::]}" , v)
200
201
self._run_test("{a:[1:10]}" , v[1:10])
202
self._run_test("{a:[-10:-1]}", v[-10:-1])
203
self._run_test("{a:[5:]}" , v[5:])
204
self._run_test("{a:[50:]}", v[50:])
205
self._run_test("{a:[:5]}" , v[:5])
206
self._run_test("{a:[:50]}", v[:50])
207
self._run_test("{a:[:]}" , v)
208
self._run_test("{a:[1:10:2]}" , v[1:10:2])
209
self._run_test("{a:[-10:-1:2]}", v[-10:-1:2])
210
self._run_test("{a:[5::2]}" , v[5::2])
211
self._run_test("{a:[50::2]}", v[50::2])
212
self._run_test("{a:[:5:2]}" , v[:5:2])
213
self._run_test("{a:[:50:2]}", v[:50:2])
214
self._run_test("{a:[::]}" , v)
215
216
def test_slice_bytes(self):
217
v = self.kwdict["j"]
218
self._run_test("{j[b1:10]}" , v[1:3])
219
self._run_test("{j[b-10:-1]}", v[-3:-1])
220
self._run_test("{j[b5:]}" , v[2:])
221
self._run_test("{j[b50:]}" , v[50:])
222
self._run_test("{j[b:5]}" , v[:1])
223
self._run_test("{j[b:50]}" , v[:50])
224
self._run_test("{j[b:]}" , v)
225
self._run_test("{j[b::]}" , v)
226
227
self._run_test("{j:[b1:10]}" , v[1:3])
228
self._run_test("{j:[b-10:-1]}", v[-3:-1])
229
self._run_test("{j:[b5:]}" , v[2:])
230
self._run_test("{j:[b50:]}" , v[50:])
231
self._run_test("{j:[b:5]}" , v[:1])
232
self._run_test("{j:[b:50]}" , v[:50])
233
self._run_test("{j:[b:]}" , v)
234
self._run_test("{j:[b::]}" , v)
235
236
def test_specifier_maxlen(self):
237
v = self.kwdict["a"]
238
self._run_test("{a:L5/foo/}" , "foo")
239
self._run_test("{a:L50/foo/}", v)
240
self._run_test("{a:L50/foo/>50}", " " * 39 + v)
241
self._run_test("{a:L50/foo/>51}", "foo")
242
self._run_test("{a:Lab/foo/}", "foo")
243
244
def test_specifier_join(self):
245
self._run_test("{l:J}" , "abc")
246
self._run_test("{l:J,}" , "a,b,c")
247
self._run_test("{l:J,/}" , "a,b,c")
248
self._run_test("{l:J,/>20}" , " a,b,c")
249
self._run_test("{l:J - }" , "a - b - c")
250
self._run_test("{l:J - /}" , "a - b - c")
251
self._run_test("{l:J - />20}", " a - b - c")
252
253
self._run_test("{a:J/}" , self.kwdict["a"])
254
self._run_test("{a:J, /}" , self.kwdict["a"])
255
256
def test_specifier_replace(self):
257
self._run_test("{a:Rh/C/}" , "CElLo wOrLd")
258
self._run_test("{a!l:Rh/C/}", "Cello world")
259
self._run_test("{a!u:Rh/C/}", "HELLO WORLD")
260
261
self._run_test("{a!l:Rl/_/}", "he__o wor_d")
262
self._run_test("{a!l:Rl//}" , "heo word")
263
self._run_test("{name:Rame/othing/}", "Nothing")
264
265
def test_specifier_datetime(self):
266
self._run_test("{ds:D%Y-%m-%dT%H:%M:%S%z}", "2010-01-01 00:00:00")
267
self._run_test("{ds:D%Y}", "2010-01-01T01:00:00+01:00")
268
self._run_test("{l:D%Y}", "None")
269
270
def test_specifier_offset(self):
271
self._run_test("{dt:O 01:00}", "2010-01-01 01:00:00")
272
self._run_test("{dt:O+02:00}", "2010-01-01 02:00:00")
273
self._run_test("{dt:O-03:45}", "2009-12-31 20:15:00")
274
275
self._run_test("{dt:O12}", "2010-01-01 12:00:00")
276
self._run_test("{dt:O-24}", "2009-12-31 00:00:00")
277
278
self._run_test("{ds:D%Y-%m-%dT%H:%M:%S%z/O1}", "2010-01-01 01:00:00")
279
self._run_test("{t!d:O2}", "2010-01-01 02:00:00")
280
281
def test_specifier_offset_local(self):
282
ts = self.kwdict["dt"].replace(
283
tzinfo=datetime.timezone.utc).timestamp()
284
offset = time.localtime(ts).tm_gmtoff
285
dt = self.kwdict["dt"] + datetime.timedelta(seconds=offset)
286
self._run_test("{dt:O}", str(dt))
287
self._run_test("{dt:Olocal}", str(dt))
288
289
ts = self.kwdict["dt_dst"].replace(
290
tzinfo=datetime.timezone.utc).timestamp()
291
offset = time.localtime(ts).tm_gmtoff
292
dt = self.kwdict["dt_dst"] + datetime.timedelta(seconds=offset)
293
self._run_test("{dt_dst:O}", str(dt))
294
self._run_test("{dt_dst:Olocal}", str(dt))
295
296
def test_specifier_sort(self):
297
self._run_test("{l:S}" , "['a', 'b', 'c']")
298
self._run_test("{l:Sa}", "['a', 'b', 'c']")
299
self._run_test("{l:Sd}", "['c', 'b', 'a']")
300
self._run_test("{l:Sr}", "['c', 'b', 'a']")
301
302
self._run_test(
303
"{a:S}", "[' ', 'E', 'L', 'L', 'O', 'd', 'h', 'l', 'o', 'r', 'w']")
304
self._run_test(
305
"{a:S-asc}", # starts with 'S', contains 'a'
306
"[' ', 'E', 'L', 'L', 'O', 'd', 'h', 'l', 'o', 'r', 'w']")
307
self._run_test(
308
"{a:Sort-reverse}", # starts with 'S', contains 'r'
309
"['w', 'r', 'o', 'l', 'h', 'd', 'O', 'L', 'L', 'E', ' ']")
310
311
def test_specifier_arithmetic(self):
312
self._run_test("{i:A+1}", "3")
313
self._run_test("{i:A-1}", "1")
314
self._run_test("{i:A*3}", "6")
315
316
def test_specifier_conversions(self):
317
self._run_test("{a:Cl}" , "hello world")
318
self._run_test("{h:CHC}" , "Foo & Bar")
319
self._run_test("{l:CSulc}", "A, b, c")
320
321
def test_specifier_limit(self):
322
self._run_test("{a:X20/ */}", "hElLo wOrLd")
323
self._run_test("{a:X10/ */}", "hElLo wO *")
324
325
with self.assertRaises(ValueError):
326
self._run_test("{a:Xfoo/ */}", "hello wo *")
327
328
def test_specifier_map(self):
329
self._run_test("{L:Mname/}" ,
330
"['John Doe', 'Jane Smith', 'Max Mustermann']")
331
self._run_test("{L:Mage/}" ,
332
"[42, 24, False]")
333
334
self._run_test("{a:Mname}", self.kwdict["a"])
335
self._run_test("{n:Mname}", "None")
336
self._run_test("{title4:Mname}", "0")
337
338
with self.assertRaises(ValueError):
339
self._run_test("{t:Mname", "")
340
341
def test_chain_special(self):
342
# multiple replacements
343
self._run_test("{a:Rh/C/RE/e/RL/l/}", "Cello wOrld")
344
self._run_test("{d[b]!s:R1/Q/R2/A/R0/Y/}", "Y")
345
346
# join-and-replace
347
self._run_test("{l:J-/Rb/E/}", "a-E-c")
348
349
# join and slice
350
self._run_test("{l:J-/[1:-1]}", "-b-")
351
352
# optional-and-maxlen
353
self._run_test("{d[a]:?</>/L1/too long/}", "<too long>")
354
self._run_test("{d[c]:?</>/L5/too long/}", "")
355
356
# parse and format datetime
357
self._run_test("{ds:D%Y-%m-%dT%H:%M:%S%z/%Y%m%d}", "20100101")
358
359
# sort and join
360
self._run_test("{a:S/J}", " ELLOdhlorw")
361
362
# map and join
363
self._run_test("{L:Mname/J-}", "John Doe-Jane Smith-Max Mustermann")
364
365
def test_separator(self):
366
orig_separator = formatter._SEPARATOR
367
try:
368
formatter._SEPARATOR = "|"
369
self._run_test("{a:Rh|C|RE|e|RL|l|}", "Cello wOrld")
370
self._run_test("{d[b]!s:R1|Q|R2|A|R0|Y|}", "Y")
371
372
formatter._SEPARATOR = "##"
373
self._run_test("{l:J-##Rb##E##}", "a-E-c")
374
self._run_test("{l:J-##[1:-1]}", "-b-")
375
376
formatter._SEPARATOR = "\0"
377
self._run_test("{d[a]:?<\0>\0L1\0too long\0}", "<too long>")
378
self._run_test("{d[c]:?<\0>\0L5\0too long\0}", "")
379
380
formatter._SEPARATOR = "?"
381
self._run_test("{ds:D%Y-%m-%dT%H:%M:%S%z?%Y%m%d}", "20100101")
382
finally:
383
formatter._SEPARATOR = orig_separator
384
385
def test_globals_env(self):
386
os.environ["FORMATTER_TEST"] = value = self.kwdict["a"]
387
388
self._run_test("{_env[FORMATTER_TEST]}" , value)
389
self._run_test("{_env[FORMATTER_TEST]!l}", value.lower())
390
self._run_test("{z|_env[FORMATTER_TEST]}", value)
391
392
def test_globals_now(self):
393
fmt = formatter.parse("{_now}")
394
out1 = fmt.format_map(self.kwdict)
395
self.assertRegex(out1, r"^\d{4}-\d\d-\d\d \d\d:\d\d:\d\d(\.\d+)?$")
396
397
out = formatter.parse("{_now:%Y%m%d}").format_map(self.kwdict)
398
now = datetime.datetime.now()
399
self.assertRegex(out, r"^\d{8}$")
400
self.assertEqual(out, format(now, "%Y%m%d"))
401
402
out = formatter.parse("{z|_now:%Y}").format_map(self.kwdict)
403
self.assertRegex(out, r"^\d{4}$")
404
self.assertEqual(out, format(now, "%Y"))
405
406
out2 = fmt.format_map(self.kwdict)
407
self.assertRegex(out1, r"^\d{4}-\d\d-\d\d \d\d:\d\d:\d\d(\.\d+)?$")
408
self.assertNotEqual(out1, out2)
409
410
def test_globals_nul(self):
411
value = "None"
412
413
self._run_test("{_nul}" , value)
414
self._run_test("{_nul[key]}" , value)
415
self._run_test("{z|_nul}" , value)
416
self._run_test("{z|_nul:%Y%m%s}", value)
417
418
def test_literals(self):
419
value = "foo"
420
421
self._run_test("{'foo'}" , value)
422
self._run_test("{'foo'!u}" , value.upper())
423
self._run_test("{'f00':R0/o/}", value)
424
425
self._run_test("{z|'foo'}" , value)
426
self._run_test("{z|''|'foo'}" , value)
427
self._run_test("{z|'foo'!u}" , value.upper())
428
self._run_test("{z|'f00':R0/o/}", value)
429
430
self._run_test("{_lit[foo]}" , value)
431
self._run_test("{_lit[foo]!u}" , value.upper())
432
self._run_test("{_lit[f00]:R0/o/}" , value)
433
self._run_test("{_lit[foobar][:3]}", value)
434
self._run_test("{z|_lit[foo]}" , value)
435
436
# empty (#4492)
437
self._run_test("{z|''}" , "")
438
self._run_test("{''|''}", "")
439
440
# special characters (dots, brackets, singlee quotes) (#5539)
441
self._run_test("{'f.o.o'}" , "f.o.o")
442
self._run_test("{_lit[f.o.o]}", "f.o.o")
443
self._run_test("{_lit[f'o'o]}", "f'o'o")
444
self._run_test("{'f.[].[]'}" , "f.[].[]")
445
self._run_test("{z|'f.[].[]'}", "f.[].[]")
446
447
def test_template(self):
448
with tempfile.TemporaryDirectory() as tmpdirname:
449
path1 = os.path.join(tmpdirname, "tpl1")
450
path2 = os.path.join(tmpdirname, "tpl2")
451
452
with open(path1, "w") as fp:
453
fp.write("{a}")
454
fmt1 = formatter.parse(f"\fT {path1}")
455
456
with open(path2, "w") as fp:
457
fp.write("{a!u:Rh/C/}\nFooBar")
458
fmt2 = formatter.parse(f"\fT {path2}")
459
460
self.assertEqual(fmt1.format_map(self.kwdict), self.kwdict["a"])
461
self.assertEqual(fmt2.format_map(self.kwdict), "HELLO WORLD\nFooBar")
462
463
with self.assertRaises(OSError):
464
formatter.parse("\fT /")
465
466
def test_expression(self):
467
self._run_test("\fE a", self.kwdict["a"])
468
self._run_test(
469
"\fE name * 2 + ' ' + a",
470
f"{self.kwdict['name']}{self.kwdict['name']} {self.kwdict['a']}")
471
472
def test_fstring(self):
473
self._run_test("\fF {a}", self.kwdict["a"])
474
self._run_test(
475
"\fF {name}{name} {a}",
476
f"{self.kwdict['name']}{self.kwdict['name']} {self.kwdict['a']}")
477
self._run_test(
478
"\fF foo-'\"{a.upper()}\"'-bar",
479
f"""foo-'"{self.kwdict['a'].upper()}"'-bar""")
480
481
def test_template_fstring(self):
482
with tempfile.TemporaryDirectory() as tmpdirname:
483
path1 = os.path.join(tmpdirname, "tpl1")
484
path2 = os.path.join(tmpdirname, "tpl2")
485
486
with open(path1, "w") as fp:
487
fp.write("{a}")
488
fmt1 = formatter.parse(f"\fTF {path1}")
489
490
with open(path2, "w") as fp:
491
fp.write("foo-'\"{a.upper()}\"'-bar")
492
fmt2 = formatter.parse(f"\fTF {path2}")
493
494
self.assertEqual(fmt1.format_map(self.kwdict), self.kwdict["a"])
495
self.assertEqual(fmt2.format_map(self.kwdict),
496
f"""foo-'"{self.kwdict['a'].upper()}"'-bar""")
497
498
with self.assertRaises(OSError):
499
formatter.parse("\fTF /")
500
501
@unittest.skipIf(jinja2 is None, "no jinja2")
502
def test_jinja(self):
503
formatter.JinjaFormatter.env = None
504
505
self._run_test("\fJ {{a}}", self.kwdict["a"])
506
self._run_test(
507
"\fJ {{name}}{{name}} {{a}}",
508
f"{self.kwdict['name']}{self.kwdict['name']} {self.kwdict['a']}")
509
self._run_test(
510
"\fJ foo-'\"{{a | upper}}\"'-bar",
511
f"""foo-'"{self.kwdict['a'].upper()}"'-bar""")
512
513
@unittest.skipIf(jinja2 is None, "no jinja2")
514
def test_template_jinja(self):
515
formatter.JinjaFormatter.env = None
516
517
with tempfile.TemporaryDirectory() as tmpdirname:
518
path1 = os.path.join(tmpdirname, "tpl1")
519
path2 = os.path.join(tmpdirname, "tpl2")
520
521
with open(path1, "w") as fp:
522
fp.write("{{a}}")
523
fmt1 = formatter.parse(f"\fTJ {path1}")
524
525
with open(path2, "w") as fp:
526
fp.write("foo-'\"{{a | upper}}\"'-bar")
527
fmt2 = formatter.parse(f"\fTJ {path2}")
528
529
self.assertEqual(fmt1.format_map(self.kwdict), self.kwdict["a"])
530
self.assertEqual(fmt2.format_map(self.kwdict),
531
f"""foo-'"{self.kwdict['a'].upper()}"'-bar""")
532
533
with self.assertRaises(OSError):
534
formatter.parse("\fTJ /")
535
536
@unittest.skipIf(jinja2 is None, "no jinja2")
537
def test_template_jinja_opts(self):
538
formatter.JinjaFormatter.env = None
539
540
with tempfile.TemporaryDirectory() as tmpdirname:
541
path_filters = os.path.join(tmpdirname, "jinja_filters.py")
542
path_template = os.path.join(tmpdirname, "jinja_template.txt")
543
544
config.set((), "jinja", {
545
"environment": {
546
"variable_start_string": "(((",
547
"variable_end_string" : ")))",
548
"keep_trailing_newline": True,
549
},
550
"filters": path_filters,
551
})
552
553
with open(path_filters, "w") as fp:
554
fp.write(r"""
555
import re
556
557
def datetime_format(value, format="%H:%M %d-%m-%y"):
558
return value.strftime(format)
559
560
def sanitize(value):
561
return re.sub(r"\s+", " ", value.strip())
562
563
__filters__ = {
564
"dt_fmt": datetime_format,
565
"sanitize_whitespace": sanitize,
566
}
567
""")
568
569
with open(path_template, "w") as fp:
570
fp.write("""\
571
Present Day is ((( dt | dt_fmt("%B %d, %Y") )))
572
Present Time is ((( dt | dt_fmt("%H:%M:%S") )))
573
574
Hello ((( s | sanitize_whitespace ))).
575
I hope there is enough "(((S|sanitize_whitespace)))" for you.
576
""")
577
fmt = formatter.parse(f"\fTJ {path_template}")
578
579
self.assertEqual(fmt.format_map(self.kwdict), """\
580
Present Day is January 01, 2010
581
Present Time is 00:00:00
582
583
Hello SPACE.
584
I hope there is enough "S P A C E" for you.
585
""")
586
587
def test_module(self):
588
with tempfile.TemporaryDirectory() as tmpdirname:
589
path = os.path.join(tmpdirname, "testmod.py")
590
591
with open(path, "w") as fp:
592
fp.write("""
593
def gentext(kwdict):
594
name = kwdict.get("Name") or kwdict.get("name") or "foo"
595
return "'{title1}' by {}".format(name, **kwdict)
596
597
def lengths(kwdict):
598
a = 0
599
for k, v in kwdict.items():
600
if k == k.lower():
601
try:
602
a += len(v)
603
except TypeError:
604
pass
605
return format(a)
606
607
def noarg():
608
return ""
609
""")
610
sys.path.insert(0, tmpdirname)
611
try:
612
fmt1 = formatter.parse("\fM testmod:gentext")
613
fmt2 = formatter.parse("\fM testmod:lengths")
614
fmt0 = formatter.parse("\fM testmod:noarg")
615
616
with self.assertRaises(AttributeError):
617
formatter.parse("\fM testmod:missing")
618
with self.assertRaises(ImportError):
619
formatter.parse("\fM missing:missing")
620
finally:
621
sys.path.pop(0)
622
623
fmt3 = formatter.parse(f"\fM {path}:gentext")
624
fmt4 = formatter.parse(f"\fM {path}:lengths")
625
626
self.assertEqual(fmt1.format_map(self.kwdict), "'Title' by Name")
627
self.assertEqual(fmt2.format_map(self.kwdict), "139")
628
629
self.assertEqual(fmt3.format_map(self.kwdict), "'Title' by Name")
630
self.assertEqual(fmt4.format_map(self.kwdict), "139")
631
632
with self.assertRaises(TypeError):
633
self.assertEqual(fmt0.format_map(self.kwdict), "")
634
635
def _run_test(self, format_string, result, default=None, fmt=format):
636
fmt = formatter.parse(format_string, default, fmt)
637
output = fmt.format_map(self.kwdict)
638
self.assertEqual(output, result, format_string)
639
640
641
if __name__ == "__main__":
642
unittest.main()
643
644