##// END OF EJS Templates
tests: add tests for poorly behaving HTTP server...
tests: add tests for poorly behaving HTTP server I've spent several hours over the past few weeks investigating networking failures involving hg.mozilla.org. As part of this, it has become clear that the Mercurial client's error handling when it encounters network failures is far from robust. To prove this is true, I've devised a battery of tests simulating various network failures, notably premature connection closes. To achieve this, I've implemented an extension that monkeypatches the built-in HTTP server and hooks in at the socket level and allows various events to occur based on config options. For example, you can refuse to accept() a client socket or you can close() the socket after N bytes have been sent or received. The latter effectively simulates an unexpected connection drop (and these occur all the time in the real world). The new test file launches servers exhibiting various "bad" behaviors and points a client at them. As the many TODO comments in the test call attention to, Mercurial often displays unhelpful errors when network-related failures occur. This makes it difficult for users to understand what's going on and difficult for server administrators to pinpoint root causes without packet tracing. Upcoming patches will attempt to fix these error handling deficiencies.

File last commit:

r31928:277b3e2d default
r32001:c85f19c6 default
Show More
templater.py
1371 lines | 46.5 KiB | text/x-python | PythonLexer
Vadim Gelfer
add doc comments to template code.
r1909 # templater.py - template expansion for output
#
# Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
#
Martin Geisler
updated license to be explicit about GPL version 2
r8225 # This software may be used and distributed according to the terms of the
Matt Mackall
Update license to GPLv2+
r10263 # GNU General Public License version 2 or any later version.
Vadim Gelfer
add doc comments to template code.
r1909
Gregory Szorc
templater: use absolute_import
r25985 from __future__ import absolute_import
import os
import re
Weiwen
template engine: convert generator-based iterator to list-based iterator...
r17982 import types
Gregory Szorc
templater: use absolute_import
r25985
from .i18n import _
from . import (
Yuya Nishihara
templater: make pad() strip color codes before computing width (issue5416)...
r31521 color,
Gregory Szorc
templater: use absolute_import
r25985 config,
Yuya Nishihara
templater: make pad() compute actual width...
r31520 encoding,
Gregory Szorc
templater: use absolute_import
r25985 error,
minirst,
parser,
Pulkit Goyal
py3: replace os.sep with pycompat.ossep (part 3 of 4)
r30615 pycompat,
FUJIWARA Katsunori
templater: use templatefunc to mark a function as template function...
r28696 registrar,
Gregory Szorc
templater: use absolute_import
r25985 revset as revsetmod,
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024 revsetlang,
Gregory Szorc
templater: use absolute_import
r25985 templatefilters,
templatekw,
util,
)
Matt Mackall
templater: use the parser.py parser to extend the templater syntax
r13176
# template parsing
elements = {
Yuya Nishihara
parser: separate actions for primary expression and prefix operator...
r25815 # token-type: binding-strength, primary, prefix, infix, suffix
"(": (20, None, ("group", 1, ")"), ("func", 1, ")"), None),
Yuya Nishihara
templater: adjust binding strengths to make room for key-value operator...
r31884 "%": (16, None, None, ("%", 16), None),
"|": (15, None, None, ("|", 15), None),
"*": (5, None, None, ("*", 5), None),
"/": (5, None, None, ("/", 5), None),
"+": (4, None, None, ("+", 4), None),
"-": (4, None, ("negate", 19), ("-", 4), None),
Yuya Nishihara
templater: add parsing rule for key-value pair...
r31885 "=": (3, None, None, ("keyvalue", 3), None),
Yuya Nishihara
templater: sort token table by binding strength...
r31883 ",": (2, None, None, ("list", 2), None),
")": (0, None, None, None, None),
Yuya Nishihara
parser: separate actions for primary expression and prefix operator...
r25815 "integer": (0, "integer", None, None, None),
"symbol": (0, "symbol", None, None, None),
"string": (0, "string", None, None, None),
"template": (0, "template", None, None, None),
"end": (0, None, None, None, None),
Matt Mackall
templater: use the parser.py parser to extend the templater syntax
r13176 }
Yuya Nishihara
templater: add function to parse whole string as template expression...
r28911 def tokenize(program, start, end, term=None):
"""Parse a template expression into a stream of tokens, which must end
with term if specified"""
Matt Mackall
templater: use the parser.py parser to extend the templater syntax
r13176 pos = start
while pos < end:
c = program[pos]
if c.isspace(): # skip inter-token whitespace
pass
Yuya Nishihara
templater: add parsing rule for key-value pair...
r31885 elif c in "(=,)%|+-*/": # handle simple operators
Matt Mackall
templater: use the parser.py parser to extend the templater syntax
r13176 yield (c, None, pos)
Yuya Nishihara
templater: introduce one-pass parsing of nested template strings...
r25783 elif c in '"\'': # handle quoted templates
s = pos + 1
data, pos = _parsetemplate(program, s, end, c)
yield ('template', data, s)
pos -= 1
Yuya Nishihara
templater: remove processing of "string" literals from tokenizer...
r25784 elif c == 'r' and program[pos:pos + 2] in ("r'", 'r"'):
# handle quoted strings
c = program[pos + 1]
s = pos = pos + 2
Matt Mackall
templater: use the parser.py parser to extend the templater syntax
r13176 while pos < end: # find closing quote
d = program[pos]
Yuya Nishihara
templater: fix handling of \-escapes in raw string literals...
r25638 if d == '\\': # skip over escaped characters
Matt Mackall
templater: use the parser.py parser to extend the templater syntax
r13176 pos += 2
continue
if d == c:
Yuya Nishihara
templater: unify "string" and "rawstring"...
r25785 yield ('string', program[s:pos], s)
Matt Mackall
templater: use the parser.py parser to extend the templater syntax
r13176 break
pos += 1
else:
raise error.ParseError(_("unterminated string"), s)
Simon Farnsworth
templater: provide arithmetic operations on integers...
r30115 elif c.isdigit():
Yuya Nishihara
templater: tokenize decimal integer literal (issue4638) (BC)...
r25002 s = pos
while pos < end:
d = program[pos]
if not d.isdigit():
break
pos += 1
yield ('integer', program[s:pos], s)
pos -= 1
Yuya Nishihara
templater: parse \"...\" as string for 2.9.2-3.4 compatibility (issue4733)...
r25676 elif (c == '\\' and program[pos:pos + 2] in (r"\'", r'\"')
or c == 'r' and program[pos:pos + 3] in (r"r\'", r'r\"')):
# handle escaped quoted strings for compatibility with 2.9.2-3.4,
# where some of nested templates were preprocessed as strings and
# then compiled. therefore, \"...\" was allowed. (issue4733)
#
# processing flow of _evalifliteral() at 5ab28a2e9962:
# outer template string -> stringify() -> compiletemplate()
# ------------------------ ------------ ------------------
# {f("\\\\ {g(\"\\\"\")}"} \\ {g("\"")} [r'\\', {g("\"")}]
# ~~~~~~~~
# escaped quoted string
if c == 'r':
pos += 1
Yuya Nishihara
templater: unify "string" and "rawstring"...
r25785 token = 'string'
Yuya Nishihara
templater: parse \"...\" as string for 2.9.2-3.4 compatibility (issue4733)...
r25676 else:
Yuya Nishihara
templater: introduce one-pass parsing of nested template strings...
r25783 token = 'template'
Yuya Nishihara
templater: parse \"...\" as string for 2.9.2-3.4 compatibility (issue4733)...
r25676 quote = program[pos:pos + 2]
s = pos = pos + 2
while pos < end: # find closing escaped quote
if program.startswith('\\\\\\', pos, end):
pos += 4 # skip over double escaped characters
continue
if program.startswith(quote, pos, end):
Matt Mackall
templater: create string unescape helper (issue4798)...
r26215 # interpret as if it were a part of an outer string
Yuya Nishihara
parser: move unescape helper from templater...
r26231 data = parser.unescapestr(program[s:pos])
Yuya Nishihara
templater: introduce one-pass parsing of nested template strings...
r25783 if token == 'template':
data = _parsetemplate(data, 0, len(data))[0]
Yuya Nishihara
templater: parse \"...\" as string for 2.9.2-3.4 compatibility (issue4733)...
r25676 yield (token, data, s)
pos += 1
break
pos += 1
else:
raise error.ParseError(_("unterminated string"), s)
Matt Mackall
templater: use the parser.py parser to extend the templater syntax
r13176 elif c.isalnum() or c in '_':
s = pos
pos += 1
while pos < end: # find end of symbol
d = program[pos]
if not (d.isalnum() or d == "_"):
break
pos += 1
sym = program[s:pos]
Brendan Cully
templater: back out 0615b22da148, it breaks schemes ({1})
r18893 yield ('symbol', sym, s)
Matt Mackall
templater: use the parser.py parser to extend the templater syntax
r13176 pos -= 1
Yuya Nishihara
templater: add function to parse whole string as template expression...
r28911 elif c == term:
Yuya Nishihara
templater: check existence of closing brace of template string
r25782 yield ('end', None, pos + 1)
return
Matt Mackall
templater: use the parser.py parser to extend the templater syntax
r13176 else:
raise error.ParseError(_("syntax error"), pos)
pos += 1
Yuya Nishihara
templater: add function to parse whole string as template expression...
r28911 if term:
raise error.ParseError(_("unterminated template expansion"), start)
yield ('end', None, pos)
Matt Mackall
templater: use the parser.py parser to extend the templater syntax
r13176
Yuya Nishihara
templater: introduce one-pass parsing of nested template strings...
r25783 def _parsetemplate(tmpl, start, stop, quote=''):
r"""
>>> _parsetemplate('foo{bar}"baz', 0, 12)
([('string', 'foo'), ('symbol', 'bar'), ('string', '"baz')], 12)
>>> _parsetemplate('foo{bar}"baz', 0, 12, quote='"')
([('string', 'foo'), ('symbol', 'bar')], 9)
>>> _parsetemplate('foo"{bar}', 0, 9, quote='"')
([('string', 'foo')], 4)
>>> _parsetemplate(r'foo\"bar"baz', 0, 12, quote='"')
([('string', 'foo"'), ('string', 'bar')], 9)
>>> _parsetemplate(r'foo\\"bar', 0, 10, quote='"')
Yuya Nishihara
templater: unify "string" and "rawstring"...
r25785 ([('string', 'foo\\')], 6)
Yuya Nishihara
templater: introduce one-pass parsing of nested template strings...
r25783 """
Matt Mackall
templater: use the parser.py parser to extend the templater syntax
r13176 parsed = []
Yuya Nishihara
templater: introduce one-pass parsing of nested template strings...
r25783 sepchars = '{' + quote
Yuya Nishihara
templater: extract function that parses template string...
r25781 pos = start
Yuya Nishihara
parser: accept iterator of tokens instead of tokenizer function and program...
r25654 p = parser.parser(elements)
Matt Mackall
templater: use the parser.py parser to extend the templater syntax
r13176 while pos < stop:
Yuya Nishihara
templater: introduce one-pass parsing of nested template strings...
r25783 n = min((tmpl.find(c, pos, stop) for c in sepchars),
key=lambda n: (n < 0, n))
Matt Mackall
templater: use the parser.py parser to extend the templater syntax
r13176 if n < 0:
Yuya Nishihara
parser: move unescape helper from templater...
r26231 parsed.append(('string', parser.unescapestr(tmpl[pos:stop])))
Yuya Nishihara
templater: respect stop position while parsing template string...
r25780 pos = stop
Matt Mackall
templater: use the parser.py parser to extend the templater syntax
r13176 break
Yuya Nishihara
templater: introduce one-pass parsing of nested template strings...
r25783 c = tmpl[n]
Yuya Nishihara
templater: strictly parse leading backslashes of '{' (issue4569) (BC)...
r24949 bs = (n - pos) - len(tmpl[pos:n].rstrip('\\'))
Yuya Nishihara
templater: drop strtoken argument from compiletemplate()...
r25598 if bs % 2 == 1:
# escaped (e.g. '\{', '\\\{', but not '\\{')
Yuya Nishihara
parser: move unescape helper from templater...
r26231 parsed.append(('string', parser.unescapestr(tmpl[pos:n - 1]) + c))
Matt Mackall
templater: use the parser.py parser to extend the templater syntax
r13176 pos = n + 1
continue
if n > pos:
Yuya Nishihara
parser: move unescape helper from templater...
r26231 parsed.append(('string', parser.unescapestr(tmpl[pos:n])))
Yuya Nishihara
templater: introduce one-pass parsing of nested template strings...
r25783 if c == quote:
return parsed, n + 1
Matt Mackall
templater: use the parser.py parser to extend the templater syntax
r13176
Yuya Nishihara
templater: add function to parse whole string as template expression...
r28911 parseres, pos = p.parse(tokenize(tmpl, n + 1, stop, '}'))
Bernhard Leiner
revset: report a parse error if a revset is not parsed completely (issue2654)
r13665 parsed.append(parseres)
Yuya Nishihara
templater: introduce one-pass parsing of nested template strings...
r25783
if quote:
raise error.ParseError(_("unterminated string"), start)
Yuya Nishihara
templater: extract function that parses template string...
r25781 return parsed, pos
Matt Mackall
templater: use the parser.py parser to extend the templater syntax
r13176
Yuya Nishihara
templater: expand list of parsed templates to template node...
r28547 def _unnesttemplatelist(tree):
"""Expand list of templates to node tuple
>>> def f(tree):
... print prettyformat(_unnesttemplatelist(tree))
>>> f(('template', []))
('string', '')
>>> f(('template', [('string', 'foo')]))
('string', 'foo')
>>> f(('template', [('string', 'foo'), ('symbol', 'rev')]))
(template
('string', 'foo')
('symbol', 'rev'))
>>> f(('template', [('symbol', 'rev')])) # template(rev) -> str
(template
('symbol', 'rev'))
>>> f(('template', [('template', [('string', 'foo')])]))
('string', 'foo')
"""
if not isinstance(tree, tuple):
return tree
op = tree[0]
if op != 'template':
return (op,) + tuple(_unnesttemplatelist(x) for x in tree[1:])
assert len(tree) == 2
xs = tuple(_unnesttemplatelist(x) for x in tree[1])
if not xs:
return ('string', '') # empty template ""
elif len(xs) == 1 and xs[0][0] == 'string':
return xs[0] # fast path for string with no template fragment "x"
else:
return (op,) + xs
Yuya Nishihara
templater: lift parsed and compiled templates to generic data types...
r28545 def parse(tmpl):
"""Parse template string into tree"""
Yuya Nishihara
templater: extract function that parses template string...
r25781 parsed, pos = _parsetemplate(tmpl, 0, len(tmpl))
Yuya Nishihara
templater: lift parsed and compiled templates to generic data types...
r28545 assert pos == len(tmpl), 'unquoted template should be consumed'
Yuya Nishihara
templater: expand list of parsed templates to template node...
r28547 return _unnesttemplatelist(('template', parsed))
Yuya Nishihara
templater: add function to parse whole string as template expression...
r28911 def _parseexpr(expr):
"""Parse a template expression into tree
>>> _parseexpr('"foo"')
('string', 'foo')
>>> _parseexpr('foo(bar)')
('func', ('symbol', 'foo'), ('symbol', 'bar'))
>>> _parseexpr('foo(')
Traceback (most recent call last):
...
ParseError: ('not a prefix: end', 4)
>>> _parseexpr('"foo" "bar"')
Traceback (most recent call last):
...
ParseError: ('invalid token', 7)
"""
p = parser.parser(elements)
tree, pos = p.parse(tokenize(expr, 0, len(expr)))
if pos != len(expr):
raise error.ParseError(_('invalid token'), pos)
return _unnesttemplatelist(tree)
Yuya Nishihara
templater: expand list of parsed templates to template node...
r28547 def prettyformat(tree):
return parser.prettyformat(tree, ('integer', 'string', 'symbol'))
Yuya Nishihara
templater: lift parsed and compiled templates to generic data types...
r28545
Yuya Nishihara
templater: switch methods table on compileexp() of func args and inner expr...
r25001 def compileexp(exp, context, curmethods):
Yuya Nishihara
templater: inline compiletemplate() function into engine...
r28956 """Compile parsed template tree to (func, data) pair"""
Matt Mackall
templater: use the parser.py parser to extend the templater syntax
r13176 t = exp[0]
Yuya Nishihara
templater: switch methods table on compileexp() of func args and inner expr...
r25001 if t in curmethods:
return curmethods[t](exp, context)
Matt Mackall
templater: use the parser.py parser to extend the templater syntax
r13176 raise error.ParseError(_("unknown method '%s'") % t)
# template evaluation
def getsymbol(exp):
if exp[0] == 'symbol':
return exp[1]
Ryan McElroy
templater: add symbol to error...
r21822 raise error.ParseError(_("expected a symbol, got '%s'") % exp[0])
Matt Mackall
templater: use the parser.py parser to extend the templater syntax
r13176
def getlist(x):
if not x:
return []
if x[0] == 'list':
return getlist(x[1]) + [x[2]]
return [x]
def gettemplate(exp, context):
Yuya Nishihara
templater: lift parsed and compiled templates to generic data types...
r28545 """Compile given template tree or load named template from map file;
returns (func, data) pair"""
Yuya Nishihara
templater: relax type of mapped template...
r28546 if exp[0] in ('template', 'string'):
Yuya Nishihara
templater: lift parsed and compiled templates to generic data types...
r28545 return compileexp(exp, context, methods)
Matt Mackall
templater: use the parser.py parser to extend the templater syntax
r13176 if exp[0] == 'symbol':
Yuya Nishihara
templater: comment that gettemplate() has different name resolution order...
r25599 # unlike runsymbol(), here 'symbol' is always taken as template name
# even if it exists in mapping. this allows us to override mapping
# by web templates, e.g. 'changelogtag' is redefined in map file.
Matt Mackall
templater: use the parser.py parser to extend the templater syntax
r13176 return context._load(exp[1])
raise error.ParseError(_("expected template specifier"))
Yuya Nishihara
templater: find keyword name more thoroughly on filtering error...
r31927 def findsymbolicname(arg):
"""Find symbolic name for the given compiled expression; returns None
if nothing found reliably"""
while True:
func, data = arg
if func is runsymbol:
return data
elif func is runfilter:
arg = data[0]
else:
return None
Yuya Nishihara
templater: extract helper that evaluates filter or function argument...
r26124 def evalfuncarg(context, mapping, arg):
func, data = arg
# func() may return string, generator of strings or arbitrary object such
# as date tuple, but filter does not want generator.
thing = func(context, mapping, data)
if isinstance(thing, types.GeneratorType):
thing = stringify(thing)
return thing
Yuya Nishihara
templater: fix if() to not evaluate False as bool('False')...
r29816 def evalboolean(context, mapping, arg):
Yuya Nishihara
templater: make pad() evaluate boolean argument (BC)...
r29817 """Evaluate given argument as boolean, but also takes boolean literals"""
Yuya Nishihara
templater: fix if() to not evaluate False as bool('False')...
r29816 func, data = arg
Yuya Nishihara
templater: make pad() evaluate boolean argument (BC)...
r29817 if func is runsymbol:
thing = func(context, mapping, data, default=None)
if thing is None:
# not a template keyword, takes as a boolean literal
thing = util.parsebool(data)
else:
thing = func(context, mapping, data)
Yuya Nishihara
templater: fix if() to not evaluate False as bool('False')...
r29816 if isinstance(thing, bool):
return thing
# other objects are evaluated as strings, which means 0 is True, but
# empty dict/list should be False as they are expected to be ''
return bool(stringify(thing))
Yuya Nishihara
templater: factor out function that evaluates argument as integer...
r28343 def evalinteger(context, mapping, arg, err):
Yuya Nishihara
templater: drop redundant type conversion when evaluating integer argument...
r28344 v = evalfuncarg(context, mapping, arg)
Yuya Nishihara
templater: factor out function that evaluates argument as integer...
r28343 try:
Yuya Nishihara
templater: drop redundant type conversion when evaluating integer argument...
r28344 return int(v)
except (TypeError, ValueError):
Yuya Nishihara
templater: factor out function that evaluates argument as integer...
r28343 raise error.ParseError(err)
Yuya Nishihara
templater: factor out thin helper that evaluates argument as string...
r28348 def evalstring(context, mapping, arg):
func, data = arg
return stringify(func(context, mapping, data))
Yuya Nishihara
templater: make label() take unknown symbol as color literal...
r28373 def evalstringliteral(context, mapping, arg):
"""Evaluate given argument as string template, but returns symbol name
if it is unknown"""
func, data = arg
if func is runsymbol:
thing = func(context, mapping, data, default=data)
else:
thing = func(context, mapping, data)
return stringify(thing)
Yuya Nishihara
templater: tokenize decimal integer literal (issue4638) (BC)...
r25002 def runinteger(context, mapping, data):
return int(data)
Matt Mackall
templater: use the parser.py parser to extend the templater syntax
r13176 def runstring(context, mapping, data):
return data
Yuya Nishihara
templater: abort if infinite recursion detected while evaluation (issue4758)...
r27939 def _recursivesymbolblocker(key):
def showrecursion(**args):
raise error.Abort(_("recursive reference '%s' in template") % key)
return showrecursion
Yuya Nishihara
templater: abort if infinite recursion detected while compiling...
r27940 def _runrecursivesymbol(context, mapping, key):
raise error.Abort(_("recursive reference '%s' in template") % key)
Yuya Nishihara
templater: make label() take unknown symbol as color literal...
r28373 def runsymbol(context, mapping, key, default=''):
Matt Mackall
templater: use the parser.py parser to extend the templater syntax
r13176 v = mapping.get(key)
if v is None:
Alexander Plavin
templater: support using templates with non-standard names from map file...
r19770 v = context._defaults.get(key)
if v is None:
Yuya Nishihara
templater: abort if infinite recursion detected while evaluation (issue4758)...
r27939 # put poison to cut recursion. we can't move this to parsing phase
# because "x = {x}" is allowed if "x" is a keyword. (issue4758)
safemapping = mapping.copy()
safemapping[key] = _recursivesymbolblocker(key)
Alexander Plavin
templater: support using templates with non-standard names from map file...
r19770 try:
Yuya Nishihara
templater: abort if infinite recursion detected while evaluation (issue4758)...
r27939 v = context.process(key, safemapping)
Alexander Plavin
templater: support using templates with non-standard names from map file...
r19770 except TemplateNotFound:
Yuya Nishihara
templater: make label() take unknown symbol as color literal...
r28373 v = default
Augie Fackler
templater: restore use of callable() since it was readded in Python 3.2
r21798 if callable(v):
Matt Mackall
templater: use the parser.py parser to extend the templater syntax
r13176 return v(**mapping)
return v
Yuya Nishihara
templater: take any string literals as template, but not for rawstring (BC)...
r25596 def buildtemplate(exp, context):
Yuya Nishihara
templater: expand list of parsed templates to template node...
r28547 ctmpl = [compileexp(e, context, methods) for e in exp[1:]]
Yuya Nishihara
templater: take any string literals as template, but not for rawstring (BC)...
r25596 return (runtemplate, ctmpl)
Yuya Nishihara
templater: move runtemplate function out of buildmap/runmap pair...
r25595 def runtemplate(context, mapping, template):
for func, data in template:
yield func(context, mapping, data)
Matt Mackall
templater: use the parser.py parser to extend the templater syntax
r13176 def buildfilter(exp, context):
Yuya Nishihara
templater: inline getfilter() to buildfilter()...
r26104 n = getsymbol(exp[2])
if n in context._filters:
filt = context._filters[n]
Yuya Nishihara
templater: add support for keyword arguments...
r31886 arg = compileexp(exp[1], context, methods)
Yuya Nishihara
templater: drop unneeded destructuring of argument tuple at buildfilter...
r26125 return (runfilter, (arg, filt))
Yuya Nishihara
templater: introduce unified filter syntax for unary functions...
r26105 if n in funcs:
f = funcs[n]
Yuya Nishihara
templater: add support for keyword arguments...
r31886 args = _buildfuncargs(exp[1], context, methods, n, f._argspec)
return (f, args)
Yuya Nishihara
templater: inline getfilter() to buildfilter()...
r26104 raise error.ParseError(_("unknown function '%s'") % n)
Matt Mackall
templater: use the parser.py parser to extend the templater syntax
r13176
def runfilter(context, mapping, data):
Yuya Nishihara
templater: drop unneeded destructuring of argument tuple at buildfilter...
r26125 arg, filt = data
thing = evalfuncarg(context, mapping, arg)
Neil Kodner
templater: abort when a template filter raises an exception (issue2987)
r17383 try:
Yuya Nishihara
templater: allow piping generator-type function output to filters...
r24280 return filt(thing)
Neil Kodner
templater: abort when a template filter raises an exception (issue2987)
r17383 except (ValueError, AttributeError, TypeError):
Yuya Nishihara
templater: find keyword name more thoroughly on filtering error...
r31927 sym = findsymbolicname(arg)
if sym:
msg = (_("template filter '%s' is not compatible with keyword '%s'")
% (filt.func_name, sym))
Neil Kodner
templater: abort when a template filter raises an exception (issue2987)
r17383 else:
Yuya Nishihara
templater: find keyword name more thoroughly on filtering error...
r31927 msg = _("incompatible use of template filter '%s'") % filt.func_name
raise error.Abort(msg)
Matt Mackall
templater: use the parser.py parser to extend the templater syntax
r13176
def buildmap(exp, context):
Yuya Nishihara
templater: switch methods table on compileexp() of func args and inner expr...
r25001 func, data = compileexp(exp[1], context, methods)
Yuya Nishihara
templater: lift parsed and compiled templates to generic data types...
r28545 tfunc, tdata = gettemplate(exp[2], context)
return (runmap, (func, data, tfunc, tdata))
Matt Mackall
templater: use the parser.py parser to extend the templater syntax
r13176
def runmap(context, mapping, data):
Yuya Nishihara
templater: lift parsed and compiled templates to generic data types...
r28545 func, data, tfunc, tdata = data
Matt Mackall
templater: use the parser.py parser to extend the templater syntax
r13176 d = func(context, mapping, data)
Yuya Nishihara
templater: make _hybrid not callable to avoid conflicting semantics...
r27891 if util.safehasattr(d, 'itermaps'):
Yuya Nishihara
templater: handle exception when applying map operator to non-iterable object...
r28349 diter = d.itermaps()
else:
try:
diter = iter(d)
except TypeError:
if func is runsymbol:
raise error.ParseError(_("keyword '%s' is not iterable") % data)
else:
raise error.ParseError(_("%r is not iterable") % d)
Matt Mackall
templating: make new-style templating features work with command line lists
r17631
Yuya Nishihara
templater: provide loop counter as "index" keyword...
r31807 for i, v in enumerate(diter):
Kostia Balytskyi
templater: fix list templating bug...
r28225 lm = mapping.copy()
Yuya Nishihara
templater: provide loop counter as "index" keyword...
r31807 lm['index'] = i
Yuya Nishihara
templater: rename variable "i" to "v" in runmap()...
r31806 if isinstance(v, dict):
lm.update(v)
Weiwen
hgweb: display diff for a changeset against any parents (issue2810)...
r17991 lm['originalnode'] = mapping.get('node')
Yuya Nishihara
templater: lift parsed and compiled templates to generic data types...
r28545 yield tfunc(context, lm, tdata)
Matt Mackall
templater: use the parser.py parser to extend the templater syntax
r13176 else:
# v is not an iterable of dicts, this happen when 'key'
# has been fully expanded already and format is useless.
# If so, return the expanded value.
Yuya Nishihara
templater: rename variable "i" to "v" in runmap()...
r31806 yield v
Matt Mackall
templater: use the parser.py parser to extend the templater syntax
r13176
Simon Farnsworth
templater: provide arithmetic operations on integers...
r30115 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'))
Simon Farnsworth
templater: handle division by zero in arithmetic...
r30116 try:
return func(left, right)
except ZeroDivisionError:
raise error.Abort(_('division by zero is not defined'))
Simon Farnsworth
templater: provide arithmetic operations on integers...
r30115
Matt Mackall
templater: use the parser.py parser to extend the templater syntax
r13176 def buildfunc(exp, context):
n = getsymbol(exp[1])
Matt Mackall
templater: use a global funcs table
r14925 if n in funcs:
f = funcs[n]
Yuya Nishihara
templater: add support for keyword arguments...
r31886 args = _buildfuncargs(exp[2], context, exprmethods, n, f._argspec)
Matt Mackall
templater: use a global funcs table
r14925 return (f, args)
Matt Mackall
templater: use the parser.py parser to extend the templater syntax
r13176 if n in context._filters:
Yuya Nishihara
templater: add support for keyword arguments...
r31886 args = _buildfuncargs(exp[2], context, exprmethods, n, argspec=None)
Matt Mackall
templater: use the parser.py parser to extend the templater syntax
r13176 if len(args) != 1:
raise error.ParseError(_("filter %s expects one argument") % n)
f = context._filters[n]
Yuya Nishihara
templater: drop unneeded destructuring of argument tuple at buildfilter...
r26125 return (runfilter, (args[0], f))
Sean Farley
templater: raise error for unknown func...
r20857 raise error.ParseError(_("unknown function '%s'") % n)
Matt Mackall
templater: use the parser.py parser to extend the templater syntax
r13176
Yuya Nishihara
templater: add support for keyword arguments...
r31886 def _buildfuncargs(exp, context, curmethods, funcname, argspec):
"""Compile parsed tree of function arguments into list or dict of
Yuya Nishihara
parser: extend buildargsdict() to support arbitrary number of **kwargs...
r31921 (func, data) pairs
>>> context = engine(lambda t: (runsymbol, t))
>>> def fargs(expr, argspec):
... x = _parseexpr(expr)
... n = getsymbol(x[1])
... return _buildfuncargs(x[2], context, exprmethods, n, argspec)
Yuya Nishihara
parser: preserve order of keyword arguments...
r31922 >>> fargs('a(l=1, k=2)', 'k l m').keys()
['l', 'k']
Yuya Nishihara
parser: extend buildargsdict() to support arbitrary number of **kwargs...
r31921 >>> args = fargs('a(opts=1, k=2)', '**opts')
Yuya Nishihara
parser: preserve order of keyword arguments...
r31922 >>> args.keys(), args['opts'].keys()
(['opts'], ['opts', 'k'])
Yuya Nishihara
parser: extend buildargsdict() to support arbitrary number of **kwargs...
r31921 """
Yuya Nishihara
templater: add support for keyword arguments...
r31886 def compiledict(xs):
Yuya Nishihara
parser: preserve order of keyword arguments...
r31922 return util.sortdict((k, compileexp(x, context, curmethods))
for k, x in xs.iteritems())
Yuya Nishihara
templater: add support for keyword arguments...
r31886 def compilelist(xs):
return [compileexp(x, context, curmethods) for x in xs]
if not argspec:
# filter or function with no argspec: return list of positional args
return compilelist(getlist(exp))
# function with argspec: return dict of named args
Yuya Nishihara
parser: extend buildargsdict() to support arbitrary number of **kwargs...
r31921 _poskeys, varkey, _keys, optkey = argspec = parser.splitargspec(argspec)
Yuya Nishihara
templater: add support for keyword arguments...
r31886 treeargs = parser.buildargsdict(getlist(exp), funcname, argspec,
keyvaluenode='keyvalue', keynode='symbol')
Yuya Nishihara
parser: preserve order of keyword arguments...
r31922 compargs = util.sortdict()
Yuya Nishihara
templater: add support for keyword arguments...
r31886 if varkey:
compargs[varkey] = compilelist(treeargs.pop(varkey))
Yuya Nishihara
parser: extend buildargsdict() to support arbitrary number of **kwargs...
r31921 if optkey:
compargs[optkey] = compiledict(treeargs.pop(optkey))
Yuya Nishihara
templater: add support for keyword arguments...
r31886 compargs.update(compiledict(treeargs))
return compargs
Yuya Nishihara
templater: add parsing rule for key-value pair...
r31885 def buildkeyvaluepair(exp, content):
raise error.ParseError(_("can't use a key-value pair in this context"))
FUJIWARA Katsunori
templater: use templatefunc to mark a function as template function...
r28696 # dict of template built-in functions
funcs = {}
templatefunc = registrar.templatefunc(funcs)
@templatefunc('date(date[, fmt])')
Alexander Plavin
templater: sort functions alphabetically, as filters are
r19390 def date(context, mapping, args):
FUJIWARA Katsunori
templater: use templatefunc to mark a function as template function...
r28696 """Format a date. See :hg:`help dates` for formatting
Yuya Nishihara
templatefilters: remove redundant 'date' and 'strip' filters...
r26106 strings. The default is a Unix date format, including the timezone:
"Mon Sep 04 15:13:13 2006 0700"."""
Alexander Plavin
templater: sort functions alphabetically, as filters are
r19390 if not (1 <= len(args) <= 2):
FUJIWARA Katsunori
i18n: add i18n comment to error messages of template functions
r23112 # i18n: "date" is a keyword
Alexander Plavin
templater: sort functions alphabetically, as filters are
r19390 raise error.ParseError(_("date expects one or two arguments"))
Yuya Nishihara
templater: make date() use helper function to evaluate argument...
r28334 date = evalfuncarg(context, mapping, args[0])
Yuya Nishihara
templater: fix crash by passing invalid object to date() function...
r24903 fmt = None
Alexander Plavin
templater: sort functions alphabetically, as filters are
r19390 if len(args) == 2:
Yuya Nishihara
templater: factor out thin helper that evaluates argument as string...
r28348 fmt = evalstring(context, mapping, args[1])
Yuya Nishihara
templater: fix crash by passing invalid object to date() function...
r24903 try:
if fmt is None:
return util.datestr(date)
else:
return util.datestr(date, fmt)
except (TypeError, ValueError):
# i18n: "date" is a keyword
raise error.ParseError(_("date expects a date information"))
Alexander Plavin
templater: sort functions alphabetically, as filters are
r19390
Yuya Nishihara
templater: add shorthand for building a dict like {"key": key}...
r31928 @templatefunc('dict([[key=]value...])', argspec='*args **kwargs')
Yuya Nishihara
templater: add dict() constructor...
r31926 def dict_(context, mapping, args):
Yuya Nishihara
templater: add shorthand for building a dict like {"key": key}...
r31928 """Construct a dict from key-value pairs. A key may be omitted if
a value expression can provide an unambiguous name."""
Yuya Nishihara
templater: add dict() constructor...
r31926 data = util.sortdict()
Yuya Nishihara
templater: add shorthand for building a dict like {"key": key}...
r31928
for v in args['args']:
k = findsymbolicname(v)
if not k:
raise error.ParseError(_('dict key cannot be inferred'))
if k in data or k in args['kwargs']:
raise error.ParseError(_("duplicated dict key '%s' inferred") % k)
data[k] = evalfuncarg(context, mapping, v)
Yuya Nishihara
templater: add dict() constructor...
r31926 data.update((k, evalfuncarg(context, mapping, v))
for k, v in args['kwargs'].iteritems())
return templatekw.hybriddict(data)
FUJIWARA Katsunori
templater: use templatefunc to mark a function as template function...
r28696 @templatefunc('diff([includepattern [, excludepattern]])')
FUJIWARA Katsunori
templater: add "diff" template function...
r22434 def diff(context, mapping, args):
FUJIWARA Katsunori
templater: use templatefunc to mark a function as template function...
r28696 """Show a diff, optionally
Gregory Szorc
templater: add consistent docstrings to functions...
r24586 specifying files to include or exclude."""
FUJIWARA Katsunori
templater: add "diff" template function...
r22434 if len(args) > 2:
# i18n: "diff" is a keyword
timeless
grammar: favor zero, one, two over ... or no
r27293 raise error.ParseError(_("diff expects zero, one, or two arguments"))
FUJIWARA Katsunori
templater: add "diff" template function...
r22434
def getpatterns(i):
if i < len(args):
Yuya Nishihara
templater: factor out thin helper that evaluates argument as string...
r28348 s = evalstring(context, mapping, args[i]).strip()
FUJIWARA Katsunori
templater: add "diff" template function...
r22434 if s:
return [s]
return []
ctx = mapping['ctx']
chunks = ctx.diff(match=ctx.match([], getpatterns(0), getpatterns(1)))
return ''.join(chunks)
Hannes Oldenburg
templates: add built-in files() function...
r30008 @templatefunc('files(pattern)')
def files(context, mapping, args):
"""All files of the current changeset matching the pattern. See
:hg:`help patterns`."""
if not len(args) == 1:
# i18n: "files" is a keyword
raise error.ParseError(_("files expects one argument"))
raw = evalstring(context, mapping, args[0])
ctx = mapping['ctx']
m = ctx.match([raw])
files = list(ctx.matches(m))
return templatekw.showlist("file", files, **mapping)
FUJIWARA Katsunori
templater: use templatefunc to mark a function as template function...
r28696 @templatefunc('fill(text[, width[, initialident[, hangindent]]])')
Alexander Plavin
templater: sort functions alphabetically, as filters are
r19390 def fill(context, mapping, args):
FUJIWARA Katsunori
templater: use templatefunc to mark a function as template function...
r28696 """Fill many
Gregory Szorc
templater: add consistent docstrings to functions...
r24586 paragraphs with optional indentation. See the "fill" filter."""
Alexander Plavin
templater: sort functions alphabetically, as filters are
r19390 if not (1 <= len(args) <= 4):
FUJIWARA Katsunori
i18n: add i18n comment to error messages of template functions
r23112 # i18n: "fill" is a keyword
Alexander Plavin
templater: sort functions alphabetically, as filters are
r19390 raise error.ParseError(_("fill expects one to four arguments"))
Yuya Nishihara
templater: factor out thin helper that evaluates argument as string...
r28348 text = evalstring(context, mapping, args[0])
Alexander Plavin
templater: sort functions alphabetically, as filters are
r19390 width = 76
initindent = ''
hangindent = ''
if 2 <= len(args) <= 4:
Yuya Nishihara
templater: factor out function that evaluates argument as integer...
r28343 width = evalinteger(context, mapping, args[1],
# i18n: "fill" is a keyword
_("fill expects an integer width"))
Alexander Plavin
templater: sort functions alphabetically, as filters are
r19390 try:
Yuya Nishihara
templater: factor out thin helper that evaluates argument as string...
r28348 initindent = evalstring(context, mapping, args[2])
hangindent = evalstring(context, mapping, args[3])
Alexander Plavin
templater: sort functions alphabetically, as filters are
r19390 except IndexError:
pass
return templatefilters.fill(text, width, initindent, hangindent)
Yuya Nishihara
templater: port formatnode filter from changeset_templater...
r31169 @templatefunc('formatnode(node)')
def formatnode(context, mapping, args):
"""Obtain the preferred form of a changeset hash. (DEPRECATED)"""
if len(args) != 1:
# i18n: "formatnode" is a keyword
raise error.ParseError(_("formatnode expects one argument"))
ui = mapping['ui']
node = evalstring(context, mapping, args[0])
if ui.debugflag:
return node
return templatefilters.short(node)
Yuya Nishihara
templater: port pad() to take keyword arguments...
r31887 @templatefunc('pad(text, width[, fillchar=\' \'[, left=False]])',
argspec='text width fillchar left')
Durham Goode
template: add pad function for padding output...
r20370 def pad(context, mapping, args):
FUJIWARA Katsunori
templater: use templatefunc to mark a function as template function...
r28696 """Pad text with a
Gregory Szorc
templater: add consistent docstrings to functions...
r24586 fill character."""
Yuya Nishihara
templater: port pad() to take keyword arguments...
r31887 if 'text' not in args or 'width' not in args:
FUJIWARA Katsunori
i18n: add i18n comment to error messages of template functions
r23112 # i18n: "pad" is a keyword
Durham Goode
template: add pad function for padding output...
r20370 raise error.ParseError(_("pad() expects two to four arguments"))
Yuya Nishihara
templater: port pad() to take keyword arguments...
r31887 width = evalinteger(context, mapping, args['width'],
Yuya Nishihara
templater: fix pad() to evaluate int argument and handle error
r28345 # i18n: "pad" is a keyword
_("pad() expects an integer width"))
Durham Goode
template: add pad function for padding output...
r20370
Yuya Nishihara
templater: port pad() to take keyword arguments...
r31887 text = evalstring(context, mapping, args['text'])
Durham Goode
template: add pad function for padding output...
r20370
Yuya Nishihara
templater: rename "right" argument of pad() function...
r29818 left = False
Durham Goode
template: add pad function for padding output...
r20370 fillchar = ' '
Yuya Nishihara
templater: port pad() to take keyword arguments...
r31887 if 'fillchar' in args:
fillchar = evalstring(context, mapping, args['fillchar'])
Yuya Nishihara
templater: make pad() strip color codes before computing width (issue5416)...
r31521 if len(color.stripeffects(fillchar)) != 1:
Yuya Nishihara
templater: reject bad fillchar argument passed to pad()...
r31519 # i18n: "pad" is a keyword
raise error.ParseError(_("pad() expects a single fill character"))
Yuya Nishihara
templater: port pad() to take keyword arguments...
r31887 if 'left' in args:
left = evalboolean(context, mapping, args['left'])
Durham Goode
template: add pad function for padding output...
r20370
Yuya Nishihara
templater: make pad() strip color codes before computing width (issue5416)...
r31521 fillwidth = width - encoding.colwidth(color.stripeffects(text))
Yuya Nishihara
templater: make pad() compute actual width...
r31520 if fillwidth <= 0:
return text
Yuya Nishihara
templater: rename "right" argument of pad() function...
r29818 if left:
Yuya Nishihara
templater: make pad() compute actual width...
r31520 return fillchar * fillwidth + text
Durham Goode
template: add pad function for padding output...
r20370 else:
Yuya Nishihara
templater: make pad() compute actual width...
r31520 return text + fillchar * fillwidth
Durham Goode
template: add pad function for padding output...
r20370
FUJIWARA Katsunori
templater: use templatefunc to mark a function as template function...
r28696 @templatefunc('indent(text, indentchars[, firstline])')
Ryan McElroy
templater: introduce indent function
r25489 def indent(context, mapping, args):
FUJIWARA Katsunori
templater: use templatefunc to mark a function as template function...
r28696 """Indents all non-empty lines
Ryan McElroy
templater: introduce indent function
r25489 with the characters given in the indentchars string. An optional
third parameter will override the indent for the first line only
if present."""
if not (2 <= len(args) <= 3):
# i18n: "indent" is a keyword
raise error.ParseError(_("indent() expects two or three arguments"))
Yuya Nishihara
templater: factor out thin helper that evaluates argument as string...
r28348 text = evalstring(context, mapping, args[0])
indent = evalstring(context, mapping, args[1])
Ryan McElroy
templater: introduce indent function
r25489
if len(args) == 3:
Yuya Nishihara
templater: factor out thin helper that evaluates argument as string...
r28348 firstline = evalstring(context, mapping, args[2])
Ryan McElroy
templater: introduce indent function
r25489 else:
firstline = indent
# the indent function doesn't indent the first line, so we do it here
return templatefilters.indent(firstline + text, indent)
FUJIWARA Katsunori
templater: use templatefunc to mark a function as template function...
r28696 @templatefunc('get(dict, key)')
Benoit Boissinot
templater: add get() function to access dict element (e.g. extra)
r18582 def get(context, mapping, args):
FUJIWARA Katsunori
templater: use templatefunc to mark a function as template function...
r28696 """Get an attribute/key from an object. Some keywords
Gregory Szorc
templater: add consistent docstrings to functions...
r24586 are complex types. This function allows you to obtain the value of an
timeless@mozdev.org
templater: fix get English
r26197 attribute on these types."""
Benoit Boissinot
templater: add get() function to access dict element (e.g. extra)
r18582 if len(args) != 2:
# i18n: "get" is a keyword
raise error.ParseError(_("get() expects two arguments"))
Yuya Nishihara
templater: fix get() to evaluate arguments eagerly...
r28331 dictarg = evalfuncarg(context, mapping, args[0])
Benoit Boissinot
templater: add get() function to access dict element (e.g. extra)
r18582 if not util.safehasattr(dictarg, 'get'):
# i18n: "get" is a keyword
raise error.ParseError(_("get() expects a dict as first argument"))
Yuya Nishihara
templater: fix get() to evaluate arguments eagerly...
r28331 key = evalfuncarg(context, mapping, args[1])
Yuya Nishihara
templater: make get(dict, key) return a single value...
r27892 return dictarg.get(key)
Benoit Boissinot
templater: add get() function to access dict element (e.g. extra)
r18582
FUJIWARA Katsunori
templater: use templatefunc to mark a function as template function...
r28696 @templatefunc('if(expr, then[, else])')
Matt Mackall
templater: add if/ifeq conditionals
r17636 def if_(context, mapping, args):
FUJIWARA Katsunori
templater: use templatefunc to mark a function as template function...
r28696 """Conditionally execute based on the result of
Gregory Szorc
templater: add consistent docstrings to functions...
r24586 an expression."""
Matt Mackall
templater: add if/ifeq conditionals
r17636 if not (2 <= len(args) <= 3):
FUJIWARA Katsunori
i18n: add "i18n" comment to error messages of template functions
r17890 # i18n: "if" is a keyword
Matt Mackall
templater: add if/ifeq conditionals
r17636 raise error.ParseError(_("if expects two or three arguments"))
Yuya Nishihara
templater: fix if() to not evaluate False as bool('False')...
r29816 test = evalboolean(context, mapping, args[0])
Matt Mackall
templater: add if/ifeq conditionals
r17636 if test:
Yuya Nishihara
templater: do not reevaluate rawstring as template (BC)...
r25597 yield args[1][0](context, mapping, args[1][1])
Matt Mackall
templater: add if/ifeq conditionals
r17636 elif len(args) == 3:
Yuya Nishihara
templater: do not reevaluate rawstring as template (BC)...
r25597 yield args[2][0](context, mapping, args[2][1])
Matt Mackall
templater: add if/ifeq conditionals
r17636
av6
templater: use "needle" and "haystack" as (meta-)variables for ifcontains()...
r30049 @templatefunc('ifcontains(needle, haystack, then[, else])')
Durham Goode
template: add ifcontains template function...
r20518 def ifcontains(context, mapping, args):
FUJIWARA Katsunori
templater: use templatefunc to mark a function as template function...
r28696 """Conditionally execute based
av6
templater: use "needle" and "haystack" as (meta-)variables for ifcontains()...
r30049 on whether the item "needle" is in "haystack"."""
Durham Goode
template: add ifcontains template function...
r20518 if not (3 <= len(args) <= 4):
# i18n: "ifcontains" is a keyword
raise error.ParseError(_("ifcontains expects three or four arguments"))
av6
templater: use "needle" and "haystack" as (meta-)variables for ifcontains()...
r30049 needle = evalstring(context, mapping, args[0])
haystack = evalfuncarg(context, mapping, args[1])
Durham Goode
template: add ifcontains template function...
r20518
av6
templater: use "needle" and "haystack" as (meta-)variables for ifcontains()...
r30049 if needle in haystack:
Yuya Nishihara
templater: do not reevaluate rawstring as template (BC)...
r25597 yield args[2][0](context, mapping, args[2][1])
Durham Goode
template: add ifcontains template function...
r20518 elif len(args) == 4:
Yuya Nishihara
templater: do not reevaluate rawstring as template (BC)...
r25597 yield args[3][0](context, mapping, args[3][1])
Durham Goode
template: add ifcontains template function...
r20518
FUJIWARA Katsunori
templater: use templatefunc to mark a function as template function...
r28696 @templatefunc('ifeq(expr1, expr2, then[, else])')
Matt Mackall
templater: add if/ifeq conditionals
r17636 def ifeq(context, mapping, args):
FUJIWARA Katsunori
templater: use templatefunc to mark a function as template function...
r28696 """Conditionally execute based on
Gregory Szorc
templater: add consistent docstrings to functions...
r24586 whether 2 items are equivalent."""
Matt Mackall
templater: add if/ifeq conditionals
r17636 if not (3 <= len(args) <= 4):
FUJIWARA Katsunori
i18n: add "i18n" comment to error messages of template functions
r17890 # i18n: "ifeq" is a keyword
Matt Mackall
templater: add if/ifeq conditionals
r17636 raise error.ParseError(_("ifeq expects three or four arguments"))
Yuya Nishihara
templater: factor out thin helper that evaluates argument as string...
r28348 test = evalstring(context, mapping, args[0])
match = evalstring(context, mapping, args[1])
Matt Mackall
templater: add if/ifeq conditionals
r17636 if test == match:
Yuya Nishihara
templater: do not reevaluate rawstring as template (BC)...
r25597 yield args[2][0](context, mapping, args[2][1])
Matt Mackall
templater: add if/ifeq conditionals
r17636 elif len(args) == 4:
Yuya Nishihara
templater: do not reevaluate rawstring as template (BC)...
r25597 yield args[3][0](context, mapping, args[3][1])
Matt Mackall
templater: add if/ifeq conditionals
r17636
FUJIWARA Katsunori
templater: use templatefunc to mark a function as template function...
r28696 @templatefunc('join(list, sep)')
Alexander Plavin
templater: sort functions alphabetically, as filters are
r19390 def join(context, mapping, args):
FUJIWARA Katsunori
templater: use templatefunc to mark a function as template function...
r28696 """Join items in a list with a delimiter."""
Alexander Plavin
templater: sort functions alphabetically, as filters are
r19390 if not (1 <= len(args) <= 2):
# i18n: "join" is a keyword
raise error.ParseError(_("join expects one or two arguments"))
joinset = args[0][0](context, mapping, args[0][1])
Yuya Nishihara
templater: make _hybrid not callable to avoid conflicting semantics...
r27891 if util.safehasattr(joinset, 'itermaps'):
Alexander Plavin
templater: sort functions alphabetically, as filters are
r19390 jf = joinset.joinfmt
Yuya Nishihara
templater: make _hybrid not callable to avoid conflicting semantics...
r27891 joinset = [jf(x) for x in joinset.itermaps()]
Alexander Plavin
templater: sort functions alphabetically, as filters are
r19390
joiner = " "
if len(args) > 1:
Yuya Nishihara
templater: factor out thin helper that evaluates argument as string...
r28348 joiner = evalstring(context, mapping, args[1])
Alexander Plavin
templater: sort functions alphabetically, as filters are
r19390
first = True
for x in joinset:
if first:
first = False
else:
yield joiner
yield x
FUJIWARA Katsunori
templater: use templatefunc to mark a function as template function...
r28696 @templatefunc('label(label, expr)')
Sean Farley
templater: add no-op template function 'label'
r18289 def label(context, mapping, args):
FUJIWARA Katsunori
templater: use templatefunc to mark a function as template function...
r28696 """Apply a label to generated content. Content with
Gregory Szorc
templater: add consistent docstrings to functions...
r24586 a label applied can result in additional post-processing, such as
automatic colorization."""
Sean Farley
templater: add no-op template function 'label'
r18289 if len(args) != 2:
# i18n: "label" is a keyword
raise error.ParseError(_("label expects two arguments"))
Yuya Nishihara
templater: make label() just fail if ui object isn't available...
r28462 ui = mapping['ui']
Yuya Nishihara
templater: move label() function from color extension...
r28374 thing = evalstring(context, mapping, args[1])
# preserve unknown symbol as literal so effects like 'red', 'bold',
# etc. don't need to be quoted
label = evalstringliteral(context, mapping, args[0])
Kostia Balytskyi
formatter: make labels work with templated output...
r28384 return ui.label(thing, label)
Sean Farley
templater: add no-op template function 'label'
r18289
FUJIWARA Katsunori
templater: use templatefunc to mark a function as template function...
r28696 @templatefunc('latesttag([pattern])')
Matt Harbison
templater: introduce {latesttag()} function to match a pattern (issue4184)...
r26485 def latesttag(context, mapping, args):
FUJIWARA Katsunori
templater: use templatefunc to mark a function as template function...
r28696 """The global tags matching the given pattern on the
Matt Harbison
templatekw: clarify the result of {latesttag} when no tag exists...
r31850 most recent globally tagged ancestor of this changeset.
If no such tags exist, the "{tag}" template resolves to
the string "null"."""
Matt Harbison
templater: introduce {latesttag()} function to match a pattern (issue4184)...
r26485 if len(args) > 1:
# i18n: "latesttag" is a keyword
raise error.ParseError(_("latesttag expects at most one argument"))
pattern = None
if len(args) == 1:
Yuya Nishihara
templater: factor out thin helper that evaluates argument as string...
r28348 pattern = evalstring(context, mapping, args[0])
Matt Harbison
templater: introduce {latesttag()} function to match a pattern (issue4184)...
r26485
return templatekw.showlatesttags(pattern, **mapping)
FUJIWARA Katsunori
templater: use templatefunc to mark a function as template function...
r28696 @templatefunc('localdate(date[, tz])')
Yuya Nishihara
templater: port localdate filter to a function...
r26127 def localdate(context, mapping, args):
FUJIWARA Katsunori
templater: use templatefunc to mark a function as template function...
r28696 """Converts a date to the specified timezone.
Yuya Nishihara
templater: add optional timezone argument to localdate()...
r26128 The default is local date."""
if not (1 <= len(args) <= 2):
Yuya Nishihara
templater: port localdate filter to a function...
r26127 # i18n: "localdate" is a keyword
Yuya Nishihara
templater: add optional timezone argument to localdate()...
r26128 raise error.ParseError(_("localdate expects one or two arguments"))
Yuya Nishihara
templater: port localdate filter to a function...
r26127
date = evalfuncarg(context, mapping, args[0])
try:
date = util.parsedate(date)
except AttributeError: # not str nor date tuple
# i18n: "localdate" is a keyword
raise error.ParseError(_("localdate expects a date information"))
Yuya Nishihara
templater: add optional timezone argument to localdate()...
r26128 if len(args) >= 2:
tzoffset = None
tz = evalfuncarg(context, mapping, args[1])
if isinstance(tz, str):
Matt Mackall
date: refactor timezone parsing...
r29636 tzoffset, remainder = util.parsetimezone(tz)
if remainder:
tzoffset = None
Yuya Nishihara
templater: add optional timezone argument to localdate()...
r26128 if tzoffset is None:
try:
tzoffset = int(tz)
except (TypeError, ValueError):
# i18n: "localdate" is a keyword
raise error.ParseError(_("localdate expects a timezone"))
else:
tzoffset = util.makedate()[1]
Yuya Nishihara
templater: port localdate filter to a function...
r26127 return (date[0], tzoffset)
Simon Farnsworth
templater: provide arithmetic operations on integers...
r30115 @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"))
Simon Farnsworth
templater: handle division by zero in arithmetic...
r30116 func = lambda a, b: a % b
return runarithmetic(context, mapping, (func, args[0], args[1]))
Simon Farnsworth
templater: provide arithmetic operations on integers...
r30115
Yuya Nishihara
templater: add relpath() to convert repo path to relative path (issue5394)...
r30083 @templatefunc('relpath(path)')
def relpath(context, mapping, args):
"""Convert a repository-absolute path into a filesystem path relative to
the current working directory."""
if len(args) != 1:
# i18n: "relpath" is a keyword
raise error.ParseError(_("relpath expects one argument"))
repo = mapping['ctx'].repo()
path = evalstring(context, mapping, args[0])
return repo.pathto(path)
FUJIWARA Katsunori
templater: use templatefunc to mark a function as template function...
r28696 @templatefunc('revset(query[, formatargs...])')
Durham Goode
template: add revset() template function...
r20519 def revset(context, mapping, args):
FUJIWARA Katsunori
templater: use templatefunc to mark a function as template function...
r28696 """Execute a revision set query. See
Gregory Szorc
templater: add consistent docstrings to functions...
r24586 :hg:`help revset`."""
Durham Goode
template: add revset() template function...
r20519 if not len(args) > 0:
# i18n: "revset" is a keyword
raise error.ParseError(_("revset expects one or more arguments"))
Yuya Nishihara
templater: factor out thin helper that evaluates argument as string...
r28348 raw = evalstring(context, mapping, args[0])
Durham Goode
template: add revset() template function...
r20519 ctx = mapping['ctx']
Matt Harbison
templater: replace 'ctx._repo' with 'ctx.repo()'
r24337 repo = ctx.repo()
Durham Goode
template: add revset() template function...
r20519
FUJIWARA Katsunori
templater: enable alias predicates to be used in "revset()" function...
r22304 def query(expr):
m = revsetmod.match(repo.ui, expr)
Yuya Nishihara
revset: make match function initiate query from full set by default...
r24114 return m(repo)
FUJIWARA Katsunori
templater: enable alias predicates to be used in "revset()" function...
r22304
Durham Goode
template: add revset() template function...
r20519 if len(args) > 1:
Yuya Nishihara
templater: fix revset() to evaluate format arguments eagerly...
r28333 formatargs = [evalfuncarg(context, mapping, a) for a in args[1:]]
Yuya Nishihara
revset: split language services to revsetlang module (API)...
r31024 revs = query(revsetlang.formatspec(raw, *formatargs))
Yuya Nishihara
templater: factor out type conversion of revset() result...
r28178 revs = list(revs)
Durham Goode
template: add revset() template function...
r20519 else:
revsetcache = mapping['cache'].setdefault("revsetcache", {})
if raw in revsetcache:
revs = revsetcache[raw]
else:
FUJIWARA Katsunori
templater: enable alias predicates to be used in "revset()" function...
r22304 revs = query(raw)
Yuya Nishihara
templater: factor out type conversion of revset() result...
r28178 revs = list(revs)
Durham Goode
template: add revset() template function...
r20519 revsetcache[raw] = revs
Yuya Nishihara
templater: switch ctx of list expression to rev of revset() (BC)...
r26234 return templatekw.showrevslist("revision", revs, **mapping)
Durham Goode
template: add revset() template function...
r20519
FUJIWARA Katsunori
templater: use templatefunc to mark a function as template function...
r28696 @templatefunc('rstdoc(text, style)')
Dan Villiom Podlaski Christiansen
hgweb: generate HTML documentation...
r18747 def rstdoc(context, mapping, args):
Mads Kiilerich
spelling: fixes of non-dictionary words
r30332 """Format reStructuredText."""
Dan Villiom Podlaski Christiansen
hgweb: generate HTML documentation...
r18747 if len(args) != 2:
# i18n: "rstdoc" is a keyword
raise error.ParseError(_("rstdoc expects two arguments"))
Yuya Nishihara
templater: factor out thin helper that evaluates argument as string...
r28348 text = evalstring(context, mapping, args[0])
style = evalstring(context, mapping, args[1])
Dan Villiom Podlaski Christiansen
hgweb: generate HTML documentation...
r18747
Alexander Plavin
hgweb: make help verbose again (issue3899)...
r19079 return minirst.format(text, style=style, keep=['verbose'])
Dan Villiom Podlaski Christiansen
hgweb: generate HTML documentation...
r18747
Yuya Nishihara
templater: add support for keyword arguments...
r31886 @templatefunc('separate(sep, args)', argspec='sep *args')
Martin von Zweigbergk
templater: add separate() template function...
r29085 def separate(context, mapping, args):
"""Add a separator between non-empty arguments."""
Yuya Nishihara
templater: add support for keyword arguments...
r31886 if 'sep' not in args:
Martin von Zweigbergk
templater: add separate() template function...
r29085 # i18n: "separate" is a keyword
raise error.ParseError(_("separate expects at least one argument"))
Yuya Nishihara
templater: add support for keyword arguments...
r31886 sep = evalstring(context, mapping, args['sep'])
Martin von Zweigbergk
templater: add separate() template function...
r29085 first = True
Yuya Nishihara
templater: add support for keyword arguments...
r31886 for arg in args['args']:
Martin von Zweigbergk
templater: add separate() template function...
r29085 argstr = evalstring(context, mapping, arg)
if not argstr:
continue
if first:
first = False
else:
yield sep
yield argstr
FUJIWARA Katsunori
templater: use templatefunc to mark a function as template function...
r28696 @templatefunc('shortest(node, minlength=4)')
Durham Goode
template: add shortest(node) template function...
r20369 def shortest(context, mapping, args):
FUJIWARA Katsunori
templater: use templatefunc to mark a function as template function...
r28696 """Obtain the shortest representation of
Gregory Szorc
templater: add consistent docstrings to functions...
r24586 a node."""
Durham Goode
template: add shortest(node) template function...
r20369 if not (1 <= len(args) <= 2):
FUJIWARA Katsunori
i18n: add i18n comment to error messages of template functions
r23112 # i18n: "shortest" is a keyword
Durham Goode
template: add shortest(node) template function...
r20369 raise error.ParseError(_("shortest() expects one or two arguments"))
Yuya Nishihara
templater: factor out thin helper that evaluates argument as string...
r28348 node = evalstring(context, mapping, args[0])
Durham Goode
template: add shortest(node) template function...
r20369
minlength = 4
if len(args) > 1:
Yuya Nishihara
templater: fix shortest() to evaluate int argument and handle error
r28346 minlength = evalinteger(context, mapping, args[1],
# i18n: "shortest" is a keyword
_("shortest() expects an integer minlength"))
Durham Goode
template: add shortest(node) template function...
r20369
Yuya Nishihara
templater: use unfiltered changelog to calculate shortest() at constant time...
r30232 # _partialmatch() of filtered changelog could take O(len(repo)) time,
# which would be unacceptably slow. so we look for hash collision in
# unfiltered space, which means some hashes may be slightly longer.
cl = mapping['ctx']._repo.unfiltered().changelog
Durham Goode
template: add shortest(node) template function...
r20369 def isvalid(test):
try:
Yuya Nishihara
templater: do not use index.partialmatch() directly to calculate shortest()...
r30231 if cl._partialmatch(test) is None:
return False
Durham Goode
template: fix shortest(node) function in pure mercurial...
r20371
Durham Goode
template: add shortest(node) template function...
r20369 try:
Sean Farley
templater: shorten pure integers...
r20539 i = int(test)
# if we are a pure int, then starting with zero will not be
# confused as a rev; or, obviously, if the int is larger than
# the value of the tip rev
if test[0] == '0' or i > len(cl):
return True
Durham Goode
template: add shortest(node) template function...
r20369 return False
except ValueError:
return True
except error.RevlogError:
return False
shortest = node
startlength = max(6, minlength)
length = startlength
while True:
test = node[:length]
if isvalid(test):
shortest = test
if length == minlength or length > startlength:
return shortest
length -= 1
else:
length += 1
if len(shortest) <= length:
return shortest
FUJIWARA Katsunori
templater: use templatefunc to mark a function as template function...
r28696 @templatefunc('strip(text[, chars])')
Alexander Plavin
templater: add strip function with chars as an extra argument...
r19330 def strip(context, mapping, args):
FUJIWARA Katsunori
templater: use templatefunc to mark a function as template function...
r28696 """Strip characters from a string. By default,
Yuya Nishihara
templatefilters: remove redundant 'date' and 'strip' filters...
r26106 strips all leading and trailing whitespace."""
Alexander Plavin
templater: add strip function with chars as an extra argument...
r19330 if not (1 <= len(args) <= 2):
FUJIWARA Katsunori
i18n: add i18n comment to error messages of template functions
r23112 # i18n: "strip" is a keyword
Alexander Plavin
templater: add strip function with chars as an extra argument...
r19330 raise error.ParseError(_("strip expects one or two arguments"))
Yuya Nishihara
templater: factor out thin helper that evaluates argument as string...
r28348 text = evalstring(context, mapping, args[0])
Alexander Plavin
templater: add strip function with chars as an extra argument...
r19330 if len(args) == 2:
Yuya Nishihara
templater: factor out thin helper that evaluates argument as string...
r28348 chars = evalstring(context, mapping, args[1])
Alexander Plavin
templater: add strip function with chars as an extra argument...
r19330 return text.strip(chars)
return text.strip()
FUJIWARA Katsunori
templater: use templatefunc to mark a function as template function...
r28696 @templatefunc('sub(pattern, replacement, expression)')
Alexander Plavin
templater: sort functions alphabetically, as filters are
r19390 def sub(context, mapping, args):
FUJIWARA Katsunori
templater: use templatefunc to mark a function as template function...
r28696 """Perform text substitution
Gregory Szorc
templater: add consistent docstrings to functions...
r24586 using regular expressions."""
Alexander Plavin
templater: sort functions alphabetically, as filters are
r19390 if len(args) != 3:
# i18n: "sub" is a keyword
raise error.ParseError(_("sub expects three arguments"))
Yuya Nishihara
templater: factor out thin helper that evaluates argument as string...
r28348 pat = evalstring(context, mapping, args[0])
rpl = evalstring(context, mapping, args[1])
src = evalstring(context, mapping, args[2])
Yuya Nishihara
templater: catch regexp error at sub() function...
r26188 try:
patre = re.compile(pat)
except re.error:
# i18n: "sub" is a keyword
raise error.ParseError(_("sub got an invalid pattern: %s") % pat)
try:
yield patre.sub(rpl, src)
except re.error:
# i18n: "sub" is a keyword
raise error.ParseError(_("sub got an invalid replacement: %s") % rpl)
Alexander Plavin
templater: sort functions alphabetically, as filters are
r19390
FUJIWARA Katsunori
templater: use templatefunc to mark a function as template function...
r28696 @templatefunc('startswith(pattern, text)')
Ryan McElroy
templater: introduce startswith function...
r21821 def startswith(context, mapping, args):
FUJIWARA Katsunori
templater: use templatefunc to mark a function as template function...
r28696 """Returns the value from the "text" argument
Gregory Szorc
templater: add consistent docstrings to functions...
r24586 if it begins with the content from the "pattern" argument."""
Ryan McElroy
templater: introduce startswith function...
r21821 if len(args) != 2:
FUJIWARA Katsunori
templater: add i18n comments to error messages of newly added functions...
r21960 # i18n: "startswith" is a keyword
Ryan McElroy
templater: introduce startswith function...
r21821 raise error.ParseError(_("startswith expects two arguments"))
Yuya Nishihara
templater: factor out thin helper that evaluates argument as string...
r28348 patn = evalstring(context, mapping, args[0])
text = evalstring(context, mapping, args[1])
Ryan McElroy
templater: introduce startswith function...
r21821 if text.startswith(patn):
return text
return ''
FUJIWARA Katsunori
templater: use templatefunc to mark a function as template function...
r28696 @templatefunc('word(number, text[, separator])')
Ryan McElroy
templater: introduce word function...
r21846 def word(context, mapping, args):
FUJIWARA Katsunori
templater: use templatefunc to mark a function as template function...
r28696 """Return the nth word from a string."""
Ryan McElroy
templater: introduce word function...
r21846 if not (2 <= len(args) <= 3):
FUJIWARA Katsunori
templater: add i18n comments to error messages of newly added functions...
r21960 # i18n: "word" is a keyword
Ryan McElroy
templater: introduce word function...
r21846 raise error.ParseError(_("word expects two or three arguments, got %d")
% len(args))
Yuya Nishihara
templater: factor out function that evaluates argument as integer...
r28343 num = evalinteger(context, mapping, args[0],
# i18n: "word" is a keyword
_("word expects an integer index"))
Yuya Nishihara
templater: factor out thin helper that evaluates argument as string...
r28348 text = evalstring(context, mapping, args[1])
Ryan McElroy
templater: introduce word function...
r21846 if len(args) == 3:
Yuya Nishihara
templater: factor out thin helper that evaluates argument as string...
r28348 splitter = evalstring(context, mapping, args[2])
Ryan McElroy
templater: introduce word function...
r21846 else:
splitter = None
tokens = text.split(splitter)
Matt Harbison
templater: protect word() from crashing on out of range negative value...
r26502 if num >= len(tokens) or num < -len(tokens):
Ryan McElroy
templater: introduce word function...
r21846 return ''
else:
return tokens[num]
Yuya Nishihara
templater: switch methods table on compileexp() of func args and inner expr...
r25001 # methods to interpret function arguments or inner expressions (e.g. {_(x)})
exprmethods = {
Yuya Nishihara
templater: tokenize decimal integer literal (issue4638) (BC)...
r25002 "integer": lambda e, c: (runinteger, e[1]),
Matt Mackall
templater: use the parser.py parser to extend the templater syntax
r13176 "string": lambda e, c: (runstring, e[1]),
"symbol": lambda e, c: (runsymbol, e[1]),
Yuya Nishihara
templater: take any string literals as template, but not for rawstring (BC)...
r25596 "template": buildtemplate,
Yuya Nishihara
templater: switch methods table on compileexp() of func args and inner expr...
r25001 "group": lambda e, c: compileexp(e[1], c, exprmethods),
Matt Mackall
templater: use the parser.py parser to extend the templater syntax
r13176 # ".": buildmember,
"|": buildfilter,
"%": buildmap,
"func": buildfunc,
Yuya Nishihara
templater: add parsing rule for key-value pair...
r31885 "keyvalue": buildkeyvaluepair,
Simon Farnsworth
templater: provide arithmetic operations on integers...
r30115 "+": 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),
Matt Mackall
templater: use the parser.py parser to extend the templater syntax
r13176 }
Yuya Nishihara
templater: switch methods table on compileexp() of func args and inner expr...
r25001 # methods to interpret top-level template (e.g. {x}, {x|_}, {x % "y"})
methods = exprmethods.copy()
Yuya Nishihara
templater: tokenize decimal integer literal (issue4638) (BC)...
r25002 methods["integer"] = exprmethods["symbol"] # '{1}' as variable
Yuya Nishihara
templater: switch methods table on compileexp() of func args and inner expr...
r25001
Yuya Nishihara
templater: add parsing and expansion rules to process "templatealias" section...
r28912 class _aliasrules(parser.basealiasrules):
"""Parsing and expansion rule set of template aliases"""
_section = _('template alias')
_parse = staticmethod(_parseexpr)
@staticmethod
def _trygetfunc(tree):
"""Return (name, args) if tree is func(...) or ...|filter; otherwise
None"""
if tree[0] == 'func' and tree[1][0] == 'symbol':
return tree[1][1], getlist(tree[2])
if tree[0] == '|' and tree[2][0] == 'symbol':
return tree[2][1], [tree[1]]
def expandaliases(tree, aliases):
"""Return new tree of aliases are expanded"""
aliasmap = _aliasrules.buildmap(aliases)
return _aliasrules.expand(aliasmap, tree)
Matt Mackall
templater: use the parser.py parser to extend the templater syntax
r13176 # template engine
Vadim Gelfer
use safer string parser for template engine.
r1901
Dirkjan Ochtman
templater: provide the standard template filters by default
r8360 stringify = templatefilters.stringify
Brendan Cully
Allow hgweb to search for templates in more than one path....
r7107
Matt Mackall
templater: use recursive flattening...
r10850 def _flatten(thing):
'''yield a single stream from a possibly nested set of iterators'''
Yuya Nishihara
templater: remove __iter__() from _hybrid, resolve it explicitly...
r31880 thing = templatekw.unwraphybrid(thing)
Matt Mackall
templater: use recursive flattening...
r10850 if isinstance(thing, str):
yield thing
Yuya Nishihara
templater: make it clearer that _flatten() omits None
r29815 elif thing is None:
pass
Augie Fackler
globally: use safehasattr(x, '__iter__') instead of hasattr(x, '__iter__')
r14944 elif not util.safehasattr(thing, '__iter__'):
Yuya Nishihara
templater: make it clearer that _flatten() omits None
r29815 yield str(thing)
Matt Mackall
templater: raise nested functions
r10852 else:
Matt Mackall
templater: use recursive flattening...
r10850 for i in thing:
Yuya Nishihara
templater: remove __iter__() from _hybrid, resolve it explicitly...
r31880 i = templatekw.unwraphybrid(i)
Matt Mackall
templater: use recursive flattening...
r10850 if isinstance(i, str):
yield i
Yuya Nishihara
templater: make it clearer that _flatten() omits None
r29815 elif i is None:
pass
Augie Fackler
globally: use safehasattr(x, '__iter__') instead of hasattr(x, '__iter__')
r14944 elif not util.safehasattr(i, '__iter__'):
Yuya Nishihara
templater: make it clearer that _flatten() omits None
r29815 yield str(i)
else:
Matt Mackall
templater: use recursive flattening...
r10850 for j in _flatten(i):
yield j
Yuya Nishihara
templater: rename parsestring() to unquotestring() (API)...
r24988 def unquotestring(s):
Yuya Nishihara
templater: relax unquotestring() to fall back to bare string...
r28630 '''unwrap quotes if any; otherwise returns unmodified string'''
Yuya Nishihara
templater: do not strip non-quote characters from template config...
r28687 if len(s) < 2 or s[0] not in "'\"" or s[0] != s[-1]:
Yuya Nishihara
templater: relax unquotestring() to fall back to bare string...
r28630 return s
Yuya Nishihara
templater: remove workaround for escaped quoted string in quoted template...
r25696 return s[1:-1]
Vadim Gelfer
move hgweb template code out to templater
r1896
Dirkjan Ochtman
templater: separate template management and actual string processing
r8218 class engine(object):
Vadim Gelfer
add doc comments to template code.
r1909 '''template expansion engine.
template expansion works like this. a map file contains key=value
pairs. if value is quoted, it is treated as string. otherwise, it
is treated as name of template file.
templater is asked to expand a key in map. it looks up key, and
TK Soh
minor typo fix in templater's docstring
r4334 looks for strings like this: {foo}. it expands {foo} by looking up
Vadim Gelfer
add doc comments to template code.
r1909 foo in map, and substituting it. expansion is recursive: it stops
when there is no more {foo} to replace.
expansion also allows formatting and filtering.
format uses key to expand each item in list. syntax is
{key%format}.
filter uses function to transform value. syntax is
{key|filter1|filter2|...}.'''
Yuya Nishihara
templater: load and expand aliases by template engine (API) (issue4842)...
r28957 def __init__(self, loader, filters=None, defaults=None, aliases=()):
Matt Mackall
templater: privatize class variables
r10848 self._loader = loader
Pierre-Yves David
templater: remove a mutable default argument...
r26330 if filters is None:
filters = {}
Matt Mackall
templater: privatize class variables
r10848 self._filters = filters
Pierre-Yves David
templater: remove a mutable default argument...
r26331 if defaults is None:
defaults = {}
Matt Mackall
templater: privatize class variables
r10848 self._defaults = defaults
Yuya Nishihara
templater: load and expand aliases by template engine (API) (issue4842)...
r28957 self._aliasmap = _aliasrules.buildmap(aliases)
Yuya Nishihara
templater: lift parsed and compiled templates to generic data types...
r28545 self._cache = {} # key: (func, data)
Dirkjan Ochtman
templater: separate template management and actual string processing
r8218
Matt Mackall
templater: use the parser.py parser to extend the templater syntax
r13176 def _load(self, t):
'''load, parse, and cache a template'''
if t not in self._cache:
Yuya Nishihara
templater: abort if infinite recursion detected while compiling...
r27940 # put poison to cut recursion while compiling 't'
Yuya Nishihara
templater: lift parsed and compiled templates to generic data types...
r28545 self._cache[t] = (_runrecursivesymbol, t)
Yuya Nishihara
templater: abort if infinite recursion detected while compiling...
r27940 try:
Yuya Nishihara
templater: inline compiletemplate() function into engine...
r28956 x = parse(self._loader(t))
Yuya Nishihara
templater: load and expand aliases by template engine (API) (issue4842)...
r28957 if self._aliasmap:
x = _aliasrules.expand(self._aliasmap, x)
Yuya Nishihara
templater: inline compiletemplate() function into engine...
r28956 self._cache[t] = compileexp(x, self, methods)
Yuya Nishihara
templater: abort if infinite recursion detected while compiling...
r27940 except: # re-raises
del self._cache[t]
raise
Matt Mackall
templater: use the parser.py parser to extend the templater syntax
r13176 return self._cache[t]
Matt Mackall
templater: drop raw method
r10853 def process(self, t, mapping):
'''Perform expansion. t is name of map element to expand.
mapping contains added elements for use during expansion. Is a
generator.'''
Yuya Nishihara
templater: lift parsed and compiled templates to generic data types...
r28545 func, data = self._load(t)
return _flatten(func(self, mapping, data))
Dirkjan Ochtman
templater: separate template management and actual string processing
r8218
Dirkjan Ochtman
templater: make the templating engine pluggable to some extent
r8361 engines = {'default': engine}
Iulian Stana
templater: show the style list when I try to use a wrong one...
r19125 def stylelist():
Mads Kiilerich
templater: introduce templatepaths for getting paths searched for templates...
r22634 paths = templatepaths()
Simon Heimberg
templater: selecting a style with no templates does not crash (issue4140)...
r20312 if not paths:
return _('no templates found, try `hg debuginstall` for more info')
timeless
cleanup: remove superfluous space after space after equals (python)
r27637 dirlist = os.listdir(paths[0])
Iulian Stana
templater: show the style list when I try to use a wrong one...
r19125 stylelist = []
for file in dirlist:
split = file.split(".")
timeless
templater: ignore orig/rej files...
r28403 if split[-1] in ('orig', 'rej'):
continue
Iulian Stana
templater: show the style list when I try to use a wrong one...
r19125 if split[0] == "map-cmdline":
stylelist.append(split[1])
Augie Fackler
templater: fix output instability from gsoc patches
r19127 return ", ".join(sorted(stylelist))
Iulian Stana
templater: show the style list when I try to use a wrong one...
r19125
Yuya Nishihara
templater: extract function that loads template map file...
r28953 def _readmapfile(mapfile):
"""Load template elements from the given map file"""
if not os.path.exists(mapfile):
raise error.Abort(_("style '%s' not found") % mapfile,
hint=_("available styles: %s") % stylelist())
base = os.path.dirname(mapfile)
conf = config.config(includepaths=templatepaths())
conf.read(mapfile)
cache = {}
tmap = {}
for key, val in conf[''].items():
if not val:
raise error.ParseError(_('missing value'), conf.source('', key))
if val[0] in "'\"":
if val[0] != val[-1]:
raise error.ParseError(_('unmatched quotes'),
conf.source('', key))
cache[key] = unquotestring(val)
Matt Mackall
templater: add inheritance support to style maps...
r29812 elif key == "__base__":
# treat as a pointer to a base class for this style
path = util.normpath(os.path.join(base, val))
Matt Mackall
templater: add template path to __base__ search...
r29848
# fallback check in template paths
if not os.path.exists(path):
for p in templatepaths():
p2 = util.normpath(os.path.join(p, val))
if os.path.isfile(p2):
path = p2
break
p3 = util.normpath(os.path.join(p2, "map"))
if os.path.isfile(p3):
path = p3
break
Matt Mackall
templater: add inheritance support to style maps...
r29812 bcache, btmap = _readmapfile(path)
for k in bcache:
if k not in cache:
cache[k] = bcache[k]
for k in btmap:
if k not in tmap:
tmap[k] = btmap[k]
Yuya Nishihara
templater: extract function that loads template map file...
r28953 else:
val = 'default', val
if ':' in val[1]:
val = val[1].split(':', 1)
tmap[key] = val[0], os.path.join(base, val[1])
return cache, tmap
Pierre-Yves David
error: get Abort from 'error' instead of 'util'...
r26587 class TemplateNotFound(error.Abort):
Alexander Plavin
templater: support using templates with non-standard names from map file...
r19770 pass
Dirkjan Ochtman
templater: separate template management and actual string processing
r8218 class templater(object):
Yuya Nishihara
templater: load and expand aliases by template engine (API) (issue4842)...
r28957 def __init__(self, filters=None, defaults=None, cache=None, aliases=(),
Brendan Cully
templater: return data in increasing chunk sizes...
r7396 minchunk=1024, maxchunk=65536):
Vadim Gelfer
add doc comments to template code.
r1909 '''set up template engine.
filters is dict of functions. each transforms a value into another.
Yuya Nishihara
templater: load and expand aliases by template engine (API) (issue4842)...
r28957 defaults is dict of default map definitions.
aliases is list of alias (name, replacement) pairs.
'''
Pierre-Yves David
templater: remove a mutable default argument...
r26332 if filters is None:
filters = {}
Pierre-Yves David
templater: remove a mutable default argument...
r26333 if defaults is None:
defaults = {}
Pierre-Yves David
templater: remove a mutable default argument...
r26334 if cache is None:
cache = {}
Shun-ichi Goto
Duplicate cache when creating templater.
r1975 self.cache = cache.copy()
Vadim Gelfer
move hgweb template code out to templater
r1896 self.map = {}
Dirkjan Ochtman
templater: provide the standard template filters by default
r8360 self.filters = templatefilters.filters.copy()
self.filters.update(filters)
Vadim Gelfer
fix template bug that made hgweb break....
r1964 self.defaults = defaults
Yuya Nishihara
templater: load and expand aliases by template engine (API) (issue4842)...
r28957 self._aliases = aliases
Brendan Cully
templater: return data in increasing chunk sizes...
r7396 self.minchunk, self.maxchunk = minchunk, maxchunk
Matt Mackall
templater: clarify engine caching
r13187 self.ecache = {}
Vadim Gelfer
move hgweb template code out to templater
r1896
Yuya Nishihara
templater: separate function to create templater from map file (API)...
r28954 @classmethod
def frommapfile(cls, mapfile, filters=None, defaults=None, cache=None,
minchunk=1024, maxchunk=65536):
"""Create templater from the specified map file"""
Yuya Nishihara
templater: load and expand aliases by template engine (API) (issue4842)...
r28957 t = cls(filters, defaults, cache, [], minchunk, maxchunk)
Yuya Nishihara
templater: extract function that loads template map file...
r28953 cache, tmap = _readmapfile(mapfile)
Yuya Nishihara
templater: separate function to create templater from map file (API)...
r28954 t.cache.update(cache)
t.map = tmap
return t
Vadim Gelfer
move hgweb template code out to templater
r1896
Vadim Gelfer
many small changes to templater....
r1899 def __contains__(self, key):
Matt Mackall
templater: simplify cache and remove filter argument in __call__
r3637 return key in self.cache or key in self.map
Vadim Gelfer
many small changes to templater....
r1899
Dirkjan Ochtman
templater: separate template management and actual string processing
r8218 def load(self, t):
Dirkjan Ochtman
templater: make a template a string-only iterator
r6783 '''Get the template for the given template name. Use a local cache.'''
Brodie Rao
cleanup: "not x in y" -> "x not in y"
r16686 if t not in self.cache:
Vadim Gelfer
improve template errors when something is wrong.
r1905 try:
Dan Villiom Podlaski Christiansen
prevent transient leaks of file handle by using new helper functions...
r14168 self.cache[t] = util.readfile(self.map[t][1])
Gregory Szorc
global: mass rewrite to use modern exception syntax...
r25660 except KeyError as inst:
Alexander Plavin
templater: support using templates with non-standard names from map file...
r19770 raise TemplateNotFound(_('"%s" not in template map') %
inst.args[0])
Gregory Szorc
global: mass rewrite to use modern exception syntax...
r25660 except IOError as inst:
Vadim Gelfer
improve template errors when something is wrong.
r1905 raise IOError(inst.args[0], _('template file %s: %s') %
Dirkjan Ochtman
templater: make the templating engine pluggable to some extent
r8361 (self.map[t][1], inst.args[1]))
Dirkjan Ochtman
templater: make a template a string-only iterator
r6783 return self.cache[t]
Vadim Gelfer
move hgweb template code out to templater
r1896
Matt Mackall
templater: map -> mapping
r10847 def __call__(self, t, **mapping):
Dirkjan Ochtman
templater: make the templating engine pluggable to some extent
r8361 ttype = t in self.map and self.map[t][0] or 'default'
Matt Mackall
templater: clarify engine caching
r13187 if ttype not in self.ecache:
Yuya Nishihara
templater: give better error message for invalid engine type...
r28831 try:
ecls = engines[ttype]
except KeyError:
raise error.Abort(_('invalid template engine: %s') % ttype)
Yuya Nishihara
templater: load and expand aliases by template engine (API) (issue4842)...
r28957 self.ecache[ttype] = ecls(self.load, self.filters, self.defaults,
self._aliases)
Matt Mackall
templater: clarify engine caching
r13187 proc = self.ecache[ttype]
Dirkjan Ochtman
templater: make the templating engine pluggable to some extent
r8361
Matt Mackall
templater: map -> mapping
r10847 stream = proc.process(t, mapping)
Brendan Cully
templater: return data in increasing chunk sizes...
r7396 if self.minchunk:
stream = util.increasingchunks(stream, min=self.minchunk,
max=self.maxchunk)
return stream
Dirkjan Ochtman
kill some trailing spaces
r7434
Mads Kiilerich
templater: introduce templatepaths for getting paths searched for templates...
r22634 def templatepaths():
'''return locations used for template files.'''
Mads Kiilerich
templater: don't search randomly for templates - trust util.datapath...
r22636 pathsrel = ['templates']
Mads Kiilerich
templater: inline global 'path' list in templatepaths
r22635 paths = [os.path.normpath(os.path.join(util.datapath, f))
for f in pathsrel]
return [p for p in paths if os.path.isdir(p)]
Vadim Gelfer
move changeset_templater into templater module.
r2189
Mads Kiilerich
templater: introduce templatepaths for getting paths searched for templates...
r22634 def templatepath(name):
'''return location of template file. returns None if not found.'''
for p in templatepaths():
f = os.path.join(p, name)
if os.path.exists(f):
return f
return None
Vadim Gelfer
move changeset_templater into templater module.
r2189
Dirkjan Ochtman
hgweb: don't choke when an inexistent style is requested (issue1901)
r9842 def stylemap(styles, paths=None):
Dirkjan Ochtman
templater: move stylemap function from hgweb to templater
r7966 """Return path to mapfile for a given style.
Searches mapfile in the following locations:
1. templatepath/style/map
2. templatepath/map-style
3. templatepath/map
"""
if paths is None:
Mads Kiilerich
templater: introduce templatepaths for getting paths searched for templates...
r22634 paths = templatepaths()
Dirkjan Ochtman
templater: move stylemap function from hgweb to templater
r7966 elif isinstance(paths, str):
Dirkjan Ochtman
templater: fix little problem from stylemap() changes
r8223 paths = [paths]
Dirkjan Ochtman
templater: move stylemap function from hgweb to templater
r7966
Dirkjan Ochtman
hgweb: don't choke when an inexistent style is requested (issue1901)
r9842 if isinstance(styles, str):
styles = [styles]
for style in styles:
Yuya Nishihara
hgweb: prevent loading style map from directories other than specified paths...
r24296 # only plain name is allowed to honor template paths
if (not style
or style in (os.curdir, os.pardir)
Pulkit Goyal
py3: replace os.sep with pycompat.ossep (part 3 of 4)
r30615 or pycompat.ossep in style
Pulkit Goyal
py3: replace os.altsep with pycompat.altsep...
r30625 or pycompat.osaltsep and pycompat.osaltsep in style):
Dirkjan Ochtman
hgweb: don't choke when an inexistent style is requested (issue1901)
r9842 continue
locations = [os.path.join(style, 'map'), 'map-' + style]
locations.append('map')
for path in paths:
for location in locations:
mapfile = os.path.join(path, location)
if os.path.isfile(mapfile):
return style, mapfile
Dirkjan Ochtman
templater: move stylemap function from hgweb to templater
r7966
raise RuntimeError("No hgweb templates found in %r" % paths)
Yuya Nishihara
templater: tell hggettext to collect help of template functions
r24601
FUJIWARA Katsunori
registrar: add templatefunc to mark a function as template function (API)...
r28695 def loadfunction(ui, extname, registrarobj):
"""Load template function from specified registrarobj
"""
for name, func in registrarobj._table.iteritems():
funcs[name] = func
Yuya Nishihara
templater: tell hggettext to collect help of template functions
r24601 # tell hggettext to extract docstrings from these functions:
i18nfunctions = funcs.values()