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