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