##// END OF EJS Templates
templater: add i18n comments to error messages of newly added functions...
FUJIWARA Katsunori -
r21960:2896d450 stable
parent child Browse files
Show More
@@ -1,742 +1,744 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, templatekw, 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 "rawstring": (0, ("rawstring",), None),
25 25 "end": (0, None, None),
26 26 }
27 27
28 28 def tokenizer(data):
29 29 program, start, end = data
30 30 pos = start
31 31 while pos < end:
32 32 c = program[pos]
33 33 if c.isspace(): # skip inter-token whitespace
34 34 pass
35 35 elif c in "(,)%|": # handle simple operators
36 36 yield (c, None, pos)
37 37 elif (c in '"\'' or c == 'r' and
38 38 program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings
39 39 if c == 'r':
40 40 pos += 1
41 41 c = program[pos]
42 42 decode = False
43 43 else:
44 44 decode = True
45 45 pos += 1
46 46 s = pos
47 47 while pos < end: # find closing quote
48 48 d = program[pos]
49 49 if decode and d == '\\': # skip over escaped characters
50 50 pos += 2
51 51 continue
52 52 if d == c:
53 53 if not decode:
54 54 yield ('rawstring', program[s:pos], s)
55 55 break
56 56 yield ('string', program[s:pos], s)
57 57 break
58 58 pos += 1
59 59 else:
60 60 raise error.ParseError(_("unterminated string"), s)
61 61 elif c.isalnum() or c in '_':
62 62 s = pos
63 63 pos += 1
64 64 while pos < end: # find end of symbol
65 65 d = program[pos]
66 66 if not (d.isalnum() or d == "_"):
67 67 break
68 68 pos += 1
69 69 sym = program[s:pos]
70 70 yield ('symbol', sym, s)
71 71 pos -= 1
72 72 elif c == '}':
73 73 pos += 1
74 74 break
75 75 else:
76 76 raise error.ParseError(_("syntax error"), pos)
77 77 pos += 1
78 78 yield ('end', None, pos)
79 79
80 80 def compiletemplate(tmpl, context, strtoken="string"):
81 81 parsed = []
82 82 pos, stop = 0, len(tmpl)
83 83 p = parser.parser(tokenizer, elements)
84 84 while pos < stop:
85 85 n = tmpl.find('{', pos)
86 86 if n < 0:
87 87 parsed.append((strtoken, tmpl[pos:]))
88 88 break
89 89 if n > 0 and tmpl[n - 1] == '\\':
90 90 # escaped
91 91 parsed.append((strtoken, (tmpl[pos:n - 1] + "{")))
92 92 pos = n + 1
93 93 continue
94 94 if n > pos:
95 95 parsed.append((strtoken, 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, got '%s'") % exp[0])
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' or exp[0] == 'rawstring':
131 131 return compiletemplate(exp[1], context, strtoken=exp[0])
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.decode("string-escape")
138 138
139 139 def runrawstring(context, mapping, data):
140 140 return data
141 141
142 142 def runsymbol(context, mapping, key):
143 143 v = mapping.get(key)
144 144 if v is None:
145 145 v = context._defaults.get(key)
146 146 if v is None:
147 147 try:
148 148 v = context.process(key, mapping)
149 149 except TemplateNotFound:
150 150 v = ''
151 151 if callable(v):
152 152 return v(**mapping)
153 153 if isinstance(v, types.GeneratorType):
154 154 v = list(v)
155 155 mapping[key] = v
156 156 return v
157 157 return v
158 158
159 159 def buildfilter(exp, context):
160 160 func, data = compileexp(exp[1], context)
161 161 filt = getfilter(exp[2], context)
162 162 return (runfilter, (func, data, filt))
163 163
164 164 def runfilter(context, mapping, data):
165 165 func, data, filt = data
166 166 try:
167 167 return filt(func(context, mapping, data))
168 168 except (ValueError, AttributeError, TypeError):
169 169 if isinstance(data, tuple):
170 170 dt = data[1]
171 171 else:
172 172 dt = data
173 173 raise util.Abort(_("template filter '%s' is not compatible with "
174 174 "keyword '%s'") % (filt.func_name, dt))
175 175
176 176 def buildmap(exp, context):
177 177 func, data = compileexp(exp[1], context)
178 178 ctmpl = gettemplate(exp[2], context)
179 179 return (runmap, (func, data, ctmpl))
180 180
181 181 def runtemplate(context, mapping, template):
182 182 for func, data in template:
183 183 yield func(context, mapping, data)
184 184
185 185 def runmap(context, mapping, data):
186 186 func, data, ctmpl = data
187 187 d = func(context, mapping, data)
188 188 if callable(d):
189 189 d = d()
190 190
191 191 lm = mapping.copy()
192 192
193 193 for i in d:
194 194 if isinstance(i, dict):
195 195 lm.update(i)
196 196 lm['originalnode'] = mapping.get('node')
197 197 yield runtemplate(context, lm, ctmpl)
198 198 else:
199 199 # v is not an iterable of dicts, this happen when 'key'
200 200 # has been fully expanded already and format is useless.
201 201 # If so, return the expanded value.
202 202 yield i
203 203
204 204 def buildfunc(exp, context):
205 205 n = getsymbol(exp[1])
206 206 args = [compileexp(x, context) for x in getlist(exp[2])]
207 207 if n in funcs:
208 208 f = funcs[n]
209 209 return (f, args)
210 210 if n in context._filters:
211 211 if len(args) != 1:
212 212 raise error.ParseError(_("filter %s expects one argument") % n)
213 213 f = context._filters[n]
214 214 return (runfilter, (args[0][0], args[0][1], f))
215 215 raise error.ParseError(_("unknown function '%s'") % n)
216 216
217 217 def date(context, mapping, args):
218 218 if not (1 <= len(args) <= 2):
219 219 raise error.ParseError(_("date expects one or two arguments"))
220 220
221 221 date = args[0][0](context, mapping, args[0][1])
222 222 if len(args) == 2:
223 223 fmt = stringify(args[1][0](context, mapping, args[1][1]))
224 224 return util.datestr(date, fmt)
225 225 return util.datestr(date)
226 226
227 227 def fill(context, mapping, args):
228 228 if not (1 <= len(args) <= 4):
229 229 raise error.ParseError(_("fill expects one to four arguments"))
230 230
231 231 text = stringify(args[0][0](context, mapping, args[0][1]))
232 232 width = 76
233 233 initindent = ''
234 234 hangindent = ''
235 235 if 2 <= len(args) <= 4:
236 236 try:
237 237 width = int(stringify(args[1][0](context, mapping, args[1][1])))
238 238 except ValueError:
239 239 raise error.ParseError(_("fill expects an integer width"))
240 240 try:
241 241 initindent = stringify(_evalifliteral(args[2], context, mapping))
242 242 hangindent = stringify(_evalifliteral(args[3], context, mapping))
243 243 except IndexError:
244 244 pass
245 245
246 246 return templatefilters.fill(text, width, initindent, hangindent)
247 247
248 248 def pad(context, mapping, args):
249 249 """usage: pad(text, width, fillchar=' ', right=False)
250 250 """
251 251 if not (2 <= len(args) <= 4):
252 252 raise error.ParseError(_("pad() expects two to four arguments"))
253 253
254 254 width = int(args[1][1])
255 255
256 256 text = stringify(args[0][0](context, mapping, args[0][1]))
257 257 if args[0][0] == runstring:
258 258 text = stringify(runtemplate(context, mapping,
259 259 compiletemplate(text, context)))
260 260
261 261 right = False
262 262 fillchar = ' '
263 263 if len(args) > 2:
264 264 fillchar = stringify(args[2][0](context, mapping, args[2][1]))
265 265 if len(args) > 3:
266 266 right = util.parsebool(args[3][1])
267 267
268 268 if right:
269 269 return text.rjust(width, fillchar)
270 270 else:
271 271 return text.ljust(width, fillchar)
272 272
273 273 def get(context, mapping, args):
274 274 if len(args) != 2:
275 275 # i18n: "get" is a keyword
276 276 raise error.ParseError(_("get() expects two arguments"))
277 277
278 278 dictarg = args[0][0](context, mapping, args[0][1])
279 279 if not util.safehasattr(dictarg, 'get'):
280 280 # i18n: "get" is a keyword
281 281 raise error.ParseError(_("get() expects a dict as first argument"))
282 282
283 283 key = args[1][0](context, mapping, args[1][1])
284 284 yield dictarg.get(key)
285 285
286 286 def _evalifliteral(arg, context, mapping):
287 287 t = stringify(arg[0](context, mapping, arg[1]))
288 288 if arg[0] == runstring or arg[0] == runrawstring:
289 289 yield runtemplate(context, mapping,
290 290 compiletemplate(t, context, strtoken='rawstring'))
291 291 else:
292 292 yield t
293 293
294 294 def if_(context, mapping, args):
295 295 if not (2 <= len(args) <= 3):
296 296 # i18n: "if" is a keyword
297 297 raise error.ParseError(_("if expects two or three arguments"))
298 298
299 299 test = stringify(args[0][0](context, mapping, args[0][1]))
300 300 if test:
301 301 yield _evalifliteral(args[1], context, mapping)
302 302 elif len(args) == 3:
303 303 yield _evalifliteral(args[2], context, mapping)
304 304
305 305 def ifcontains(context, mapping, args):
306 306 if not (3 <= len(args) <= 4):
307 307 # i18n: "ifcontains" is a keyword
308 308 raise error.ParseError(_("ifcontains expects three or four arguments"))
309 309
310 310 item = stringify(args[0][0](context, mapping, args[0][1]))
311 311 items = args[1][0](context, mapping, args[1][1])
312 312
313 313 # Iterating over items gives a formatted string, so we iterate
314 314 # directly over the raw values.
315 315 if item in [i.values()[0] for i in items()]:
316 316 yield _evalifliteral(args[2], context, mapping)
317 317 elif len(args) == 4:
318 318 yield _evalifliteral(args[3], context, mapping)
319 319
320 320 def ifeq(context, mapping, args):
321 321 if not (3 <= len(args) <= 4):
322 322 # i18n: "ifeq" is a keyword
323 323 raise error.ParseError(_("ifeq expects three or four arguments"))
324 324
325 325 test = stringify(args[0][0](context, mapping, args[0][1]))
326 326 match = stringify(args[1][0](context, mapping, args[1][1]))
327 327 if test == match:
328 328 yield _evalifliteral(args[2], context, mapping)
329 329 elif len(args) == 4:
330 330 yield _evalifliteral(args[3], context, mapping)
331 331
332 332 def join(context, mapping, args):
333 333 if not (1 <= len(args) <= 2):
334 334 # i18n: "join" is a keyword
335 335 raise error.ParseError(_("join expects one or two arguments"))
336 336
337 337 joinset = args[0][0](context, mapping, args[0][1])
338 338 if callable(joinset):
339 339 jf = joinset.joinfmt
340 340 joinset = [jf(x) for x in joinset()]
341 341
342 342 joiner = " "
343 343 if len(args) > 1:
344 344 joiner = stringify(args[1][0](context, mapping, args[1][1]))
345 345
346 346 first = True
347 347 for x in joinset:
348 348 if first:
349 349 first = False
350 350 else:
351 351 yield joiner
352 352 yield x
353 353
354 354 def label(context, mapping, args):
355 355 if len(args) != 2:
356 356 # i18n: "label" is a keyword
357 357 raise error.ParseError(_("label expects two arguments"))
358 358
359 359 # ignore args[0] (the label string) since this is supposed to be a a no-op
360 360 yield _evalifliteral(args[1], context, mapping)
361 361
362 362 def revset(context, mapping, args):
363 363 """usage: revset(query[, formatargs...])
364 364 """
365 365 if not len(args) > 0:
366 366 # i18n: "revset" is a keyword
367 367 raise error.ParseError(_("revset expects one or more arguments"))
368 368
369 369 raw = args[0][1]
370 370 ctx = mapping['ctx']
371 371 repo = ctx._repo
372 372
373 373 if len(args) > 1:
374 374 formatargs = list([a[0](context, mapping, a[1]) for a in args[1:]])
375 375 revs = repo.revs(raw, *formatargs)
376 376 revs = list([str(r) for r in revs])
377 377 else:
378 378 revsetcache = mapping['cache'].setdefault("revsetcache", {})
379 379 if raw in revsetcache:
380 380 revs = revsetcache[raw]
381 381 else:
382 382 revs = repo.revs(raw)
383 383 revs = list([str(r) for r in revs])
384 384 revsetcache[raw] = revs
385 385
386 386 return templatekw.showlist("revision", revs, **mapping)
387 387
388 388 def rstdoc(context, mapping, args):
389 389 if len(args) != 2:
390 390 # i18n: "rstdoc" is a keyword
391 391 raise error.ParseError(_("rstdoc expects two arguments"))
392 392
393 393 text = stringify(args[0][0](context, mapping, args[0][1]))
394 394 style = stringify(args[1][0](context, mapping, args[1][1]))
395 395
396 396 return minirst.format(text, style=style, keep=['verbose'])
397 397
398 398 def shortest(context, mapping, args):
399 399 """usage: shortest(node, minlength=4)
400 400 """
401 401 if not (1 <= len(args) <= 2):
402 402 raise error.ParseError(_("shortest() expects one or two arguments"))
403 403
404 404 node = stringify(args[0][0](context, mapping, args[0][1]))
405 405
406 406 minlength = 4
407 407 if len(args) > 1:
408 408 minlength = int(args[1][1])
409 409
410 410 cl = mapping['ctx']._repo.changelog
411 411 def isvalid(test):
412 412 try:
413 413 try:
414 414 cl.index.partialmatch(test)
415 415 except AttributeError:
416 416 # Pure mercurial doesn't support partialmatch on the index.
417 417 # Fallback to the slow way.
418 418 if cl._partialmatch(test) is None:
419 419 return False
420 420
421 421 try:
422 422 i = int(test)
423 423 # if we are a pure int, then starting with zero will not be
424 424 # confused as a rev; or, obviously, if the int is larger than
425 425 # the value of the tip rev
426 426 if test[0] == '0' or i > len(cl):
427 427 return True
428 428 return False
429 429 except ValueError:
430 430 return True
431 431 except error.RevlogError:
432 432 return False
433 433
434 434 shortest = node
435 435 startlength = max(6, minlength)
436 436 length = startlength
437 437 while True:
438 438 test = node[:length]
439 439 if isvalid(test):
440 440 shortest = test
441 441 if length == minlength or length > startlength:
442 442 return shortest
443 443 length -= 1
444 444 else:
445 445 length += 1
446 446 if len(shortest) <= length:
447 447 return shortest
448 448
449 449 def strip(context, mapping, args):
450 450 if not (1 <= len(args) <= 2):
451 451 raise error.ParseError(_("strip expects one or two arguments"))
452 452
453 453 text = stringify(args[0][0](context, mapping, args[0][1]))
454 454 if len(args) == 2:
455 455 chars = stringify(args[1][0](context, mapping, args[1][1]))
456 456 return text.strip(chars)
457 457 return text.strip()
458 458
459 459 def sub(context, mapping, args):
460 460 if len(args) != 3:
461 461 # i18n: "sub" is a keyword
462 462 raise error.ParseError(_("sub expects three arguments"))
463 463
464 464 pat = stringify(args[0][0](context, mapping, args[0][1]))
465 465 rpl = stringify(args[1][0](context, mapping, args[1][1]))
466 466 src = stringify(_evalifliteral(args[2], context, mapping))
467 467 yield re.sub(pat, rpl, src)
468 468
469 469 def startswith(context, mapping, args):
470 470 if len(args) != 2:
471 # i18n: "startswith" is a keyword
471 472 raise error.ParseError(_("startswith expects two arguments"))
472 473
473 474 patn = stringify(args[0][0](context, mapping, args[0][1]))
474 475 text = stringify(args[1][0](context, mapping, args[1][1]))
475 476 if text.startswith(patn):
476 477 return text
477 478 return ''
478 479
479 480
480 481 def word(context, mapping, args):
481 482 """return nth word from a string"""
482 483 if not (2 <= len(args) <= 3):
484 # i18n: "word" is a keyword
483 485 raise error.ParseError(_("word expects two or three arguments, got %d")
484 486 % len(args))
485 487
486 488 num = int(stringify(args[0][0](context, mapping, args[0][1])))
487 489 text = stringify(args[1][0](context, mapping, args[1][1]))
488 490 if len(args) == 3:
489 491 splitter = stringify(args[2][0](context, mapping, args[2][1]))
490 492 else:
491 493 splitter = None
492 494
493 495 tokens = text.split(splitter)
494 496 if num >= len(tokens):
495 497 return ''
496 498 else:
497 499 return tokens[num]
498 500
499 501 methods = {
500 502 "string": lambda e, c: (runstring, e[1]),
501 503 "rawstring": lambda e, c: (runrawstring, e[1]),
502 504 "symbol": lambda e, c: (runsymbol, e[1]),
503 505 "group": lambda e, c: compileexp(e[1], c),
504 506 # ".": buildmember,
505 507 "|": buildfilter,
506 508 "%": buildmap,
507 509 "func": buildfunc,
508 510 }
509 511
510 512 funcs = {
511 513 "date": date,
512 514 "fill": fill,
513 515 "get": get,
514 516 "if": if_,
515 517 "ifcontains": ifcontains,
516 518 "ifeq": ifeq,
517 519 "join": join,
518 520 "label": label,
519 521 "pad": pad,
520 522 "revset": revset,
521 523 "rstdoc": rstdoc,
522 524 "shortest": shortest,
523 525 "startswith": startswith,
524 526 "strip": strip,
525 527 "sub": sub,
526 528 "word": word,
527 529 }
528 530
529 531 # template engine
530 532
531 533 path = ['templates', '../templates']
532 534 stringify = templatefilters.stringify
533 535
534 536 def _flatten(thing):
535 537 '''yield a single stream from a possibly nested set of iterators'''
536 538 if isinstance(thing, str):
537 539 yield thing
538 540 elif not util.safehasattr(thing, '__iter__'):
539 541 if thing is not None:
540 542 yield str(thing)
541 543 else:
542 544 for i in thing:
543 545 if isinstance(i, str):
544 546 yield i
545 547 elif not util.safehasattr(i, '__iter__'):
546 548 if i is not None:
547 549 yield str(i)
548 550 elif i is not None:
549 551 for j in _flatten(i):
550 552 yield j
551 553
552 554 def parsestring(s, quoted=True):
553 555 '''parse a string using simple c-like syntax.
554 556 string must be in quotes if quoted is True.'''
555 557 if quoted:
556 558 if len(s) < 2 or s[0] != s[-1]:
557 559 raise SyntaxError(_('unmatched quotes'))
558 560 return s[1:-1].decode('string_escape')
559 561
560 562 return s.decode('string_escape')
561 563
562 564 class engine(object):
563 565 '''template expansion engine.
564 566
565 567 template expansion works like this. a map file contains key=value
566 568 pairs. if value is quoted, it is treated as string. otherwise, it
567 569 is treated as name of template file.
568 570
569 571 templater is asked to expand a key in map. it looks up key, and
570 572 looks for strings like this: {foo}. it expands {foo} by looking up
571 573 foo in map, and substituting it. expansion is recursive: it stops
572 574 when there is no more {foo} to replace.
573 575
574 576 expansion also allows formatting and filtering.
575 577
576 578 format uses key to expand each item in list. syntax is
577 579 {key%format}.
578 580
579 581 filter uses function to transform value. syntax is
580 582 {key|filter1|filter2|...}.'''
581 583
582 584 def __init__(self, loader, filters={}, defaults={}):
583 585 self._loader = loader
584 586 self._filters = filters
585 587 self._defaults = defaults
586 588 self._cache = {}
587 589
588 590 def _load(self, t):
589 591 '''load, parse, and cache a template'''
590 592 if t not in self._cache:
591 593 self._cache[t] = compiletemplate(self._loader(t), self)
592 594 return self._cache[t]
593 595
594 596 def process(self, t, mapping):
595 597 '''Perform expansion. t is name of map element to expand.
596 598 mapping contains added elements for use during expansion. Is a
597 599 generator.'''
598 600 return _flatten(runtemplate(self, mapping, self._load(t)))
599 601
600 602 engines = {'default': engine}
601 603
602 604 def stylelist():
603 605 paths = templatepath()
604 606 if not paths:
605 607 return _('no templates found, try `hg debuginstall` for more info')
606 608 dirlist = os.listdir(paths[0])
607 609 stylelist = []
608 610 for file in dirlist:
609 611 split = file.split(".")
610 612 if split[0] == "map-cmdline":
611 613 stylelist.append(split[1])
612 614 return ", ".join(sorted(stylelist))
613 615
614 616 class TemplateNotFound(util.Abort):
615 617 pass
616 618
617 619 class templater(object):
618 620
619 621 def __init__(self, mapfile, filters={}, defaults={}, cache={},
620 622 minchunk=1024, maxchunk=65536):
621 623 '''set up template engine.
622 624 mapfile is name of file to read map definitions from.
623 625 filters is dict of functions. each transforms a value into another.
624 626 defaults is dict of default map definitions.'''
625 627 self.mapfile = mapfile or 'template'
626 628 self.cache = cache.copy()
627 629 self.map = {}
628 630 self.base = (mapfile and os.path.dirname(mapfile)) or ''
629 631 self.filters = templatefilters.filters.copy()
630 632 self.filters.update(filters)
631 633 self.defaults = defaults
632 634 self.minchunk, self.maxchunk = minchunk, maxchunk
633 635 self.ecache = {}
634 636
635 637 if not mapfile:
636 638 return
637 639 if not os.path.exists(mapfile):
638 640 raise util.Abort(_("style '%s' not found") % mapfile,
639 641 hint=_("available styles: %s") % stylelist())
640 642
641 643 conf = config.config()
642 644 conf.read(mapfile)
643 645
644 646 for key, val in conf[''].items():
645 647 if not val:
646 648 raise SyntaxError(_('%s: missing value') % conf.source('', key))
647 649 if val[0] in "'\"":
648 650 try:
649 651 self.cache[key] = parsestring(val)
650 652 except SyntaxError, inst:
651 653 raise SyntaxError('%s: %s' %
652 654 (conf.source('', key), inst.args[0]))
653 655 else:
654 656 val = 'default', val
655 657 if ':' in val[1]:
656 658 val = val[1].split(':', 1)
657 659 self.map[key] = val[0], os.path.join(self.base, val[1])
658 660
659 661 def __contains__(self, key):
660 662 return key in self.cache or key in self.map
661 663
662 664 def load(self, t):
663 665 '''Get the template for the given template name. Use a local cache.'''
664 666 if t not in self.cache:
665 667 try:
666 668 self.cache[t] = util.readfile(self.map[t][1])
667 669 except KeyError, inst:
668 670 raise TemplateNotFound(_('"%s" not in template map') %
669 671 inst.args[0])
670 672 except IOError, inst:
671 673 raise IOError(inst.args[0], _('template file %s: %s') %
672 674 (self.map[t][1], inst.args[1]))
673 675 return self.cache[t]
674 676
675 677 def __call__(self, t, **mapping):
676 678 ttype = t in self.map and self.map[t][0] or 'default'
677 679 if ttype not in self.ecache:
678 680 self.ecache[ttype] = engines[ttype](self.load,
679 681 self.filters, self.defaults)
680 682 proc = self.ecache[ttype]
681 683
682 684 stream = proc.process(t, mapping)
683 685 if self.minchunk:
684 686 stream = util.increasingchunks(stream, min=self.minchunk,
685 687 max=self.maxchunk)
686 688 return stream
687 689
688 690 def templatepath(name=None):
689 691 '''return location of template file or directory (if no name).
690 692 returns None if not found.'''
691 693 normpaths = []
692 694
693 695 # executable version (py2exe) doesn't support __file__
694 696 if util.mainfrozen():
695 697 module = sys.executable
696 698 else:
697 699 module = __file__
698 700 for f in path:
699 701 if f.startswith('/'):
700 702 p = f
701 703 else:
702 704 fl = f.split('/')
703 705 p = os.path.join(os.path.dirname(module), *fl)
704 706 if name:
705 707 p = os.path.join(p, name)
706 708 if name and os.path.exists(p):
707 709 return os.path.normpath(p)
708 710 elif os.path.isdir(p):
709 711 normpaths.append(os.path.normpath(p))
710 712
711 713 return normpaths
712 714
713 715 def stylemap(styles, paths=None):
714 716 """Return path to mapfile for a given style.
715 717
716 718 Searches mapfile in the following locations:
717 719 1. templatepath/style/map
718 720 2. templatepath/map-style
719 721 3. templatepath/map
720 722 """
721 723
722 724 if paths is None:
723 725 paths = templatepath()
724 726 elif isinstance(paths, str):
725 727 paths = [paths]
726 728
727 729 if isinstance(styles, str):
728 730 styles = [styles]
729 731
730 732 for style in styles:
731 733 if not style:
732 734 continue
733 735 locations = [os.path.join(style, 'map'), 'map-' + style]
734 736 locations.append('map')
735 737
736 738 for path in paths:
737 739 for location in locations:
738 740 mapfile = os.path.join(path, location)
739 741 if os.path.isfile(mapfile):
740 742 return style, mapfile
741 743
742 744 raise RuntimeError("No hgweb templates found in %r" % paths)
General Comments 0
You need to be logged in to leave comments. Login now