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