mercurial/templater.py
changeset 28696 efa192203623
parent 28695 cc103bd0dbf9
child 28831 6b86ce3e3576
equal deleted inserted replaced
28695:cc103bd0dbf9 28696:efa192203623
    15 from . import (
    15 from . import (
    16     config,
    16     config,
    17     error,
    17     error,
    18     minirst,
    18     minirst,
    19     parser,
    19     parser,
       
    20     registrar,
    20     revset as revsetmod,
    21     revset as revsetmod,
    21     templatefilters,
    22     templatefilters,
    22     templatekw,
    23     templatekw,
    23     util,
    24     util,
    24 )
    25 )
   391             raise error.ParseError(_("filter %s expects one argument") % n)
   392             raise error.ParseError(_("filter %s expects one argument") % n)
   392         f = context._filters[n]
   393         f = context._filters[n]
   393         return (runfilter, (args[0], f))
   394         return (runfilter, (args[0], f))
   394     raise error.ParseError(_("unknown function '%s'") % n)
   395     raise error.ParseError(_("unknown function '%s'") % n)
   395 
   396 
       
   397 # dict of template built-in functions
       
   398 funcs = {}
       
   399 
       
   400 templatefunc = registrar.templatefunc(funcs)
       
   401 
       
   402 @templatefunc('date(date[, fmt])')
   396 def date(context, mapping, args):
   403 def date(context, mapping, args):
   397     """:date(date[, fmt]): Format a date. See :hg:`help dates` for formatting
   404     """Format a date. See :hg:`help dates` for formatting
   398     strings. The default is a Unix date format, including the timezone:
   405     strings. The default is a Unix date format, including the timezone:
   399     "Mon Sep 04 15:13:13 2006 0700"."""
   406     "Mon Sep 04 15:13:13 2006 0700"."""
   400     if not (1 <= len(args) <= 2):
   407     if not (1 <= len(args) <= 2):
   401         # i18n: "date" is a keyword
   408         # i18n: "date" is a keyword
   402         raise error.ParseError(_("date expects one or two arguments"))
   409         raise error.ParseError(_("date expects one or two arguments"))
   412             return util.datestr(date, fmt)
   419             return util.datestr(date, fmt)
   413     except (TypeError, ValueError):
   420     except (TypeError, ValueError):
   414         # i18n: "date" is a keyword
   421         # i18n: "date" is a keyword
   415         raise error.ParseError(_("date expects a date information"))
   422         raise error.ParseError(_("date expects a date information"))
   416 
   423 
       
   424 @templatefunc('diff([includepattern [, excludepattern]])')
   417 def diff(context, mapping, args):
   425 def diff(context, mapping, args):
   418     """:diff([includepattern [, excludepattern]]): Show a diff, optionally
   426     """Show a diff, optionally
   419     specifying files to include or exclude."""
   427     specifying files to include or exclude."""
   420     if len(args) > 2:
   428     if len(args) > 2:
   421         # i18n: "diff" is a keyword
   429         # i18n: "diff" is a keyword
   422         raise error.ParseError(_("diff expects zero, one, or two arguments"))
   430         raise error.ParseError(_("diff expects zero, one, or two arguments"))
   423 
   431 
   431     ctx = mapping['ctx']
   439     ctx = mapping['ctx']
   432     chunks = ctx.diff(match=ctx.match([], getpatterns(0), getpatterns(1)))
   440     chunks = ctx.diff(match=ctx.match([], getpatterns(0), getpatterns(1)))
   433 
   441 
   434     return ''.join(chunks)
   442     return ''.join(chunks)
   435 
   443 
       
   444 @templatefunc('fill(text[, width[, initialident[, hangindent]]])')
   436 def fill(context, mapping, args):
   445 def fill(context, mapping, args):
   437     """:fill(text[, width[, initialident[, hangindent]]]): Fill many
   446     """Fill many
   438     paragraphs with optional indentation. See the "fill" filter."""
   447     paragraphs with optional indentation. See the "fill" filter."""
   439     if not (1 <= len(args) <= 4):
   448     if not (1 <= len(args) <= 4):
   440         # i18n: "fill" is a keyword
   449         # i18n: "fill" is a keyword
   441         raise error.ParseError(_("fill expects one to four arguments"))
   450         raise error.ParseError(_("fill expects one to four arguments"))
   442 
   451 
   454         except IndexError:
   463         except IndexError:
   455             pass
   464             pass
   456 
   465 
   457     return templatefilters.fill(text, width, initindent, hangindent)
   466     return templatefilters.fill(text, width, initindent, hangindent)
   458 
   467 
       
   468 @templatefunc('pad(text, width[, fillchar=\' \'[, right=False]])')
   459 def pad(context, mapping, args):
   469 def pad(context, mapping, args):
   460     """:pad(text, width[, fillchar=' '[, right=False]]): Pad text with a
   470     """Pad text with a
   461     fill character."""
   471     fill character."""
   462     if not (2 <= len(args) <= 4):
   472     if not (2 <= len(args) <= 4):
   463         # i18n: "pad" is a keyword
   473         # i18n: "pad" is a keyword
   464         raise error.ParseError(_("pad() expects two to four arguments"))
   474         raise error.ParseError(_("pad() expects two to four arguments"))
   465 
   475 
   479     if right:
   489     if right:
   480         return text.rjust(width, fillchar)
   490         return text.rjust(width, fillchar)
   481     else:
   491     else:
   482         return text.ljust(width, fillchar)
   492         return text.ljust(width, fillchar)
   483 
   493 
       
   494 @templatefunc('indent(text, indentchars[, firstline])')
   484 def indent(context, mapping, args):
   495 def indent(context, mapping, args):
   485     """:indent(text, indentchars[, firstline]): Indents all non-empty lines
   496     """Indents all non-empty lines
   486     with the characters given in the indentchars string. An optional
   497     with the characters given in the indentchars string. An optional
   487     third parameter will override the indent for the first line only
   498     third parameter will override the indent for the first line only
   488     if present."""
   499     if present."""
   489     if not (2 <= len(args) <= 3):
   500     if not (2 <= len(args) <= 3):
   490         # i18n: "indent" is a keyword
   501         # i18n: "indent" is a keyword
   499         firstline = indent
   510         firstline = indent
   500 
   511 
   501     # the indent function doesn't indent the first line, so we do it here
   512     # the indent function doesn't indent the first line, so we do it here
   502     return templatefilters.indent(firstline + text, indent)
   513     return templatefilters.indent(firstline + text, indent)
   503 
   514 
       
   515 @templatefunc('get(dict, key)')
   504 def get(context, mapping, args):
   516 def get(context, mapping, args):
   505     """:get(dict, key): Get an attribute/key from an object. Some keywords
   517     """Get an attribute/key from an object. Some keywords
   506     are complex types. This function allows you to obtain the value of an
   518     are complex types. This function allows you to obtain the value of an
   507     attribute on these types."""
   519     attribute on these types."""
   508     if len(args) != 2:
   520     if len(args) != 2:
   509         # i18n: "get" is a keyword
   521         # i18n: "get" is a keyword
   510         raise error.ParseError(_("get() expects two arguments"))
   522         raise error.ParseError(_("get() expects two arguments"))
   515         raise error.ParseError(_("get() expects a dict as first argument"))
   527         raise error.ParseError(_("get() expects a dict as first argument"))
   516 
   528 
   517     key = evalfuncarg(context, mapping, args[1])
   529     key = evalfuncarg(context, mapping, args[1])
   518     return dictarg.get(key)
   530     return dictarg.get(key)
   519 
   531 
       
   532 @templatefunc('if(expr, then[, else])')
   520 def if_(context, mapping, args):
   533 def if_(context, mapping, args):
   521     """:if(expr, then[, else]): Conditionally execute based on the result of
   534     """Conditionally execute based on the result of
   522     an expression."""
   535     an expression."""
   523     if not (2 <= len(args) <= 3):
   536     if not (2 <= len(args) <= 3):
   524         # i18n: "if" is a keyword
   537         # i18n: "if" is a keyword
   525         raise error.ParseError(_("if expects two or three arguments"))
   538         raise error.ParseError(_("if expects two or three arguments"))
   526 
   539 
   528     if test:
   541     if test:
   529         yield args[1][0](context, mapping, args[1][1])
   542         yield args[1][0](context, mapping, args[1][1])
   530     elif len(args) == 3:
   543     elif len(args) == 3:
   531         yield args[2][0](context, mapping, args[2][1])
   544         yield args[2][0](context, mapping, args[2][1])
   532 
   545 
       
   546 @templatefunc('ifcontains(search, thing, then[, else])')
   533 def ifcontains(context, mapping, args):
   547 def ifcontains(context, mapping, args):
   534     """:ifcontains(search, thing, then[, else]): Conditionally execute based
   548     """Conditionally execute based
   535     on whether the item "search" is in "thing"."""
   549     on whether the item "search" is in "thing"."""
   536     if not (3 <= len(args) <= 4):
   550     if not (3 <= len(args) <= 4):
   537         # i18n: "ifcontains" is a keyword
   551         # i18n: "ifcontains" is a keyword
   538         raise error.ParseError(_("ifcontains expects three or four arguments"))
   552         raise error.ParseError(_("ifcontains expects three or four arguments"))
   539 
   553 
   543     if item in items:
   557     if item in items:
   544         yield args[2][0](context, mapping, args[2][1])
   558         yield args[2][0](context, mapping, args[2][1])
   545     elif len(args) == 4:
   559     elif len(args) == 4:
   546         yield args[3][0](context, mapping, args[3][1])
   560         yield args[3][0](context, mapping, args[3][1])
   547 
   561 
       
   562 @templatefunc('ifeq(expr1, expr2, then[, else])')
   548 def ifeq(context, mapping, args):
   563 def ifeq(context, mapping, args):
   549     """:ifeq(expr1, expr2, then[, else]): Conditionally execute based on
   564     """Conditionally execute based on
   550     whether 2 items are equivalent."""
   565     whether 2 items are equivalent."""
   551     if not (3 <= len(args) <= 4):
   566     if not (3 <= len(args) <= 4):
   552         # i18n: "ifeq" is a keyword
   567         # i18n: "ifeq" is a keyword
   553         raise error.ParseError(_("ifeq expects three or four arguments"))
   568         raise error.ParseError(_("ifeq expects three or four arguments"))
   554 
   569 
   557     if test == match:
   572     if test == match:
   558         yield args[2][0](context, mapping, args[2][1])
   573         yield args[2][0](context, mapping, args[2][1])
   559     elif len(args) == 4:
   574     elif len(args) == 4:
   560         yield args[3][0](context, mapping, args[3][1])
   575         yield args[3][0](context, mapping, args[3][1])
   561 
   576 
       
   577 @templatefunc('join(list, sep)')
   562 def join(context, mapping, args):
   578 def join(context, mapping, args):
   563     """:join(list, sep): Join items in a list with a delimiter."""
   579     """Join items in a list with a delimiter."""
   564     if not (1 <= len(args) <= 2):
   580     if not (1 <= len(args) <= 2):
   565         # i18n: "join" is a keyword
   581         # i18n: "join" is a keyword
   566         raise error.ParseError(_("join expects one or two arguments"))
   582         raise error.ParseError(_("join expects one or two arguments"))
   567 
   583 
   568     joinset = args[0][0](context, mapping, args[0][1])
   584     joinset = args[0][0](context, mapping, args[0][1])
   580             first = False
   596             first = False
   581         else:
   597         else:
   582             yield joiner
   598             yield joiner
   583         yield x
   599         yield x
   584 
   600 
       
   601 @templatefunc('label(label, expr)')
   585 def label(context, mapping, args):
   602 def label(context, mapping, args):
   586     """:label(label, expr): Apply a label to generated content. Content with
   603     """Apply a label to generated content. Content with
   587     a label applied can result in additional post-processing, such as
   604     a label applied can result in additional post-processing, such as
   588     automatic colorization."""
   605     automatic colorization."""
   589     if len(args) != 2:
   606     if len(args) != 2:
   590         # i18n: "label" is a keyword
   607         # i18n: "label" is a keyword
   591         raise error.ParseError(_("label expects two arguments"))
   608         raise error.ParseError(_("label expects two arguments"))
   596     # etc. don't need to be quoted
   613     # etc. don't need to be quoted
   597     label = evalstringliteral(context, mapping, args[0])
   614     label = evalstringliteral(context, mapping, args[0])
   598 
   615 
   599     return ui.label(thing, label)
   616     return ui.label(thing, label)
   600 
   617 
       
   618 @templatefunc('latesttag([pattern])')
   601 def latesttag(context, mapping, args):
   619 def latesttag(context, mapping, args):
   602     """:latesttag([pattern]): The global tags matching the given pattern on the
   620     """The global tags matching the given pattern on the
   603     most recent globally tagged ancestor of this changeset."""
   621     most recent globally tagged ancestor of this changeset."""
   604     if len(args) > 1:
   622     if len(args) > 1:
   605         # i18n: "latesttag" is a keyword
   623         # i18n: "latesttag" is a keyword
   606         raise error.ParseError(_("latesttag expects at most one argument"))
   624         raise error.ParseError(_("latesttag expects at most one argument"))
   607 
   625 
   609     if len(args) == 1:
   627     if len(args) == 1:
   610         pattern = evalstring(context, mapping, args[0])
   628         pattern = evalstring(context, mapping, args[0])
   611 
   629 
   612     return templatekw.showlatesttags(pattern, **mapping)
   630     return templatekw.showlatesttags(pattern, **mapping)
   613 
   631 
       
   632 @templatefunc('localdate(date[, tz])')
   614 def localdate(context, mapping, args):
   633 def localdate(context, mapping, args):
   615     """:localdate(date[, tz]): Converts a date to the specified timezone.
   634     """Converts a date to the specified timezone.
   616     The default is local date."""
   635     The default is local date."""
   617     if not (1 <= len(args) <= 2):
   636     if not (1 <= len(args) <= 2):
   618         # i18n: "localdate" is a keyword
   637         # i18n: "localdate" is a keyword
   619         raise error.ParseError(_("localdate expects one or two arguments"))
   638         raise error.ParseError(_("localdate expects one or two arguments"))
   620 
   639 
   637                 raise error.ParseError(_("localdate expects a timezone"))
   656                 raise error.ParseError(_("localdate expects a timezone"))
   638     else:
   657     else:
   639         tzoffset = util.makedate()[1]
   658         tzoffset = util.makedate()[1]
   640     return (date[0], tzoffset)
   659     return (date[0], tzoffset)
   641 
   660 
       
   661 @templatefunc('revset(query[, formatargs...])')
   642 def revset(context, mapping, args):
   662 def revset(context, mapping, args):
   643     """:revset(query[, formatargs...]): Execute a revision set query. See
   663     """Execute a revision set query. See
   644     :hg:`help revset`."""
   664     :hg:`help revset`."""
   645     if not len(args) > 0:
   665     if not len(args) > 0:
   646         # i18n: "revset" is a keyword
   666         # i18n: "revset" is a keyword
   647         raise error.ParseError(_("revset expects one or more arguments"))
   667         raise error.ParseError(_("revset expects one or more arguments"))
   648 
   668 
   667             revs = list(revs)
   687             revs = list(revs)
   668             revsetcache[raw] = revs
   688             revsetcache[raw] = revs
   669 
   689 
   670     return templatekw.showrevslist("revision", revs, **mapping)
   690     return templatekw.showrevslist("revision", revs, **mapping)
   671 
   691 
       
   692 @templatefunc('rstdoc(text, style)')
   672 def rstdoc(context, mapping, args):
   693 def rstdoc(context, mapping, args):
   673     """:rstdoc(text, style): Format ReStructuredText."""
   694     """Format ReStructuredText."""
   674     if len(args) != 2:
   695     if len(args) != 2:
   675         # i18n: "rstdoc" is a keyword
   696         # i18n: "rstdoc" is a keyword
   676         raise error.ParseError(_("rstdoc expects two arguments"))
   697         raise error.ParseError(_("rstdoc expects two arguments"))
   677 
   698 
   678     text = evalstring(context, mapping, args[0])
   699     text = evalstring(context, mapping, args[0])
   679     style = evalstring(context, mapping, args[1])
   700     style = evalstring(context, mapping, args[1])
   680 
   701 
   681     return minirst.format(text, style=style, keep=['verbose'])
   702     return minirst.format(text, style=style, keep=['verbose'])
   682 
   703 
       
   704 @templatefunc('shortest(node, minlength=4)')
   683 def shortest(context, mapping, args):
   705 def shortest(context, mapping, args):
   684     """:shortest(node, minlength=4): Obtain the shortest representation of
   706     """Obtain the shortest representation of
   685     a node."""
   707     a node."""
   686     if not (1 <= len(args) <= 2):
   708     if not (1 <= len(args) <= 2):
   687         # i18n: "shortest" is a keyword
   709         # i18n: "shortest" is a keyword
   688         raise error.ParseError(_("shortest() expects one or two arguments"))
   710         raise error.ParseError(_("shortest() expects one or two arguments"))
   689 
   711 
   732         else:
   754         else:
   733             length += 1
   755             length += 1
   734             if len(shortest) <= length:
   756             if len(shortest) <= length:
   735                 return shortest
   757                 return shortest
   736 
   758 
       
   759 @templatefunc('strip(text[, chars])')
   737 def strip(context, mapping, args):
   760 def strip(context, mapping, args):
   738     """:strip(text[, chars]): Strip characters from a string. By default,
   761     """Strip characters from a string. By default,
   739     strips all leading and trailing whitespace."""
   762     strips all leading and trailing whitespace."""
   740     if not (1 <= len(args) <= 2):
   763     if not (1 <= len(args) <= 2):
   741         # i18n: "strip" is a keyword
   764         # i18n: "strip" is a keyword
   742         raise error.ParseError(_("strip expects one or two arguments"))
   765         raise error.ParseError(_("strip expects one or two arguments"))
   743 
   766 
   745     if len(args) == 2:
   768     if len(args) == 2:
   746         chars = evalstring(context, mapping, args[1])
   769         chars = evalstring(context, mapping, args[1])
   747         return text.strip(chars)
   770         return text.strip(chars)
   748     return text.strip()
   771     return text.strip()
   749 
   772 
       
   773 @templatefunc('sub(pattern, replacement, expression)')
   750 def sub(context, mapping, args):
   774 def sub(context, mapping, args):
   751     """:sub(pattern, replacement, expression): Perform text substitution
   775     """Perform text substitution
   752     using regular expressions."""
   776     using regular expressions."""
   753     if len(args) != 3:
   777     if len(args) != 3:
   754         # i18n: "sub" is a keyword
   778         # i18n: "sub" is a keyword
   755         raise error.ParseError(_("sub expects three arguments"))
   779         raise error.ParseError(_("sub expects three arguments"))
   756 
   780 
   766         yield patre.sub(rpl, src)
   790         yield patre.sub(rpl, src)
   767     except re.error:
   791     except re.error:
   768         # i18n: "sub" is a keyword
   792         # i18n: "sub" is a keyword
   769         raise error.ParseError(_("sub got an invalid replacement: %s") % rpl)
   793         raise error.ParseError(_("sub got an invalid replacement: %s") % rpl)
   770 
   794 
       
   795 @templatefunc('startswith(pattern, text)')
   771 def startswith(context, mapping, args):
   796 def startswith(context, mapping, args):
   772     """:startswith(pattern, text): Returns the value from the "text" argument
   797     """Returns the value from the "text" argument
   773     if it begins with the content from the "pattern" argument."""
   798     if it begins with the content from the "pattern" argument."""
   774     if len(args) != 2:
   799     if len(args) != 2:
   775         # i18n: "startswith" is a keyword
   800         # i18n: "startswith" is a keyword
   776         raise error.ParseError(_("startswith expects two arguments"))
   801         raise error.ParseError(_("startswith expects two arguments"))
   777 
   802 
   779     text = evalstring(context, mapping, args[1])
   804     text = evalstring(context, mapping, args[1])
   780     if text.startswith(patn):
   805     if text.startswith(patn):
   781         return text
   806         return text
   782     return ''
   807     return ''
   783 
   808 
   784 
   809 @templatefunc('word(number, text[, separator])')
   785 def word(context, mapping, args):
   810 def word(context, mapping, args):
   786     """:word(number, text[, separator]): Return the nth word from a string."""
   811     """Return the nth word from a string."""
   787     if not (2 <= len(args) <= 3):
   812     if not (2 <= len(args) <= 3):
   788         # i18n: "word" is a keyword
   813         # i18n: "word" is a keyword
   789         raise error.ParseError(_("word expects two or three arguments, got %d")
   814         raise error.ParseError(_("word expects two or three arguments, got %d")
   790                                % len(args))
   815                                % len(args))
   791 
   816 
   818     }
   843     }
   819 
   844 
   820 # methods to interpret top-level template (e.g. {x}, {x|_}, {x % "y"})
   845 # methods to interpret top-level template (e.g. {x}, {x|_}, {x % "y"})
   821 methods = exprmethods.copy()
   846 methods = exprmethods.copy()
   822 methods["integer"] = exprmethods["symbol"]  # '{1}' as variable
   847 methods["integer"] = exprmethods["symbol"]  # '{1}' as variable
   823 
       
   824 funcs = {
       
   825     "date": date,
       
   826     "diff": diff,
       
   827     "fill": fill,
       
   828     "get": get,
       
   829     "if": if_,
       
   830     "ifcontains": ifcontains,
       
   831     "ifeq": ifeq,
       
   832     "indent": indent,
       
   833     "join": join,
       
   834     "label": label,
       
   835     "latesttag": latesttag,
       
   836     "localdate": localdate,
       
   837     "pad": pad,
       
   838     "revset": revset,
       
   839     "rstdoc": rstdoc,
       
   840     "shortest": shortest,
       
   841     "startswith": startswith,
       
   842     "strip": strip,
       
   843     "sub": sub,
       
   844     "word": word,
       
   845 }
       
   846 
   848 
   847 # template engine
   849 # template engine
   848 
   850 
   849 stringify = templatefilters.stringify
   851 stringify = templatefilters.stringify
   850 
   852