1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
|
# Copyright (C) 2015 Kristoffer Gronlund <kgronlund@suse.com>
# See COPYING for license information.
import re
headmatcher = re.compile(r'\{\{(\#|\^)?([A-Za-z0-9\#\$:_-]+)\}\}')
class value(object):
"""
An object that is indexable in mustasches,
but also evaluates to a value itself.
"""
def __init__(self, obj, value):
self.value = value
self.obj = obj
self.get = obj.get
def __call__(self):
return self.value
def __repr__(self):
return "handles.value(%s, %s)" % (repr(self.obj), repr(self.value))
def __str__(self):
return "handles.value(%s, %s)" % (repr(self.obj), repr(self.value))
def _join(d1, d2):
d = d1.copy()
d.update(d2)
return d
def _resolve(path, context, strict):
for values in context:
r = path
p = values
while r and p is not None:
p, r = p.get(r[0]), r[1:]
if strict and r:
continue
if callable(p):
p = p()
if p is not None:
return p
if strict:
raise ValueError("Not set: %s" % (':'.join(path)))
return None
def _push(path, value, context):
root = {}
leaf = root
for x in path[:-1]:
leaf = {}
root[x] = leaf
leaf[path[-1]] = value
ret = [root] + context
return ret
def _textify(obj):
if obj is None:
return ''
elif obj is True:
return 'true'
elif obj is False:
return 'false'
return str(obj)
def _parse(template, context, strict):
ret = ""
while template:
head = headmatcher.search(template)
if head is None:
ret += template
break
istart, iend, prefix, key = head.start(0), head.end(0), head.group(1), head.group(2)
if istart > 0:
ret += template[:istart]
path, block, invert = key.split(':'), prefix == '#', prefix == '^'
if not path:
raise ValueError("empty {{}} block found")
obj = _resolve(path, context, strict)
if block or invert:
tailtag = '{{/%s}}' % (key)
tailidx = iend + template[head.end(0):].find(tailtag)
if tailidx < iend:
raise ValueError("Unclosed conditional: %s" % head.group(0))
iend = tailidx + len(tailtag)
body = template[head.end(0):tailidx]
if body.startswith('\n') and (not ret or ret.endswith('\n')):
ret = ret[:-1]
if block:
if obj in (None, False):
pass
elif isinstance(obj, (tuple, list)):
for it in obj:
ret += _parse(body, _push(path, it, context), strict)
else:
ret += _parse(body, context, strict)
elif not obj:
ret += _parse(body, _push(path, "", context), strict)
if ret.endswith('\n') and template[iend:].startswith('\n'):
iend += 1
elif obj is not None:
ret += _textify(obj)
template = template[iend:]
return ret
def parse(template, values, strict=False):
"""
Takes as input a template string and a dict
of values, and replaces the following:
{{object:key}} = look up key in object and insert value
{{object}} = insert value if not None or False.
{{#object}} ... {{/object}} = if object is a dict or value, process text. if object
is a list, process text for each item in the list
(can't nest these for items with the same name)
{{^object}} ... {{/object}} = if object is falsy, process text.
If a path evaluates to a callable, the callable will be invoked to get the value.
"""
return _parse(template, [values], strict)
|