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