##// END OF EJS Templates
templater: drop redundant return in _flatten
Matt Mackall -
r17729:9a3cb3ce default
parent child Browse files
Show More
@@ -1,473 +1,471
1 # templater.py - template expansion for output
1 # templater.py - template expansion for output
2 #
2 #
3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from i18n import _
8 from i18n import _
9 import sys, os, re
9 import sys, os, re
10 import util, config, templatefilters, parser, error
10 import util, config, templatefilters, parser, error
11
11
12 # template parsing
12 # template parsing
13
13
14 elements = {
14 elements = {
15 "(": (20, ("group", 1, ")"), ("func", 1, ")")),
15 "(": (20, ("group", 1, ")"), ("func", 1, ")")),
16 ",": (2, None, ("list", 2)),
16 ",": (2, None, ("list", 2)),
17 "|": (5, None, ("|", 5)),
17 "|": (5, None, ("|", 5)),
18 "%": (6, None, ("%", 6)),
18 "%": (6, None, ("%", 6)),
19 ")": (0, None, None),
19 ")": (0, None, None),
20 "symbol": (0, ("symbol",), None),
20 "symbol": (0, ("symbol",), None),
21 "string": (0, ("string",), None),
21 "string": (0, ("string",), None),
22 "end": (0, None, None),
22 "end": (0, None, None),
23 }
23 }
24
24
25 def tokenizer(data):
25 def tokenizer(data):
26 program, start, end = data
26 program, start, end = data
27 pos = start
27 pos = start
28 while pos < end:
28 while pos < end:
29 c = program[pos]
29 c = program[pos]
30 if c.isspace(): # skip inter-token whitespace
30 if c.isspace(): # skip inter-token whitespace
31 pass
31 pass
32 elif c in "(,)%|": # handle simple operators
32 elif c in "(,)%|": # handle simple operators
33 yield (c, None, pos)
33 yield (c, None, pos)
34 elif (c in '"\'' or c == 'r' and
34 elif (c in '"\'' or c == 'r' and
35 program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings
35 program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings
36 if c == 'r':
36 if c == 'r':
37 pos += 1
37 pos += 1
38 c = program[pos]
38 c = program[pos]
39 decode = False
39 decode = False
40 else:
40 else:
41 decode = True
41 decode = True
42 pos += 1
42 pos += 1
43 s = pos
43 s = pos
44 while pos < end: # find closing quote
44 while pos < end: # find closing quote
45 d = program[pos]
45 d = program[pos]
46 if decode and d == '\\': # skip over escaped characters
46 if decode and d == '\\': # skip over escaped characters
47 pos += 2
47 pos += 2
48 continue
48 continue
49 if d == c:
49 if d == c:
50 if not decode:
50 if not decode:
51 yield ('string', program[s:pos].replace('\\', r'\\'), s)
51 yield ('string', program[s:pos].replace('\\', r'\\'), s)
52 break
52 break
53 yield ('string', program[s:pos].decode('string-escape'), s)
53 yield ('string', program[s:pos].decode('string-escape'), s)
54 break
54 break
55 pos += 1
55 pos += 1
56 else:
56 else:
57 raise error.ParseError(_("unterminated string"), s)
57 raise error.ParseError(_("unterminated string"), s)
58 elif c.isalnum() or c in '_':
58 elif c.isalnum() or c in '_':
59 s = pos
59 s = pos
60 pos += 1
60 pos += 1
61 while pos < end: # find end of symbol
61 while pos < end: # find end of symbol
62 d = program[pos]
62 d = program[pos]
63 if not (d.isalnum() or d == "_"):
63 if not (d.isalnum() or d == "_"):
64 break
64 break
65 pos += 1
65 pos += 1
66 sym = program[s:pos]
66 sym = program[s:pos]
67 yield ('symbol', sym, s)
67 yield ('symbol', sym, s)
68 pos -= 1
68 pos -= 1
69 elif c == '}':
69 elif c == '}':
70 pos += 1
70 pos += 1
71 break
71 break
72 else:
72 else:
73 raise error.ParseError(_("syntax error"), pos)
73 raise error.ParseError(_("syntax error"), pos)
74 pos += 1
74 pos += 1
75 yield ('end', None, pos)
75 yield ('end', None, pos)
76
76
77 def compiletemplate(tmpl, context):
77 def compiletemplate(tmpl, context):
78 parsed = []
78 parsed = []
79 pos, stop = 0, len(tmpl)
79 pos, stop = 0, len(tmpl)
80 p = parser.parser(tokenizer, elements)
80 p = parser.parser(tokenizer, elements)
81
81
82 while pos < stop:
82 while pos < stop:
83 n = tmpl.find('{', pos)
83 n = tmpl.find('{', pos)
84 if n < 0:
84 if n < 0:
85 parsed.append(("string", tmpl[pos:]))
85 parsed.append(("string", tmpl[pos:]))
86 break
86 break
87 if n > 0 and tmpl[n - 1] == '\\':
87 if n > 0 and tmpl[n - 1] == '\\':
88 # escaped
88 # escaped
89 parsed.append(("string", tmpl[pos:n - 1] + "{"))
89 parsed.append(("string", tmpl[pos:n - 1] + "{"))
90 pos = n + 1
90 pos = n + 1
91 continue
91 continue
92 if n > pos:
92 if n > pos:
93 parsed.append(("string", tmpl[pos:n]))
93 parsed.append(("string", tmpl[pos:n]))
94
94
95 pd = [tmpl, n + 1, stop]
95 pd = [tmpl, n + 1, stop]
96 parseres, pos = p.parse(pd)
96 parseres, pos = p.parse(pd)
97 parsed.append(parseres)
97 parsed.append(parseres)
98
98
99 return [compileexp(e, context) for e in parsed]
99 return [compileexp(e, context) for e in parsed]
100
100
101 def compileexp(exp, context):
101 def compileexp(exp, context):
102 t = exp[0]
102 t = exp[0]
103 if t in methods:
103 if t in methods:
104 return methods[t](exp, context)
104 return methods[t](exp, context)
105 raise error.ParseError(_("unknown method '%s'") % t)
105 raise error.ParseError(_("unknown method '%s'") % t)
106
106
107 # template evaluation
107 # template evaluation
108
108
109 def getsymbol(exp):
109 def getsymbol(exp):
110 if exp[0] == 'symbol':
110 if exp[0] == 'symbol':
111 return exp[1]
111 return exp[1]
112 raise error.ParseError(_("expected a symbol"))
112 raise error.ParseError(_("expected a symbol"))
113
113
114 def getlist(x):
114 def getlist(x):
115 if not x:
115 if not x:
116 return []
116 return []
117 if x[0] == 'list':
117 if x[0] == 'list':
118 return getlist(x[1]) + [x[2]]
118 return getlist(x[1]) + [x[2]]
119 return [x]
119 return [x]
120
120
121 def getfilter(exp, context):
121 def getfilter(exp, context):
122 f = getsymbol(exp)
122 f = getsymbol(exp)
123 if f not in context._filters:
123 if f not in context._filters:
124 raise error.ParseError(_("unknown function '%s'") % f)
124 raise error.ParseError(_("unknown function '%s'") % f)
125 return context._filters[f]
125 return context._filters[f]
126
126
127 def gettemplate(exp, context):
127 def gettemplate(exp, context):
128 if exp[0] == 'string':
128 if exp[0] == 'string':
129 return compiletemplate(exp[1], context)
129 return compiletemplate(exp[1], context)
130 if exp[0] == 'symbol':
130 if exp[0] == 'symbol':
131 return context._load(exp[1])
131 return context._load(exp[1])
132 raise error.ParseError(_("expected template specifier"))
132 raise error.ParseError(_("expected template specifier"))
133
133
134 def runstring(context, mapping, data):
134 def runstring(context, mapping, data):
135 return data
135 return data
136
136
137 def runsymbol(context, mapping, key):
137 def runsymbol(context, mapping, key):
138 v = mapping.get(key)
138 v = mapping.get(key)
139 if v is None:
139 if v is None:
140 v = context._defaults.get(key, '')
140 v = context._defaults.get(key, '')
141 if util.safehasattr(v, '__call__'):
141 if util.safehasattr(v, '__call__'):
142 return v(**mapping)
142 return v(**mapping)
143 return v
143 return v
144
144
145 def buildfilter(exp, context):
145 def buildfilter(exp, context):
146 func, data = compileexp(exp[1], context)
146 func, data = compileexp(exp[1], context)
147 filt = getfilter(exp[2], context)
147 filt = getfilter(exp[2], context)
148 return (runfilter, (func, data, filt))
148 return (runfilter, (func, data, filt))
149
149
150 def runfilter(context, mapping, data):
150 def runfilter(context, mapping, data):
151 func, data, filt = data
151 func, data, filt = data
152 try:
152 try:
153 return filt(func(context, mapping, data))
153 return filt(func(context, mapping, data))
154 except (ValueError, AttributeError, TypeError):
154 except (ValueError, AttributeError, TypeError):
155 if isinstance(data, tuple):
155 if isinstance(data, tuple):
156 dt = data[1]
156 dt = data[1]
157 else:
157 else:
158 dt = data
158 dt = data
159 raise util.Abort(_("template filter '%s' is not compatible with "
159 raise util.Abort(_("template filter '%s' is not compatible with "
160 "keyword '%s'") % (filt.func_name, dt))
160 "keyword '%s'") % (filt.func_name, dt))
161
161
162 def buildmap(exp, context):
162 def buildmap(exp, context):
163 func, data = compileexp(exp[1], context)
163 func, data = compileexp(exp[1], context)
164 ctmpl = gettemplate(exp[2], context)
164 ctmpl = gettemplate(exp[2], context)
165 return (runmap, (func, data, ctmpl))
165 return (runmap, (func, data, ctmpl))
166
166
167 def runtemplate(context, mapping, template):
167 def runtemplate(context, mapping, template):
168 for func, data in template:
168 for func, data in template:
169 yield func(context, mapping, data)
169 yield func(context, mapping, data)
170
170
171 def runmap(context, mapping, data):
171 def runmap(context, mapping, data):
172 func, data, ctmpl = data
172 func, data, ctmpl = data
173 d = func(context, mapping, data)
173 d = func(context, mapping, data)
174 if util.safehasattr(d, '__call__'):
174 if util.safehasattr(d, '__call__'):
175 d = d()
175 d = d()
176
176
177 lm = mapping.copy()
177 lm = mapping.copy()
178
178
179 for i in d:
179 for i in d:
180 if isinstance(i, dict):
180 if isinstance(i, dict):
181 lm.update(i)
181 lm.update(i)
182 yield runtemplate(context, lm, ctmpl)
182 yield runtemplate(context, lm, ctmpl)
183 else:
183 else:
184 # v is not an iterable of dicts, this happen when 'key'
184 # v is not an iterable of dicts, this happen when 'key'
185 # has been fully expanded already and format is useless.
185 # has been fully expanded already and format is useless.
186 # If so, return the expanded value.
186 # If so, return the expanded value.
187 yield i
187 yield i
188
188
189 def buildfunc(exp, context):
189 def buildfunc(exp, context):
190 n = getsymbol(exp[1])
190 n = getsymbol(exp[1])
191 args = [compileexp(x, context) for x in getlist(exp[2])]
191 args = [compileexp(x, context) for x in getlist(exp[2])]
192 if n in funcs:
192 if n in funcs:
193 f = funcs[n]
193 f = funcs[n]
194 return (f, args)
194 return (f, args)
195 if n in templatefilters.funcs:
195 if n in templatefilters.funcs:
196 f = templatefilters.funcs[n]
196 f = templatefilters.funcs[n]
197 return (f, args)
197 return (f, args)
198 if n in context._filters:
198 if n in context._filters:
199 if len(args) != 1:
199 if len(args) != 1:
200 raise error.ParseError(_("filter %s expects one argument") % n)
200 raise error.ParseError(_("filter %s expects one argument") % n)
201 f = context._filters[n]
201 f = context._filters[n]
202 return (runfilter, (args[0][0], args[0][1], f))
202 return (runfilter, (args[0][0], args[0][1], f))
203
203
204 def join(context, mapping, args):
204 def join(context, mapping, args):
205 if not (1 <= len(args) <= 2):
205 if not (1 <= len(args) <= 2):
206 raise error.ParseError(_("join expects one or two arguments"))
206 raise error.ParseError(_("join expects one or two arguments"))
207
207
208 joinset = args[0][0](context, mapping, args[0][1])
208 joinset = args[0][0](context, mapping, args[0][1])
209 if util.safehasattr(joinset, '__call__'):
209 if util.safehasattr(joinset, '__call__'):
210 joinset = [x.values()[0] for x in joinset()]
210 joinset = [x.values()[0] for x in joinset()]
211
211
212 joiner = " "
212 joiner = " "
213 if len(args) > 1:
213 if len(args) > 1:
214 joiner = args[1][0](context, mapping, args[1][1])
214 joiner = args[1][0](context, mapping, args[1][1])
215
215
216 first = True
216 first = True
217 for x in joinset:
217 for x in joinset:
218 if first:
218 if first:
219 first = False
219 first = False
220 else:
220 else:
221 yield joiner
221 yield joiner
222 yield x
222 yield x
223
223
224 def sub(context, mapping, args):
224 def sub(context, mapping, args):
225 if len(args) != 3:
225 if len(args) != 3:
226 raise error.ParseError(_("sub expects three arguments"))
226 raise error.ParseError(_("sub expects three arguments"))
227
227
228 pat = stringify(args[0][0](context, mapping, args[0][1]))
228 pat = stringify(args[0][0](context, mapping, args[0][1]))
229 rpl = stringify(args[1][0](context, mapping, args[1][1]))
229 rpl = stringify(args[1][0](context, mapping, args[1][1]))
230 src = stringify(args[2][0](context, mapping, args[2][1]))
230 src = stringify(args[2][0](context, mapping, args[2][1]))
231 yield re.sub(pat, rpl, src)
231 yield re.sub(pat, rpl, src)
232
232
233 def if_(context, mapping, args):
233 def if_(context, mapping, args):
234 if not (2 <= len(args) <= 3):
234 if not (2 <= len(args) <= 3):
235 raise error.ParseError(_("if expects two or three arguments"))
235 raise error.ParseError(_("if expects two or three arguments"))
236
236
237 test = stringify(args[0][0](context, mapping, args[0][1]))
237 test = stringify(args[0][0](context, mapping, args[0][1]))
238 if test:
238 if test:
239 t = stringify(args[1][0](context, mapping, args[1][1]))
239 t = stringify(args[1][0](context, mapping, args[1][1]))
240 yield runtemplate(context, mapping, compiletemplate(t, context))
240 yield runtemplate(context, mapping, compiletemplate(t, context))
241 elif len(args) == 3:
241 elif len(args) == 3:
242 t = stringify(args[2][0](context, mapping, args[2][1]))
242 t = stringify(args[2][0](context, mapping, args[2][1]))
243 yield runtemplate(context, mapping, compiletemplate(t, context))
243 yield runtemplate(context, mapping, compiletemplate(t, context))
244
244
245 def ifeq(context, mapping, args):
245 def ifeq(context, mapping, args):
246 if not (3 <= len(args) <= 4):
246 if not (3 <= len(args) <= 4):
247 raise error.ParseError(_("ifeq expects three or four arguments"))
247 raise error.ParseError(_("ifeq expects three or four arguments"))
248
248
249 test = stringify(args[0][0](context, mapping, args[0][1]))
249 test = stringify(args[0][0](context, mapping, args[0][1]))
250 match = stringify(args[1][0](context, mapping, args[1][1]))
250 match = stringify(args[1][0](context, mapping, args[1][1]))
251 if test == match:
251 if test == match:
252 t = stringify(args[2][0](context, mapping, args[2][1]))
252 t = stringify(args[2][0](context, mapping, args[2][1]))
253 yield runtemplate(context, mapping, compiletemplate(t, context))
253 yield runtemplate(context, mapping, compiletemplate(t, context))
254 elif len(args) == 4:
254 elif len(args) == 4:
255 t = stringify(args[3][0](context, mapping, args[3][1]))
255 t = stringify(args[3][0](context, mapping, args[3][1]))
256 yield runtemplate(context, mapping, compiletemplate(t, context))
256 yield runtemplate(context, mapping, compiletemplate(t, context))
257
257
258 methods = {
258 methods = {
259 "string": lambda e, c: (runstring, e[1]),
259 "string": lambda e, c: (runstring, e[1]),
260 "symbol": lambda e, c: (runsymbol, e[1]),
260 "symbol": lambda e, c: (runsymbol, e[1]),
261 "group": lambda e, c: compileexp(e[1], c),
261 "group": lambda e, c: compileexp(e[1], c),
262 # ".": buildmember,
262 # ".": buildmember,
263 "|": buildfilter,
263 "|": buildfilter,
264 "%": buildmap,
264 "%": buildmap,
265 "func": buildfunc,
265 "func": buildfunc,
266 }
266 }
267
267
268 funcs = {
268 funcs = {
269 "if": if_,
269 "if": if_,
270 "ifeq": ifeq,
270 "ifeq": ifeq,
271 "join": join,
271 "join": join,
272 "sub": sub,
272 "sub": sub,
273 }
273 }
274
274
275 # template engine
275 # template engine
276
276
277 path = ['templates', '../templates']
277 path = ['templates', '../templates']
278 stringify = templatefilters.stringify
278 stringify = templatefilters.stringify
279
279
280 def _flatten(thing):
280 def _flatten(thing):
281 '''yield a single stream from a possibly nested set of iterators'''
281 '''yield a single stream from a possibly nested set of iterators'''
282 if isinstance(thing, str):
282 if isinstance(thing, str):
283 yield thing
283 yield thing
284 elif not util.safehasattr(thing, '__iter__'):
284 elif not util.safehasattr(thing, '__iter__'):
285 if thing is not None:
285 if thing is not None:
286 yield str(thing)
286 yield str(thing)
287 else:
287 else:
288 for i in thing:
288 for i in thing:
289 if isinstance(i, str):
289 if isinstance(i, str):
290 yield i
290 yield i
291 elif not util.safehasattr(i, '__iter__'):
291 elif not util.safehasattr(i, '__iter__'):
292 if i is not None:
292 if i is not None:
293 yield str(i)
293 yield str(i)
294 elif i is not None:
294 elif i is not None:
295 for j in _flatten(i):
295 for j in _flatten(i):
296 yield j
296 yield j
297
297
298 def parsestring(s, quoted=True):
298 def parsestring(s, quoted=True):
299 '''parse a string using simple c-like syntax.
299 '''parse a string using simple c-like syntax.
300 string must be in quotes if quoted is True.'''
300 string must be in quotes if quoted is True.'''
301 if quoted:
301 if quoted:
302 if len(s) < 2 or s[0] != s[-1]:
302 if len(s) < 2 or s[0] != s[-1]:
303 raise SyntaxError(_('unmatched quotes'))
303 raise SyntaxError(_('unmatched quotes'))
304 return s[1:-1].decode('string_escape')
304 return s[1:-1].decode('string_escape')
305
305
306 return s.decode('string_escape')
306 return s.decode('string_escape')
307
307
308 class engine(object):
308 class engine(object):
309 '''template expansion engine.
309 '''template expansion engine.
310
310
311 template expansion works like this. a map file contains key=value
311 template expansion works like this. a map file contains key=value
312 pairs. if value is quoted, it is treated as string. otherwise, it
312 pairs. if value is quoted, it is treated as string. otherwise, it
313 is treated as name of template file.
313 is treated as name of template file.
314
314
315 templater is asked to expand a key in map. it looks up key, and
315 templater is asked to expand a key in map. it looks up key, and
316 looks for strings like this: {foo}. it expands {foo} by looking up
316 looks for strings like this: {foo}. it expands {foo} by looking up
317 foo in map, and substituting it. expansion is recursive: it stops
317 foo in map, and substituting it. expansion is recursive: it stops
318 when there is no more {foo} to replace.
318 when there is no more {foo} to replace.
319
319
320 expansion also allows formatting and filtering.
320 expansion also allows formatting and filtering.
321
321
322 format uses key to expand each item in list. syntax is
322 format uses key to expand each item in list. syntax is
323 {key%format}.
323 {key%format}.
324
324
325 filter uses function to transform value. syntax is
325 filter uses function to transform value. syntax is
326 {key|filter1|filter2|...}.'''
326 {key|filter1|filter2|...}.'''
327
327
328 def __init__(self, loader, filters={}, defaults={}):
328 def __init__(self, loader, filters={}, defaults={}):
329 self._loader = loader
329 self._loader = loader
330 self._filters = filters
330 self._filters = filters
331 self._defaults = defaults
331 self._defaults = defaults
332 self._cache = {}
332 self._cache = {}
333
333
334 def _load(self, t):
334 def _load(self, t):
335 '''load, parse, and cache a template'''
335 '''load, parse, and cache a template'''
336 if t not in self._cache:
336 if t not in self._cache:
337 self._cache[t] = compiletemplate(self._loader(t), self)
337 self._cache[t] = compiletemplate(self._loader(t), self)
338 return self._cache[t]
338 return self._cache[t]
339
339
340 def process(self, t, mapping):
340 def process(self, t, mapping):
341 '''Perform expansion. t is name of map element to expand.
341 '''Perform expansion. t is name of map element to expand.
342 mapping contains added elements for use during expansion. Is a
342 mapping contains added elements for use during expansion. Is a
343 generator.'''
343 generator.'''
344 return _flatten(func(self, mapping, data) for func, data in
345 self._load(t))
346 return _flatten(runtemplate(self, mapping, self._load(t)))
344 return _flatten(runtemplate(self, mapping, self._load(t)))
347
345
348 engines = {'default': engine}
346 engines = {'default': engine}
349
347
350 class templater(object):
348 class templater(object):
351
349
352 def __init__(self, mapfile, filters={}, defaults={}, cache={},
350 def __init__(self, mapfile, filters={}, defaults={}, cache={},
353 minchunk=1024, maxchunk=65536):
351 minchunk=1024, maxchunk=65536):
354 '''set up template engine.
352 '''set up template engine.
355 mapfile is name of file to read map definitions from.
353 mapfile is name of file to read map definitions from.
356 filters is dict of functions. each transforms a value into another.
354 filters is dict of functions. each transforms a value into another.
357 defaults is dict of default map definitions.'''
355 defaults is dict of default map definitions.'''
358 self.mapfile = mapfile or 'template'
356 self.mapfile = mapfile or 'template'
359 self.cache = cache.copy()
357 self.cache = cache.copy()
360 self.map = {}
358 self.map = {}
361 self.base = (mapfile and os.path.dirname(mapfile)) or ''
359 self.base = (mapfile and os.path.dirname(mapfile)) or ''
362 self.filters = templatefilters.filters.copy()
360 self.filters = templatefilters.filters.copy()
363 self.filters.update(filters)
361 self.filters.update(filters)
364 self.defaults = defaults
362 self.defaults = defaults
365 self.minchunk, self.maxchunk = minchunk, maxchunk
363 self.minchunk, self.maxchunk = minchunk, maxchunk
366 self.ecache = {}
364 self.ecache = {}
367
365
368 if not mapfile:
366 if not mapfile:
369 return
367 return
370 if not os.path.exists(mapfile):
368 if not os.path.exists(mapfile):
371 raise util.Abort(_('style not found: %s') % mapfile)
369 raise util.Abort(_('style not found: %s') % mapfile)
372
370
373 conf = config.config()
371 conf = config.config()
374 conf.read(mapfile)
372 conf.read(mapfile)
375
373
376 for key, val in conf[''].items():
374 for key, val in conf[''].items():
377 if not val:
375 if not val:
378 raise SyntaxError(_('%s: missing value') % conf.source('', key))
376 raise SyntaxError(_('%s: missing value') % conf.source('', key))
379 if val[0] in "'\"":
377 if val[0] in "'\"":
380 try:
378 try:
381 self.cache[key] = parsestring(val)
379 self.cache[key] = parsestring(val)
382 except SyntaxError, inst:
380 except SyntaxError, inst:
383 raise SyntaxError('%s: %s' %
381 raise SyntaxError('%s: %s' %
384 (conf.source('', key), inst.args[0]))
382 (conf.source('', key), inst.args[0]))
385 else:
383 else:
386 val = 'default', val
384 val = 'default', val
387 if ':' in val[1]:
385 if ':' in val[1]:
388 val = val[1].split(':', 1)
386 val = val[1].split(':', 1)
389 self.map[key] = val[0], os.path.join(self.base, val[1])
387 self.map[key] = val[0], os.path.join(self.base, val[1])
390
388
391 def __contains__(self, key):
389 def __contains__(self, key):
392 return key in self.cache or key in self.map
390 return key in self.cache or key in self.map
393
391
394 def load(self, t):
392 def load(self, t):
395 '''Get the template for the given template name. Use a local cache.'''
393 '''Get the template for the given template name. Use a local cache.'''
396 if t not in self.cache:
394 if t not in self.cache:
397 try:
395 try:
398 self.cache[t] = util.readfile(self.map[t][1])
396 self.cache[t] = util.readfile(self.map[t][1])
399 except KeyError, inst:
397 except KeyError, inst:
400 raise util.Abort(_('"%s" not in template map') % inst.args[0])
398 raise util.Abort(_('"%s" not in template map') % inst.args[0])
401 except IOError, inst:
399 except IOError, inst:
402 raise IOError(inst.args[0], _('template file %s: %s') %
400 raise IOError(inst.args[0], _('template file %s: %s') %
403 (self.map[t][1], inst.args[1]))
401 (self.map[t][1], inst.args[1]))
404 return self.cache[t]
402 return self.cache[t]
405
403
406 def __call__(self, t, **mapping):
404 def __call__(self, t, **mapping):
407 ttype = t in self.map and self.map[t][0] or 'default'
405 ttype = t in self.map and self.map[t][0] or 'default'
408 if ttype not in self.ecache:
406 if ttype not in self.ecache:
409 self.ecache[ttype] = engines[ttype](self.load,
407 self.ecache[ttype] = engines[ttype](self.load,
410 self.filters, self.defaults)
408 self.filters, self.defaults)
411 proc = self.ecache[ttype]
409 proc = self.ecache[ttype]
412
410
413 stream = proc.process(t, mapping)
411 stream = proc.process(t, mapping)
414 if self.minchunk:
412 if self.minchunk:
415 stream = util.increasingchunks(stream, min=self.minchunk,
413 stream = util.increasingchunks(stream, min=self.minchunk,
416 max=self.maxchunk)
414 max=self.maxchunk)
417 return stream
415 return stream
418
416
419 def templatepath(name=None):
417 def templatepath(name=None):
420 '''return location of template file or directory (if no name).
418 '''return location of template file or directory (if no name).
421 returns None if not found.'''
419 returns None if not found.'''
422 normpaths = []
420 normpaths = []
423
421
424 # executable version (py2exe) doesn't support __file__
422 # executable version (py2exe) doesn't support __file__
425 if util.mainfrozen():
423 if util.mainfrozen():
426 module = sys.executable
424 module = sys.executable
427 else:
425 else:
428 module = __file__
426 module = __file__
429 for f in path:
427 for f in path:
430 if f.startswith('/'):
428 if f.startswith('/'):
431 p = f
429 p = f
432 else:
430 else:
433 fl = f.split('/')
431 fl = f.split('/')
434 p = os.path.join(os.path.dirname(module), *fl)
432 p = os.path.join(os.path.dirname(module), *fl)
435 if name:
433 if name:
436 p = os.path.join(p, name)
434 p = os.path.join(p, name)
437 if name and os.path.exists(p):
435 if name and os.path.exists(p):
438 return os.path.normpath(p)
436 return os.path.normpath(p)
439 elif os.path.isdir(p):
437 elif os.path.isdir(p):
440 normpaths.append(os.path.normpath(p))
438 normpaths.append(os.path.normpath(p))
441
439
442 return normpaths
440 return normpaths
443
441
444 def stylemap(styles, paths=None):
442 def stylemap(styles, paths=None):
445 """Return path to mapfile for a given style.
443 """Return path to mapfile for a given style.
446
444
447 Searches mapfile in the following locations:
445 Searches mapfile in the following locations:
448 1. templatepath/style/map
446 1. templatepath/style/map
449 2. templatepath/map-style
447 2. templatepath/map-style
450 3. templatepath/map
448 3. templatepath/map
451 """
449 """
452
450
453 if paths is None:
451 if paths is None:
454 paths = templatepath()
452 paths = templatepath()
455 elif isinstance(paths, str):
453 elif isinstance(paths, str):
456 paths = [paths]
454 paths = [paths]
457
455
458 if isinstance(styles, str):
456 if isinstance(styles, str):
459 styles = [styles]
457 styles = [styles]
460
458
461 for style in styles:
459 for style in styles:
462 if not style:
460 if not style:
463 continue
461 continue
464 locations = [os.path.join(style, 'map'), 'map-' + style]
462 locations = [os.path.join(style, 'map'), 'map-' + style]
465 locations.append('map')
463 locations.append('map')
466
464
467 for path in paths:
465 for path in paths:
468 for location in locations:
466 for location in locations:
469 mapfile = os.path.join(path, location)
467 mapfile = os.path.join(path, location)
470 if os.path.isfile(mapfile):
468 if os.path.isfile(mapfile):
471 return style, mapfile
469 return style, mapfile
472
470
473 raise RuntimeError("No hgweb templates found in %r" % paths)
471 raise RuntimeError("No hgweb templates found in %r" % paths)
General Comments 0
You need to be logged in to leave comments. Login now