##// END OF EJS Templates
namespaces: let namespaces override singlenode() definition...
namespaces: let namespaces override singlenode() definition Some namespaces have multiple nodes per name (meaning that their namemap() returns multiple nodes). One such namespace is the "topics" namespace (from the evolve repo). We also have our own internal namespace at Google (for review units) that has multiple nodes per name. These namespaces may not want to use the default "pick highest revnum" resolution that we currently use when resolving a name to a single node. As an example, they may decide that `hg co <name>` should check out a commit that's last in some sense even if an earlier commit had just been amended and thus had a higher revnum [1]. This patch gives the namespace the option to continue to return multiple nodes and to override how the best node is picked. Allowing namespaces to override that may also be useful as an optimization (it may be cheaper for the namespace to find just that node). I have been arguing (in D3715) for using all the nodes returned from namemap() when resolving the symbol to a revset, so e.g. `hg log -r stable` would resolve to *all* nodes on stable, not just the one with the highest revnum (except that I don't actually think we should change it for the branch namespace because of BC). Most people seem opposed to that. If we decide not to do it, I think we can deprecate the namemap() function in favor of the new singlenode() (I find it weird to have namespaces, like the branch namespace, where namemap() isn't nodemap()'s inverse). I therefore think this patch makes sense regardless of what we decide on that issue. [1] Actually, even the branch namespace would have wanted to override singlenode() if it had supported multiple nodes. That's because closes branch heads are mostly ignored, so "hg co default" will not check out the highest-revnum node if that's a closed head. Differential Revision: https://phab.mercurial-scm.org/D3852

File last commit:

r38459:3813c6b7 @51 default
r38505:4c068365 @58 default
Show More
templater.py
985 lines | 34.6 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
Yuya Nishihara
templater: add brief doc about internal data types...
r37032 """Slightly complicated template engine for commands and hgweb
This module provides low-level interface to the template engine. See the
formatter and cmdutil modules if you are looking for high-level functions
such as ``cmdutil.rendertemplate(ctx, tmpl)``.
Internal Data Types
-------------------
Template keywords and functions take a dictionary of current symbols and
resources (a "mapping") and return result. Inputs and outputs must be one
of the following data types:
bytes
a byte string, which is generally a human-readable text in local encoding.
generator
a lazily-evaluated byte string, which is a possibly nested generator of
values of any printable types, and will be folded by ``stringify()``
or ``flatten()``.
None
sometimes represents an empty value, which can be stringified to ''.
True, False, int, float
can be stringified as such.
Yuya Nishihara
templater: add wrapped types for pure non-list/dict values...
r38228 wrappedbytes, wrappedvalue
a wrapper for the above printable types.
Yuya Nishihara
templater: introduce a wrapper for date tuple (BC)...
r38304 date
represents a (unixtime, offset) tuple.
Yuya Nishihara
templater: add brief doc about internal data types...
r37032
hybrid
represents a list/dict of printable values, which can also be converted
to mappings by % operator.
Yuya Nishihara
templater: rename mappable to hybriditem as it is the primary use case...
r38302 hybriditem
Yuya Nishihara
templater: add brief doc about internal data types...
r37032 represents a scalar printable value, also supports % operator.
Yuya Nishihara
templater: add class representing a nested mappings...
r37417
mappinggenerator, mappinglist
represents mappings (i.e. a list of dicts), which may have default
output format.
Yuya Nishihara
templater: wrap result of '%' operation so it never looks like a thunk...
r37517
mappedgenerator
a lazily-evaluated list of byte strings, which is e.g. a result of %
operation.
Yuya Nishihara
templater: add brief doc about internal data types...
r37032 """
Yuya Nishihara
doctest: use print_function and convert bytes to unicode where needed
r34139 from __future__ import absolute_import, print_function
Gregory Szorc
templater: use absolute_import
r25985
Yuya Nishihara
templater: introduce resourcemapper class...
r37091 import abc
Gregory Szorc
templater: use absolute_import
r25985 import os
from .i18n import _
from . import (
config,
Yuya Nishihara
templater: make pad() compute actual width...
r31520 encoding,
Gregory Szorc
templater: use absolute_import
r25985 error,
parser,
Pulkit Goyal
py3: replace os.sep with pycompat.ossep (part 3 of 4)
r30615 pycompat,
Gregory Szorc
templater: use absolute_import
r25985 templatefilters,
Yuya Nishihara
templater: split template functions to new module...
r36940 templatefuncs,
Yuya Nishihara
templater: extract template evaluation utility to new module...
r36931 templateutil,
Gregory Szorc
templater: use absolute_import
r25985 util,
)
Yuya Nishihara
stringutil: bulk-replace call sites to point to new module...
r37102 from .utils import (
stringutil,
)
Yuya Nishihara
templater: move specialized exception types to top...
r36461
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: add dot operator to easily access a sub item...
r34536 ".": (18, None, None, (".", 18), None),
Yuya Nishihara
templater: adjust binding strength of '%' and '|' operators (BC)...
r34330 "%": (15, None, None, ("%", 15), None),
Yuya Nishihara
templater: adjust binding strengths to make room for key-value operator...
r31884 "|": (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
Pulkit Goyal
py3: use pycompat.bytestr instead of bytes
r32154 program = pycompat.bytestr(program)
Matt Mackall
templater: use the parser.py parser to extend the templater syntax
r13176 while pos < end:
c = program[pos]
if c.isspace(): # skip inter-token whitespace
pass
Yuya Nishihara
templater: add dot operator to easily access a sub item...
r34536 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
py3: fix type of string literals in templater.tokenize()...
r36564 elif (c == '\\' and program[pos:pos + 2] in (br"\'", br'\"')
or c == 'r' and program[pos:pos + 3] in (br"r\'", br'r\"')):
Yuya Nishihara
templater: parse \"...\" as string for 2.9.2-3.4 compatibility (issue4733)...
r25676 # 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: fix position of terminator character in error message...
r36709 yield ('end', None, pos)
Yuya Nishihara
templater: check existence of closing brace of template string
r25782 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"""
Yuya Nishihara
doctest: bulk-replace string literals with b'' for Python 3...
r34133 >>> _parsetemplate(b'foo{bar}"baz', 0, 12)
Yuya Nishihara
templater: introduce one-pass parsing of nested template strings...
r25783 ([('string', 'foo'), ('symbol', 'bar'), ('string', '"baz')], 12)
Yuya Nishihara
doctest: bulk-replace string literals with b'' for Python 3...
r34133 >>> _parsetemplate(b'foo{bar}"baz', 0, 12, quote=b'"')
Yuya Nishihara
templater: introduce one-pass parsing of nested template strings...
r25783 ([('string', 'foo'), ('symbol', 'bar')], 9)
Yuya Nishihara
doctest: bulk-replace string literals with b'' for Python 3...
r34133 >>> _parsetemplate(b'foo"{bar}', 0, 9, quote=b'"')
Yuya Nishihara
templater: introduce one-pass parsing of nested template strings...
r25783 ([('string', 'foo')], 4)
Yuya Nishihara
doctest: bulk-replace string literals with b'' for Python 3...
r34133 >>> _parsetemplate(br'foo\"bar"baz', 0, 12, quote=b'"')
Yuya Nishihara
templater: introduce one-pass parsing of nested template strings...
r25783 ([('string', 'foo"'), ('string', 'bar')], 9)
Yuya Nishihara
doctest: bulk-replace string literals with b'' for Python 3...
r34133 >>> _parsetemplate(br'foo\\"bar', 0, 10, quote=b'"')
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: extract function scanning template string...
r36258 for typ, val, pos in _scantemplate(tmpl, start, stop, quote):
if typ == 'string':
parsed.append((typ, val))
elif typ == 'template':
parsed.append(val)
elif typ == 'end':
return parsed, pos
else:
raise error.ProgrammingError('unexpected type: %s' % typ)
raise error.ProgrammingError('unterminated scanning of template')
Yuya Nishihara
templater: add option to parse template string just like raw string literal...
r36527 def scantemplate(tmpl, raw=False):
r"""Scan (type, start, end) positions of outermost elements in template
If raw=True, a backslash is not taken as an escape character just like
r'' string in Python. Note that this is different from r'' literal in
template in that no template fragment can appear in r'', e.g. r'{foo}'
is a literal '{foo}', but ('{foo}', raw=True) is a template expression
'foo'.
Yuya Nishihara
templater: add function to help substituting patterns in template string...
r36259
>>> list(scantemplate(b'foo{bar}"baz'))
[('string', 0, 3), ('template', 3, 8), ('string', 8, 12)]
>>> list(scantemplate(b'outer{"inner"}outer'))
[('string', 0, 5), ('template', 5, 14), ('string', 14, 19)]
>>> list(scantemplate(b'foo\\{escaped}'))
[('string', 0, 5), ('string', 5, 13)]
Yuya Nishihara
templater: add option to parse template string just like raw string literal...
r36527 >>> list(scantemplate(b'foo\\{escaped}', raw=True))
[('string', 0, 4), ('template', 4, 13)]
Yuya Nishihara
templater: add function to help substituting patterns in template string...
r36259 """
last = None
Yuya Nishihara
templater: add option to parse template string just like raw string literal...
r36527 for typ, val, pos in _scantemplate(tmpl, 0, len(tmpl), raw=raw):
Yuya Nishihara
templater: add function to help substituting patterns in template string...
r36259 if last:
yield last + (pos,)
if typ == 'end':
return
else:
last = (typ, pos)
raise error.ProgrammingError('unterminated scanning of template')
Yuya Nishihara
templater: add option to parse template string just like raw string literal...
r36527 def _scantemplate(tmpl, start, stop, quote='', raw=False):
Yuya Nishihara
templater: extract function scanning template string...
r36258 """Parse template string into chunks of strings and template expressions"""
Yuya Nishihara
templater: introduce one-pass parsing of nested template strings...
r25783 sepchars = '{' + quote
Yuya Nishihara
templater: add option to parse template string just like raw string literal...
r36527 unescape = [parser.unescapestr, pycompat.identity][raw]
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)
Ryan McElroy
templater: add hint to template parse errors to help locate issues...
r36687 try:
while pos < stop:
Pulkit Goyal
py3: bytestr() bytes to get bytechar while iterating on it...
r38096 n = min((tmpl.find(c, pos, stop)
for c in pycompat.bytestr(sepchars)),
Ryan McElroy
templater: add hint to template parse errors to help locate issues...
r36687 key=lambda n: (n < 0, n))
if n < 0:
yield ('string', unescape(tmpl[pos:stop]), pos)
pos = stop
break
c = tmpl[n:n + 1]
bs = 0 # count leading backslashes
if not raw:
bs = (n - pos) - len(tmpl[pos:n].rstrip('\\'))
if bs % 2 == 1:
# escaped (e.g. '\{', '\\\{', but not '\\{')
yield ('string', unescape(tmpl[pos:n - 1]) + c, pos)
pos = n + 1
continue
if n > pos:
yield ('string', unescape(tmpl[pos:n]), pos)
if c == quote:
yield ('end', None, n + 1)
return
Matt Mackall
templater: use the parser.py parser to extend the templater syntax
r13176
Ryan McElroy
templater: add hint to template parse errors to help locate issues...
r36687 parseres, pos = p.parse(tokenize(tmpl, n + 1, stop, '}'))
Yuya Nishihara
templater: fix position of terminator character in error message...
r36709 if not tmpl.startswith('}', pos):
Ryan McElroy
templater: add hint to template parse errors to help locate issues...
r36687 raise error.ParseError(_("invalid token"), pos)
yield ('template', parseres, n)
Yuya Nishihara
templater: fix position of terminator character in error message...
r36709 pos += 1
Yuya Nishihara
templater: introduce one-pass parsing of nested template strings...
r25783
Ryan McElroy
templater: add hint to template parse errors to help locate issues...
r36687 if quote:
raise error.ParseError(_("unterminated string"), start)
except error.ParseError as inst:
if len(inst.args) > 1: # has location
loc = inst.args[1]
Ryan McElroy
templater: provide hint for multi-line templates with parse errors...
r36688 # Offset the caret location by the number of newlines before the
# location of the error, since we will replace one-char newlines
# with the two-char literal r'\n'.
offset = tmpl[:loc].count('\n')
tmpl = tmpl.replace('\n', br'\n')
# We want the caret to point to the place in the template that
# failed to parse, but in a hint we get a open paren at the
Yuya Nishihara
templater: fix position of terminator character in error message...
r36709 # start. Therefore, we print "loc + 1" spaces (instead of "loc")
Ryan McElroy
templater: provide hint for multi-line templates with parse errors...
r36688 # to line up the caret with the location of the error.
Yuya Nishihara
templater: fix position of terminator character in error message...
r36709 inst.hint = (tmpl + '\n'
+ ' ' * (loc + 1 + offset) + '^ ' + _('here'))
Ryan McElroy
templater: add hint to template parse errors to help locate issues...
r36687 raise
Yuya Nishihara
templater: extract function scanning template string...
r36258 yield ('end', None, 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):
Yuya Nishihara
doctest: use print_function and convert bytes to unicode where needed
r34139 ... print(pycompat.sysstr(prettyformat(_unnesttemplatelist(tree))))
Yuya Nishihara
doctest: bulk-replace string literals with b'' for Python 3...
r34133 >>> f((b'template', []))
Yuya Nishihara
parser: stabilize output of prettyformat() by using byte-safe repr()...
r34075 (string '')
Yuya Nishihara
doctest: bulk-replace string literals with b'' for Python 3...
r34133 >>> f((b'template', [(b'string', b'foo')]))
Yuya Nishihara
parser: stabilize output of prettyformat() by using byte-safe repr()...
r34075 (string 'foo')
Yuya Nishihara
doctest: bulk-replace string literals with b'' for Python 3...
r34133 >>> f((b'template', [(b'string', b'foo'), (b'symbol', b'rev')]))
Yuya Nishihara
templater: expand list of parsed templates to template node...
r28547 (template
Yuya Nishihara
parser: stabilize output of prettyformat() by using byte-safe repr()...
r34075 (string 'foo')
(symbol 'rev'))
Yuya Nishihara
doctest: bulk-replace string literals with b'' for Python 3...
r34133 >>> f((b'template', [(b'symbol', b'rev')])) # template(rev) -> str
Yuya Nishihara
templater: expand list of parsed templates to template node...
r28547 (template
Yuya Nishihara
parser: stabilize output of prettyformat() by using byte-safe repr()...
r34075 (symbol 'rev'))
Yuya Nishihara
doctest: bulk-replace string literals with b'' for Python 3...
r34133 >>> f((b'template', [(b'template', [(b'string', b'foo')])]))
Yuya Nishihara
parser: stabilize output of prettyformat() by using byte-safe repr()...
r34075 (string 'foo')
Yuya Nishihara
templater: expand list of parsed templates to template node...
r28547 """
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
Yuya Nishihara
doctest: bulk-replace string literals with b'' for Python 3...
r34133 >>> _parseexpr(b'"foo"')
Yuya Nishihara
templater: add function to parse whole string as template expression...
r28911 ('string', 'foo')
Yuya Nishihara
doctest: bulk-replace string literals with b'' for Python 3...
r34133 >>> _parseexpr(b'foo(bar)')
Yuya Nishihara
templater: add function to parse whole string as template expression...
r28911 ('func', ('symbol', 'foo'), ('symbol', 'bar'))
Yuya Nishihara
doctest: bulk-replace string literals with b'' for Python 3...
r34133 >>> _parseexpr(b'foo(')
Yuya Nishihara
templater: add function to parse whole string as template expression...
r28911 Traceback (most recent call last):
...
ParseError: ('not a prefix: end', 4)
Yuya Nishihara
doctest: bulk-replace string literals with b'' for Python 3...
r34133 >>> _parseexpr(b'"foo" "bar"')
Yuya Nishihara
templater: add function to parse whole string as template expression...
r28911 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"""
Yuya Nishihara
templater: fix crash by empty group expression...
r35762 if not exp:
raise error.ParseError(_("missing argument"))
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: 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: 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: extract template evaluation utility to new module...
r36931 return (templateutil.runtemplate, ctmpl)
Yuya Nishihara
templater: move runtemplate function out of buildmap/runmap pair...
r25595
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: extract template evaluation utility to new module...
r36931 return (templateutil.runfilter, (arg, filt))
Yuya Nishihara
templater: move function table to the "context" object...
r36930 if n in context._funcs:
f = context._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 buildmap(exp, context):
Yuya Nishihara
templater: do not destructure operands in buildmap()...
r34326 darg = compileexp(exp[1], context, methods)
targ = gettemplate(exp[2], context)
Yuya Nishihara
templater: extract template evaluation utility to new module...
r36931 return (templateutil.runmap, (darg, targ))
Matt Mackall
templater: use the parser.py parser to extend the templater syntax
r13176
Yuya Nishihara
templater: add dot operator to easily access a sub item...
r34536 def buildmember(exp, context):
darg = compileexp(exp[1], context, methods)
memb = getsymbol(exp[2])
Yuya Nishihara
templater: extract template evaluation utility to new module...
r36931 return (templateutil.runmember, (darg, memb))
Yuya Nishihara
templater: add dot operator to easily access a sub item...
r34536
Simon Farnsworth
templater: provide arithmetic operations on integers...
r30115 def buildnegate(exp, context):
arg = compileexp(exp[1], context, exprmethods)
Yuya Nishihara
templater: extract template evaluation utility to new module...
r36931 return (templateutil.runnegate, arg)
Simon Farnsworth
templater: provide arithmetic operations on integers...
r30115
def buildarithmetic(exp, context, func):
left = compileexp(exp[1], context, exprmethods)
right = compileexp(exp[2], context, exprmethods)
Yuya Nishihara
templater: extract template evaluation utility to new module...
r36931 return (templateutil.runarithmetic, (func, left, right))
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])
Yuya Nishihara
templater: move function table to the "context" object...
r36930 if n in context._funcs:
f = context._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: extract template evaluation utility to new module...
r36931 return (templateutil.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
Yuya Nishihara
templater: fix invalid reference of runsymbol in doctest...
r37002 >>> context = engine(lambda t: (templateutil.runsymbol, t))
Yuya Nishihara
parser: extend buildargsdict() to support arbitrary number of **kwargs...
r31921 >>> def fargs(expr, argspec):
... x = _parseexpr(expr)
... n = getsymbol(x[1])
... return _buildfuncargs(x[2], context, exprmethods, n, argspec)
Yuya Nishihara
doctest: coerce dict.keys() to list...
r34141 >>> list(fargs(b'a(l=1, k=2)', b'k l m').keys())
Yuya Nishihara
parser: preserve order of keyword arguments...
r31922 ['l', 'k']
Yuya Nishihara
doctest: bulk-replace string literals with b'' for Python 3...
r34133 >>> args = fargs(b'a(opts=1, k=2)', b'**opts')
Yuya Nishihara
doctest: coerce dict.keys() to list...
r34141 >>> list(args.keys()), list(args[b'opts'].keys())
Yuya Nishihara
parser: preserve order of keyword arguments...
r31922 (['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"))
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: extract template evaluation utility to new module...
r36931 "integer": lambda e, c: (templateutil.runinteger, e[1]),
"string": lambda e, c: (templateutil.runstring, e[1]),
"symbol": lambda e, c: (templateutil.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),
Yuya Nishihara
templater: add dot operator to easily access a sub item...
r34536 ".": buildmember,
Matt Mackall
templater: use the parser.py parser to extend the templater syntax
r13176 "|": 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
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
Yuya Nishihara
templater: introduce resourcemapper class...
r37091 class resourcemapper(object):
"""Mapper of internal template resources"""
__metaclass__ = abc.ABCMeta
@abc.abstractmethod
Yuya Nishihara
templater: drop symbols which should be overridden by new 'ctx' (issue5612)...
r37093 def availablekeys(self, context, mapping):
"""Return a set of available resource keys based on the given mapping"""
@abc.abstractmethod
Yuya Nishihara
templater: introduce resourcemapper class...
r37091 def knownkeys(self):
"""Return a set of supported resource keys"""
@abc.abstractmethod
def lookup(self, context, mapping, key):
"""Return a resource for the key if available; otherwise None"""
Yuya Nishihara
templater: add hook point to populate additional mapping items...
r37120 @abc.abstractmethod
def populatemap(self, context, origmapping, newmapping):
"""Return a dict of additional mapping items which should be paired
with the given new mapping"""
Yuya Nishihara
templater: introduce resourcemapper class...
r37091 class nullresourcemapper(resourcemapper):
Yuya Nishihara
templater: drop symbols which should be overridden by new 'ctx' (issue5612)...
r37093 def availablekeys(self, context, mapping):
return set()
Yuya Nishihara
templater: introduce resourcemapper class...
r37091 def knownkeys(self):
return set()
def lookup(self, context, mapping, key):
return None
Yuya Nishihara
templater: add hook point to populate additional mapping items...
r37120 def populatemap(self, context, origmapping, newmapping):
return {}
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: parse template string to tree by templater class...
r38373 def __init__(self, loader, filters=None, defaults=None, resources=None):
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
Yuya Nishihara
templater: split template functions to new module...
r36940 self._funcs = templatefuncs.funcs # make this a parameter if needed
Pierre-Yves David
templater: remove a mutable default argument...
r26331 if defaults is None:
defaults = {}
Yuya Nishihara
templater: keep default resources per template engine (API)...
r35484 if resources is None:
Yuya Nishihara
templater: introduce resourcemapper class...
r37091 resources = nullresourcemapper()
Matt Mackall
templater: privatize class variables
r10848 self._defaults = defaults
Yuya Nishihara
templater: keep default resources per template engine (API)...
r35484 self._resources = resources
Yuya Nishihara
templater: lift parsed and compiled templates to generic data types...
r28545 self._cache = {} # key: (func, data)
Yuya Nishihara
templater: add function that expands internal literal templates...
r37416 self._tmplcache = {} # literal template: (func, data)
Dirkjan Ochtman
templater: separate template management and actual string processing
r8218
Yuya Nishihara
templater: factor out function to create mapping dict for nested evaluation...
r37092 def overlaymap(self, origmapping, newmapping):
"""Create combined mapping from the original mapping and partial
mapping to override the original"""
Yuya Nishihara
templater: drop symbols which should be overridden by new 'ctx' (issue5612)...
r37093 # do not copy symbols which overrides the defaults depending on
# new resources, so the defaults will be re-evaluated (issue5612)
knownres = self._resources.knownkeys()
newres = self._resources.availablekeys(self, newmapping)
mapping = {k: v for k, v in origmapping.iteritems()
if (k in knownres # not a symbol per self.symbol()
or newres.isdisjoint(self._defaultrequires(k)))}
Yuya Nishihara
templater: factor out function to create mapping dict for nested evaluation...
r37092 mapping.update(newmapping)
Yuya Nishihara
templater: add hook point to populate additional mapping items...
r37120 mapping.update(
self._resources.populatemap(self, origmapping, newmapping))
Yuya Nishihara
templater: factor out function to create mapping dict for nested evaluation...
r37092 return mapping
Yuya Nishihara
templater: drop symbols which should be overridden by new 'ctx' (issue5612)...
r37093 def _defaultrequires(self, key):
"""Resource keys required by the specified default symbol function"""
v = self._defaults.get(key)
if v is None or not callable(v):
return ()
return getattr(v, '_requires', ())
Yuya Nishihara
templater: look up mapping table through template engine...
r35483 def symbol(self, mapping, key):
"""Resolve symbol to value or function; None if nothing found"""
Yuya Nishihara
templater: look up symbols/resources as if they were separated (issue5699)...
r35486 v = None
Yuya Nishihara
templater: introduce resourcemapper class...
r37091 if key not in self._resources.knownkeys():
Yuya Nishihara
templater: look up symbols/resources as if they were separated (issue5699)...
r35486 v = mapping.get(key)
Yuya Nishihara
templater: look up mapping table through template engine...
r35483 if v is None:
v = self._defaults.get(key)
return v
Yuya Nishihara
templater: add public interface returning a set of resource keys...
r37519 def availableresourcekeys(self, mapping):
"""Return a set of available resource keys based on the given mapping"""
return self._resources.availablekeys(self, mapping)
def knownresourcekeys(self):
"""Return a set of supported resource keys"""
return self._resources.knownkeys()
Yuya Nishihara
templater: look up mapping table through template engine...
r35483 def resource(self, mapping, key):
"""Return internal data (e.g. cache) used for keyword/function
evaluation"""
Yuya Nishihara
templater: introduce resourcemapper class...
r37091 v = self._resources.lookup(self, mapping, key)
Yuya Nishihara
templater: keep default resources per template engine (API)...
r35484 if v is None:
Yuya Nishihara
templater: extract template evaluation utility to new module...
r36931 raise templateutil.ResourceUnavailable(
_('template resource not available: %s') % key)
Yuya Nishihara
templater: keep default resources per template engine (API)...
r35484 return v
Yuya Nishihara
templater: look up mapping table through template engine...
r35483
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: parse template string to tree by templater class...
r38373 x = self._loader(t)
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 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]
Yuya Nishihara
templater: add function that expands internal literal templates...
r37416 def _parse(self, tmpl):
"""Parse and cache a literal template"""
if tmpl not in self._tmplcache:
x = parse(tmpl)
self._tmplcache[tmpl] = compileexp(x, self, methods)
return self._tmplcache[tmpl]
Yuya Nishihara
templater: add context.preload(t) to test if the specified template exists...
r37085 def preload(self, t):
"""Load, parse, and cache the specified template if available"""
try:
self._load(t)
return True
except templateutil.TemplateNotFound:
return False
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)
Yuya Nishihara
templater: add function that expands internal literal templates...
r37416 return self._expand(func, data, mapping)
def expand(self, tmpl, mapping):
"""Perform expansion over a literal template
No user aliases will be expanded since this is supposed to be called
with an internal template string.
"""
func, data = self._parse(tmpl)
return self._expand(func, data, mapping)
def _expand(self, func, data, mapping):
Yuya Nishihara
templater: add hook point to populate additional mapping items...
r37120 # populate additional items only if they don't exist in the given
# mapping. this is slightly different from overlaymap() because the
# initial 'revcache' may contain pre-computed items.
extramapping = self._resources.populatemap(self, {}, mapping)
if extramapping:
extramapping.update(mapping)
mapping = extramapping
Yuya Nishihara
templater: pass (context, mapping) down to unwraphybrid()...
r37290 return templateutil.flatten(self, mapping, func(self, mapping, data))
Dirkjan Ochtman
templater: separate template management and actual string processing
r8218
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())
Yuya Nishihara
templater: load template fragments from [templates] section in map file...
r34715 conf.read(mapfile, remap={'': 'templates'})
Yuya Nishihara
templater: extract function that loads template map file...
r28953
cache = {}
tmap = {}
Yuya Nishihara
templater: load aliases from [templatealias] section in map file...
r34716 aliases = []
Yuya Nishihara
templater: simplify merge of __base__ dicts by reading it first
r34713
Yuya Nishihara
templater: load template fragments from [templates] section in map file...
r34715 val = conf.get('templates', '__base__')
Yuya Nishihara
templater: simplify merge of __base__ dicts by reading it first
r34713 if val and val[0] not in "'\"":
# treat as a pointer to a base class for this style
path = util.normpath(os.path.join(base, val))
# 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
Yuya Nishihara
templater: load aliases from [templatealias] section in map file...
r34716 cache, tmap, aliases = _readmapfile(path)
Yuya Nishihara
templater: simplify merge of __base__ dicts by reading it first
r34713
Yuya Nishihara
templater: load template fragments from [templates] section in map file...
r34715 for key, val in conf['templates'].items():
Yuya Nishihara
templater: extract function that loads template map file...
r28953 if not val:
Yuya Nishihara
templater: load template fragments from [templates] section in map file...
r34715 raise error.ParseError(_('missing value'),
conf.source('templates', key))
Yuya Nishihara
templater: extract function that loads template map file...
r28953 if val[0] in "'\"":
if val[0] != val[-1]:
raise error.ParseError(_('unmatched quotes'),
Yuya Nishihara
templater: load template fragments from [templates] section in map file...
r34715 conf.source('templates', key))
Yuya Nishihara
templater: extract function that loads template map file...
r28953 cache[key] = unquotestring(val)
Yuya Nishihara
templater: simplify merge of __base__ dicts by reading it first
r34713 elif key != '__base__':
Yuya Nishihara
templater: drop extension point of engine classes (API)...
r38372 tmap[key] = os.path.join(base, val)
Yuya Nishihara
templater: load aliases from [templatealias] section in map file...
r34716 aliases.extend(conf['templatealias'].items())
return cache, tmap, aliases
Yuya Nishihara
templater: extract function that loads template map file...
r28953
Yuya Nishihara
templater: extract template loader to separate class...
r38457 class loader(object):
"""Load template fragments optionally from a map file"""
Yuya Nishihara
templater: rewrite docstring of templater.__init__()...
r35497
Yuya Nishihara
templater: extract template loader to separate class...
r38457 def __init__(self, cache, aliases):
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()
Yuya Nishihara
templater: mark most attributes as private
r38370 self._map = {}
Yuya Nishihara
templater: parse template string to tree by templater class...
r38373 self._aliasmap = _aliasrules.buildmap(aliases)
Vadim Gelfer
move hgweb template code out to templater
r1896
Vadim Gelfer
many small changes to templater....
r1899 def __contains__(self, key):
Yuya Nishihara
templater: mark most attributes as private
r38370 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):
Yuya Nishihara
templater: parse template string to tree by templater class...
r38373 """Get parsed tree 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:
Yuya Nishihara
templater: drop extension point of engine classes (API)...
r38372 self.cache[t] = util.readfile(self._map[t])
Gregory Szorc
global: mass rewrite to use modern exception syntax...
r25660 except KeyError as inst:
Yuya Nishihara
templater: extract template evaluation utility to new module...
r36931 raise templateutil.TemplateNotFound(
_('"%s" not in template map') % inst.args[0])
Gregory Szorc
global: mass rewrite to use modern exception syntax...
r25660 except IOError as inst:
Yuya Nishihara
py3: move between bytes and unicode when re-raising IOError...
r36518 reason = (_('template file %s: %s')
Yuya Nishihara
templater: drop extension point of engine classes (API)...
r38372 % (self._map[t],
Yuya Nishihara
stringutil: bulk-replace call sites to point to new module...
r37102 stringutil.forcebytestr(inst.args[1])))
Yuya Nishihara
py3: move between bytes and unicode when re-raising IOError...
r36518 raise IOError(inst.args[0], encoding.strfromlocal(reason))
Yuya Nishihara
templater: parse template string to tree by templater class...
r38373 return self._parse(self.cache[t])
def _parse(self, tmpl):
x = parse(tmpl)
if self._aliasmap:
x = _aliasrules.expand(self._aliasmap, x)
return x
Vadim Gelfer
move hgweb template code out to templater
r1896
Yuya Nishihara
templater: add function to look up symbols used in template...
r38374 def _findsymbolsused(self, tree, syms):
if not tree:
return
op = tree[0]
if op == 'symbol':
s = tree[1]
if s in syms[0]:
return # avoid recursion: s -> cache[s] -> s
syms[0].add(s)
if s in self.cache or s in self._map:
# s may be a reference for named template
self._findsymbolsused(self.load(s), syms)
return
if op in {'integer', 'string'}:
return
# '{arg|func}' == '{func(arg)}'
if op == '|':
syms[1].add(getsymbol(tree[2]))
self._findsymbolsused(tree[1], syms)
return
if op == 'func':
syms[1].add(getsymbol(tree[1]))
self._findsymbolsused(tree[2], syms)
return
for x in tree[1:]:
self._findsymbolsused(x, syms)
Yuya Nishihara
templater: extract template loader to separate class...
r38457 def symbolsused(self, t):
"""Look up (keywords, filters/functions) referenced from the name
template 't'
This may load additional templates from the map file.
"""
syms = (set(), set())
self._findsymbolsused(self.load(t), syms)
return syms
class templater(object):
def __init__(self, filters=None, defaults=None, resources=None,
cache=None, aliases=(), minchunk=1024, maxchunk=65536):
"""Create template engine optionally with preloaded template fragments
- ``filters``: a dict of functions to transform a value into another.
- ``defaults``: a dict of symbol values/functions; may be overridden
by a ``mapping`` dict.
- ``resources``: a resourcemapper object to look up internal data
(e.g. cache), inaccessible from user template.
- ``cache``: a dict of preloaded template fragments.
- ``aliases``: a list of alias (name, replacement) pairs.
self.cache may be updated later to register additional template
fragments.
"""
Yuya Nishihara
templater: remove redundant member variables from templater class...
r38459 allfilters = templatefilters.filters.copy()
if filters:
allfilters.update(filters)
Yuya Nishihara
templater: extract template loader to separate class...
r38457 self._loader = loader(cache, aliases)
Yuya Nishihara
templater: remove redundant member variables from templater class...
r38459 self._proc = engine(self._loader.load, allfilters, defaults, resources)
Yuya Nishihara
templater: extract template loader to separate class...
r38457 self._minchunk, self._maxchunk = minchunk, maxchunk
@classmethod
def frommapfile(cls, mapfile, filters=None, defaults=None, resources=None,
cache=None, minchunk=1024, maxchunk=65536):
"""Create templater from the specified map file"""
t = cls(filters, defaults, resources, cache, [], minchunk, maxchunk)
cache, tmap, aliases = _readmapfile(mapfile)
t._loader.cache.update(cache)
t._loader._map = tmap
t._loader._aliasmap = _aliasrules.buildmap(aliases)
return t
def __contains__(self, key):
return key in self._loader
@property
def cache(self):
return self._loader.cache
Yuya Nishihara
templater: remove redundant member variables from templater class...
r38459 # for highlight extension to insert one-time 'colorize' filter
@property
def _filters(self):
return self._proc._filters
@property
def defaults(self):
return self._proc._defaults
Yuya Nishihara
templater: extract template loader to separate class...
r38457 def load(self, t):
"""Get parsed tree for the given template name. Use a local cache."""
return self._loader.load(t)
Yuya Nishihara
templater: add function to look up symbols used in template...
r38374 def symbolsuseddefault(self):
"""Look up (keywords, filters/functions) referenced from the default
unnamed template
This may load additional templates from the map file.
"""
return self.symbolsused('')
def symbolsused(self, t):
"""Look up (keywords, filters/functions) referenced from the name
template 't'
This may load additional templates from the map file.
"""
Yuya Nishihara
templater: extract template loader to separate class...
r38457 return self._loader.symbolsused(t)
Yuya Nishihara
templater: add function to look up symbols used in template...
r38374
Yuya Nishihara
templater: rename .render(mapping) to .renderdefault(mapping) (API)...
r37003 def renderdefault(self, mapping):
Yuya Nishihara
templater: add simple interface for unnamed template (API)...
r32873 """Render the default unnamed template and return result as string"""
Yuya Nishihara
templater: factor out helper that renders named template as string...
r37004 return self.render('', mapping)
def render(self, t, mapping):
"""Render the specified named template and return result as string"""
Yuya Nishihara
templater: do not use stringify() to concatenate flattened template output
r37176 return b''.join(self.generate(t, mapping))
Yuya Nishihara
templater: add simple interface for unnamed template (API)...
r32873
Yuya Nishihara
templater: use named function to expand template against mapping dict (API)...
r37037 def generate(self, t, mapping):
"""Return a generator that renders the specified named template and
yields chunks"""
Yuya Nishihara
templater: resurrect cache of engine instance...
r38458 stream = self._proc.process(t, mapping)
Yuya Nishihara
templater: mark most attributes as private
r38370 if self._minchunk:
stream = util.increasingchunks(stream, min=self._minchunk,
max=self._maxchunk)
Brendan Cully
templater: return data in increasing chunk sizes...
r7396 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()
Yuya Nishihara
py3: replace type 'str' by 'bytes' in templater.py
r36568 elif isinstance(paths, bytes):
Dirkjan Ochtman
templater: fix little problem from stylemap() changes
r8223 paths = [paths]
Dirkjan Ochtman
templater: move stylemap function from hgweb to templater
r7966
Yuya Nishihara
py3: replace type 'str' by 'bytes' in templater.py
r36568 if isinstance(styles, bytes):
Dirkjan Ochtman
hgweb: don't choke when an inexistent style is requested (issue1901)
r9842 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
Yuya Nishihara
py3: make os.curdir a bytes
r36666 or style in (pycompat.oscurdir, pycompat.ospardir)
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)