comparison mercurial/templatefuncs.py @ 43077:687b865b95ad

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