##// END OF EJS Templates
templater: privatize class variables
Matt Mackall -
r10848:01346cea default
parent child Browse files
Show More
@@ -1,277 +1,275
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 or any later version.
7 7
8 8 from i18n import _
9 9 import 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 def __init__(self, loader, filters={}, defaults={}):
46 self.loader = loader
47 self.filters = filters
48 self.defaults = defaults
49 self.cache = {}
50 self.parsecache = {}
46 self._loader = loader
47 self._filters = filters
48 self._defaults = defaults
49 self._cache = {}
51 50
52 51 def process(self, t, mapping):
53 52 '''Perform expansion. t is name of map element to expand. mapping contains
54 53 added elements for use during expansion. Is a generator.'''
55 if t not in self.parsecache:
56 tmpl = self.loader(t)
57 self.parsecache[t] = self._parse(tmpl)
58 parsed = self.parsecache[t]
54 if t not in self._cache:
55 self._cache[t] = self._parse(self._loader(t))
56 parsed = self._cache[t]
59 57 iters = [self._process(parsed, mapping)]
60 58 while iters:
61 59 try:
62 60 item = iters[0].next()
63 61 except StopIteration:
64 62 iters.pop(0)
65 63 continue
66 64 if isinstance(item, str):
67 65 yield item
68 66 elif item is None:
69 67 yield ''
70 68 elif hasattr(item, '__iter__'):
71 69 iters.insert(0, iter(item))
72 70 else:
73 71 yield str(item)
74 72
75 73 def _parse(self, tmpl):
76 74 '''preparse a template'''
77 75
78 76 def getter(mapping, key):
79 77 v = mapping.get(key)
80 78 if v is None:
81 79 v = self.defaults.get(key, '')
82 80 if hasattr(v, '__call__'):
83 81 v = v(**mapping)
84 82 return v
85 83
86 84 def raw(mapping, x):
87 85 return x
88 86 def filt(mapping, parts):
89 87 filters, val = parts
90 88 x = getter(mapping, val)
91 89 for f in filters:
92 90 x = f(x)
93 91 return x
94 92 def formatter(mapping, args):
95 93 key, format = args
96 94 v = getter(mapping, key)
97 95 if not hasattr(v, '__iter__'):
98 96 raise SyntaxError(_("error expanding '%s%%%s'")
99 97 % (key, format))
100 98 lm = mapping.copy()
101 99 for i in v:
102 100 if isinstance(i, dict):
103 101 lm.update(i)
104 102 yield self.process(format, lm)
105 103 else:
106 104 # v is not an iterable of dicts, this happen when 'key'
107 105 # has been fully expanded already and format is useless.
108 106 # If so, return the expanded value.
109 107 yield i
110 108
111 109 parsed = []
112 110 pos, stop = 0, len(tmpl)
113 111 while pos < stop:
114 112 n = tmpl.find('{', pos)
115 113 if n < 0:
116 114 parsed.append((raw, tmpl[pos:stop]))
117 115 break
118 116 if n > 0 and tmpl[n - 1] == '\\':
119 117 # escaped
120 118 parsed.append((raw, tmpl[pos:n + 1]))
121 119 pos = n + 1
122 120 continue
123 121 if n > pos:
124 122 parsed.append((raw, tmpl[pos:n]))
125 123
126 124 pos = n
127 125 n = tmpl.find('}', pos)
128 126 if n < 0:
129 127 # no closing
130 128 parsed.append((raw, tmpl[pos:stop]))
131 129 break
132 130
133 131 expr = tmpl[pos + 1:n]
134 132 pos = n + 1
135 133
136 134 if '%' in expr:
137 135 parsed.append((formatter, expr.split('%')))
138 136 elif '|' in expr:
139 137 parts = expr.split('|')
140 138 val = parts[0]
141 139 try:
142 filters = [self.filters[f] for f in parts[1:]]
140 filters = [self._filters[f] for f in parts[1:]]
143 141 except KeyError, i:
144 142 raise SyntaxError(_("unknown filter '%s'") % i[0])
145 143 parsed.append((filt, (filters, val)))
146 144 else:
147 145 parsed.append((getter, expr))
148 146
149 147 return parsed
150 148
151 149 def _process(self, parsed, mapping):
152 150 '''Render a template. Returns a generator.'''
153 151 for f, e in parsed:
154 152 yield f(mapping, e)
155 153
156 154 engines = {'default': engine}
157 155
158 156 class templater(object):
159 157
160 158 def __init__(self, mapfile, filters={}, defaults={}, cache={},
161 159 minchunk=1024, maxchunk=65536):
162 160 '''set up template engine.
163 161 mapfile is name of file to read map definitions from.
164 162 filters is dict of functions. each transforms a value into another.
165 163 defaults is dict of default map definitions.'''
166 164 self.mapfile = mapfile or 'template'
167 165 self.cache = cache.copy()
168 166 self.map = {}
169 167 self.base = (mapfile and os.path.dirname(mapfile)) or ''
170 168 self.filters = templatefilters.filters.copy()
171 169 self.filters.update(filters)
172 170 self.defaults = defaults
173 171 self.minchunk, self.maxchunk = minchunk, maxchunk
174 172 self.engines = {}
175 173
176 174 if not mapfile:
177 175 return
178 176 if not os.path.exists(mapfile):
179 177 raise util.Abort(_('style not found: %s') % mapfile)
180 178
181 179 conf = config.config()
182 180 conf.read(mapfile)
183 181
184 182 for key, val in conf[''].items():
185 183 if val[0] in "'\"":
186 184 try:
187 185 self.cache[key] = parsestring(val)
188 186 except SyntaxError, inst:
189 187 raise SyntaxError('%s: %s' %
190 188 (conf.source('', key), inst.args[0]))
191 189 else:
192 190 val = 'default', val
193 191 if ':' in val[1]:
194 192 val = val[1].split(':', 1)
195 193 self.map[key] = val[0], os.path.join(self.base, val[1])
196 194
197 195 def __contains__(self, key):
198 196 return key in self.cache or key in self.map
199 197
200 198 def load(self, t):
201 199 '''Get the template for the given template name. Use a local cache.'''
202 200 if not t in self.cache:
203 201 try:
204 202 self.cache[t] = open(self.map[t][1]).read()
205 203 except IOError, inst:
206 204 raise IOError(inst.args[0], _('template file %s: %s') %
207 205 (self.map[t][1], inst.args[1]))
208 206 return self.cache[t]
209 207
210 208 def __call__(self, t, **mapping):
211 209 ttype = t in self.map and self.map[t][0] or 'default'
212 210 proc = self.engines.get(ttype)
213 211 if proc is None:
214 212 proc = engines[ttype](self.load, self.filters, self.defaults)
215 213 self.engines[ttype] = proc
216 214
217 215 stream = proc.process(t, mapping)
218 216 if self.minchunk:
219 217 stream = util.increasingchunks(stream, min=self.minchunk,
220 218 max=self.maxchunk)
221 219 return stream
222 220
223 221 def templatepath(name=None):
224 222 '''return location of template file or directory (if no name).
225 223 returns None if not found.'''
226 224 normpaths = []
227 225
228 226 # executable version (py2exe) doesn't support __file__
229 227 if hasattr(sys, 'frozen'):
230 228 module = sys.executable
231 229 else:
232 230 module = __file__
233 231 for f in path:
234 232 if f.startswith('/'):
235 233 p = f
236 234 else:
237 235 fl = f.split('/')
238 236 p = os.path.join(os.path.dirname(module), *fl)
239 237 if name:
240 238 p = os.path.join(p, name)
241 239 if name and os.path.exists(p):
242 240 return os.path.normpath(p)
243 241 elif os.path.isdir(p):
244 242 normpaths.append(os.path.normpath(p))
245 243
246 244 return normpaths
247 245
248 246 def stylemap(styles, paths=None):
249 247 """Return path to mapfile for a given style.
250 248
251 249 Searches mapfile in the following locations:
252 250 1. templatepath/style/map
253 251 2. templatepath/map-style
254 252 3. templatepath/map
255 253 """
256 254
257 255 if paths is None:
258 256 paths = templatepath()
259 257 elif isinstance(paths, str):
260 258 paths = [paths]
261 259
262 260 if isinstance(styles, str):
263 261 styles = [styles]
264 262
265 263 for style in styles:
266 264 if not style:
267 265 continue
268 266 locations = [os.path.join(style, 'map'), 'map-' + style]
269 267 locations.append('map')
270 268
271 269 for path in paths:
272 270 for location in locations:
273 271 mapfile = os.path.join(path, location)
274 272 if os.path.isfile(mapfile):
275 273 return style, mapfile
276 274
277 275 raise RuntimeError("No hgweb templates found in %r" % paths)
General Comments 0
You need to be logged in to leave comments. Login now