##// END OF EJS Templates
templater: sort functions alphabetically, as filters are
Alexander Plavin -
r19390:3af3a165 default
parent child Browse files
Show More
@@ -1,104 +1,104 b''
1 1 Mercurial allows you to customize output of commands through
2 2 templates. You can either pass in a template from the command
3 3 line, via the --template option, or select an existing
4 4 template-style (--style).
5 5
6 6 You can customize output for any "log-like" command: log,
7 7 outgoing, incoming, tip, parents, heads and glog.
8 8
9 9 Five styles are packaged with Mercurial: default (the style used
10 10 when no explicit preference is passed), compact, changelog, phases
11 11 and xml.
12 12 Usage::
13 13
14 14 $ hg log -r1 --style changelog
15 15
16 16 A template is a piece of text, with markup to invoke variable
17 17 expansion::
18 18
19 19 $ hg log -r1 --template "{node}\n"
20 20 b56ce7b07c52de7d5fd79fb89701ea538af65746
21 21
22 22 Strings in curly braces are called keywords. The availability of
23 23 keywords depends on the exact context of the templater. These
24 24 keywords are usually available for templating a log-like command:
25 25
26 26 .. keywordsmarker
27 27
28 28 The "date" keyword does not produce human-readable output. If you
29 29 want to use a date in your output, you can use a filter to process
30 30 it. Filters are functions which return a string based on the input
31 31 variable. Be sure to use the stringify filter first when you're
32 32 applying a string-input filter to a list-like input variable.
33 33 You can also use a chain of filters to get the desired output::
34 34
35 35 $ hg tip --template "{date|isodate}\n"
36 36 2008-08-21 18:22 +0000
37 37
38 38 List of filters:
39 39
40 40 .. filtersmarker
41 41
42 42 Note that a filter is nothing more than a function call, i.e.
43 43 ``expr|filter`` is equivalent to ``filter(expr)``.
44 44
45 45 In addition to filters, there are some basic built-in functions:
46 46
47 47 - date(date[, fmt])
48 48
49 49 - fill(text[, width])
50 50
51 51 - get(dict, key)
52 52
53 53 - if(expr, then[, else])
54 54
55 55 - ifeq(expr, expr, then[, else])
56 56
57 57 - join(list, sep)
58 58
59 59 - label(label, expr)
60 60
61 - sub(pat, repl, expr)
62
63 61 - rstdoc(text, style)
64 62
65 - strip(text, chars)
63 - strip(text[, chars])
64
65 - sub(pat, repl, expr)
66 66
67 67 Also, for any expression that returns a list, there is a list operator:
68 68
69 69 - expr % "{template}"
70 70
71 71 Some sample command line templates:
72 72
73 73 - Format lists, e.g. files::
74 74
75 75 $ hg log -r 0 --template "files:\n{files % ' {file}\n'}"
76 76
77 77 - Join the list of files with a ", "::
78 78
79 79 $ hg log -r 0 --template "files: {join(files, ', ')}\n"
80 80
81 81 - Format date::
82 82
83 83 $ hg log -r 0 --template "{date(date, '%Y')}\n"
84 84
85 85 - Output the description set to a fill-width of 30::
86 86
87 87 $ hg log -r 0 --template "{fill(desc, '30')}"
88 88
89 89 - Use a conditional to test for the default branch::
90 90
91 91 $ hg log -r 0 --template "{ifeq(branch, 'default', 'on the main branch',
92 92 'on branch {branch}')}\n"
93 93
94 94 - Append a newline if not empty::
95 95
96 96 $ hg tip --template "{if(author, '{author}\n')}"
97 97
98 98 - Label the output for use with the color extension::
99 99
100 100 $ hg log -r 0 --template "{label('changeset.{phase}', node|short)}\n"
101 101
102 102 - Invert the firstline filter, i.e. everything but the first line::
103 103
104 104 $ hg log -r 0 --template "{sub(r'^.*\n?\n?', '', desc)}\n"
@@ -1,576 +1,576 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, re
10 10 import util, config, templatefilters, parser, error
11 11 import types
12 12 import minirst
13 13
14 14 # template parsing
15 15
16 16 elements = {
17 17 "(": (20, ("group", 1, ")"), ("func", 1, ")")),
18 18 ",": (2, None, ("list", 2)),
19 19 "|": (5, None, ("|", 5)),
20 20 "%": (6, None, ("%", 6)),
21 21 ")": (0, None, None),
22 22 "symbol": (0, ("symbol",), None),
23 23 "string": (0, ("string",), None),
24 24 "end": (0, None, None),
25 25 }
26 26
27 27 def tokenizer(data):
28 28 program, start, end = data
29 29 pos = start
30 30 while pos < end:
31 31 c = program[pos]
32 32 if c.isspace(): # skip inter-token whitespace
33 33 pass
34 34 elif c in "(,)%|": # handle simple operators
35 35 yield (c, None, pos)
36 36 elif (c in '"\'' or c == 'r' and
37 37 program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings
38 38 if c == 'r':
39 39 pos += 1
40 40 c = program[pos]
41 41 decode = False
42 42 else:
43 43 decode = True
44 44 pos += 1
45 45 s = pos
46 46 while pos < end: # find closing quote
47 47 d = program[pos]
48 48 if decode and d == '\\': # skip over escaped characters
49 49 pos += 2
50 50 continue
51 51 if d == c:
52 52 if not decode:
53 53 yield ('string', program[s:pos].replace('\\', r'\\'), s)
54 54 break
55 55 yield ('string', program[s:pos].decode('string-escape'), s)
56 56 break
57 57 pos += 1
58 58 else:
59 59 raise error.ParseError(_("unterminated string"), s)
60 60 elif c.isalnum() or c in '_':
61 61 s = pos
62 62 pos += 1
63 63 while pos < end: # find end of symbol
64 64 d = program[pos]
65 65 if not (d.isalnum() or d == "_"):
66 66 break
67 67 pos += 1
68 68 sym = program[s:pos]
69 69 yield ('symbol', sym, s)
70 70 pos -= 1
71 71 elif c == '}':
72 72 pos += 1
73 73 break
74 74 else:
75 75 raise error.ParseError(_("syntax error"), pos)
76 76 pos += 1
77 77 yield ('end', None, pos)
78 78
79 79 def compiletemplate(tmpl, context):
80 80 parsed = []
81 81 pos, stop = 0, len(tmpl)
82 82 p = parser.parser(tokenizer, elements)
83 83
84 84 while pos < stop:
85 85 n = tmpl.find('{', pos)
86 86 if n < 0:
87 87 parsed.append(("string", tmpl[pos:]))
88 88 break
89 89 if n > 0 and tmpl[n - 1] == '\\':
90 90 # escaped
91 91 parsed.append(("string", tmpl[pos:n - 1] + "{"))
92 92 pos = n + 1
93 93 continue
94 94 if n > pos:
95 95 parsed.append(("string", tmpl[pos:n]))
96 96
97 97 pd = [tmpl, n + 1, stop]
98 98 parseres, pos = p.parse(pd)
99 99 parsed.append(parseres)
100 100
101 101 return [compileexp(e, context) for e in parsed]
102 102
103 103 def compileexp(exp, context):
104 104 t = exp[0]
105 105 if t in methods:
106 106 return methods[t](exp, context)
107 107 raise error.ParseError(_("unknown method '%s'") % t)
108 108
109 109 # template evaluation
110 110
111 111 def getsymbol(exp):
112 112 if exp[0] == 'symbol':
113 113 return exp[1]
114 114 raise error.ParseError(_("expected a symbol"))
115 115
116 116 def getlist(x):
117 117 if not x:
118 118 return []
119 119 if x[0] == 'list':
120 120 return getlist(x[1]) + [x[2]]
121 121 return [x]
122 122
123 123 def getfilter(exp, context):
124 124 f = getsymbol(exp)
125 125 if f not in context._filters:
126 126 raise error.ParseError(_("unknown function '%s'") % f)
127 127 return context._filters[f]
128 128
129 129 def gettemplate(exp, context):
130 130 if exp[0] == 'string':
131 131 return compiletemplate(exp[1], context)
132 132 if exp[0] == 'symbol':
133 133 return context._load(exp[1])
134 134 raise error.ParseError(_("expected template specifier"))
135 135
136 136 def runstring(context, mapping, data):
137 137 return data
138 138
139 139 def runsymbol(context, mapping, key):
140 140 v = mapping.get(key)
141 141 if v is None:
142 142 v = context._defaults.get(key, '')
143 143 if util.safehasattr(v, '__call__'):
144 144 return v(**mapping)
145 145 if isinstance(v, types.GeneratorType):
146 146 v = list(v)
147 147 mapping[key] = v
148 148 return v
149 149 return v
150 150
151 151 def buildfilter(exp, context):
152 152 func, data = compileexp(exp[1], context)
153 153 filt = getfilter(exp[2], context)
154 154 return (runfilter, (func, data, filt))
155 155
156 156 def runfilter(context, mapping, data):
157 157 func, data, filt = data
158 158 try:
159 159 return filt(func(context, mapping, data))
160 160 except (ValueError, AttributeError, TypeError):
161 161 if isinstance(data, tuple):
162 162 dt = data[1]
163 163 else:
164 164 dt = data
165 165 raise util.Abort(_("template filter '%s' is not compatible with "
166 166 "keyword '%s'") % (filt.func_name, dt))
167 167
168 168 def buildmap(exp, context):
169 169 func, data = compileexp(exp[1], context)
170 170 ctmpl = gettemplate(exp[2], context)
171 171 return (runmap, (func, data, ctmpl))
172 172
173 173 def runtemplate(context, mapping, template):
174 174 for func, data in template:
175 175 yield func(context, mapping, data)
176 176
177 177 def runmap(context, mapping, data):
178 178 func, data, ctmpl = data
179 179 d = func(context, mapping, data)
180 180 if util.safehasattr(d, '__call__'):
181 181 d = d()
182 182
183 183 lm = mapping.copy()
184 184
185 185 for i in d:
186 186 if isinstance(i, dict):
187 187 lm.update(i)
188 188 lm['originalnode'] = mapping.get('node')
189 189 yield runtemplate(context, lm, ctmpl)
190 190 else:
191 191 # v is not an iterable of dicts, this happen when 'key'
192 192 # has been fully expanded already and format is useless.
193 193 # If so, return the expanded value.
194 194 yield i
195 195
196 196 def buildfunc(exp, context):
197 197 n = getsymbol(exp[1])
198 198 args = [compileexp(x, context) for x in getlist(exp[2])]
199 199 if n in funcs:
200 200 f = funcs[n]
201 201 return (f, args)
202 202 if n in context._filters:
203 203 if len(args) != 1:
204 204 raise error.ParseError(_("filter %s expects one argument") % n)
205 205 f = context._filters[n]
206 206 return (runfilter, (args[0][0], args[0][1], f))
207 207
208 def date(context, mapping, args):
209 if not (1 <= len(args) <= 2):
210 raise error.ParseError(_("date expects one or two arguments"))
211
212 date = args[0][0](context, mapping, args[0][1])
213 if len(args) == 2:
214 fmt = stringify(args[1][0](context, mapping, args[1][1]))
215 return util.datestr(date, fmt)
216 return util.datestr(date)
217
218 def fill(context, mapping, args):
219 if not (1 <= len(args) <= 4):
220 raise error.ParseError(_("fill expects one to four arguments"))
221
222 text = stringify(args[0][0](context, mapping, args[0][1]))
223 width = 76
224 initindent = ''
225 hangindent = ''
226 if 2 <= len(args) <= 4:
227 try:
228 width = int(stringify(args[1][0](context, mapping, args[1][1])))
229 except ValueError:
230 raise error.ParseError(_("fill expects an integer width"))
231 try:
232 initindent = stringify(args[2][0](context, mapping, args[2][1]))
233 initindent = stringify(runtemplate(context, mapping,
234 compiletemplate(initindent, context)))
235 hangindent = stringify(args[3][0](context, mapping, args[3][1]))
236 hangindent = stringify(runtemplate(context, mapping,
237 compiletemplate(hangindent, context)))
238 except IndexError:
239 pass
240
241 return templatefilters.fill(text, width, initindent, hangindent)
242
208 243 def get(context, mapping, args):
209 244 if len(args) != 2:
210 245 # i18n: "get" is a keyword
211 246 raise error.ParseError(_("get() expects two arguments"))
212 247
213 248 dictarg = args[0][0](context, mapping, args[0][1])
214 249 if not util.safehasattr(dictarg, 'get'):
215 250 # i18n: "get" is a keyword
216 251 raise error.ParseError(_("get() expects a dict as first argument"))
217 252
218 253 key = args[1][0](context, mapping, args[1][1])
219 254 yield dictarg.get(key)
220 255
221 def join(context, mapping, args):
222 if not (1 <= len(args) <= 2):
223 # i18n: "join" is a keyword
224 raise error.ParseError(_("join expects one or two arguments"))
225
226 joinset = args[0][0](context, mapping, args[0][1])
227 if util.safehasattr(joinset, '__call__'):
228 jf = joinset.joinfmt
229 joinset = [jf(x) for x in joinset()]
230
231 joiner = " "
232 if len(args) > 1:
233 joiner = args[1][0](context, mapping, args[1][1])
234
235 first = True
236 for x in joinset:
237 if first:
238 first = False
239 else:
240 yield joiner
241 yield x
242
243 def sub(context, mapping, args):
244 if len(args) != 3:
245 # i18n: "sub" is a keyword
246 raise error.ParseError(_("sub expects three arguments"))
247
248 pat = stringify(args[0][0](context, mapping, args[0][1]))
249 rpl = stringify(args[1][0](context, mapping, args[1][1]))
250 src = stringify(args[2][0](context, mapping, args[2][1]))
251 src = stringify(runtemplate(context, mapping,
252 compiletemplate(src, context)))
253 yield re.sub(pat, rpl, src)
254
255 256 def if_(context, mapping, args):
256 257 if not (2 <= len(args) <= 3):
257 258 # i18n: "if" is a keyword
258 259 raise error.ParseError(_("if expects two or three arguments"))
259 260
260 261 test = stringify(args[0][0](context, mapping, args[0][1]))
261 262 if test:
262 263 t = stringify(args[1][0](context, mapping, args[1][1]))
263 264 yield runtemplate(context, mapping, compiletemplate(t, context))
264 265 elif len(args) == 3:
265 266 t = stringify(args[2][0](context, mapping, args[2][1]))
266 267 yield runtemplate(context, mapping, compiletemplate(t, context))
267 268
268 269 def ifeq(context, mapping, args):
269 270 if not (3 <= len(args) <= 4):
270 271 # i18n: "ifeq" is a keyword
271 272 raise error.ParseError(_("ifeq expects three or four arguments"))
272 273
273 274 test = stringify(args[0][0](context, mapping, args[0][1]))
274 275 match = stringify(args[1][0](context, mapping, args[1][1]))
275 276 if test == match:
276 277 t = stringify(args[2][0](context, mapping, args[2][1]))
277 278 yield runtemplate(context, mapping, compiletemplate(t, context))
278 279 elif len(args) == 4:
279 280 t = stringify(args[3][0](context, mapping, args[3][1]))
280 281 yield runtemplate(context, mapping, compiletemplate(t, context))
281 282
283 def join(context, mapping, args):
284 if not (1 <= len(args) <= 2):
285 # i18n: "join" is a keyword
286 raise error.ParseError(_("join expects one or two arguments"))
287
288 joinset = args[0][0](context, mapping, args[0][1])
289 if util.safehasattr(joinset, '__call__'):
290 jf = joinset.joinfmt
291 joinset = [jf(x) for x in joinset()]
292
293 joiner = " "
294 if len(args) > 1:
295 joiner = args[1][0](context, mapping, args[1][1])
296
297 first = True
298 for x in joinset:
299 if first:
300 first = False
301 else:
302 yield joiner
303 yield x
304
282 305 def label(context, mapping, args):
283 306 if len(args) != 2:
284 307 # i18n: "label" is a keyword
285 308 raise error.ParseError(_("label expects two arguments"))
286 309
287 310 # ignore args[0] (the label string) since this is supposed to be a a no-op
288 311 t = stringify(args[1][0](context, mapping, args[1][1]))
289 312 yield runtemplate(context, mapping, compiletemplate(t, context))
290 313
291 314 def rstdoc(context, mapping, args):
292 315 if len(args) != 2:
293 316 # i18n: "rstdoc" is a keyword
294 317 raise error.ParseError(_("rstdoc expects two arguments"))
295 318
296 319 text = stringify(args[0][0](context, mapping, args[0][1]))
297 320 style = stringify(args[1][0](context, mapping, args[1][1]))
298 321
299 322 return minirst.format(text, style=style, keep=['verbose'])
300 323
301 def fill(context, mapping, args):
302 if not (1 <= len(args) <= 4):
303 raise error.ParseError(_("fill expects one to four arguments"))
304
305 text = stringify(args[0][0](context, mapping, args[0][1]))
306 width = 76
307 initindent = ''
308 hangindent = ''
309 if 2 <= len(args) <= 4:
310 try:
311 width = int(stringify(args[1][0](context, mapping, args[1][1])))
312 except ValueError:
313 raise error.ParseError(_("fill expects an integer width"))
314 try:
315 initindent = stringify(args[2][0](context, mapping, args[2][1]))
316 initindent = stringify(runtemplate(context, mapping,
317 compiletemplate(initindent, context)))
318 hangindent = stringify(args[3][0](context, mapping, args[3][1]))
319 hangindent = stringify(runtemplate(context, mapping,
320 compiletemplate(hangindent, context)))
321 except IndexError:
322 pass
323
324 return templatefilters.fill(text, width, initindent, hangindent)
325
326 def date(context, mapping, args):
327 if not (1 <= len(args) <= 2):
328 raise error.ParseError(_("date expects one or two arguments"))
329
330 date = args[0][0](context, mapping, args[0][1])
331 if len(args) == 2:
332 fmt = stringify(args[1][0](context, mapping, args[1][1]))
333 return util.datestr(date, fmt)
334 return util.datestr(date)
335
336 324 def strip(context, mapping, args):
337 325 if not (1 <= len(args) <= 2):
338 326 raise error.ParseError(_("strip expects one or two arguments"))
339 327
340 328 text = args[0][0](context, mapping, args[0][1])
341 329 if len(args) == 2:
342 330 chars = args[1][0](context, mapping, args[1][1])
343 331 return text.strip(chars)
344 332 return text.strip()
345 333
334 def sub(context, mapping, args):
335 if len(args) != 3:
336 # i18n: "sub" is a keyword
337 raise error.ParseError(_("sub expects three arguments"))
338
339 pat = stringify(args[0][0](context, mapping, args[0][1]))
340 rpl = stringify(args[1][0](context, mapping, args[1][1]))
341 src = stringify(args[2][0](context, mapping, args[2][1]))
342 src = stringify(runtemplate(context, mapping,
343 compiletemplate(src, context)))
344 yield re.sub(pat, rpl, src)
345
346 346 methods = {
347 347 "string": lambda e, c: (runstring, e[1]),
348 348 "symbol": lambda e, c: (runsymbol, e[1]),
349 349 "group": lambda e, c: compileexp(e[1], c),
350 350 # ".": buildmember,
351 351 "|": buildfilter,
352 352 "%": buildmap,
353 353 "func": buildfunc,
354 354 }
355 355
356 356 funcs = {
357 "date": date,
358 "fill": fill,
357 359 "get": get,
358 360 "if": if_,
359 361 "ifeq": ifeq,
360 362 "join": join,
361 363 "label": label,
362 364 "rstdoc": rstdoc,
365 "strip": strip,
363 366 "sub": sub,
364 "fill": fill,
365 "date": date,
366 "strip": strip,
367 367 }
368 368
369 369 # template engine
370 370
371 371 path = ['templates', '../templates']
372 372 stringify = templatefilters.stringify
373 373
374 374 def _flatten(thing):
375 375 '''yield a single stream from a possibly nested set of iterators'''
376 376 if isinstance(thing, str):
377 377 yield thing
378 378 elif not util.safehasattr(thing, '__iter__'):
379 379 if thing is not None:
380 380 yield str(thing)
381 381 else:
382 382 for i in thing:
383 383 if isinstance(i, str):
384 384 yield i
385 385 elif not util.safehasattr(i, '__iter__'):
386 386 if i is not None:
387 387 yield str(i)
388 388 elif i is not None:
389 389 for j in _flatten(i):
390 390 yield j
391 391
392 392 def parsestring(s, quoted=True):
393 393 '''parse a string using simple c-like syntax.
394 394 string must be in quotes if quoted is True.'''
395 395 if quoted:
396 396 if len(s) < 2 or s[0] != s[-1]:
397 397 raise SyntaxError(_('unmatched quotes'))
398 398 return s[1:-1].decode('string_escape')
399 399
400 400 return s.decode('string_escape')
401 401
402 402 class engine(object):
403 403 '''template expansion engine.
404 404
405 405 template expansion works like this. a map file contains key=value
406 406 pairs. if value is quoted, it is treated as string. otherwise, it
407 407 is treated as name of template file.
408 408
409 409 templater is asked to expand a key in map. it looks up key, and
410 410 looks for strings like this: {foo}. it expands {foo} by looking up
411 411 foo in map, and substituting it. expansion is recursive: it stops
412 412 when there is no more {foo} to replace.
413 413
414 414 expansion also allows formatting and filtering.
415 415
416 416 format uses key to expand each item in list. syntax is
417 417 {key%format}.
418 418
419 419 filter uses function to transform value. syntax is
420 420 {key|filter1|filter2|...}.'''
421 421
422 422 def __init__(self, loader, filters={}, defaults={}):
423 423 self._loader = loader
424 424 self._filters = filters
425 425 self._defaults = defaults
426 426 self._cache = {}
427 427
428 428 def _load(self, t):
429 429 '''load, parse, and cache a template'''
430 430 if t not in self._cache:
431 431 self._cache[t] = compiletemplate(self._loader(t), self)
432 432 return self._cache[t]
433 433
434 434 def process(self, t, mapping):
435 435 '''Perform expansion. t is name of map element to expand.
436 436 mapping contains added elements for use during expansion. Is a
437 437 generator.'''
438 438 return _flatten(runtemplate(self, mapping, self._load(t)))
439 439
440 440 engines = {'default': engine}
441 441
442 442 def stylelist():
443 443 path = templatepath()[0]
444 444 dirlist = os.listdir(path)
445 445 stylelist = []
446 446 for file in dirlist:
447 447 split = file.split(".")
448 448 if split[0] == "map-cmdline":
449 449 stylelist.append(split[1])
450 450 return ", ".join(sorted(stylelist))
451 451
452 452 class templater(object):
453 453
454 454 def __init__(self, mapfile, filters={}, defaults={}, cache={},
455 455 minchunk=1024, maxchunk=65536):
456 456 '''set up template engine.
457 457 mapfile is name of file to read map definitions from.
458 458 filters is dict of functions. each transforms a value into another.
459 459 defaults is dict of default map definitions.'''
460 460 self.mapfile = mapfile or 'template'
461 461 self.cache = cache.copy()
462 462 self.map = {}
463 463 self.base = (mapfile and os.path.dirname(mapfile)) or ''
464 464 self.filters = templatefilters.filters.copy()
465 465 self.filters.update(filters)
466 466 self.defaults = defaults
467 467 self.minchunk, self.maxchunk = minchunk, maxchunk
468 468 self.ecache = {}
469 469
470 470 if not mapfile:
471 471 return
472 472 if not os.path.exists(mapfile):
473 473 raise util.Abort(_("style '%s' not found") % mapfile,
474 474 hint=_("available styles: %s") % stylelist())
475 475
476 476 conf = config.config()
477 477 conf.read(mapfile)
478 478
479 479 for key, val in conf[''].items():
480 480 if not val:
481 481 raise SyntaxError(_('%s: missing value') % conf.source('', key))
482 482 if val[0] in "'\"":
483 483 try:
484 484 self.cache[key] = parsestring(val)
485 485 except SyntaxError, inst:
486 486 raise SyntaxError('%s: %s' %
487 487 (conf.source('', key), inst.args[0]))
488 488 else:
489 489 val = 'default', val
490 490 if ':' in val[1]:
491 491 val = val[1].split(':', 1)
492 492 self.map[key] = val[0], os.path.join(self.base, val[1])
493 493
494 494 def __contains__(self, key):
495 495 return key in self.cache or key in self.map
496 496
497 497 def load(self, t):
498 498 '''Get the template for the given template name. Use a local cache.'''
499 499 if t not in self.cache:
500 500 try:
501 501 self.cache[t] = util.readfile(self.map[t][1])
502 502 except KeyError, inst:
503 503 raise util.Abort(_('"%s" not in template map') % inst.args[0])
504 504 except IOError, inst:
505 505 raise IOError(inst.args[0], _('template file %s: %s') %
506 506 (self.map[t][1], inst.args[1]))
507 507 return self.cache[t]
508 508
509 509 def __call__(self, t, **mapping):
510 510 ttype = t in self.map and self.map[t][0] or 'default'
511 511 if ttype not in self.ecache:
512 512 self.ecache[ttype] = engines[ttype](self.load,
513 513 self.filters, self.defaults)
514 514 proc = self.ecache[ttype]
515 515
516 516 stream = proc.process(t, mapping)
517 517 if self.minchunk:
518 518 stream = util.increasingchunks(stream, min=self.minchunk,
519 519 max=self.maxchunk)
520 520 return stream
521 521
522 522 def templatepath(name=None):
523 523 '''return location of template file or directory (if no name).
524 524 returns None if not found.'''
525 525 normpaths = []
526 526
527 527 # executable version (py2exe) doesn't support __file__
528 528 if util.mainfrozen():
529 529 module = sys.executable
530 530 else:
531 531 module = __file__
532 532 for f in path:
533 533 if f.startswith('/'):
534 534 p = f
535 535 else:
536 536 fl = f.split('/')
537 537 p = os.path.join(os.path.dirname(module), *fl)
538 538 if name:
539 539 p = os.path.join(p, name)
540 540 if name and os.path.exists(p):
541 541 return os.path.normpath(p)
542 542 elif os.path.isdir(p):
543 543 normpaths.append(os.path.normpath(p))
544 544
545 545 return normpaths
546 546
547 547 def stylemap(styles, paths=None):
548 548 """Return path to mapfile for a given style.
549 549
550 550 Searches mapfile in the following locations:
551 551 1. templatepath/style/map
552 552 2. templatepath/map-style
553 553 3. templatepath/map
554 554 """
555 555
556 556 if paths is None:
557 557 paths = templatepath()
558 558 elif isinstance(paths, str):
559 559 paths = [paths]
560 560
561 561 if isinstance(styles, str):
562 562 styles = [styles]
563 563
564 564 for style in styles:
565 565 if not style:
566 566 continue
567 567 locations = [os.path.join(style, 'map'), 'map-' + style]
568 568 locations.append('map')
569 569
570 570 for path in paths:
571 571 for location in locations:
572 572 mapfile = os.path.join(path, location)
573 573 if os.path.isfile(mapfile):
574 574 return style, mapfile
575 575
576 576 raise RuntimeError("No hgweb templates found in %r" % paths)
General Comments 0
You need to be logged in to leave comments. Login now