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