##// END OF EJS Templates
templatefuncs: show hint if extdata source is evaluated to empty (issue5843)
Yuya Nishihara -
r37950:faa41fd2 default
parent child Browse files
Show More
@@ -1,696 +1,703 b''
1 # templatefuncs.py - common template functions
1 # templatefuncs.py - common template functions
2 #
2 #
3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import re
10 import re
11
11
12 from .i18n import _
12 from .i18n import _
13 from .node import (
13 from .node import (
14 bin,
14 bin,
15 wdirid,
15 wdirid,
16 )
16 )
17 from . import (
17 from . import (
18 color,
18 color,
19 encoding,
19 encoding,
20 error,
20 error,
21 minirst,
21 minirst,
22 obsutil,
22 obsutil,
23 pycompat,
23 pycompat,
24 registrar,
24 registrar,
25 revset as revsetmod,
25 revset as revsetmod,
26 revsetlang,
26 revsetlang,
27 scmutil,
27 scmutil,
28 templatefilters,
28 templatefilters,
29 templatekw,
29 templatekw,
30 templateutil,
30 templateutil,
31 util,
31 util,
32 )
32 )
33 from .utils import (
33 from .utils import (
34 dateutil,
34 dateutil,
35 stringutil,
35 stringutil,
36 )
36 )
37
37
38 evalrawexp = templateutil.evalrawexp
38 evalrawexp = templateutil.evalrawexp
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 @templatefunc('date(date[, fmt])')
50 @templatefunc('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(_("date expects one or two arguments"))
57 raise error.ParseError(_("date expects one or two arguments"))
58
58
59 date = evaldate(context, mapping, args[0],
59 date = evaldate(context, mapping, args[0],
60 # i18n: "date" is a keyword
60 # i18n: "date" is a keyword
61 _("date expects a date information"))
61 _("date expects a date information"))
62 fmt = None
62 fmt = None
63 if len(args) == 2:
63 if len(args) == 2:
64 fmt = evalstring(context, mapping, args[1])
64 fmt = evalstring(context, mapping, args[1])
65 if fmt is None:
65 if fmt is None:
66 return dateutil.datestr(date)
66 return dateutil.datestr(date)
67 else:
67 else:
68 return dateutil.datestr(date, fmt)
68 return dateutil.datestr(date, fmt)
69
69
70 @templatefunc('dict([[key=]value...])', argspec='*args **kwargs')
70 @templatefunc('dict([[key=]value...])', argspec='*args **kwargs')
71 def dict_(context, mapping, args):
71 def dict_(context, mapping, args):
72 """Construct a dict from key-value pairs. A key may be omitted if
72 """Construct a dict from key-value pairs. A key may be omitted if
73 a value expression can provide an unambiguous name."""
73 a value expression can provide an unambiguous name."""
74 data = util.sortdict()
74 data = util.sortdict()
75
75
76 for v in args['args']:
76 for v in args['args']:
77 k = templateutil.findsymbolicname(v)
77 k = templateutil.findsymbolicname(v)
78 if not k:
78 if not k:
79 raise error.ParseError(_('dict key cannot be inferred'))
79 raise error.ParseError(_('dict key cannot be inferred'))
80 if k in data or k in args['kwargs']:
80 if k in data or k in args['kwargs']:
81 raise error.ParseError(_("duplicated dict key '%s' inferred") % k)
81 raise error.ParseError(_("duplicated dict key '%s' inferred") % k)
82 data[k] = evalfuncarg(context, mapping, v)
82 data[k] = evalfuncarg(context, mapping, v)
83
83
84 data.update((k, evalfuncarg(context, mapping, v))
84 data.update((k, evalfuncarg(context, mapping, v))
85 for k, v in args['kwargs'].iteritems())
85 for k, v in args['kwargs'].iteritems())
86 return templateutil.hybriddict(data)
86 return templateutil.hybriddict(data)
87
87
88 @templatefunc('diff([includepattern [, excludepattern]])')
88 @templatefunc('diff([includepattern [, excludepattern]])')
89 def diff(context, mapping, args):
89 def diff(context, mapping, args):
90 """Show a diff, optionally
90 """Show a diff, optionally
91 specifying files to include or exclude."""
91 specifying files to include or exclude."""
92 if len(args) > 2:
92 if len(args) > 2:
93 # i18n: "diff" is a keyword
93 # i18n: "diff" is a keyword
94 raise error.ParseError(_("diff expects zero, one, or two arguments"))
94 raise error.ParseError(_("diff expects zero, one, or two arguments"))
95
95
96 def getpatterns(i):
96 def getpatterns(i):
97 if i < len(args):
97 if i < len(args):
98 s = evalstring(context, mapping, args[i]).strip()
98 s = evalstring(context, mapping, args[i]).strip()
99 if s:
99 if s:
100 return [s]
100 return [s]
101 return []
101 return []
102
102
103 ctx = context.resource(mapping, 'ctx')
103 ctx = context.resource(mapping, 'ctx')
104 chunks = ctx.diff(match=ctx.match([], getpatterns(0), getpatterns(1)))
104 chunks = ctx.diff(match=ctx.match([], getpatterns(0), getpatterns(1)))
105
105
106 return ''.join(chunks)
106 return ''.join(chunks)
107
107
108 @templatefunc('extdata(source)', argspec='source')
108 @templatefunc('extdata(source)', argspec='source')
109 def extdata(context, mapping, args):
109 def extdata(context, mapping, args):
110 """Show a text read from the specified extdata source. (EXPERIMENTAL)"""
110 """Show a text read from the specified extdata source. (EXPERIMENTAL)"""
111 if 'source' not in args:
111 if 'source' not in args:
112 # i18n: "extdata" is a keyword
112 # i18n: "extdata" is a keyword
113 raise error.ParseError(_('extdata expects one argument'))
113 raise error.ParseError(_('extdata expects one argument'))
114
114
115 source = evalstring(context, mapping, args['source'])
115 source = evalstring(context, mapping, args['source'])
116 if not source:
117 sym = templateutil.findsymbolicname(args['source'])
118 if sym:
119 raise error.ParseError(_('empty data source specified'),
120 hint=_("did you mean extdata('%s')?") % sym)
121 else:
122 raise error.ParseError(_('empty data source specified'))
116 cache = context.resource(mapping, 'cache').setdefault('extdata', {})
123 cache = context.resource(mapping, 'cache').setdefault('extdata', {})
117 ctx = context.resource(mapping, 'ctx')
124 ctx = context.resource(mapping, 'ctx')
118 if source in cache:
125 if source in cache:
119 data = cache[source]
126 data = cache[source]
120 else:
127 else:
121 data = cache[source] = scmutil.extdatasource(ctx.repo(), source)
128 data = cache[source] = scmutil.extdatasource(ctx.repo(), source)
122 return data.get(ctx.rev(), '')
129 return data.get(ctx.rev(), '')
123
130
124 @templatefunc('files(pattern)')
131 @templatefunc('files(pattern)')
125 def files(context, mapping, args):
132 def files(context, mapping, args):
126 """All files of the current changeset matching the pattern. See
133 """All files of the current changeset matching the pattern. See
127 :hg:`help patterns`."""
134 :hg:`help patterns`."""
128 if not len(args) == 1:
135 if not len(args) == 1:
129 # i18n: "files" is a keyword
136 # i18n: "files" is a keyword
130 raise error.ParseError(_("files expects one argument"))
137 raise error.ParseError(_("files expects one argument"))
131
138
132 raw = evalstring(context, mapping, args[0])
139 raw = evalstring(context, mapping, args[0])
133 ctx = context.resource(mapping, 'ctx')
140 ctx = context.resource(mapping, 'ctx')
134 m = ctx.match([raw])
141 m = ctx.match([raw])
135 files = list(ctx.matches(m))
142 files = list(ctx.matches(m))
136 return templateutil.compatlist(context, mapping, "file", files)
143 return templateutil.compatlist(context, mapping, "file", files)
137
144
138 @templatefunc('fill(text[, width[, initialident[, hangindent]]])')
145 @templatefunc('fill(text[, width[, initialident[, hangindent]]])')
139 def fill(context, mapping, args):
146 def fill(context, mapping, args):
140 """Fill many
147 """Fill many
141 paragraphs with optional indentation. See the "fill" filter."""
148 paragraphs with optional indentation. See the "fill" filter."""
142 if not (1 <= len(args) <= 4):
149 if not (1 <= len(args) <= 4):
143 # i18n: "fill" is a keyword
150 # i18n: "fill" is a keyword
144 raise error.ParseError(_("fill expects one to four arguments"))
151 raise error.ParseError(_("fill expects one to four arguments"))
145
152
146 text = evalstring(context, mapping, args[0])
153 text = evalstring(context, mapping, args[0])
147 width = 76
154 width = 76
148 initindent = ''
155 initindent = ''
149 hangindent = ''
156 hangindent = ''
150 if 2 <= len(args) <= 4:
157 if 2 <= len(args) <= 4:
151 width = evalinteger(context, mapping, args[1],
158 width = evalinteger(context, mapping, args[1],
152 # i18n: "fill" is a keyword
159 # i18n: "fill" is a keyword
153 _("fill expects an integer width"))
160 _("fill expects an integer width"))
154 try:
161 try:
155 initindent = evalstring(context, mapping, args[2])
162 initindent = evalstring(context, mapping, args[2])
156 hangindent = evalstring(context, mapping, args[3])
163 hangindent = evalstring(context, mapping, args[3])
157 except IndexError:
164 except IndexError:
158 pass
165 pass
159
166
160 return templatefilters.fill(text, width, initindent, hangindent)
167 return templatefilters.fill(text, width, initindent, hangindent)
161
168
162 @templatefunc('formatnode(node)')
169 @templatefunc('formatnode(node)')
163 def formatnode(context, mapping, args):
170 def formatnode(context, mapping, args):
164 """Obtain the preferred form of a changeset hash. (DEPRECATED)"""
171 """Obtain the preferred form of a changeset hash. (DEPRECATED)"""
165 if len(args) != 1:
172 if len(args) != 1:
166 # i18n: "formatnode" is a keyword
173 # i18n: "formatnode" is a keyword
167 raise error.ParseError(_("formatnode expects one argument"))
174 raise error.ParseError(_("formatnode expects one argument"))
168
175
169 ui = context.resource(mapping, 'ui')
176 ui = context.resource(mapping, 'ui')
170 node = evalstring(context, mapping, args[0])
177 node = evalstring(context, mapping, args[0])
171 if ui.debugflag:
178 if ui.debugflag:
172 return node
179 return node
173 return templatefilters.short(node)
180 return templatefilters.short(node)
174
181
175 @templatefunc('mailmap(author)')
182 @templatefunc('mailmap(author)')
176 def mailmap(context, mapping, args):
183 def mailmap(context, mapping, args):
177 """Return the author, updated according to the value
184 """Return the author, updated according to the value
178 set in the .mailmap file"""
185 set in the .mailmap file"""
179 if len(args) != 1:
186 if len(args) != 1:
180 raise error.ParseError(_("mailmap expects one argument"))
187 raise error.ParseError(_("mailmap expects one argument"))
181
188
182 author = evalstring(context, mapping, args[0])
189 author = evalstring(context, mapping, args[0])
183
190
184 cache = context.resource(mapping, 'cache')
191 cache = context.resource(mapping, 'cache')
185 repo = context.resource(mapping, 'repo')
192 repo = context.resource(mapping, 'repo')
186
193
187 if 'mailmap' not in cache:
194 if 'mailmap' not in cache:
188 data = repo.wvfs.tryread('.mailmap')
195 data = repo.wvfs.tryread('.mailmap')
189 cache['mailmap'] = stringutil.parsemailmap(data)
196 cache['mailmap'] = stringutil.parsemailmap(data)
190
197
191 return stringutil.mapname(cache['mailmap'], author)
198 return stringutil.mapname(cache['mailmap'], author)
192
199
193 @templatefunc('pad(text, width[, fillchar=\' \'[, left=False]])',
200 @templatefunc('pad(text, width[, fillchar=\' \'[, left=False]])',
194 argspec='text width fillchar left')
201 argspec='text width fillchar left')
195 def pad(context, mapping, args):
202 def pad(context, mapping, args):
196 """Pad text with a
203 """Pad text with a
197 fill character."""
204 fill character."""
198 if 'text' not in args or 'width' not in args:
205 if 'text' not in args or 'width' not in args:
199 # i18n: "pad" is a keyword
206 # i18n: "pad" is a keyword
200 raise error.ParseError(_("pad() expects two to four arguments"))
207 raise error.ParseError(_("pad() expects two to four arguments"))
201
208
202 width = evalinteger(context, mapping, args['width'],
209 width = evalinteger(context, mapping, args['width'],
203 # i18n: "pad" is a keyword
210 # i18n: "pad" is a keyword
204 _("pad() expects an integer width"))
211 _("pad() expects an integer width"))
205
212
206 text = evalstring(context, mapping, args['text'])
213 text = evalstring(context, mapping, args['text'])
207
214
208 left = False
215 left = False
209 fillchar = ' '
216 fillchar = ' '
210 if 'fillchar' in args:
217 if 'fillchar' in args:
211 fillchar = evalstring(context, mapping, args['fillchar'])
218 fillchar = evalstring(context, mapping, args['fillchar'])
212 if len(color.stripeffects(fillchar)) != 1:
219 if len(color.stripeffects(fillchar)) != 1:
213 # i18n: "pad" is a keyword
220 # i18n: "pad" is a keyword
214 raise error.ParseError(_("pad() expects a single fill character"))
221 raise error.ParseError(_("pad() expects a single fill character"))
215 if 'left' in args:
222 if 'left' in args:
216 left = evalboolean(context, mapping, args['left'])
223 left = evalboolean(context, mapping, args['left'])
217
224
218 fillwidth = width - encoding.colwidth(color.stripeffects(text))
225 fillwidth = width - encoding.colwidth(color.stripeffects(text))
219 if fillwidth <= 0:
226 if fillwidth <= 0:
220 return text
227 return text
221 if left:
228 if left:
222 return fillchar * fillwidth + text
229 return fillchar * fillwidth + text
223 else:
230 else:
224 return text + fillchar * fillwidth
231 return text + fillchar * fillwidth
225
232
226 @templatefunc('indent(text, indentchars[, firstline])')
233 @templatefunc('indent(text, indentchars[, firstline])')
227 def indent(context, mapping, args):
234 def indent(context, mapping, args):
228 """Indents all non-empty lines
235 """Indents all non-empty lines
229 with the characters given in the indentchars string. An optional
236 with the characters given in the indentchars string. An optional
230 third parameter will override the indent for the first line only
237 third parameter will override the indent for the first line only
231 if present."""
238 if present."""
232 if not (2 <= len(args) <= 3):
239 if not (2 <= len(args) <= 3):
233 # i18n: "indent" is a keyword
240 # i18n: "indent" is a keyword
234 raise error.ParseError(_("indent() expects two or three arguments"))
241 raise error.ParseError(_("indent() expects two or three arguments"))
235
242
236 text = evalstring(context, mapping, args[0])
243 text = evalstring(context, mapping, args[0])
237 indent = evalstring(context, mapping, args[1])
244 indent = evalstring(context, mapping, args[1])
238
245
239 if len(args) == 3:
246 if len(args) == 3:
240 firstline = evalstring(context, mapping, args[2])
247 firstline = evalstring(context, mapping, args[2])
241 else:
248 else:
242 firstline = indent
249 firstline = indent
243
250
244 # the indent function doesn't indent the first line, so we do it here
251 # the indent function doesn't indent the first line, so we do it here
245 return templatefilters.indent(firstline + text, indent)
252 return templatefilters.indent(firstline + text, indent)
246
253
247 @templatefunc('get(dict, key)')
254 @templatefunc('get(dict, key)')
248 def get(context, mapping, args):
255 def get(context, mapping, args):
249 """Get an attribute/key from an object. Some keywords
256 """Get an attribute/key from an object. Some keywords
250 are complex types. This function allows you to obtain the value of an
257 are complex types. This function allows you to obtain the value of an
251 attribute on these types."""
258 attribute on these types."""
252 if len(args) != 2:
259 if len(args) != 2:
253 # i18n: "get" is a keyword
260 # i18n: "get" is a keyword
254 raise error.ParseError(_("get() expects two arguments"))
261 raise error.ParseError(_("get() expects two arguments"))
255
262
256 dictarg = evalfuncarg(context, mapping, args[0])
263 dictarg = evalfuncarg(context, mapping, args[0])
257 if not util.safehasattr(dictarg, 'get'):
264 if not util.safehasattr(dictarg, 'get'):
258 # i18n: "get" is a keyword
265 # i18n: "get" is a keyword
259 raise error.ParseError(_("get() expects a dict as first argument"))
266 raise error.ParseError(_("get() expects a dict as first argument"))
260
267
261 key = evalfuncarg(context, mapping, args[1])
268 key = evalfuncarg(context, mapping, args[1])
262 return templateutil.getdictitem(dictarg, key)
269 return templateutil.getdictitem(dictarg, key)
263
270
264 @templatefunc('if(expr, then[, else])')
271 @templatefunc('if(expr, then[, else])')
265 def if_(context, mapping, args):
272 def if_(context, mapping, args):
266 """Conditionally execute based on the result of
273 """Conditionally execute based on the result of
267 an expression."""
274 an expression."""
268 if not (2 <= len(args) <= 3):
275 if not (2 <= len(args) <= 3):
269 # i18n: "if" is a keyword
276 # i18n: "if" is a keyword
270 raise error.ParseError(_("if expects two or three arguments"))
277 raise error.ParseError(_("if expects two or three arguments"))
271
278
272 test = evalboolean(context, mapping, args[0])
279 test = evalboolean(context, mapping, args[0])
273 if test:
280 if test:
274 return evalrawexp(context, mapping, args[1])
281 return evalrawexp(context, mapping, args[1])
275 elif len(args) == 3:
282 elif len(args) == 3:
276 return evalrawexp(context, mapping, args[2])
283 return evalrawexp(context, mapping, args[2])
277
284
278 @templatefunc('ifcontains(needle, haystack, then[, else])')
285 @templatefunc('ifcontains(needle, haystack, then[, else])')
279 def ifcontains(context, mapping, args):
286 def ifcontains(context, mapping, args):
280 """Conditionally execute based
287 """Conditionally execute based
281 on whether the item "needle" is in "haystack"."""
288 on whether the item "needle" is in "haystack"."""
282 if not (3 <= len(args) <= 4):
289 if not (3 <= len(args) <= 4):
283 # i18n: "ifcontains" is a keyword
290 # i18n: "ifcontains" is a keyword
284 raise error.ParseError(_("ifcontains expects three or four arguments"))
291 raise error.ParseError(_("ifcontains expects three or four arguments"))
285
292
286 haystack = evalfuncarg(context, mapping, args[1])
293 haystack = evalfuncarg(context, mapping, args[1])
287 keytype = getattr(haystack, 'keytype', None)
294 keytype = getattr(haystack, 'keytype', None)
288 try:
295 try:
289 needle = evalrawexp(context, mapping, args[0])
296 needle = evalrawexp(context, mapping, args[0])
290 needle = templateutil.unwrapastype(context, mapping, needle,
297 needle = templateutil.unwrapastype(context, mapping, needle,
291 keytype or bytes)
298 keytype or bytes)
292 found = (needle in haystack)
299 found = (needle in haystack)
293 except error.ParseError:
300 except error.ParseError:
294 found = False
301 found = False
295
302
296 if found:
303 if found:
297 return evalrawexp(context, mapping, args[2])
304 return evalrawexp(context, mapping, args[2])
298 elif len(args) == 4:
305 elif len(args) == 4:
299 return evalrawexp(context, mapping, args[3])
306 return evalrawexp(context, mapping, args[3])
300
307
301 @templatefunc('ifeq(expr1, expr2, then[, else])')
308 @templatefunc('ifeq(expr1, expr2, then[, else])')
302 def ifeq(context, mapping, args):
309 def ifeq(context, mapping, args):
303 """Conditionally execute based on
310 """Conditionally execute based on
304 whether 2 items are equivalent."""
311 whether 2 items are equivalent."""
305 if not (3 <= len(args) <= 4):
312 if not (3 <= len(args) <= 4):
306 # i18n: "ifeq" is a keyword
313 # i18n: "ifeq" is a keyword
307 raise error.ParseError(_("ifeq expects three or four arguments"))
314 raise error.ParseError(_("ifeq expects three or four arguments"))
308
315
309 test = evalstring(context, mapping, args[0])
316 test = evalstring(context, mapping, args[0])
310 match = evalstring(context, mapping, args[1])
317 match = evalstring(context, mapping, args[1])
311 if test == match:
318 if test == match:
312 return evalrawexp(context, mapping, args[2])
319 return evalrawexp(context, mapping, args[2])
313 elif len(args) == 4:
320 elif len(args) == 4:
314 return evalrawexp(context, mapping, args[3])
321 return evalrawexp(context, mapping, args[3])
315
322
316 @templatefunc('join(list, sep)')
323 @templatefunc('join(list, sep)')
317 def join(context, mapping, args):
324 def join(context, mapping, args):
318 """Join items in a list with a delimiter."""
325 """Join items in a list with a delimiter."""
319 if not (1 <= len(args) <= 2):
326 if not (1 <= len(args) <= 2):
320 # i18n: "join" is a keyword
327 # i18n: "join" is a keyword
321 raise error.ParseError(_("join expects one or two arguments"))
328 raise error.ParseError(_("join expects one or two arguments"))
322
329
323 joinset = evalrawexp(context, mapping, args[0])
330 joinset = evalrawexp(context, mapping, args[0])
324 joiner = " "
331 joiner = " "
325 if len(args) > 1:
332 if len(args) > 1:
326 joiner = evalstring(context, mapping, args[1])
333 joiner = evalstring(context, mapping, args[1])
327 if isinstance(joinset, templateutil.wrapped):
334 if isinstance(joinset, templateutil.wrapped):
328 return joinset.join(context, mapping, joiner)
335 return joinset.join(context, mapping, joiner)
329 # TODO: perhaps a generator should be stringify()-ed here, but we can't
336 # TODO: perhaps a generator should be stringify()-ed here, but we can't
330 # because hgweb abuses it as a keyword that returns a list of dicts.
337 # because hgweb abuses it as a keyword that returns a list of dicts.
331 joinset = templateutil.unwrapvalue(context, mapping, joinset)
338 joinset = templateutil.unwrapvalue(context, mapping, joinset)
332 return templateutil.joinitems(pycompat.maybebytestr(joinset), joiner)
339 return templateutil.joinitems(pycompat.maybebytestr(joinset), joiner)
333
340
334 @templatefunc('label(label, expr)')
341 @templatefunc('label(label, expr)')
335 def label(context, mapping, args):
342 def label(context, mapping, args):
336 """Apply a label to generated content. Content with
343 """Apply a label to generated content. Content with
337 a label applied can result in additional post-processing, such as
344 a label applied can result in additional post-processing, such as
338 automatic colorization."""
345 automatic colorization."""
339 if len(args) != 2:
346 if len(args) != 2:
340 # i18n: "label" is a keyword
347 # i18n: "label" is a keyword
341 raise error.ParseError(_("label expects two arguments"))
348 raise error.ParseError(_("label expects two arguments"))
342
349
343 ui = context.resource(mapping, 'ui')
350 ui = context.resource(mapping, 'ui')
344 thing = evalstring(context, mapping, args[1])
351 thing = evalstring(context, mapping, args[1])
345 # preserve unknown symbol as literal so effects like 'red', 'bold',
352 # preserve unknown symbol as literal so effects like 'red', 'bold',
346 # etc. don't need to be quoted
353 # etc. don't need to be quoted
347 label = evalstringliteral(context, mapping, args[0])
354 label = evalstringliteral(context, mapping, args[0])
348
355
349 return ui.label(thing, label)
356 return ui.label(thing, label)
350
357
351 @templatefunc('latesttag([pattern])')
358 @templatefunc('latesttag([pattern])')
352 def latesttag(context, mapping, args):
359 def latesttag(context, mapping, args):
353 """The global tags matching the given pattern on the
360 """The global tags matching the given pattern on the
354 most recent globally tagged ancestor of this changeset.
361 most recent globally tagged ancestor of this changeset.
355 If no such tags exist, the "{tag}" template resolves to
362 If no such tags exist, the "{tag}" template resolves to
356 the string "null"."""
363 the string "null"."""
357 if len(args) > 1:
364 if len(args) > 1:
358 # i18n: "latesttag" is a keyword
365 # i18n: "latesttag" is a keyword
359 raise error.ParseError(_("latesttag expects at most one argument"))
366 raise error.ParseError(_("latesttag expects at most one argument"))
360
367
361 pattern = None
368 pattern = None
362 if len(args) == 1:
369 if len(args) == 1:
363 pattern = evalstring(context, mapping, args[0])
370 pattern = evalstring(context, mapping, args[0])
364 return templatekw.showlatesttags(context, mapping, pattern)
371 return templatekw.showlatesttags(context, mapping, pattern)
365
372
366 @templatefunc('localdate(date[, tz])')
373 @templatefunc('localdate(date[, tz])')
367 def localdate(context, mapping, args):
374 def localdate(context, mapping, args):
368 """Converts a date to the specified timezone.
375 """Converts a date to the specified timezone.
369 The default is local date."""
376 The default is local date."""
370 if not (1 <= len(args) <= 2):
377 if not (1 <= len(args) <= 2):
371 # i18n: "localdate" is a keyword
378 # i18n: "localdate" is a keyword
372 raise error.ParseError(_("localdate expects one or two arguments"))
379 raise error.ParseError(_("localdate expects one or two arguments"))
373
380
374 date = evaldate(context, mapping, args[0],
381 date = evaldate(context, mapping, args[0],
375 # i18n: "localdate" is a keyword
382 # i18n: "localdate" is a keyword
376 _("localdate expects a date information"))
383 _("localdate expects a date information"))
377 if len(args) >= 2:
384 if len(args) >= 2:
378 tzoffset = None
385 tzoffset = None
379 tz = evalfuncarg(context, mapping, args[1])
386 tz = evalfuncarg(context, mapping, args[1])
380 if isinstance(tz, bytes):
387 if isinstance(tz, bytes):
381 tzoffset, remainder = dateutil.parsetimezone(tz)
388 tzoffset, remainder = dateutil.parsetimezone(tz)
382 if remainder:
389 if remainder:
383 tzoffset = None
390 tzoffset = None
384 if tzoffset is None:
391 if tzoffset is None:
385 try:
392 try:
386 tzoffset = int(tz)
393 tzoffset = int(tz)
387 except (TypeError, ValueError):
394 except (TypeError, ValueError):
388 # i18n: "localdate" is a keyword
395 # i18n: "localdate" is a keyword
389 raise error.ParseError(_("localdate expects a timezone"))
396 raise error.ParseError(_("localdate expects a timezone"))
390 else:
397 else:
391 tzoffset = dateutil.makedate()[1]
398 tzoffset = dateutil.makedate()[1]
392 return (date[0], tzoffset)
399 return (date[0], tzoffset)
393
400
394 @templatefunc('max(iterable)')
401 @templatefunc('max(iterable)')
395 def max_(context, mapping, args, **kwargs):
402 def max_(context, mapping, args, **kwargs):
396 """Return the max of an iterable"""
403 """Return the max of an iterable"""
397 if len(args) != 1:
404 if len(args) != 1:
398 # i18n: "max" is a keyword
405 # i18n: "max" is a keyword
399 raise error.ParseError(_("max expects one argument"))
406 raise error.ParseError(_("max expects one argument"))
400
407
401 iterable = evalfuncarg(context, mapping, args[0])
408 iterable = evalfuncarg(context, mapping, args[0])
402 try:
409 try:
403 x = max(pycompat.maybebytestr(iterable))
410 x = max(pycompat.maybebytestr(iterable))
404 except (TypeError, ValueError):
411 except (TypeError, ValueError):
405 # i18n: "max" is a keyword
412 # i18n: "max" is a keyword
406 raise error.ParseError(_("max first argument should be an iterable"))
413 raise error.ParseError(_("max first argument should be an iterable"))
407 return templateutil.wraphybridvalue(iterable, x, x)
414 return templateutil.wraphybridvalue(iterable, x, x)
408
415
409 @templatefunc('min(iterable)')
416 @templatefunc('min(iterable)')
410 def min_(context, mapping, args, **kwargs):
417 def min_(context, mapping, args, **kwargs):
411 """Return the min of an iterable"""
418 """Return the min of an iterable"""
412 if len(args) != 1:
419 if len(args) != 1:
413 # i18n: "min" is a keyword
420 # i18n: "min" is a keyword
414 raise error.ParseError(_("min expects one argument"))
421 raise error.ParseError(_("min expects one argument"))
415
422
416 iterable = evalfuncarg(context, mapping, args[0])
423 iterable = evalfuncarg(context, mapping, args[0])
417 try:
424 try:
418 x = min(pycompat.maybebytestr(iterable))
425 x = min(pycompat.maybebytestr(iterable))
419 except (TypeError, ValueError):
426 except (TypeError, ValueError):
420 # i18n: "min" is a keyword
427 # i18n: "min" is a keyword
421 raise error.ParseError(_("min first argument should be an iterable"))
428 raise error.ParseError(_("min first argument should be an iterable"))
422 return templateutil.wraphybridvalue(iterable, x, x)
429 return templateutil.wraphybridvalue(iterable, x, x)
423
430
424 @templatefunc('mod(a, b)')
431 @templatefunc('mod(a, b)')
425 def mod(context, mapping, args):
432 def mod(context, mapping, args):
426 """Calculate a mod b such that a / b + a mod b == a"""
433 """Calculate a mod b such that a / b + a mod b == a"""
427 if not len(args) == 2:
434 if not len(args) == 2:
428 # i18n: "mod" is a keyword
435 # i18n: "mod" is a keyword
429 raise error.ParseError(_("mod expects two arguments"))
436 raise error.ParseError(_("mod expects two arguments"))
430
437
431 func = lambda a, b: a % b
438 func = lambda a, b: a % b
432 return templateutil.runarithmetic(context, mapping,
439 return templateutil.runarithmetic(context, mapping,
433 (func, args[0], args[1]))
440 (func, args[0], args[1]))
434
441
435 @templatefunc('obsfateoperations(markers)')
442 @templatefunc('obsfateoperations(markers)')
436 def obsfateoperations(context, mapping, args):
443 def obsfateoperations(context, mapping, args):
437 """Compute obsfate related information based on markers (EXPERIMENTAL)"""
444 """Compute obsfate related information based on markers (EXPERIMENTAL)"""
438 if len(args) != 1:
445 if len(args) != 1:
439 # i18n: "obsfateoperations" is a keyword
446 # i18n: "obsfateoperations" is a keyword
440 raise error.ParseError(_("obsfateoperations expects one argument"))
447 raise error.ParseError(_("obsfateoperations expects one argument"))
441
448
442 markers = evalfuncarg(context, mapping, args[0])
449 markers = evalfuncarg(context, mapping, args[0])
443
450
444 try:
451 try:
445 data = obsutil.markersoperations(markers)
452 data = obsutil.markersoperations(markers)
446 return templateutil.hybridlist(data, name='operation')
453 return templateutil.hybridlist(data, name='operation')
447 except (TypeError, KeyError):
454 except (TypeError, KeyError):
448 # i18n: "obsfateoperations" is a keyword
455 # i18n: "obsfateoperations" is a keyword
449 errmsg = _("obsfateoperations first argument should be an iterable")
456 errmsg = _("obsfateoperations first argument should be an iterable")
450 raise error.ParseError(errmsg)
457 raise error.ParseError(errmsg)
451
458
452 @templatefunc('obsfatedate(markers)')
459 @templatefunc('obsfatedate(markers)')
453 def obsfatedate(context, mapping, args):
460 def obsfatedate(context, mapping, args):
454 """Compute obsfate related information based on markers (EXPERIMENTAL)"""
461 """Compute obsfate related information based on markers (EXPERIMENTAL)"""
455 if len(args) != 1:
462 if len(args) != 1:
456 # i18n: "obsfatedate" is a keyword
463 # i18n: "obsfatedate" is a keyword
457 raise error.ParseError(_("obsfatedate expects one argument"))
464 raise error.ParseError(_("obsfatedate expects one argument"))
458
465
459 markers = evalfuncarg(context, mapping, args[0])
466 markers = evalfuncarg(context, mapping, args[0])
460
467
461 try:
468 try:
462 data = obsutil.markersdates(markers)
469 data = obsutil.markersdates(markers)
463 return templateutil.hybridlist(data, name='date', fmt='%d %d')
470 return templateutil.hybridlist(data, name='date', fmt='%d %d')
464 except (TypeError, KeyError):
471 except (TypeError, KeyError):
465 # i18n: "obsfatedate" is a keyword
472 # i18n: "obsfatedate" is a keyword
466 errmsg = _("obsfatedate first argument should be an iterable")
473 errmsg = _("obsfatedate first argument should be an iterable")
467 raise error.ParseError(errmsg)
474 raise error.ParseError(errmsg)
468
475
469 @templatefunc('obsfateusers(markers)')
476 @templatefunc('obsfateusers(markers)')
470 def obsfateusers(context, mapping, args):
477 def obsfateusers(context, mapping, args):
471 """Compute obsfate related information based on markers (EXPERIMENTAL)"""
478 """Compute obsfate related information based on markers (EXPERIMENTAL)"""
472 if len(args) != 1:
479 if len(args) != 1:
473 # i18n: "obsfateusers" is a keyword
480 # i18n: "obsfateusers" is a keyword
474 raise error.ParseError(_("obsfateusers expects one argument"))
481 raise error.ParseError(_("obsfateusers expects one argument"))
475
482
476 markers = evalfuncarg(context, mapping, args[0])
483 markers = evalfuncarg(context, mapping, args[0])
477
484
478 try:
485 try:
479 data = obsutil.markersusers(markers)
486 data = obsutil.markersusers(markers)
480 return templateutil.hybridlist(data, name='user')
487 return templateutil.hybridlist(data, name='user')
481 except (TypeError, KeyError, ValueError):
488 except (TypeError, KeyError, ValueError):
482 # i18n: "obsfateusers" is a keyword
489 # i18n: "obsfateusers" is a keyword
483 msg = _("obsfateusers first argument should be an iterable of "
490 msg = _("obsfateusers first argument should be an iterable of "
484 "obsmakers")
491 "obsmakers")
485 raise error.ParseError(msg)
492 raise error.ParseError(msg)
486
493
487 @templatefunc('obsfateverb(successors, markers)')
494 @templatefunc('obsfateverb(successors, markers)')
488 def obsfateverb(context, mapping, args):
495 def obsfateverb(context, mapping, args):
489 """Compute obsfate related information based on successors (EXPERIMENTAL)"""
496 """Compute obsfate related information based on successors (EXPERIMENTAL)"""
490 if len(args) != 2:
497 if len(args) != 2:
491 # i18n: "obsfateverb" is a keyword
498 # i18n: "obsfateverb" is a keyword
492 raise error.ParseError(_("obsfateverb expects two arguments"))
499 raise error.ParseError(_("obsfateverb expects two arguments"))
493
500
494 successors = evalfuncarg(context, mapping, args[0])
501 successors = evalfuncarg(context, mapping, args[0])
495 markers = evalfuncarg(context, mapping, args[1])
502 markers = evalfuncarg(context, mapping, args[1])
496
503
497 try:
504 try:
498 return obsutil.obsfateverb(successors, markers)
505 return obsutil.obsfateverb(successors, markers)
499 except TypeError:
506 except TypeError:
500 # i18n: "obsfateverb" is a keyword
507 # i18n: "obsfateverb" is a keyword
501 errmsg = _("obsfateverb first argument should be countable")
508 errmsg = _("obsfateverb first argument should be countable")
502 raise error.ParseError(errmsg)
509 raise error.ParseError(errmsg)
503
510
504 @templatefunc('relpath(path)')
511 @templatefunc('relpath(path)')
505 def relpath(context, mapping, args):
512 def relpath(context, mapping, args):
506 """Convert a repository-absolute path into a filesystem path relative to
513 """Convert a repository-absolute path into a filesystem path relative to
507 the current working directory."""
514 the current working directory."""
508 if len(args) != 1:
515 if len(args) != 1:
509 # i18n: "relpath" is a keyword
516 # i18n: "relpath" is a keyword
510 raise error.ParseError(_("relpath expects one argument"))
517 raise error.ParseError(_("relpath expects one argument"))
511
518
512 repo = context.resource(mapping, 'ctx').repo()
519 repo = context.resource(mapping, 'ctx').repo()
513 path = evalstring(context, mapping, args[0])
520 path = evalstring(context, mapping, args[0])
514 return repo.pathto(path)
521 return repo.pathto(path)
515
522
516 @templatefunc('revset(query[, formatargs...])')
523 @templatefunc('revset(query[, formatargs...])')
517 def revset(context, mapping, args):
524 def revset(context, mapping, args):
518 """Execute a revision set query. See
525 """Execute a revision set query. See
519 :hg:`help revset`."""
526 :hg:`help revset`."""
520 if not len(args) > 0:
527 if not len(args) > 0:
521 # i18n: "revset" is a keyword
528 # i18n: "revset" is a keyword
522 raise error.ParseError(_("revset expects one or more arguments"))
529 raise error.ParseError(_("revset expects one or more arguments"))
523
530
524 raw = evalstring(context, mapping, args[0])
531 raw = evalstring(context, mapping, args[0])
525 ctx = context.resource(mapping, 'ctx')
532 ctx = context.resource(mapping, 'ctx')
526 repo = ctx.repo()
533 repo = ctx.repo()
527
534
528 def query(expr):
535 def query(expr):
529 m = revsetmod.match(repo.ui, expr, lookup=revsetmod.lookupfn(repo))
536 m = revsetmod.match(repo.ui, expr, lookup=revsetmod.lookupfn(repo))
530 return m(repo)
537 return m(repo)
531
538
532 if len(args) > 1:
539 if len(args) > 1:
533 formatargs = [evalfuncarg(context, mapping, a) for a in args[1:]]
540 formatargs = [evalfuncarg(context, mapping, a) for a in args[1:]]
534 revs = query(revsetlang.formatspec(raw, *formatargs))
541 revs = query(revsetlang.formatspec(raw, *formatargs))
535 revs = list(revs)
542 revs = list(revs)
536 else:
543 else:
537 cache = context.resource(mapping, 'cache')
544 cache = context.resource(mapping, 'cache')
538 revsetcache = cache.setdefault("revsetcache", {})
545 revsetcache = cache.setdefault("revsetcache", {})
539 if raw in revsetcache:
546 if raw in revsetcache:
540 revs = revsetcache[raw]
547 revs = revsetcache[raw]
541 else:
548 else:
542 revs = query(raw)
549 revs = query(raw)
543 revs = list(revs)
550 revs = list(revs)
544 revsetcache[raw] = revs
551 revsetcache[raw] = revs
545 return templatekw.showrevslist(context, mapping, "revision", revs)
552 return templatekw.showrevslist(context, mapping, "revision", revs)
546
553
547 @templatefunc('rstdoc(text, style)')
554 @templatefunc('rstdoc(text, style)')
548 def rstdoc(context, mapping, args):
555 def rstdoc(context, mapping, args):
549 """Format reStructuredText."""
556 """Format reStructuredText."""
550 if len(args) != 2:
557 if len(args) != 2:
551 # i18n: "rstdoc" is a keyword
558 # i18n: "rstdoc" is a keyword
552 raise error.ParseError(_("rstdoc expects two arguments"))
559 raise error.ParseError(_("rstdoc expects two arguments"))
553
560
554 text = evalstring(context, mapping, args[0])
561 text = evalstring(context, mapping, args[0])
555 style = evalstring(context, mapping, args[1])
562 style = evalstring(context, mapping, args[1])
556
563
557 return minirst.format(text, style=style, keep=['verbose'])
564 return minirst.format(text, style=style, keep=['verbose'])
558
565
559 @templatefunc('separate(sep, args)', argspec='sep *args')
566 @templatefunc('separate(sep, args)', argspec='sep *args')
560 def separate(context, mapping, args):
567 def separate(context, mapping, args):
561 """Add a separator between non-empty arguments."""
568 """Add a separator between non-empty arguments."""
562 if 'sep' not in args:
569 if 'sep' not in args:
563 # i18n: "separate" is a keyword
570 # i18n: "separate" is a keyword
564 raise error.ParseError(_("separate expects at least one argument"))
571 raise error.ParseError(_("separate expects at least one argument"))
565
572
566 sep = evalstring(context, mapping, args['sep'])
573 sep = evalstring(context, mapping, args['sep'])
567 first = True
574 first = True
568 for arg in args['args']:
575 for arg in args['args']:
569 argstr = evalstring(context, mapping, arg)
576 argstr = evalstring(context, mapping, arg)
570 if not argstr:
577 if not argstr:
571 continue
578 continue
572 if first:
579 if first:
573 first = False
580 first = False
574 else:
581 else:
575 yield sep
582 yield sep
576 yield argstr
583 yield argstr
577
584
578 @templatefunc('shortest(node, minlength=4)')
585 @templatefunc('shortest(node, minlength=4)')
579 def shortest(context, mapping, args):
586 def shortest(context, mapping, args):
580 """Obtain the shortest representation of
587 """Obtain the shortest representation of
581 a node."""
588 a node."""
582 if not (1 <= len(args) <= 2):
589 if not (1 <= len(args) <= 2):
583 # i18n: "shortest" is a keyword
590 # i18n: "shortest" is a keyword
584 raise error.ParseError(_("shortest() expects one or two arguments"))
591 raise error.ParseError(_("shortest() expects one or two arguments"))
585
592
586 hexnode = evalstring(context, mapping, args[0])
593 hexnode = evalstring(context, mapping, args[0])
587
594
588 minlength = 4
595 minlength = 4
589 if len(args) > 1:
596 if len(args) > 1:
590 minlength = evalinteger(context, mapping, args[1],
597 minlength = evalinteger(context, mapping, args[1],
591 # i18n: "shortest" is a keyword
598 # i18n: "shortest" is a keyword
592 _("shortest() expects an integer minlength"))
599 _("shortest() expects an integer minlength"))
593
600
594 repo = context.resource(mapping, 'ctx')._repo
601 repo = context.resource(mapping, 'ctx')._repo
595 if len(hexnode) > 40:
602 if len(hexnode) > 40:
596 return hexnode
603 return hexnode
597 elif len(hexnode) == 40:
604 elif len(hexnode) == 40:
598 try:
605 try:
599 node = bin(hexnode)
606 node = bin(hexnode)
600 except TypeError:
607 except TypeError:
601 return hexnode
608 return hexnode
602 else:
609 else:
603 try:
610 try:
604 node = scmutil.resolvehexnodeidprefix(repo, hexnode)
611 node = scmutil.resolvehexnodeidprefix(repo, hexnode)
605 except error.WdirUnsupported:
612 except error.WdirUnsupported:
606 node = wdirid
613 node = wdirid
607 except error.LookupError:
614 except error.LookupError:
608 return hexnode
615 return hexnode
609 if not node:
616 if not node:
610 return hexnode
617 return hexnode
611 try:
618 try:
612 return scmutil.shortesthexnodeidprefix(repo, node, minlength)
619 return scmutil.shortesthexnodeidprefix(repo, node, minlength)
613 except error.RepoLookupError:
620 except error.RepoLookupError:
614 return hexnode
621 return hexnode
615
622
616 @templatefunc('strip(text[, chars])')
623 @templatefunc('strip(text[, chars])')
617 def strip(context, mapping, args):
624 def strip(context, mapping, args):
618 """Strip characters from a string. By default,
625 """Strip characters from a string. By default,
619 strips all leading and trailing whitespace."""
626 strips all leading and trailing whitespace."""
620 if not (1 <= len(args) <= 2):
627 if not (1 <= len(args) <= 2):
621 # i18n: "strip" is a keyword
628 # i18n: "strip" is a keyword
622 raise error.ParseError(_("strip expects one or two arguments"))
629 raise error.ParseError(_("strip expects one or two arguments"))
623
630
624 text = evalstring(context, mapping, args[0])
631 text = evalstring(context, mapping, args[0])
625 if len(args) == 2:
632 if len(args) == 2:
626 chars = evalstring(context, mapping, args[1])
633 chars = evalstring(context, mapping, args[1])
627 return text.strip(chars)
634 return text.strip(chars)
628 return text.strip()
635 return text.strip()
629
636
630 @templatefunc('sub(pattern, replacement, expression)')
637 @templatefunc('sub(pattern, replacement, expression)')
631 def sub(context, mapping, args):
638 def sub(context, mapping, args):
632 """Perform text substitution
639 """Perform text substitution
633 using regular expressions."""
640 using regular expressions."""
634 if len(args) != 3:
641 if len(args) != 3:
635 # i18n: "sub" is a keyword
642 # i18n: "sub" is a keyword
636 raise error.ParseError(_("sub expects three arguments"))
643 raise error.ParseError(_("sub expects three arguments"))
637
644
638 pat = evalstring(context, mapping, args[0])
645 pat = evalstring(context, mapping, args[0])
639 rpl = evalstring(context, mapping, args[1])
646 rpl = evalstring(context, mapping, args[1])
640 src = evalstring(context, mapping, args[2])
647 src = evalstring(context, mapping, args[2])
641 try:
648 try:
642 patre = re.compile(pat)
649 patre = re.compile(pat)
643 except re.error:
650 except re.error:
644 # i18n: "sub" is a keyword
651 # i18n: "sub" is a keyword
645 raise error.ParseError(_("sub got an invalid pattern: %s") % pat)
652 raise error.ParseError(_("sub got an invalid pattern: %s") % pat)
646 try:
653 try:
647 yield patre.sub(rpl, src)
654 yield patre.sub(rpl, src)
648 except re.error:
655 except re.error:
649 # i18n: "sub" is a keyword
656 # i18n: "sub" is a keyword
650 raise error.ParseError(_("sub got an invalid replacement: %s") % rpl)
657 raise error.ParseError(_("sub got an invalid replacement: %s") % rpl)
651
658
652 @templatefunc('startswith(pattern, text)')
659 @templatefunc('startswith(pattern, text)')
653 def startswith(context, mapping, args):
660 def startswith(context, mapping, args):
654 """Returns the value from the "text" argument
661 """Returns the value from the "text" argument
655 if it begins with the content from the "pattern" argument."""
662 if it begins with the content from the "pattern" argument."""
656 if len(args) != 2:
663 if len(args) != 2:
657 # i18n: "startswith" is a keyword
664 # i18n: "startswith" is a keyword
658 raise error.ParseError(_("startswith expects two arguments"))
665 raise error.ParseError(_("startswith expects two arguments"))
659
666
660 patn = evalstring(context, mapping, args[0])
667 patn = evalstring(context, mapping, args[0])
661 text = evalstring(context, mapping, args[1])
668 text = evalstring(context, mapping, args[1])
662 if text.startswith(patn):
669 if text.startswith(patn):
663 return text
670 return text
664 return ''
671 return ''
665
672
666 @templatefunc('word(number, text[, separator])')
673 @templatefunc('word(number, text[, separator])')
667 def word(context, mapping, args):
674 def word(context, mapping, args):
668 """Return the nth word from a string."""
675 """Return the nth word from a string."""
669 if not (2 <= len(args) <= 3):
676 if not (2 <= len(args) <= 3):
670 # i18n: "word" is a keyword
677 # i18n: "word" is a keyword
671 raise error.ParseError(_("word expects two or three arguments, got %d")
678 raise error.ParseError(_("word expects two or three arguments, got %d")
672 % len(args))
679 % len(args))
673
680
674 num = evalinteger(context, mapping, args[0],
681 num = evalinteger(context, mapping, args[0],
675 # i18n: "word" is a keyword
682 # i18n: "word" is a keyword
676 _("word expects an integer index"))
683 _("word expects an integer index"))
677 text = evalstring(context, mapping, args[1])
684 text = evalstring(context, mapping, args[1])
678 if len(args) == 3:
685 if len(args) == 3:
679 splitter = evalstring(context, mapping, args[2])
686 splitter = evalstring(context, mapping, args[2])
680 else:
687 else:
681 splitter = None
688 splitter = None
682
689
683 tokens = text.split(splitter)
690 tokens = text.split(splitter)
684 if num >= len(tokens) or num < -len(tokens):
691 if num >= len(tokens) or num < -len(tokens):
685 return ''
692 return ''
686 else:
693 else:
687 return tokens[num]
694 return tokens[num]
688
695
689 def loadfunction(ui, extname, registrarobj):
696 def loadfunction(ui, extname, registrarobj):
690 """Load template function from specified registrarobj
697 """Load template function from specified registrarobj
691 """
698 """
692 for name, func in registrarobj._table.iteritems():
699 for name, func in registrarobj._table.iteritems():
693 funcs[name] = func
700 funcs[name] = func
694
701
695 # tell hggettext to extract docstrings from these functions:
702 # tell hggettext to extract docstrings from these functions:
696 i18nfunctions = funcs.values()
703 i18nfunctions = funcs.values()
@@ -1,96 +1,103 b''
1 $ hg init repo
1 $ hg init repo
2 $ cd repo
2 $ cd repo
3 $ for n in 0 1 2 3 4 5 6 7 8 9 10 11; do
3 $ for n in 0 1 2 3 4 5 6 7 8 9 10 11; do
4 > echo $n > $n
4 > echo $n > $n
5 > hg ci -qAm $n
5 > hg ci -qAm $n
6 > done
6 > done
7
7
8 test revset support
8 test revset support
9
9
10 $ cat <<'EOF' >> .hg/hgrc
10 $ cat <<'EOF' >> .hg/hgrc
11 > [extdata]
11 > [extdata]
12 > filedata = file:extdata.txt
12 > filedata = file:extdata.txt
13 > notes = notes.txt
13 > notes = notes.txt
14 > shelldata = shell:cat extdata.txt | grep 2
14 > shelldata = shell:cat extdata.txt | grep 2
15 > emptygrep = shell:cat extdata.txt | grep empty
15 > emptygrep = shell:cat extdata.txt | grep empty
16 > EOF
16 > EOF
17 $ cat <<'EOF' > extdata.txt
17 $ cat <<'EOF' > extdata.txt
18 > 2 another comment on 2
18 > 2 another comment on 2
19 > 3
19 > 3
20 > EOF
20 > EOF
21 $ cat <<'EOF' > notes.txt
21 $ cat <<'EOF' > notes.txt
22 > f6ed this change is great!
22 > f6ed this change is great!
23 > e834 this is buggy :(
23 > e834 this is buggy :(
24 > 0625 first post
24 > 0625 first post
25 > bogusnode gives no error
25 > bogusnode gives no error
26 > a ambiguous node gives no error
26 > a ambiguous node gives no error
27 > EOF
27 > EOF
28
28
29 $ hg log -qr "extdata(filedata)"
29 $ hg log -qr "extdata(filedata)"
30 2:f6ed99a58333
30 2:f6ed99a58333
31 3:9de260b1e88e
31 3:9de260b1e88e
32 $ hg log -qr "extdata(shelldata)"
32 $ hg log -qr "extdata(shelldata)"
33 2:f6ed99a58333
33 2:f6ed99a58333
34
34
35 test weight of extdata() revset
35 test weight of extdata() revset
36
36
37 $ hg debugrevspec -p optimized "extdata(filedata) & 3"
37 $ hg debugrevspec -p optimized "extdata(filedata) & 3"
38 * optimized:
38 * optimized:
39 (andsmally
39 (andsmally
40 (func
40 (func
41 (symbol 'extdata')
41 (symbol 'extdata')
42 (symbol 'filedata'))
42 (symbol 'filedata'))
43 (symbol '3'))
43 (symbol '3'))
44 3
44 3
45
45
46 test non-zero exit of shell command
46 test non-zero exit of shell command
47
47
48 $ hg log -qr "extdata(emptygrep)"
48 $ hg log -qr "extdata(emptygrep)"
49 abort: extdata command 'cat extdata.txt | grep empty' failed: exited with status 1
49 abort: extdata command 'cat extdata.txt | grep empty' failed: exited with status 1
50 [255]
50 [255]
51
51
52 test bad extdata() revset source
52 test bad extdata() revset source
53
53
54 $ hg log -qr "extdata()"
54 $ hg log -qr "extdata()"
55 hg: parse error: extdata takes at least 1 string argument
55 hg: parse error: extdata takes at least 1 string argument
56 [255]
56 [255]
57 $ hg log -qr "extdata(unknown)"
57 $ hg log -qr "extdata(unknown)"
58 abort: unknown extdata source 'unknown'
58 abort: unknown extdata source 'unknown'
59 [255]
59 [255]
60
60
61 test template support:
61 test template support:
62
62
63 $ hg log -r:3 -T "{node|short}{if(extdata('notes'), ' # {extdata('notes')}')}\n"
63 $ hg log -r:3 -T "{node|short}{if(extdata('notes'), ' # {extdata('notes')}')}\n"
64 06254b906311 # first post
64 06254b906311 # first post
65 e8342c9a2ed1 # this is buggy :(
65 e8342c9a2ed1 # this is buggy :(
66 f6ed99a58333 # this change is great!
66 f6ed99a58333 # this change is great!
67 9de260b1e88e
67 9de260b1e88e
68
68
69 test template cache:
69 test template cache:
70
70
71 $ hg log -r:3 -T '{rev} "{extdata("notes")}" "{extdata("shelldata")}"\n'
71 $ hg log -r:3 -T '{rev} "{extdata("notes")}" "{extdata("shelldata")}"\n'
72 0 "first post" ""
72 0 "first post" ""
73 1 "this is buggy :(" ""
73 1 "this is buggy :(" ""
74 2 "this change is great!" "another comment on 2"
74 2 "this change is great!" "another comment on 2"
75 3 "" ""
75 3 "" ""
76
76
77 test bad extdata() template source
77 test bad extdata() template source
78
78
79 $ hg log -T "{extdata()}\n"
79 $ hg log -T "{extdata()}\n"
80 hg: parse error: extdata expects one argument
80 hg: parse error: extdata expects one argument
81 [255]
81 [255]
82 $ hg log -T "{extdata('unknown')}\n"
82 $ hg log -T "{extdata('unknown')}\n"
83 abort: unknown extdata source 'unknown'
83 abort: unknown extdata source 'unknown'
84 [255]
84 [255]
85 $ hg log -T "{extdata(unknown)}\n"
86 hg: parse error: empty data source specified
87 (did you mean extdata('unknown')?)
88 [255]
89 $ hg log -T "{extdata('{unknown}')}\n"
90 hg: parse error: empty data source specified
91 [255]
85
92
86 we don't fix up relative file URLs, but we do run shell commands in repo root
93 we don't fix up relative file URLs, but we do run shell commands in repo root
87
94
88 $ mkdir sub
95 $ mkdir sub
89 $ cd sub
96 $ cd sub
90 $ hg log -qr "extdata(filedata)"
97 $ hg log -qr "extdata(filedata)"
91 abort: error: $ENOENT$
98 abort: error: $ENOENT$
92 [255]
99 [255]
93 $ hg log -qr "extdata(shelldata)"
100 $ hg log -qr "extdata(shelldata)"
94 2:f6ed99a58333
101 2:f6ed99a58333
95
102
96 $ cd ..
103 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now