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