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