##// END OF EJS Templates
templatefuncs: remove pycompat.iteritems()...
Gregory Szorc -
r49784:278030cc default
parent child Browse files
Show More
@@ -1,918 +1,918
1 # templatefuncs.py - common template functions
1 # templatefuncs.py - common template functions
2 #
2 #
3 # Copyright 2005, 2006 Olivia Mackall <olivia@selenic.com>
3 # Copyright 2005, 2006 Olivia Mackall <olivia@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
8
9 import re
9 import re
10
10
11 from .i18n import _
11 from .i18n import _
12 from .node import bin
12 from .node import bin
13 from . import (
13 from . import (
14 color,
14 color,
15 dagop,
15 dagop,
16 diffutil,
16 diffutil,
17 encoding,
17 encoding,
18 error,
18 error,
19 minirst,
19 minirst,
20 obsutil,
20 obsutil,
21 pycompat,
21 pycompat,
22 registrar,
22 registrar,
23 revset as revsetmod,
23 revset as revsetmod,
24 revsetlang,
24 revsetlang,
25 scmutil,
25 scmutil,
26 templatefilters,
26 templatefilters,
27 templatekw,
27 templatekw,
28 templateutil,
28 templateutil,
29 util,
29 util,
30 )
30 )
31 from .utils import (
31 from .utils import (
32 dateutil,
32 dateutil,
33 stringutil,
33 stringutil,
34 )
34 )
35
35
36 evalrawexp = templateutil.evalrawexp
36 evalrawexp = templateutil.evalrawexp
37 evalwrapped = templateutil.evalwrapped
37 evalwrapped = templateutil.evalwrapped
38 evalfuncarg = templateutil.evalfuncarg
38 evalfuncarg = templateutil.evalfuncarg
39 evalboolean = templateutil.evalboolean
39 evalboolean = templateutil.evalboolean
40 evaldate = templateutil.evaldate
40 evaldate = templateutil.evaldate
41 evalinteger = templateutil.evalinteger
41 evalinteger = templateutil.evalinteger
42 evalstring = templateutil.evalstring
42 evalstring = templateutil.evalstring
43 evalstringliteral = templateutil.evalstringliteral
43 evalstringliteral = templateutil.evalstringliteral
44
44
45 # dict of template built-in functions
45 # dict of template built-in functions
46 funcs = {}
46 funcs = {}
47 templatefunc = registrar.templatefunc(funcs)
47 templatefunc = registrar.templatefunc(funcs)
48
48
49
49
50 @templatefunc(b'date(date[, fmt])')
50 @templatefunc(b'date(date[, fmt])')
51 def date(context, mapping, args):
51 def date(context, mapping, args):
52 """Format a date. See :hg:`help dates` for formatting
52 """Format a date. See :hg:`help dates` for formatting
53 strings. The default is a Unix date format, including the timezone:
53 strings. The default is a Unix date format, including the timezone:
54 "Mon Sep 04 15:13:13 2006 0700"."""
54 "Mon Sep 04 15:13:13 2006 0700"."""
55 if not (1 <= len(args) <= 2):
55 if not (1 <= len(args) <= 2):
56 # i18n: "date" is a keyword
56 # i18n: "date" is a keyword
57 raise error.ParseError(_(b"date expects one or two arguments"))
57 raise error.ParseError(_(b"date expects one or two arguments"))
58
58
59 date = evaldate(
59 date = evaldate(
60 context,
60 context,
61 mapping,
61 mapping,
62 args[0],
62 args[0],
63 # i18n: "date" is a keyword
63 # i18n: "date" is a keyword
64 _(b"date expects a date information"),
64 _(b"date expects a date information"),
65 )
65 )
66 fmt = None
66 fmt = None
67 if len(args) == 2:
67 if len(args) == 2:
68 fmt = evalstring(context, mapping, args[1])
68 fmt = evalstring(context, mapping, args[1])
69 if fmt is None:
69 if fmt is None:
70 return dateutil.datestr(date)
70 return dateutil.datestr(date)
71 else:
71 else:
72 return dateutil.datestr(date, fmt)
72 return dateutil.datestr(date, fmt)
73
73
74
74
75 @templatefunc(b'dict([[key=]value...])', argspec=b'*args **kwargs')
75 @templatefunc(b'dict([[key=]value...])', argspec=b'*args **kwargs')
76 def dict_(context, mapping, args):
76 def dict_(context, mapping, args):
77 """Construct a dict from key-value pairs. A key may be omitted if
77 """Construct a dict from key-value pairs. A key may be omitted if
78 a value expression can provide an unambiguous name."""
78 a value expression can provide an unambiguous name."""
79 data = util.sortdict()
79 data = util.sortdict()
80
80
81 for v in args[b'args']:
81 for v in args[b'args']:
82 k = templateutil.findsymbolicname(v)
82 k = templateutil.findsymbolicname(v)
83 if not k:
83 if not k:
84 raise error.ParseError(_(b'dict key cannot be inferred'))
84 raise error.ParseError(_(b'dict key cannot be inferred'))
85 if k in data or k in args[b'kwargs']:
85 if k in data or k in args[b'kwargs']:
86 raise error.ParseError(_(b"duplicated dict key '%s' inferred") % k)
86 raise error.ParseError(_(b"duplicated dict key '%s' inferred") % k)
87 data[k] = evalfuncarg(context, mapping, v)
87 data[k] = evalfuncarg(context, mapping, v)
88
88
89 data.update(
89 data.update(
90 (k, evalfuncarg(context, mapping, v))
90 (k, evalfuncarg(context, mapping, v))
91 for k, v in pycompat.iteritems(args[b'kwargs'])
91 for k, v in args[b'kwargs'].items()
92 )
92 )
93 return templateutil.hybriddict(data)
93 return templateutil.hybriddict(data)
94
94
95
95
96 @templatefunc(
96 @templatefunc(
97 b'diff([includepattern [, excludepattern]])', requires={b'ctx', b'ui'}
97 b'diff([includepattern [, excludepattern]])', requires={b'ctx', b'ui'}
98 )
98 )
99 def diff(context, mapping, args):
99 def diff(context, mapping, args):
100 """Show a diff, optionally
100 """Show a diff, optionally
101 specifying files to include or exclude."""
101 specifying files to include or exclude."""
102 if len(args) > 2:
102 if len(args) > 2:
103 # i18n: "diff" is a keyword
103 # i18n: "diff" is a keyword
104 raise error.ParseError(_(b"diff expects zero, one, or two arguments"))
104 raise error.ParseError(_(b"diff expects zero, one, or two arguments"))
105
105
106 def getpatterns(i):
106 def getpatterns(i):
107 if i < len(args):
107 if i < len(args):
108 s = evalstring(context, mapping, args[i]).strip()
108 s = evalstring(context, mapping, args[i]).strip()
109 if s:
109 if s:
110 return [s]
110 return [s]
111 return []
111 return []
112
112
113 ctx = context.resource(mapping, b'ctx')
113 ctx = context.resource(mapping, b'ctx')
114 ui = context.resource(mapping, b'ui')
114 ui = context.resource(mapping, b'ui')
115 diffopts = diffutil.diffallopts(ui)
115 diffopts = diffutil.diffallopts(ui)
116 chunks = ctx.diff(
116 chunks = ctx.diff(
117 match=ctx.match([], getpatterns(0), getpatterns(1)), opts=diffopts
117 match=ctx.match([], getpatterns(0), getpatterns(1)), opts=diffopts
118 )
118 )
119
119
120 return b''.join(chunks)
120 return b''.join(chunks)
121
121
122
122
123 @templatefunc(
123 @templatefunc(
124 b'extdata(source)', argspec=b'source', requires={b'ctx', b'cache'}
124 b'extdata(source)', argspec=b'source', requires={b'ctx', b'cache'}
125 )
125 )
126 def extdata(context, mapping, args):
126 def extdata(context, mapping, args):
127 """Show a text read from the specified extdata source. (EXPERIMENTAL)"""
127 """Show a text read from the specified extdata source. (EXPERIMENTAL)"""
128 if b'source' not in args:
128 if b'source' not in args:
129 # i18n: "extdata" is a keyword
129 # i18n: "extdata" is a keyword
130 raise error.ParseError(_(b'extdata expects one argument'))
130 raise error.ParseError(_(b'extdata expects one argument'))
131
131
132 source = evalstring(context, mapping, args[b'source'])
132 source = evalstring(context, mapping, args[b'source'])
133 if not source:
133 if not source:
134 sym = templateutil.findsymbolicname(args[b'source'])
134 sym = templateutil.findsymbolicname(args[b'source'])
135 if sym:
135 if sym:
136 raise error.ParseError(
136 raise error.ParseError(
137 _(b'empty data source specified'),
137 _(b'empty data source specified'),
138 hint=_(b"did you mean extdata('%s')?") % sym,
138 hint=_(b"did you mean extdata('%s')?") % sym,
139 )
139 )
140 else:
140 else:
141 raise error.ParseError(_(b'empty data source specified'))
141 raise error.ParseError(_(b'empty data source specified'))
142 cache = context.resource(mapping, b'cache').setdefault(b'extdata', {})
142 cache = context.resource(mapping, b'cache').setdefault(b'extdata', {})
143 ctx = context.resource(mapping, b'ctx')
143 ctx = context.resource(mapping, b'ctx')
144 if source in cache:
144 if source in cache:
145 data = cache[source]
145 data = cache[source]
146 else:
146 else:
147 data = cache[source] = scmutil.extdatasource(ctx.repo(), source)
147 data = cache[source] = scmutil.extdatasource(ctx.repo(), source)
148 return data.get(ctx.rev(), b'')
148 return data.get(ctx.rev(), b'')
149
149
150
150
151 @templatefunc(b'files(pattern)', requires={b'ctx'})
151 @templatefunc(b'files(pattern)', requires={b'ctx'})
152 def files(context, mapping, args):
152 def files(context, mapping, args):
153 """All files of the current changeset matching the pattern. See
153 """All files of the current changeset matching the pattern. See
154 :hg:`help patterns`."""
154 :hg:`help patterns`."""
155 if not len(args) == 1:
155 if not len(args) == 1:
156 # i18n: "files" is a keyword
156 # i18n: "files" is a keyword
157 raise error.ParseError(_(b"files expects one argument"))
157 raise error.ParseError(_(b"files expects one argument"))
158
158
159 raw = evalstring(context, mapping, args[0])
159 raw = evalstring(context, mapping, args[0])
160 ctx = context.resource(mapping, b'ctx')
160 ctx = context.resource(mapping, b'ctx')
161 m = ctx.match([raw])
161 m = ctx.match([raw])
162 files = list(ctx.matches(m))
162 files = list(ctx.matches(m))
163 return templateutil.compatfileslist(context, mapping, b"file", files)
163 return templateutil.compatfileslist(context, mapping, b"file", files)
164
164
165
165
166 @templatefunc(b'fill(text[, width[, initialident[, hangindent]]])')
166 @templatefunc(b'fill(text[, width[, initialident[, hangindent]]])')
167 def fill(context, mapping, args):
167 def fill(context, mapping, args):
168 """Fill many
168 """Fill many
169 paragraphs with optional indentation. See the "fill" filter."""
169 paragraphs with optional indentation. See the "fill" filter."""
170 if not (1 <= len(args) <= 4):
170 if not (1 <= len(args) <= 4):
171 # i18n: "fill" is a keyword
171 # i18n: "fill" is a keyword
172 raise error.ParseError(_(b"fill expects one to four arguments"))
172 raise error.ParseError(_(b"fill expects one to four arguments"))
173
173
174 text = evalstring(context, mapping, args[0])
174 text = evalstring(context, mapping, args[0])
175 width = 76
175 width = 76
176 initindent = b''
176 initindent = b''
177 hangindent = b''
177 hangindent = b''
178 if 2 <= len(args) <= 4:
178 if 2 <= len(args) <= 4:
179 width = evalinteger(
179 width = evalinteger(
180 context,
180 context,
181 mapping,
181 mapping,
182 args[1],
182 args[1],
183 # i18n: "fill" is a keyword
183 # i18n: "fill" is a keyword
184 _(b"fill expects an integer width"),
184 _(b"fill expects an integer width"),
185 )
185 )
186 try:
186 try:
187 initindent = evalstring(context, mapping, args[2])
187 initindent = evalstring(context, mapping, args[2])
188 hangindent = evalstring(context, mapping, args[3])
188 hangindent = evalstring(context, mapping, args[3])
189 except IndexError:
189 except IndexError:
190 pass
190 pass
191
191
192 return templatefilters.fill(text, width, initindent, hangindent)
192 return templatefilters.fill(text, width, initindent, hangindent)
193
193
194
194
195 @templatefunc(b'filter(iterable[, expr])')
195 @templatefunc(b'filter(iterable[, expr])')
196 def filter_(context, mapping, args):
196 def filter_(context, mapping, args):
197 """Remove empty elements from a list or a dict. If expr specified, it's
197 """Remove empty elements from a list or a dict. If expr specified, it's
198 applied to each element to test emptiness."""
198 applied to each element to test emptiness."""
199 if not (1 <= len(args) <= 2):
199 if not (1 <= len(args) <= 2):
200 # i18n: "filter" is a keyword
200 # i18n: "filter" is a keyword
201 raise error.ParseError(_(b"filter expects one or two arguments"))
201 raise error.ParseError(_(b"filter expects one or two arguments"))
202 iterable = evalwrapped(context, mapping, args[0])
202 iterable = evalwrapped(context, mapping, args[0])
203 if len(args) == 1:
203 if len(args) == 1:
204
204
205 def select(w):
205 def select(w):
206 return w.tobool(context, mapping)
206 return w.tobool(context, mapping)
207
207
208 else:
208 else:
209
209
210 def select(w):
210 def select(w):
211 if not isinstance(w, templateutil.mappable):
211 if not isinstance(w, templateutil.mappable):
212 raise error.ParseError(_(b"not filterable by expression"))
212 raise error.ParseError(_(b"not filterable by expression"))
213 lm = context.overlaymap(mapping, w.tomap(context))
213 lm = context.overlaymap(mapping, w.tomap(context))
214 return evalboolean(context, lm, args[1])
214 return evalboolean(context, lm, args[1])
215
215
216 return iterable.filter(context, mapping, select)
216 return iterable.filter(context, mapping, select)
217
217
218
218
219 @templatefunc(b'formatnode(node)', requires={b'ui'})
219 @templatefunc(b'formatnode(node)', requires={b'ui'})
220 def formatnode(context, mapping, args):
220 def formatnode(context, mapping, args):
221 """Obtain the preferred form of a changeset hash. (DEPRECATED)"""
221 """Obtain the preferred form of a changeset hash. (DEPRECATED)"""
222 if len(args) != 1:
222 if len(args) != 1:
223 # i18n: "formatnode" is a keyword
223 # i18n: "formatnode" is a keyword
224 raise error.ParseError(_(b"formatnode expects one argument"))
224 raise error.ParseError(_(b"formatnode expects one argument"))
225
225
226 ui = context.resource(mapping, b'ui')
226 ui = context.resource(mapping, b'ui')
227 node = evalstring(context, mapping, args[0])
227 node = evalstring(context, mapping, args[0])
228 if ui.debugflag:
228 if ui.debugflag:
229 return node
229 return node
230 return templatefilters.short(node)
230 return templatefilters.short(node)
231
231
232
232
233 @templatefunc(b'mailmap(author)', requires={b'repo', b'cache'})
233 @templatefunc(b'mailmap(author)', requires={b'repo', b'cache'})
234 def mailmap(context, mapping, args):
234 def mailmap(context, mapping, args):
235 """Return the author, updated according to the value
235 """Return the author, updated according to the value
236 set in the .mailmap file"""
236 set in the .mailmap file"""
237 if len(args) != 1:
237 if len(args) != 1:
238 raise error.ParseError(_(b"mailmap expects one argument"))
238 raise error.ParseError(_(b"mailmap expects one argument"))
239
239
240 author = evalstring(context, mapping, args[0])
240 author = evalstring(context, mapping, args[0])
241
241
242 cache = context.resource(mapping, b'cache')
242 cache = context.resource(mapping, b'cache')
243 repo = context.resource(mapping, b'repo')
243 repo = context.resource(mapping, b'repo')
244
244
245 if b'mailmap' not in cache:
245 if b'mailmap' not in cache:
246 data = repo.wvfs.tryread(b'.mailmap')
246 data = repo.wvfs.tryread(b'.mailmap')
247 cache[b'mailmap'] = stringutil.parsemailmap(data)
247 cache[b'mailmap'] = stringutil.parsemailmap(data)
248
248
249 return stringutil.mapname(cache[b'mailmap'], author)
249 return stringutil.mapname(cache[b'mailmap'], author)
250
250
251
251
252 @templatefunc(
252 @templatefunc(
253 b'pad(text, width[, fillchar=\' \'[, left=False[, truncate=False]]])',
253 b'pad(text, width[, fillchar=\' \'[, left=False[, truncate=False]]])',
254 argspec=b'text width fillchar left truncate',
254 argspec=b'text width fillchar left truncate',
255 )
255 )
256 def pad(context, mapping, args):
256 def pad(context, mapping, args):
257 """Pad text with a
257 """Pad text with a
258 fill character."""
258 fill character."""
259 if b'text' not in args or b'width' not in args:
259 if b'text' not in args or b'width' not in args:
260 # i18n: "pad" is a keyword
260 # i18n: "pad" is a keyword
261 raise error.ParseError(_(b"pad() expects two to four arguments"))
261 raise error.ParseError(_(b"pad() expects two to four arguments"))
262
262
263 width = evalinteger(
263 width = evalinteger(
264 context,
264 context,
265 mapping,
265 mapping,
266 args[b'width'],
266 args[b'width'],
267 # i18n: "pad" is a keyword
267 # i18n: "pad" is a keyword
268 _(b"pad() expects an integer width"),
268 _(b"pad() expects an integer width"),
269 )
269 )
270
270
271 text = evalstring(context, mapping, args[b'text'])
271 text = evalstring(context, mapping, args[b'text'])
272
272
273 truncate = False
273 truncate = False
274 left = False
274 left = False
275 fillchar = b' '
275 fillchar = b' '
276 if b'fillchar' in args:
276 if b'fillchar' in args:
277 fillchar = evalstring(context, mapping, args[b'fillchar'])
277 fillchar = evalstring(context, mapping, args[b'fillchar'])
278 if len(color.stripeffects(fillchar)) != 1:
278 if len(color.stripeffects(fillchar)) != 1:
279 # i18n: "pad" is a keyword
279 # i18n: "pad" is a keyword
280 raise error.ParseError(_(b"pad() expects a single fill character"))
280 raise error.ParseError(_(b"pad() expects a single fill character"))
281 if b'left' in args:
281 if b'left' in args:
282 left = evalboolean(context, mapping, args[b'left'])
282 left = evalboolean(context, mapping, args[b'left'])
283 if b'truncate' in args:
283 if b'truncate' in args:
284 truncate = evalboolean(context, mapping, args[b'truncate'])
284 truncate = evalboolean(context, mapping, args[b'truncate'])
285
285
286 fillwidth = width - encoding.colwidth(color.stripeffects(text))
286 fillwidth = width - encoding.colwidth(color.stripeffects(text))
287 if fillwidth < 0 and truncate:
287 if fillwidth < 0 and truncate:
288 return encoding.trim(color.stripeffects(text), width, leftside=left)
288 return encoding.trim(color.stripeffects(text), width, leftside=left)
289 if fillwidth <= 0:
289 if fillwidth <= 0:
290 return text
290 return text
291 if left:
291 if left:
292 return fillchar * fillwidth + text
292 return fillchar * fillwidth + text
293 else:
293 else:
294 return text + fillchar * fillwidth
294 return text + fillchar * fillwidth
295
295
296
296
297 @templatefunc(b'indent(text, indentchars[, firstline])')
297 @templatefunc(b'indent(text, indentchars[, firstline])')
298 def indent(context, mapping, args):
298 def indent(context, mapping, args):
299 """Indents all non-empty lines
299 """Indents all non-empty lines
300 with the characters given in the indentchars string. An optional
300 with the characters given in the indentchars string. An optional
301 third parameter will override the indent for the first line only
301 third parameter will override the indent for the first line only
302 if present."""
302 if present."""
303 if not (2 <= len(args) <= 3):
303 if not (2 <= len(args) <= 3):
304 # i18n: "indent" is a keyword
304 # i18n: "indent" is a keyword
305 raise error.ParseError(_(b"indent() expects two or three arguments"))
305 raise error.ParseError(_(b"indent() expects two or three arguments"))
306
306
307 text = evalstring(context, mapping, args[0])
307 text = evalstring(context, mapping, args[0])
308 indent = evalstring(context, mapping, args[1])
308 indent = evalstring(context, mapping, args[1])
309
309
310 firstline = indent
310 firstline = indent
311 if len(args) == 3:
311 if len(args) == 3:
312 firstline = evalstring(context, mapping, args[2])
312 firstline = evalstring(context, mapping, args[2])
313
313
314 return templatefilters.indent(text, indent, firstline=firstline)
314 return templatefilters.indent(text, indent, firstline=firstline)
315
315
316
316
317 @templatefunc(b'get(dict, key)')
317 @templatefunc(b'get(dict, key)')
318 def get(context, mapping, args):
318 def get(context, mapping, args):
319 """Get an attribute/key from an object. Some keywords
319 """Get an attribute/key from an object. Some keywords
320 are complex types. This function allows you to obtain the value of an
320 are complex types. This function allows you to obtain the value of an
321 attribute on these types."""
321 attribute on these types."""
322 if len(args) != 2:
322 if len(args) != 2:
323 # i18n: "get" is a keyword
323 # i18n: "get" is a keyword
324 raise error.ParseError(_(b"get() expects two arguments"))
324 raise error.ParseError(_(b"get() expects two arguments"))
325
325
326 dictarg = evalwrapped(context, mapping, args[0])
326 dictarg = evalwrapped(context, mapping, args[0])
327 key = evalrawexp(context, mapping, args[1])
327 key = evalrawexp(context, mapping, args[1])
328 try:
328 try:
329 return dictarg.getmember(context, mapping, key)
329 return dictarg.getmember(context, mapping, key)
330 except error.ParseError as err:
330 except error.ParseError as err:
331 # i18n: "get" is a keyword
331 # i18n: "get" is a keyword
332 hint = _(b"get() expects a dict as first argument")
332 hint = _(b"get() expects a dict as first argument")
333 raise error.ParseError(bytes(err), hint=hint)
333 raise error.ParseError(bytes(err), hint=hint)
334
334
335
335
336 @templatefunc(b'config(section, name[, default])', requires={b'ui'})
336 @templatefunc(b'config(section, name[, default])', requires={b'ui'})
337 def config(context, mapping, args):
337 def config(context, mapping, args):
338 """Returns the requested hgrc config option as a string."""
338 """Returns the requested hgrc config option as a string."""
339 fn = context.resource(mapping, b'ui').config
339 fn = context.resource(mapping, b'ui').config
340 return _config(context, mapping, args, fn, evalstring)
340 return _config(context, mapping, args, fn, evalstring)
341
341
342
342
343 @templatefunc(b'configbool(section, name[, default])', requires={b'ui'})
343 @templatefunc(b'configbool(section, name[, default])', requires={b'ui'})
344 def configbool(context, mapping, args):
344 def configbool(context, mapping, args):
345 """Returns the requested hgrc config option as a boolean."""
345 """Returns the requested hgrc config option as a boolean."""
346 fn = context.resource(mapping, b'ui').configbool
346 fn = context.resource(mapping, b'ui').configbool
347 return _config(context, mapping, args, fn, evalboolean)
347 return _config(context, mapping, args, fn, evalboolean)
348
348
349
349
350 @templatefunc(b'configint(section, name[, default])', requires={b'ui'})
350 @templatefunc(b'configint(section, name[, default])', requires={b'ui'})
351 def configint(context, mapping, args):
351 def configint(context, mapping, args):
352 """Returns the requested hgrc config option as an integer."""
352 """Returns the requested hgrc config option as an integer."""
353 fn = context.resource(mapping, b'ui').configint
353 fn = context.resource(mapping, b'ui').configint
354 return _config(context, mapping, args, fn, evalinteger)
354 return _config(context, mapping, args, fn, evalinteger)
355
355
356
356
357 def _config(context, mapping, args, configfn, defaultfn):
357 def _config(context, mapping, args, configfn, defaultfn):
358 if not (2 <= len(args) <= 3):
358 if not (2 <= len(args) <= 3):
359 raise error.ParseError(_(b"config expects two or three arguments"))
359 raise error.ParseError(_(b"config expects two or three arguments"))
360
360
361 # The config option can come from any section, though we specifically
361 # The config option can come from any section, though we specifically
362 # reserve the [templateconfig] section for dynamically defining options
362 # reserve the [templateconfig] section for dynamically defining options
363 # for this function without also requiring an extension.
363 # for this function without also requiring an extension.
364 section = evalstringliteral(context, mapping, args[0])
364 section = evalstringliteral(context, mapping, args[0])
365 name = evalstringliteral(context, mapping, args[1])
365 name = evalstringliteral(context, mapping, args[1])
366 if len(args) == 3:
366 if len(args) == 3:
367 default = defaultfn(context, mapping, args[2])
367 default = defaultfn(context, mapping, args[2])
368 return configfn(section, name, default)
368 return configfn(section, name, default)
369 else:
369 else:
370 return configfn(section, name)
370 return configfn(section, name)
371
371
372
372
373 @templatefunc(b'if(expr, then[, else])')
373 @templatefunc(b'if(expr, then[, else])')
374 def if_(context, mapping, args):
374 def if_(context, mapping, args):
375 """Conditionally execute based on the result of
375 """Conditionally execute based on the result of
376 an expression."""
376 an expression."""
377 if not (2 <= len(args) <= 3):
377 if not (2 <= len(args) <= 3):
378 # i18n: "if" is a keyword
378 # i18n: "if" is a keyword
379 raise error.ParseError(_(b"if expects two or three arguments"))
379 raise error.ParseError(_(b"if expects two or three arguments"))
380
380
381 test = evalboolean(context, mapping, args[0])
381 test = evalboolean(context, mapping, args[0])
382 if test:
382 if test:
383 return evalrawexp(context, mapping, args[1])
383 return evalrawexp(context, mapping, args[1])
384 elif len(args) == 3:
384 elif len(args) == 3:
385 return evalrawexp(context, mapping, args[2])
385 return evalrawexp(context, mapping, args[2])
386
386
387
387
388 @templatefunc(b'ifcontains(needle, haystack, then[, else])')
388 @templatefunc(b'ifcontains(needle, haystack, then[, else])')
389 def ifcontains(context, mapping, args):
389 def ifcontains(context, mapping, args):
390 """Conditionally execute based
390 """Conditionally execute based
391 on whether the item "needle" is in "haystack"."""
391 on whether the item "needle" is in "haystack"."""
392 if not (3 <= len(args) <= 4):
392 if not (3 <= len(args) <= 4):
393 # i18n: "ifcontains" is a keyword
393 # i18n: "ifcontains" is a keyword
394 raise error.ParseError(_(b"ifcontains expects three or four arguments"))
394 raise error.ParseError(_(b"ifcontains expects three or four arguments"))
395
395
396 haystack = evalwrapped(context, mapping, args[1])
396 haystack = evalwrapped(context, mapping, args[1])
397 try:
397 try:
398 needle = evalrawexp(context, mapping, args[0])
398 needle = evalrawexp(context, mapping, args[0])
399 found = haystack.contains(context, mapping, needle)
399 found = haystack.contains(context, mapping, needle)
400 except error.ParseError:
400 except error.ParseError:
401 found = False
401 found = False
402
402
403 if found:
403 if found:
404 return evalrawexp(context, mapping, args[2])
404 return evalrawexp(context, mapping, args[2])
405 elif len(args) == 4:
405 elif len(args) == 4:
406 return evalrawexp(context, mapping, args[3])
406 return evalrawexp(context, mapping, args[3])
407
407
408
408
409 @templatefunc(b'ifeq(expr1, expr2, then[, else])')
409 @templatefunc(b'ifeq(expr1, expr2, then[, else])')
410 def ifeq(context, mapping, args):
410 def ifeq(context, mapping, args):
411 """Conditionally execute based on
411 """Conditionally execute based on
412 whether 2 items are equivalent."""
412 whether 2 items are equivalent."""
413 if not (3 <= len(args) <= 4):
413 if not (3 <= len(args) <= 4):
414 # i18n: "ifeq" is a keyword
414 # i18n: "ifeq" is a keyword
415 raise error.ParseError(_(b"ifeq expects three or four arguments"))
415 raise error.ParseError(_(b"ifeq expects three or four arguments"))
416
416
417 test = evalstring(context, mapping, args[0])
417 test = evalstring(context, mapping, args[0])
418 match = evalstring(context, mapping, args[1])
418 match = evalstring(context, mapping, args[1])
419 if test == match:
419 if test == match:
420 return evalrawexp(context, mapping, args[2])
420 return evalrawexp(context, mapping, args[2])
421 elif len(args) == 4:
421 elif len(args) == 4:
422 return evalrawexp(context, mapping, args[3])
422 return evalrawexp(context, mapping, args[3])
423
423
424
424
425 @templatefunc(b'join(list, sep)')
425 @templatefunc(b'join(list, sep)')
426 def join(context, mapping, args):
426 def join(context, mapping, args):
427 """Join items in a list with a delimiter."""
427 """Join items in a list with a delimiter."""
428 if not (1 <= len(args) <= 2):
428 if not (1 <= len(args) <= 2):
429 # i18n: "join" is a keyword
429 # i18n: "join" is a keyword
430 raise error.ParseError(_(b"join expects one or two arguments"))
430 raise error.ParseError(_(b"join expects one or two arguments"))
431
431
432 joinset = evalwrapped(context, mapping, args[0])
432 joinset = evalwrapped(context, mapping, args[0])
433 joiner = b" "
433 joiner = b" "
434 if len(args) > 1:
434 if len(args) > 1:
435 joiner = evalstring(context, mapping, args[1])
435 joiner = evalstring(context, mapping, args[1])
436 return joinset.join(context, mapping, joiner)
436 return joinset.join(context, mapping, joiner)
437
437
438
438
439 @templatefunc(b'label(label, expr)', requires={b'ui'})
439 @templatefunc(b'label(label, expr)', requires={b'ui'})
440 def label(context, mapping, args):
440 def label(context, mapping, args):
441 """Apply a label to generated content. Content with
441 """Apply a label to generated content. Content with
442 a label applied can result in additional post-processing, such as
442 a label applied can result in additional post-processing, such as
443 automatic colorization."""
443 automatic colorization."""
444 if len(args) != 2:
444 if len(args) != 2:
445 # i18n: "label" is a keyword
445 # i18n: "label" is a keyword
446 raise error.ParseError(_(b"label expects two arguments"))
446 raise error.ParseError(_(b"label expects two arguments"))
447
447
448 ui = context.resource(mapping, b'ui')
448 ui = context.resource(mapping, b'ui')
449 thing = evalstring(context, mapping, args[1])
449 thing = evalstring(context, mapping, args[1])
450 # preserve unknown symbol as literal so effects like 'red', 'bold',
450 # preserve unknown symbol as literal so effects like 'red', 'bold',
451 # etc. don't need to be quoted
451 # etc. don't need to be quoted
452 label = evalstringliteral(context, mapping, args[0])
452 label = evalstringliteral(context, mapping, args[0])
453
453
454 return ui.label(thing, label)
454 return ui.label(thing, label)
455
455
456
456
457 @templatefunc(b'latesttag([pattern])')
457 @templatefunc(b'latesttag([pattern])')
458 def latesttag(context, mapping, args):
458 def latesttag(context, mapping, args):
459 """The global tags matching the given pattern on the
459 """The global tags matching the given pattern on the
460 most recent globally tagged ancestor of this changeset.
460 most recent globally tagged ancestor of this changeset.
461 If no such tags exist, the "{tag}" template resolves to
461 If no such tags exist, the "{tag}" template resolves to
462 the string "null". See :hg:`help revisions.patterns` for the pattern
462 the string "null". See :hg:`help revisions.patterns` for the pattern
463 syntax.
463 syntax.
464 """
464 """
465 if len(args) > 1:
465 if len(args) > 1:
466 # i18n: "latesttag" is a keyword
466 # i18n: "latesttag" is a keyword
467 raise error.ParseError(_(b"latesttag expects at most one argument"))
467 raise error.ParseError(_(b"latesttag expects at most one argument"))
468
468
469 pattern = None
469 pattern = None
470 if len(args) == 1:
470 if len(args) == 1:
471 pattern = evalstring(context, mapping, args[0])
471 pattern = evalstring(context, mapping, args[0])
472 return templatekw.showlatesttags(context, mapping, pattern)
472 return templatekw.showlatesttags(context, mapping, pattern)
473
473
474
474
475 @templatefunc(b'localdate(date[, tz])')
475 @templatefunc(b'localdate(date[, tz])')
476 def localdate(context, mapping, args):
476 def localdate(context, mapping, args):
477 """Converts a date to the specified timezone.
477 """Converts a date to the specified timezone.
478 The default is local date."""
478 The default is local date."""
479 if not (1 <= len(args) <= 2):
479 if not (1 <= len(args) <= 2):
480 # i18n: "localdate" is a keyword
480 # i18n: "localdate" is a keyword
481 raise error.ParseError(_(b"localdate expects one or two arguments"))
481 raise error.ParseError(_(b"localdate expects one or two arguments"))
482
482
483 date = evaldate(
483 date = evaldate(
484 context,
484 context,
485 mapping,
485 mapping,
486 args[0],
486 args[0],
487 # i18n: "localdate" is a keyword
487 # i18n: "localdate" is a keyword
488 _(b"localdate expects a date information"),
488 _(b"localdate expects a date information"),
489 )
489 )
490 if len(args) >= 2:
490 if len(args) >= 2:
491 tzoffset = None
491 tzoffset = None
492 tz = evalfuncarg(context, mapping, args[1])
492 tz = evalfuncarg(context, mapping, args[1])
493 if isinstance(tz, bytes):
493 if isinstance(tz, bytes):
494 tzoffset, remainder = dateutil.parsetimezone(tz)
494 tzoffset, remainder = dateutil.parsetimezone(tz)
495 if remainder:
495 if remainder:
496 tzoffset = None
496 tzoffset = None
497 if tzoffset is None:
497 if tzoffset is None:
498 try:
498 try:
499 tzoffset = int(tz)
499 tzoffset = int(tz)
500 except (TypeError, ValueError):
500 except (TypeError, ValueError):
501 # i18n: "localdate" is a keyword
501 # i18n: "localdate" is a keyword
502 raise error.ParseError(_(b"localdate expects a timezone"))
502 raise error.ParseError(_(b"localdate expects a timezone"))
503 else:
503 else:
504 tzoffset = dateutil.makedate()[1]
504 tzoffset = dateutil.makedate()[1]
505 return templateutil.date((date[0], tzoffset))
505 return templateutil.date((date[0], tzoffset))
506
506
507
507
508 @templatefunc(b'max(iterable)')
508 @templatefunc(b'max(iterable)')
509 def max_(context, mapping, args, **kwargs):
509 def max_(context, mapping, args, **kwargs):
510 """Return the max of an iterable"""
510 """Return the max of an iterable"""
511 if len(args) != 1:
511 if len(args) != 1:
512 # i18n: "max" is a keyword
512 # i18n: "max" is a keyword
513 raise error.ParseError(_(b"max expects one argument"))
513 raise error.ParseError(_(b"max expects one argument"))
514
514
515 iterable = evalwrapped(context, mapping, args[0])
515 iterable = evalwrapped(context, mapping, args[0])
516 try:
516 try:
517 return iterable.getmax(context, mapping)
517 return iterable.getmax(context, mapping)
518 except error.ParseError as err:
518 except error.ParseError as err:
519 # i18n: "max" is a keyword
519 # i18n: "max" is a keyword
520 hint = _(b"max first argument should be an iterable")
520 hint = _(b"max first argument should be an iterable")
521 raise error.ParseError(bytes(err), hint=hint)
521 raise error.ParseError(bytes(err), hint=hint)
522
522
523
523
524 @templatefunc(b'min(iterable)')
524 @templatefunc(b'min(iterable)')
525 def min_(context, mapping, args, **kwargs):
525 def min_(context, mapping, args, **kwargs):
526 """Return the min of an iterable"""
526 """Return the min of an iterable"""
527 if len(args) != 1:
527 if len(args) != 1:
528 # i18n: "min" is a keyword
528 # i18n: "min" is a keyword
529 raise error.ParseError(_(b"min expects one argument"))
529 raise error.ParseError(_(b"min expects one argument"))
530
530
531 iterable = evalwrapped(context, mapping, args[0])
531 iterable = evalwrapped(context, mapping, args[0])
532 try:
532 try:
533 return iterable.getmin(context, mapping)
533 return iterable.getmin(context, mapping)
534 except error.ParseError as err:
534 except error.ParseError as err:
535 # i18n: "min" is a keyword
535 # i18n: "min" is a keyword
536 hint = _(b"min first argument should be an iterable")
536 hint = _(b"min first argument should be an iterable")
537 raise error.ParseError(bytes(err), hint=hint)
537 raise error.ParseError(bytes(err), hint=hint)
538
538
539
539
540 @templatefunc(b'mod(a, b)')
540 @templatefunc(b'mod(a, b)')
541 def mod(context, mapping, args):
541 def mod(context, mapping, args):
542 """Calculate a mod b such that a / b + a mod b == a"""
542 """Calculate a mod b such that a / b + a mod b == a"""
543 if not len(args) == 2:
543 if not len(args) == 2:
544 # i18n: "mod" is a keyword
544 # i18n: "mod" is a keyword
545 raise error.ParseError(_(b"mod expects two arguments"))
545 raise error.ParseError(_(b"mod expects two arguments"))
546
546
547 func = lambda a, b: a % b
547 func = lambda a, b: a % b
548 return templateutil.runarithmetic(
548 return templateutil.runarithmetic(
549 context, mapping, (func, args[0], args[1])
549 context, mapping, (func, args[0], args[1])
550 )
550 )
551
551
552
552
553 @templatefunc(b'obsfateoperations(markers)')
553 @templatefunc(b'obsfateoperations(markers)')
554 def obsfateoperations(context, mapping, args):
554 def obsfateoperations(context, mapping, args):
555 """Compute obsfate related information based on markers (EXPERIMENTAL)"""
555 """Compute obsfate related information based on markers (EXPERIMENTAL)"""
556 if len(args) != 1:
556 if len(args) != 1:
557 # i18n: "obsfateoperations" is a keyword
557 # i18n: "obsfateoperations" is a keyword
558 raise error.ParseError(_(b"obsfateoperations expects one argument"))
558 raise error.ParseError(_(b"obsfateoperations expects one argument"))
559
559
560 markers = evalfuncarg(context, mapping, args[0])
560 markers = evalfuncarg(context, mapping, args[0])
561
561
562 try:
562 try:
563 data = obsutil.markersoperations(markers)
563 data = obsutil.markersoperations(markers)
564 return templateutil.hybridlist(data, name=b'operation')
564 return templateutil.hybridlist(data, name=b'operation')
565 except (TypeError, KeyError):
565 except (TypeError, KeyError):
566 # i18n: "obsfateoperations" is a keyword
566 # i18n: "obsfateoperations" is a keyword
567 errmsg = _(b"obsfateoperations first argument should be an iterable")
567 errmsg = _(b"obsfateoperations first argument should be an iterable")
568 raise error.ParseError(errmsg)
568 raise error.ParseError(errmsg)
569
569
570
570
571 @templatefunc(b'obsfatedate(markers)')
571 @templatefunc(b'obsfatedate(markers)')
572 def obsfatedate(context, mapping, args):
572 def obsfatedate(context, mapping, args):
573 """Compute obsfate related information based on markers (EXPERIMENTAL)"""
573 """Compute obsfate related information based on markers (EXPERIMENTAL)"""
574 if len(args) != 1:
574 if len(args) != 1:
575 # i18n: "obsfatedate" is a keyword
575 # i18n: "obsfatedate" is a keyword
576 raise error.ParseError(_(b"obsfatedate expects one argument"))
576 raise error.ParseError(_(b"obsfatedate expects one argument"))
577
577
578 markers = evalfuncarg(context, mapping, args[0])
578 markers = evalfuncarg(context, mapping, args[0])
579
579
580 try:
580 try:
581 # TODO: maybe this has to be a wrapped list of date wrappers?
581 # TODO: maybe this has to be a wrapped list of date wrappers?
582 data = obsutil.markersdates(markers)
582 data = obsutil.markersdates(markers)
583 return templateutil.hybridlist(data, name=b'date', fmt=b'%d %d')
583 return templateutil.hybridlist(data, name=b'date', fmt=b'%d %d')
584 except (TypeError, KeyError):
584 except (TypeError, KeyError):
585 # i18n: "obsfatedate" is a keyword
585 # i18n: "obsfatedate" is a keyword
586 errmsg = _(b"obsfatedate first argument should be an iterable")
586 errmsg = _(b"obsfatedate first argument should be an iterable")
587 raise error.ParseError(errmsg)
587 raise error.ParseError(errmsg)
588
588
589
589
590 @templatefunc(b'obsfateusers(markers)')
590 @templatefunc(b'obsfateusers(markers)')
591 def obsfateusers(context, mapping, args):
591 def obsfateusers(context, mapping, args):
592 """Compute obsfate related information based on markers (EXPERIMENTAL)"""
592 """Compute obsfate related information based on markers (EXPERIMENTAL)"""
593 if len(args) != 1:
593 if len(args) != 1:
594 # i18n: "obsfateusers" is a keyword
594 # i18n: "obsfateusers" is a keyword
595 raise error.ParseError(_(b"obsfateusers expects one argument"))
595 raise error.ParseError(_(b"obsfateusers expects one argument"))
596
596
597 markers = evalfuncarg(context, mapping, args[0])
597 markers = evalfuncarg(context, mapping, args[0])
598
598
599 try:
599 try:
600 data = obsutil.markersusers(markers)
600 data = obsutil.markersusers(markers)
601 return templateutil.hybridlist(data, name=b'user')
601 return templateutil.hybridlist(data, name=b'user')
602 except (TypeError, KeyError, ValueError):
602 except (TypeError, KeyError, ValueError):
603 # i18n: "obsfateusers" is a keyword
603 # i18n: "obsfateusers" is a keyword
604 msg = _(
604 msg = _(
605 b"obsfateusers first argument should be an iterable of "
605 b"obsfateusers first argument should be an iterable of "
606 b"obsmakers"
606 b"obsmakers"
607 )
607 )
608 raise error.ParseError(msg)
608 raise error.ParseError(msg)
609
609
610
610
611 @templatefunc(b'obsfateverb(successors, markers)')
611 @templatefunc(b'obsfateverb(successors, markers)')
612 def obsfateverb(context, mapping, args):
612 def obsfateverb(context, mapping, args):
613 """Compute obsfate related information based on successors (EXPERIMENTAL)"""
613 """Compute obsfate related information based on successors (EXPERIMENTAL)"""
614 if len(args) != 2:
614 if len(args) != 2:
615 # i18n: "obsfateverb" is a keyword
615 # i18n: "obsfateverb" is a keyword
616 raise error.ParseError(_(b"obsfateverb expects two arguments"))
616 raise error.ParseError(_(b"obsfateverb expects two arguments"))
617
617
618 successors = evalfuncarg(context, mapping, args[0])
618 successors = evalfuncarg(context, mapping, args[0])
619 markers = evalfuncarg(context, mapping, args[1])
619 markers = evalfuncarg(context, mapping, args[1])
620
620
621 try:
621 try:
622 return obsutil.obsfateverb(successors, markers)
622 return obsutil.obsfateverb(successors, markers)
623 except TypeError:
623 except TypeError:
624 # i18n: "obsfateverb" is a keyword
624 # i18n: "obsfateverb" is a keyword
625 errmsg = _(b"obsfateverb first argument should be countable")
625 errmsg = _(b"obsfateverb first argument should be countable")
626 raise error.ParseError(errmsg)
626 raise error.ParseError(errmsg)
627
627
628
628
629 @templatefunc(b'relpath(path)', requires={b'repo'})
629 @templatefunc(b'relpath(path)', requires={b'repo'})
630 def relpath(context, mapping, args):
630 def relpath(context, mapping, args):
631 """Convert a repository-absolute path into a filesystem path relative to
631 """Convert a repository-absolute path into a filesystem path relative to
632 the current working directory."""
632 the current working directory."""
633 if len(args) != 1:
633 if len(args) != 1:
634 # i18n: "relpath" is a keyword
634 # i18n: "relpath" is a keyword
635 raise error.ParseError(_(b"relpath expects one argument"))
635 raise error.ParseError(_(b"relpath expects one argument"))
636
636
637 repo = context.resource(mapping, b'repo')
637 repo = context.resource(mapping, b'repo')
638 path = evalstring(context, mapping, args[0])
638 path = evalstring(context, mapping, args[0])
639 return repo.pathto(path)
639 return repo.pathto(path)
640
640
641
641
642 @templatefunc(b'revset(query[, formatargs...])', requires={b'repo', b'cache'})
642 @templatefunc(b'revset(query[, formatargs...])', requires={b'repo', b'cache'})
643 def revset(context, mapping, args):
643 def revset(context, mapping, args):
644 """Execute a revision set query. See
644 """Execute a revision set query. See
645 :hg:`help revset`."""
645 :hg:`help revset`."""
646 if not len(args) > 0:
646 if not len(args) > 0:
647 # i18n: "revset" is a keyword
647 # i18n: "revset" is a keyword
648 raise error.ParseError(_(b"revset expects one or more arguments"))
648 raise error.ParseError(_(b"revset expects one or more arguments"))
649
649
650 raw = evalstring(context, mapping, args[0])
650 raw = evalstring(context, mapping, args[0])
651 repo = context.resource(mapping, b'repo')
651 repo = context.resource(mapping, b'repo')
652
652
653 def query(expr):
653 def query(expr):
654 m = revsetmod.match(repo.ui, expr, lookup=revsetmod.lookupfn(repo))
654 m = revsetmod.match(repo.ui, expr, lookup=revsetmod.lookupfn(repo))
655 return m(repo)
655 return m(repo)
656
656
657 if len(args) > 1:
657 if len(args) > 1:
658 key = None # dynamically-created revs shouldn't be cached
658 key = None # dynamically-created revs shouldn't be cached
659 formatargs = [evalfuncarg(context, mapping, a) for a in args[1:]]
659 formatargs = [evalfuncarg(context, mapping, a) for a in args[1:]]
660 revs = query(revsetlang.formatspec(raw, *formatargs))
660 revs = query(revsetlang.formatspec(raw, *formatargs))
661 else:
661 else:
662 cache = context.resource(mapping, b'cache')
662 cache = context.resource(mapping, b'cache')
663 revsetcache = cache.setdefault(b"revsetcache", {})
663 revsetcache = cache.setdefault(b"revsetcache", {})
664 key = raw
664 key = raw
665 if key in revsetcache:
665 if key in revsetcache:
666 revs = revsetcache[key]
666 revs = revsetcache[key]
667 else:
667 else:
668 revs = query(raw)
668 revs = query(raw)
669 revsetcache[key] = revs
669 revsetcache[key] = revs
670 return templateutil.revslist(repo, revs, name=b'revision', cachekey=key)
670 return templateutil.revslist(repo, revs, name=b'revision', cachekey=key)
671
671
672
672
673 @templatefunc(b'rstdoc(text, style)')
673 @templatefunc(b'rstdoc(text, style)')
674 def rstdoc(context, mapping, args):
674 def rstdoc(context, mapping, args):
675 """Format reStructuredText."""
675 """Format reStructuredText."""
676 if len(args) != 2:
676 if len(args) != 2:
677 # i18n: "rstdoc" is a keyword
677 # i18n: "rstdoc" is a keyword
678 raise error.ParseError(_(b"rstdoc expects two arguments"))
678 raise error.ParseError(_(b"rstdoc expects two arguments"))
679
679
680 text = evalstring(context, mapping, args[0])
680 text = evalstring(context, mapping, args[0])
681 style = evalstring(context, mapping, args[1])
681 style = evalstring(context, mapping, args[1])
682
682
683 return minirst.format(text, style=style, keep=[b'verbose'])
683 return minirst.format(text, style=style, keep=[b'verbose'])
684
684
685
685
686 @templatefunc(b'search(pattern, text)')
686 @templatefunc(b'search(pattern, text)')
687 def search(context, mapping, args):
687 def search(context, mapping, args):
688 """Look for the first text matching the regular expression pattern.
688 """Look for the first text matching the regular expression pattern.
689 Groups are accessible as ``{1}``, ``{2}``, ... in %-mapped template."""
689 Groups are accessible as ``{1}``, ``{2}``, ... in %-mapped template."""
690 if len(args) != 2:
690 if len(args) != 2:
691 # i18n: "search" is a keyword
691 # i18n: "search" is a keyword
692 raise error.ParseError(_(b'search expects two arguments'))
692 raise error.ParseError(_(b'search expects two arguments'))
693
693
694 pat = evalstring(context, mapping, args[0])
694 pat = evalstring(context, mapping, args[0])
695 src = evalstring(context, mapping, args[1])
695 src = evalstring(context, mapping, args[1])
696 try:
696 try:
697 patre = re.compile(pat)
697 patre = re.compile(pat)
698 except re.error:
698 except re.error:
699 # i18n: "search" is a keyword
699 # i18n: "search" is a keyword
700 raise error.ParseError(_(b'search got an invalid pattern: %s') % pat)
700 raise error.ParseError(_(b'search got an invalid pattern: %s') % pat)
701 # named groups shouldn't shadow *reserved* resource keywords
701 # named groups shouldn't shadow *reserved* resource keywords
702 badgroups = context.knownresourcekeys() & set(
702 badgroups = context.knownresourcekeys() & set(
703 pycompat.byteskwargs(patre.groupindex)
703 pycompat.byteskwargs(patre.groupindex)
704 )
704 )
705 if badgroups:
705 if badgroups:
706 raise error.ParseError(
706 raise error.ParseError(
707 # i18n: "search" is a keyword
707 # i18n: "search" is a keyword
708 _(b'invalid group %(group)s in search pattern: %(pat)s')
708 _(b'invalid group %(group)s in search pattern: %(pat)s')
709 % {
709 % {
710 b'group': b', '.join(b"'%s'" % g for g in sorted(badgroups)),
710 b'group': b', '.join(b"'%s'" % g for g in sorted(badgroups)),
711 b'pat': pat,
711 b'pat': pat,
712 }
712 }
713 )
713 )
714
714
715 match = patre.search(src)
715 match = patre.search(src)
716 if not match:
716 if not match:
717 return templateutil.mappingnone()
717 return templateutil.mappingnone()
718
718
719 lm = {b'0': match.group(0)}
719 lm = {b'0': match.group(0)}
720 lm.update((b'%d' % i, v) for i, v in enumerate(match.groups(), 1))
720 lm.update((b'%d' % i, v) for i, v in enumerate(match.groups(), 1))
721 lm.update(pycompat.byteskwargs(match.groupdict()))
721 lm.update(pycompat.byteskwargs(match.groupdict()))
722 return templateutil.mappingdict(lm, tmpl=b'{0}')
722 return templateutil.mappingdict(lm, tmpl=b'{0}')
723
723
724
724
725 @templatefunc(b'separate(sep, args...)', argspec=b'sep *args')
725 @templatefunc(b'separate(sep, args...)', argspec=b'sep *args')
726 def separate(context, mapping, args):
726 def separate(context, mapping, args):
727 """Add a separator between non-empty arguments."""
727 """Add a separator between non-empty arguments."""
728 if b'sep' not in args:
728 if b'sep' not in args:
729 # i18n: "separate" is a keyword
729 # i18n: "separate" is a keyword
730 raise error.ParseError(_(b"separate expects at least one argument"))
730 raise error.ParseError(_(b"separate expects at least one argument"))
731
731
732 sep = evalstring(context, mapping, args[b'sep'])
732 sep = evalstring(context, mapping, args[b'sep'])
733 first = True
733 first = True
734 for arg in args[b'args']:
734 for arg in args[b'args']:
735 argstr = evalstring(context, mapping, arg)
735 argstr = evalstring(context, mapping, arg)
736 if not argstr:
736 if not argstr:
737 continue
737 continue
738 if first:
738 if first:
739 first = False
739 first = False
740 else:
740 else:
741 yield sep
741 yield sep
742 yield argstr
742 yield argstr
743
743
744
744
745 @templatefunc(b'shortest(node, minlength=4)', requires={b'repo', b'cache'})
745 @templatefunc(b'shortest(node, minlength=4)', requires={b'repo', b'cache'})
746 def shortest(context, mapping, args):
746 def shortest(context, mapping, args):
747 """Obtain the shortest representation of
747 """Obtain the shortest representation of
748 a node."""
748 a node."""
749 if not (1 <= len(args) <= 2):
749 if not (1 <= len(args) <= 2):
750 # i18n: "shortest" is a keyword
750 # i18n: "shortest" is a keyword
751 raise error.ParseError(_(b"shortest() expects one or two arguments"))
751 raise error.ParseError(_(b"shortest() expects one or two arguments"))
752
752
753 hexnode = evalstring(context, mapping, args[0])
753 hexnode = evalstring(context, mapping, args[0])
754
754
755 minlength = 4
755 minlength = 4
756 if len(args) > 1:
756 if len(args) > 1:
757 minlength = evalinteger(
757 minlength = evalinteger(
758 context,
758 context,
759 mapping,
759 mapping,
760 args[1],
760 args[1],
761 # i18n: "shortest" is a keyword
761 # i18n: "shortest" is a keyword
762 _(b"shortest() expects an integer minlength"),
762 _(b"shortest() expects an integer minlength"),
763 )
763 )
764
764
765 repo = context.resource(mapping, b'repo')
765 repo = context.resource(mapping, b'repo')
766 hexnodelen = 2 * repo.nodeconstants.nodelen
766 hexnodelen = 2 * repo.nodeconstants.nodelen
767 if len(hexnode) > hexnodelen:
767 if len(hexnode) > hexnodelen:
768 return hexnode
768 return hexnode
769 elif len(hexnode) == hexnodelen:
769 elif len(hexnode) == hexnodelen:
770 try:
770 try:
771 node = bin(hexnode)
771 node = bin(hexnode)
772 except TypeError:
772 except TypeError:
773 return hexnode
773 return hexnode
774 else:
774 else:
775 try:
775 try:
776 node = scmutil.resolvehexnodeidprefix(repo, hexnode)
776 node = scmutil.resolvehexnodeidprefix(repo, hexnode)
777 except error.WdirUnsupported:
777 except error.WdirUnsupported:
778 node = repo.nodeconstants.wdirid
778 node = repo.nodeconstants.wdirid
779 except error.LookupError:
779 except error.LookupError:
780 return hexnode
780 return hexnode
781 if not node:
781 if not node:
782 return hexnode
782 return hexnode
783 cache = context.resource(mapping, b'cache')
783 cache = context.resource(mapping, b'cache')
784 try:
784 try:
785 return scmutil.shortesthexnodeidprefix(repo, node, minlength, cache)
785 return scmutil.shortesthexnodeidprefix(repo, node, minlength, cache)
786 except error.RepoLookupError:
786 except error.RepoLookupError:
787 return hexnode
787 return hexnode
788
788
789
789
790 @templatefunc(b'strip(text[, chars])')
790 @templatefunc(b'strip(text[, chars])')
791 def strip(context, mapping, args):
791 def strip(context, mapping, args):
792 """Strip characters from a string. By default,
792 """Strip characters from a string. By default,
793 strips all leading and trailing whitespace."""
793 strips all leading and trailing whitespace."""
794 if not (1 <= len(args) <= 2):
794 if not (1 <= len(args) <= 2):
795 # i18n: "strip" is a keyword
795 # i18n: "strip" is a keyword
796 raise error.ParseError(_(b"strip expects one or two arguments"))
796 raise error.ParseError(_(b"strip expects one or two arguments"))
797
797
798 text = evalstring(context, mapping, args[0])
798 text = evalstring(context, mapping, args[0])
799 if len(args) == 2:
799 if len(args) == 2:
800 chars = evalstring(context, mapping, args[1])
800 chars = evalstring(context, mapping, args[1])
801 return text.strip(chars)
801 return text.strip(chars)
802 return text.strip()
802 return text.strip()
803
803
804
804
805 @templatefunc(b'sub(pattern, replacement, expression)')
805 @templatefunc(b'sub(pattern, replacement, expression)')
806 def sub(context, mapping, args):
806 def sub(context, mapping, args):
807 """Perform text substitution
807 """Perform text substitution
808 using regular expressions."""
808 using regular expressions."""
809 if len(args) != 3:
809 if len(args) != 3:
810 # i18n: "sub" is a keyword
810 # i18n: "sub" is a keyword
811 raise error.ParseError(_(b"sub expects three arguments"))
811 raise error.ParseError(_(b"sub expects three arguments"))
812
812
813 pat = evalstring(context, mapping, args[0])
813 pat = evalstring(context, mapping, args[0])
814 rpl = evalstring(context, mapping, args[1])
814 rpl = evalstring(context, mapping, args[1])
815 src = evalstring(context, mapping, args[2])
815 src = evalstring(context, mapping, args[2])
816 try:
816 try:
817 patre = re.compile(pat)
817 patre = re.compile(pat)
818 except re.error:
818 except re.error:
819 # i18n: "sub" is a keyword
819 # i18n: "sub" is a keyword
820 raise error.ParseError(_(b"sub got an invalid pattern: %s") % pat)
820 raise error.ParseError(_(b"sub got an invalid pattern: %s") % pat)
821 try:
821 try:
822 yield patre.sub(rpl, src)
822 yield patre.sub(rpl, src)
823 except re.error:
823 except re.error:
824 # i18n: "sub" is a keyword
824 # i18n: "sub" is a keyword
825 raise error.ParseError(_(b"sub got an invalid replacement: %s") % rpl)
825 raise error.ParseError(_(b"sub got an invalid replacement: %s") % rpl)
826
826
827
827
828 @templatefunc(b'startswith(pattern, text)')
828 @templatefunc(b'startswith(pattern, text)')
829 def startswith(context, mapping, args):
829 def startswith(context, mapping, args):
830 """Returns the value from the "text" argument
830 """Returns the value from the "text" argument
831 if it begins with the content from the "pattern" argument."""
831 if it begins with the content from the "pattern" argument."""
832 if len(args) != 2:
832 if len(args) != 2:
833 # i18n: "startswith" is a keyword
833 # i18n: "startswith" is a keyword
834 raise error.ParseError(_(b"startswith expects two arguments"))
834 raise error.ParseError(_(b"startswith expects two arguments"))
835
835
836 patn = evalstring(context, mapping, args[0])
836 patn = evalstring(context, mapping, args[0])
837 text = evalstring(context, mapping, args[1])
837 text = evalstring(context, mapping, args[1])
838 if text.startswith(patn):
838 if text.startswith(patn):
839 return text
839 return text
840 return b''
840 return b''
841
841
842
842
843 @templatefunc(
843 @templatefunc(
844 b'subsetparents(rev, revset)',
844 b'subsetparents(rev, revset)',
845 argspec=b'rev revset',
845 argspec=b'rev revset',
846 requires={b'repo', b'cache'},
846 requires={b'repo', b'cache'},
847 )
847 )
848 def subsetparents(context, mapping, args):
848 def subsetparents(context, mapping, args):
849 """Look up parents of the rev in the sub graph given by the revset."""
849 """Look up parents of the rev in the sub graph given by the revset."""
850 if b'rev' not in args or b'revset' not in args:
850 if b'rev' not in args or b'revset' not in args:
851 # i18n: "subsetparents" is a keyword
851 # i18n: "subsetparents" is a keyword
852 raise error.ParseError(_(b"subsetparents expects two arguments"))
852 raise error.ParseError(_(b"subsetparents expects two arguments"))
853
853
854 repo = context.resource(mapping, b'repo')
854 repo = context.resource(mapping, b'repo')
855
855
856 rev = templateutil.evalinteger(context, mapping, args[b'rev'])
856 rev = templateutil.evalinteger(context, mapping, args[b'rev'])
857
857
858 # TODO: maybe subsetparents(rev) should be allowed. the default revset
858 # TODO: maybe subsetparents(rev) should be allowed. the default revset
859 # will be the revisions specified by -rREV argument.
859 # will be the revisions specified by -rREV argument.
860 q = templateutil.evalwrapped(context, mapping, args[b'revset'])
860 q = templateutil.evalwrapped(context, mapping, args[b'revset'])
861 if not isinstance(q, templateutil.revslist):
861 if not isinstance(q, templateutil.revslist):
862 # i18n: "subsetparents" is a keyword
862 # i18n: "subsetparents" is a keyword
863 raise error.ParseError(_(b"subsetparents expects a queried revset"))
863 raise error.ParseError(_(b"subsetparents expects a queried revset"))
864 subset = q.tovalue(context, mapping)
864 subset = q.tovalue(context, mapping)
865 key = q.cachekey
865 key = q.cachekey
866
866
867 if key:
867 if key:
868 # cache only if revset query isn't dynamic
868 # cache only if revset query isn't dynamic
869 cache = context.resource(mapping, b'cache')
869 cache = context.resource(mapping, b'cache')
870 walkercache = cache.setdefault(b'subsetparentswalker', {})
870 walkercache = cache.setdefault(b'subsetparentswalker', {})
871 if key in walkercache:
871 if key in walkercache:
872 walker = walkercache[key]
872 walker = walkercache[key]
873 else:
873 else:
874 walker = dagop.subsetparentswalker(repo, subset)
874 walker = dagop.subsetparentswalker(repo, subset)
875 walkercache[key] = walker
875 walkercache[key] = walker
876 else:
876 else:
877 # for one-shot use, specify startrev to limit the search space
877 # for one-shot use, specify startrev to limit the search space
878 walker = dagop.subsetparentswalker(repo, subset, startrev=rev)
878 walker = dagop.subsetparentswalker(repo, subset, startrev=rev)
879 return templateutil.revslist(repo, walker.parentsset(rev))
879 return templateutil.revslist(repo, walker.parentsset(rev))
880
880
881
881
882 @templatefunc(b'word(number, text[, separator])')
882 @templatefunc(b'word(number, text[, separator])')
883 def word(context, mapping, args):
883 def word(context, mapping, args):
884 """Return the nth word from a string."""
884 """Return the nth word from a string."""
885 if not (2 <= len(args) <= 3):
885 if not (2 <= len(args) <= 3):
886 # i18n: "word" is a keyword
886 # i18n: "word" is a keyword
887 raise error.ParseError(
887 raise error.ParseError(
888 _(b"word expects two or three arguments, got %d") % len(args)
888 _(b"word expects two or three arguments, got %d") % len(args)
889 )
889 )
890
890
891 num = evalinteger(
891 num = evalinteger(
892 context,
892 context,
893 mapping,
893 mapping,
894 args[0],
894 args[0],
895 # i18n: "word" is a keyword
895 # i18n: "word" is a keyword
896 _(b"word expects an integer index"),
896 _(b"word expects an integer index"),
897 )
897 )
898 text = evalstring(context, mapping, args[1])
898 text = evalstring(context, mapping, args[1])
899 if len(args) == 3:
899 if len(args) == 3:
900 splitter = evalstring(context, mapping, args[2])
900 splitter = evalstring(context, mapping, args[2])
901 else:
901 else:
902 splitter = None
902 splitter = None
903
903
904 tokens = text.split(splitter)
904 tokens = text.split(splitter)
905 if num >= len(tokens) or num < -len(tokens):
905 if num >= len(tokens) or num < -len(tokens):
906 return b''
906 return b''
907 else:
907 else:
908 return tokens[num]
908 return tokens[num]
909
909
910
910
911 def loadfunction(ui, extname, registrarobj):
911 def loadfunction(ui, extname, registrarobj):
912 """Load template function from specified registrarobj"""
912 """Load template function from specified registrarobj"""
913 for name, func in registrarobj._table.items():
913 for name, func in registrarobj._table.items():
914 funcs[name] = func
914 funcs[name] = func
915
915
916
916
917 # tell hggettext to extract docstrings from these functions:
917 # tell hggettext to extract docstrings from these functions:
918 i18nfunctions = funcs.values()
918 i18nfunctions = funcs.values()
General Comments 0
You need to be logged in to leave comments. Login now