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