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