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