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