##// END OF EJS Templates
templater: replace eval with closure
Matt Mackall -
r8492:d72d1a97 default
parent child Browse files
Show More
@@ -1,242 +1,245 b''
1 1 # templater.py - template expansion for output
2 2 #
3 3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2, incorporated herein by reference.
7 7
8 8 from i18n import _
9 9 import re, sys, os
10 10 import util, config, templatefilters
11 11
12 12 path = ['templates', '../templates']
13 13 stringify = templatefilters.stringify
14 14
15 15 def parsestring(s, quoted=True):
16 16 '''parse a string using simple c-like syntax.
17 17 string must be in quotes if quoted is True.'''
18 18 if quoted:
19 19 if len(s) < 2 or s[0] != s[-1]:
20 20 raise SyntaxError(_('unmatched quotes'))
21 21 return s[1:-1].decode('string_escape')
22 22
23 23 return s.decode('string_escape')
24 24
25 25 class engine(object):
26 26 '''template expansion engine.
27 27
28 28 template expansion works like this. a map file contains key=value
29 29 pairs. if value is quoted, it is treated as string. otherwise, it
30 30 is treated as name of template file.
31 31
32 32 templater is asked to expand a key in map. it looks up key, and
33 33 looks for strings like this: {foo}. it expands {foo} by looking up
34 34 foo in map, and substituting it. expansion is recursive: it stops
35 35 when there is no more {foo} to replace.
36 36
37 37 expansion also allows formatting and filtering.
38 38
39 39 format uses key to expand each item in list. syntax is
40 40 {key%format}.
41 41
42 42 filter uses function to transform value. syntax is
43 43 {key|filter1|filter2|...}.'''
44 44
45 45 template_re = re.compile(r'{([\w\|%]+)}|#([\w\|%]+)#')
46 46
47 47 def __init__(self, loader, filters={}, defaults={}):
48 48 self.loader = loader
49 49 self.filters = filters
50 50 self.defaults = defaults
51 51 self.cache = {}
52 52
53 53 def process(self, t, map):
54 54 '''Perform expansion. t is name of map element to expand. map contains
55 55 added elements for use during expansion. Is a generator.'''
56 56 tmpl = self.loader(t)
57 57 iters = [self._process(tmpl, map)]
58 58 while iters:
59 59 try:
60 60 item = iters[0].next()
61 61 except StopIteration:
62 62 iters.pop(0)
63 63 continue
64 64 if isinstance(item, str):
65 65 yield item
66 66 elif item is None:
67 67 yield ''
68 68 elif hasattr(item, '__iter__'):
69 69 iters.insert(0, iter(item))
70 70 else:
71 71 yield str(item)
72 72
73 73 def _format(self, expr, get, map):
74 74 key, format = expr.split('%')
75 75 v = get(key)
76 76 if not hasattr(v, '__iter__'):
77 77 raise SyntaxError(_("Error expanding '%s%%%s'") % (key, format))
78 78 lm = map.copy()
79 79 for i in v:
80 80 lm.update(i)
81 81 yield self.process(format, lm)
82 82
83 83 def _filter(self, expr, get, map):
84 84 if expr not in self.cache:
85 85 parts = expr.split('|')
86 filters = parts[1:]
87 for f in filters:
88 if f not in self.filters:
89 raise SyntaxError(_("unknown filter '%s'") % expr)
90 calls = '('.join(i for i in reversed(filters))
91 end = ')' * len(filters)
92 code = "lambda _get: %s(_get('%s')%s" % (calls, parts[0], end)
93 self.cache[expr] = eval(code, self.filters)
86 val = parts[0]
87 try:
88 filters = [self.filters[f] for f in parts[1:]]
89 except KeyError, i:
90 raise SyntaxError(_("unknown filter '%s'") % i[0])
91 def apply(get):
92 x = get(val)
93 for f in filters:
94 x = f(x)
95 return x
96 self.cache[expr] = apply
94 97 return self.cache[expr](get)
95 98
96 99 def _process(self, tmpl, map):
97 100 '''Render a template. Returns a generator.'''
98 101
99 102 def get(key):
100 103 v = map.get(key)
101 104 if v is None:
102 105 v = self.defaults.get(key, '')
103 106 if hasattr(v, '__call__'):
104 107 v = v(**map)
105 108 return v
106 109
107 110 while tmpl:
108 111 m = self.template_re.search(tmpl)
109 112 if not m:
110 113 yield tmpl
111 114 break
112 115
113 116 start, end = m.span(0)
114 117 variants = m.groups()
115 118 expr = variants[0] or variants[1]
116 119
117 120 if start:
118 121 yield tmpl[:start]
119 122 tmpl = tmpl[end:]
120 123
121 124 if '%' in expr:
122 125 yield self._format(expr, get, map)
123 126 elif '|' in expr:
124 127 yield self._filter(expr, get, map)
125 128 else:
126 129 yield get(expr)
127 130
128 131 engines = {'default': engine}
129 132
130 133 class templater(object):
131 134
132 135 def __init__(self, mapfile, filters={}, defaults={}, cache={},
133 136 minchunk=1024, maxchunk=65536):
134 137 '''set up template engine.
135 138 mapfile is name of file to read map definitions from.
136 139 filters is dict of functions. each transforms a value into another.
137 140 defaults is dict of default map definitions.'''
138 141 self.mapfile = mapfile or 'template'
139 142 self.cache = cache.copy()
140 143 self.map = {}
141 144 self.base = (mapfile and os.path.dirname(mapfile)) or ''
142 145 self.filters = templatefilters.filters.copy()
143 146 self.filters.update(filters)
144 147 self.defaults = defaults
145 148 self.minchunk, self.maxchunk = minchunk, maxchunk
146 149 self.engines = {}
147 150
148 151 if not mapfile:
149 152 return
150 153 if not os.path.exists(mapfile):
151 154 raise util.Abort(_('style not found: %s') % mapfile)
152 155
153 156 conf = config.config()
154 157 conf.read(mapfile)
155 158
156 159 for key, val in conf[''].items():
157 160 if val[0] in "'\"":
158 161 try:
159 162 self.cache[key] = parsestring(val)
160 163 except SyntaxError, inst:
161 164 raise SyntaxError('%s: %s' %
162 165 (conf.source('', key), inst.args[0]))
163 166 else:
164 167 val = 'default', val
165 168 if ':' in val[1]:
166 169 val = val[1].split(':', 1)
167 170 self.map[key] = val[0], os.path.join(self.base, val[1])
168 171
169 172 def __contains__(self, key):
170 173 return key in self.cache or key in self.map
171 174
172 175 def load(self, t):
173 176 '''Get the template for the given template name. Use a local cache.'''
174 177 if not t in self.cache:
175 178 try:
176 179 self.cache[t] = open(self.map[t][1]).read()
177 180 except IOError, inst:
178 181 raise IOError(inst.args[0], _('template file %s: %s') %
179 182 (self.map[t][1], inst.args[1]))
180 183 return self.cache[t]
181 184
182 185 def __call__(self, t, **map):
183 186 ttype = t in self.map and self.map[t][0] or 'default'
184 187 proc = self.engines.get(ttype)
185 188 if proc is None:
186 189 proc = engines[ttype](self.load, self.filters, self.defaults)
187 190 self.engines[ttype] = proc
188 191
189 192 stream = proc.process(t, map)
190 193 if self.minchunk:
191 194 stream = util.increasingchunks(stream, min=self.minchunk,
192 195 max=self.maxchunk)
193 196 return stream
194 197
195 198 def templatepath(name=None):
196 199 '''return location of template file or directory (if no name).
197 200 returns None if not found.'''
198 201 normpaths = []
199 202
200 203 # executable version (py2exe) doesn't support __file__
201 204 if hasattr(sys, 'frozen'):
202 205 module = sys.executable
203 206 else:
204 207 module = __file__
205 208 for f in path:
206 209 if f.startswith('/'):
207 210 p = f
208 211 else:
209 212 fl = f.split('/')
210 213 p = os.path.join(os.path.dirname(module), *fl)
211 214 if name:
212 215 p = os.path.join(p, name)
213 216 if name and os.path.exists(p):
214 217 return os.path.normpath(p)
215 218 elif os.path.isdir(p):
216 219 normpaths.append(os.path.normpath(p))
217 220
218 221 return normpaths
219 222
220 223 def stylemap(style, paths=None):
221 224 """Return path to mapfile for a given style.
222 225
223 226 Searches mapfile in the following locations:
224 227 1. templatepath/style/map
225 228 2. templatepath/map-style
226 229 3. templatepath/map
227 230 """
228 231
229 232 if paths is None:
230 233 paths = templatepath()
231 234 elif isinstance(paths, str):
232 235 paths = [paths]
233 236
234 237 locations = style and [os.path.join(style, "map"), "map-" + style] or []
235 238 locations.append("map")
236 239 for path in paths:
237 240 for location in locations:
238 241 mapfile = os.path.join(path, location)
239 242 if os.path.isfile(mapfile):
240 243 return mapfile
241 244
242 245 raise RuntimeError("No hgweb templates found in %r" % paths)
General Comments 0
You need to be logged in to leave comments. Login now