##// END OF EJS Templates
templater: restore use of callable() since it was readded in Python 3.2
Augie Fackler -
r21798:f2c617ff default
parent child Browse files
Show More
@@ -1,710 +1,710 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"))
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 if util.safehasattr(v, '__call__'):
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 if util.safehasattr(d, '__call__'):
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 if util.safehasattr(joinset, '__call__'):
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 methods = {
470 470 "string": lambda e, c: (runstring, e[1]),
471 471 "rawstring": lambda e, c: (runrawstring, e[1]),
472 472 "symbol": lambda e, c: (runsymbol, e[1]),
473 473 "group": lambda e, c: compileexp(e[1], c),
474 474 # ".": buildmember,
475 475 "|": buildfilter,
476 476 "%": buildmap,
477 477 "func": buildfunc,
478 478 }
479 479
480 480 funcs = {
481 481 "date": date,
482 482 "fill": fill,
483 483 "get": get,
484 484 "if": if_,
485 485 "ifcontains": ifcontains,
486 486 "ifeq": ifeq,
487 487 "join": join,
488 488 "label": label,
489 489 "pad": pad,
490 490 "revset": revset,
491 491 "rstdoc": rstdoc,
492 492 "shortest": shortest,
493 493 "strip": strip,
494 494 "sub": sub,
495 495 }
496 496
497 497 # template engine
498 498
499 499 path = ['templates', '../templates']
500 500 stringify = templatefilters.stringify
501 501
502 502 def _flatten(thing):
503 503 '''yield a single stream from a possibly nested set of iterators'''
504 504 if isinstance(thing, str):
505 505 yield thing
506 506 elif not util.safehasattr(thing, '__iter__'):
507 507 if thing is not None:
508 508 yield str(thing)
509 509 else:
510 510 for i in thing:
511 511 if isinstance(i, str):
512 512 yield i
513 513 elif not util.safehasattr(i, '__iter__'):
514 514 if i is not None:
515 515 yield str(i)
516 516 elif i is not None:
517 517 for j in _flatten(i):
518 518 yield j
519 519
520 520 def parsestring(s, quoted=True):
521 521 '''parse a string using simple c-like syntax.
522 522 string must be in quotes if quoted is True.'''
523 523 if quoted:
524 524 if len(s) < 2 or s[0] != s[-1]:
525 525 raise SyntaxError(_('unmatched quotes'))
526 526 return s[1:-1].decode('string_escape')
527 527
528 528 return s.decode('string_escape')
529 529
530 530 class engine(object):
531 531 '''template expansion engine.
532 532
533 533 template expansion works like this. a map file contains key=value
534 534 pairs. if value is quoted, it is treated as string. otherwise, it
535 535 is treated as name of template file.
536 536
537 537 templater is asked to expand a key in map. it looks up key, and
538 538 looks for strings like this: {foo}. it expands {foo} by looking up
539 539 foo in map, and substituting it. expansion is recursive: it stops
540 540 when there is no more {foo} to replace.
541 541
542 542 expansion also allows formatting and filtering.
543 543
544 544 format uses key to expand each item in list. syntax is
545 545 {key%format}.
546 546
547 547 filter uses function to transform value. syntax is
548 548 {key|filter1|filter2|...}.'''
549 549
550 550 def __init__(self, loader, filters={}, defaults={}):
551 551 self._loader = loader
552 552 self._filters = filters
553 553 self._defaults = defaults
554 554 self._cache = {}
555 555
556 556 def _load(self, t):
557 557 '''load, parse, and cache a template'''
558 558 if t not in self._cache:
559 559 self._cache[t] = compiletemplate(self._loader(t), self)
560 560 return self._cache[t]
561 561
562 562 def process(self, t, mapping):
563 563 '''Perform expansion. t is name of map element to expand.
564 564 mapping contains added elements for use during expansion. Is a
565 565 generator.'''
566 566 return _flatten(runtemplate(self, mapping, self._load(t)))
567 567
568 568 engines = {'default': engine}
569 569
570 570 def stylelist():
571 571 paths = templatepath()
572 572 if not paths:
573 573 return _('no templates found, try `hg debuginstall` for more info')
574 574 dirlist = os.listdir(paths[0])
575 575 stylelist = []
576 576 for file in dirlist:
577 577 split = file.split(".")
578 578 if split[0] == "map-cmdline":
579 579 stylelist.append(split[1])
580 580 return ", ".join(sorted(stylelist))
581 581
582 582 class TemplateNotFound(util.Abort):
583 583 pass
584 584
585 585 class templater(object):
586 586
587 587 def __init__(self, mapfile, filters={}, defaults={}, cache={},
588 588 minchunk=1024, maxchunk=65536):
589 589 '''set up template engine.
590 590 mapfile is name of file to read map definitions from.
591 591 filters is dict of functions. each transforms a value into another.
592 592 defaults is dict of default map definitions.'''
593 593 self.mapfile = mapfile or 'template'
594 594 self.cache = cache.copy()
595 595 self.map = {}
596 596 self.base = (mapfile and os.path.dirname(mapfile)) or ''
597 597 self.filters = templatefilters.filters.copy()
598 598 self.filters.update(filters)
599 599 self.defaults = defaults
600 600 self.minchunk, self.maxchunk = minchunk, maxchunk
601 601 self.ecache = {}
602 602
603 603 if not mapfile:
604 604 return
605 605 if not os.path.exists(mapfile):
606 606 raise util.Abort(_("style '%s' not found") % mapfile,
607 607 hint=_("available styles: %s") % stylelist())
608 608
609 609 conf = config.config()
610 610 conf.read(mapfile)
611 611
612 612 for key, val in conf[''].items():
613 613 if not val:
614 614 raise SyntaxError(_('%s: missing value') % conf.source('', key))
615 615 if val[0] in "'\"":
616 616 try:
617 617 self.cache[key] = parsestring(val)
618 618 except SyntaxError, inst:
619 619 raise SyntaxError('%s: %s' %
620 620 (conf.source('', key), inst.args[0]))
621 621 else:
622 622 val = 'default', val
623 623 if ':' in val[1]:
624 624 val = val[1].split(':', 1)
625 625 self.map[key] = val[0], os.path.join(self.base, val[1])
626 626
627 627 def __contains__(self, key):
628 628 return key in self.cache or key in self.map
629 629
630 630 def load(self, t):
631 631 '''Get the template for the given template name. Use a local cache.'''
632 632 if t not in self.cache:
633 633 try:
634 634 self.cache[t] = util.readfile(self.map[t][1])
635 635 except KeyError, inst:
636 636 raise TemplateNotFound(_('"%s" not in template map') %
637 637 inst.args[0])
638 638 except IOError, inst:
639 639 raise IOError(inst.args[0], _('template file %s: %s') %
640 640 (self.map[t][1], inst.args[1]))
641 641 return self.cache[t]
642 642
643 643 def __call__(self, t, **mapping):
644 644 ttype = t in self.map and self.map[t][0] or 'default'
645 645 if ttype not in self.ecache:
646 646 self.ecache[ttype] = engines[ttype](self.load,
647 647 self.filters, self.defaults)
648 648 proc = self.ecache[ttype]
649 649
650 650 stream = proc.process(t, mapping)
651 651 if self.minchunk:
652 652 stream = util.increasingchunks(stream, min=self.minchunk,
653 653 max=self.maxchunk)
654 654 return stream
655 655
656 656 def templatepath(name=None):
657 657 '''return location of template file or directory (if no name).
658 658 returns None if not found.'''
659 659 normpaths = []
660 660
661 661 # executable version (py2exe) doesn't support __file__
662 662 if util.mainfrozen():
663 663 module = sys.executable
664 664 else:
665 665 module = __file__
666 666 for f in path:
667 667 if f.startswith('/'):
668 668 p = f
669 669 else:
670 670 fl = f.split('/')
671 671 p = os.path.join(os.path.dirname(module), *fl)
672 672 if name:
673 673 p = os.path.join(p, name)
674 674 if name and os.path.exists(p):
675 675 return os.path.normpath(p)
676 676 elif os.path.isdir(p):
677 677 normpaths.append(os.path.normpath(p))
678 678
679 679 return normpaths
680 680
681 681 def stylemap(styles, paths=None):
682 682 """Return path to mapfile for a given style.
683 683
684 684 Searches mapfile in the following locations:
685 685 1. templatepath/style/map
686 686 2. templatepath/map-style
687 687 3. templatepath/map
688 688 """
689 689
690 690 if paths is None:
691 691 paths = templatepath()
692 692 elif isinstance(paths, str):
693 693 paths = [paths]
694 694
695 695 if isinstance(styles, str):
696 696 styles = [styles]
697 697
698 698 for style in styles:
699 699 if not style:
700 700 continue
701 701 locations = [os.path.join(style, 'map'), 'map-' + style]
702 702 locations.append('map')
703 703
704 704 for path in paths:
705 705 for location in locations:
706 706 mapfile = os.path.join(path, location)
707 707 if os.path.isfile(mapfile):
708 708 return style, mapfile
709 709
710 710 raise RuntimeError("No hgweb templates found in %r" % paths)
General Comments 0
You need to be logged in to leave comments. Login now