diff mercurial/templater.py @ 30115:8e42dfde93d1

templater: provide arithmetic operations on integers The termwidth template keyword is of limited use without some way to ensure that margins are respected. Provide a full set of arithmetic operators (four basic operations plus the mod function, defined to match Python's // for division), so that you can create termwidth based layouts that match the user's terminal size
author Simon Farnsworth <simonfar@fb.com>
date Sun, 09 Oct 2016 05:51:04 -0700
parents bd1f043d1ea3
children 1c01fa29630f
line wrap: on
line diff
--- a/mercurial/templater.py	Sun Oct 09 15:54:42 2016 +0200
+++ b/mercurial/templater.py	Sun Oct 09 05:51:04 2016 -0700
@@ -33,6 +33,10 @@
     "|": (5, None, None, ("|", 5), None),
     "%": (6, None, None, ("%", 6), None),
     ")": (0, None, None, None, None),
+    "+": (3, None, None, ("+", 3), None),
+    "-": (3, None, ("negate", 10), ("-", 3), None),
+    "*": (4, None, None, ("*", 4), None),
+    "/": (4, None, None, ("/", 4), None),
     "integer": (0, "integer", None, None, None),
     "symbol": (0, "symbol", None, None, None),
     "string": (0, "string", None, None, None),
@@ -48,7 +52,7 @@
         c = program[pos]
         if c.isspace(): # skip inter-token whitespace
             pass
-        elif c in "(,)%|": # handle simple operators
+        elif c in "(,)%|+-*/": # handle simple operators
             yield (c, None, pos)
         elif c in '"\'': # handle quoted templates
             s = pos + 1
@@ -70,13 +74,8 @@
                 pos += 1
             else:
                 raise error.ParseError(_("unterminated string"), s)
-        elif c.isdigit() or c == '-':
+        elif c.isdigit():
             s = pos
-            if c == '-': # simply take negate operator as part of integer
-                pos += 1
-            if pos >= end or not program[pos].isdigit():
-                raise error.ParseError(_("integer literal without digits"), s)
-            pos += 1
             while pos < end:
                 d = program[pos]
                 if not d.isdigit():
@@ -420,6 +419,28 @@
             # If so, return the expanded value.
             yield i
 
+def buildnegate(exp, context):
+    arg = compileexp(exp[1], context, exprmethods)
+    return (runnegate, arg)
+
+def runnegate(context, mapping, data):
+    data = evalinteger(context, mapping, data,
+                       _('negation needs an integer argument'))
+    return -data
+
+def buildarithmetic(exp, context, func):
+    left = compileexp(exp[1], context, exprmethods)
+    right = compileexp(exp[2], context, exprmethods)
+    return (runarithmetic, (func, left, right))
+
+def runarithmetic(context, mapping, data):
+    func, left, right = data
+    left = evalinteger(context, mapping, left,
+                       _('arithmetic only defined on integers'))
+    right = evalinteger(context, mapping, right,
+                        _('arithmetic only defined on integers'))
+    return func(left, right)
+
 def buildfunc(exp, context):
     n = getsymbol(exp[1])
     args = [compileexp(x, context, exprmethods) for x in getlist(exp[2])]
@@ -713,6 +734,20 @@
         tzoffset = util.makedate()[1]
     return (date[0], tzoffset)
 
+@templatefunc('mod(a, b)')
+def mod(context, mapping, args):
+    """Calculate a mod b such that a / b + a mod b == a"""
+    if not len(args) == 2:
+        # i18n: "mod" is a keyword
+        raise error.ParseError(_("mod expects two arguments"))
+
+    left = evalinteger(context, mapping, args[0],
+                       _('arithmetic only defined on integers'))
+    right = evalinteger(context, mapping, args[1],
+                        _('arithmetic only defined on integers'))
+
+    return left % right
+
 @templatefunc('relpath(path)')
 def relpath(context, mapping, args):
     """Convert a repository-absolute path into a filesystem path relative to
@@ -926,6 +961,11 @@
     "|": buildfilter,
     "%": buildmap,
     "func": buildfunc,
+    "+": lambda e, c: buildarithmetic(e, c, lambda a, b: a + b),
+    "-": lambda e, c: buildarithmetic(e, c, lambda a, b: a - b),
+    "negate": buildnegate,
+    "*": lambda e, c: buildarithmetic(e, c, lambda a, b: a * b),
+    "/": lambda e, c: buildarithmetic(e, c, lambda a, b: a // b),
     }
 
 # methods to interpret top-level template (e.g. {x}, {x|_}, {x % "y"})