##// END OF EJS Templates
store: let _auxencode() return the list of path segments...
store: let _auxencode() return the list of path segments so we can spare us splitting the path again in _hybridencode()

File last commit:

r17383:099c778c default
r17574:81a033bb default
Show More
templater.py
402 lines | 12.3 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
Matt Mackall
Simplify i18n imports
r3891 from i18n import _
Matt Mackall
templater: directly parse templates, no regexes
r10843 import sys, os
Matt Mackall
templater: use the parser.py parser to extend the templater syntax
r13176 import util, config, templatefilters, parser, error
# template parsing
elements = {
"(": (20, ("group", 1, ")"), ("func", 1, ")")),
",": (2, None, ("list", 2)),
"|": (5, None, ("|", 5)),
"%": (6, None, ("%", 6)),
")": (0, None, None),
"symbol": (0, ("symbol",), None),
"string": (0, ("string",), None),
"end": (0, None, None),
}
def tokenizer(data):
program, start, end = data
pos = start
while pos < end:
c = program[pos]
if c.isspace(): # skip inter-token whitespace
pass
elif c in "(,)%|": # handle simple operators
yield (c, None, pos)
elif (c in '"\'' or c == 'r' and
program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings
if c == 'r':
pos += 1
c = program[pos]
decode = lambda x: x
else:
decode = lambda x: x.decode('string-escape')
pos += 1
s = pos
while pos < end: # find closing quote
d = program[pos]
if d == '\\': # skip over escaped characters
pos += 2
continue
if d == c:
yield ('string', decode(program[s:pos]), s)
break
pos += 1
else:
raise error.ParseError(_("unterminated string"), s)
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]
yield ('symbol', sym, s)
pos -= 1
elif c == '}':
pos += 1
break
else:
raise error.ParseError(_("syntax error"), pos)
pos += 1
yield ('end', None, pos)
def compiletemplate(tmpl, context):
parsed = []
pos, stop = 0, len(tmpl)
p = parser.parser(tokenizer, elements)
while pos < stop:
n = tmpl.find('{', pos)
if n < 0:
parsed.append(("string", tmpl[pos:]))
break
if n > 0 and tmpl[n - 1] == '\\':
# escaped
parsed.append(("string", tmpl[pos:n - 1] + "{"))
pos = n + 1
continue
if n > pos:
parsed.append(("string", tmpl[pos:n]))
pd = [tmpl, n + 1, stop]
Bernhard Leiner
revset: report a parse error if a revset is not parsed completely (issue2654)
r13665 parseres, pos = p.parse(pd)
parsed.append(parseres)
Matt Mackall
templater: use the parser.py parser to extend the templater syntax
r13176
return [compileexp(e, context) for e in parsed]
def compileexp(exp, context):
t = exp[0]
if t in methods:
return methods[t](exp, context)
raise error.ParseError(_("unknown method '%s'") % t)
# template evaluation
def getsymbol(exp):
if exp[0] == 'symbol':
return exp[1]
raise error.ParseError(_("expected a symbol"))
def getlist(x):
if not x:
return []
if x[0] == 'list':
return getlist(x[1]) + [x[2]]
return [x]
def getfilter(exp, context):
f = getsymbol(exp)
if f not in context._filters:
raise error.ParseError(_("unknown function '%s'") % f)
return context._filters[f]
def gettemplate(exp, context):
if exp[0] == 'string':
return compiletemplate(exp[1], context)
if exp[0] == 'symbol':
return context._load(exp[1])
raise error.ParseError(_("expected template specifier"))
def runstring(context, mapping, data):
return data
def runsymbol(context, mapping, key):
v = mapping.get(key)
if v is None:
v = context._defaults.get(key, '')
Augie Fackler
globally: use safehasattr(x, '__call__') instead of hasattr(x, '__call__')
r14943 if util.safehasattr(v, '__call__'):
Matt Mackall
templater: use the parser.py parser to extend the templater syntax
r13176 return v(**mapping)
return v
def buildfilter(exp, context):
func, data = compileexp(exp[1], context)
filt = getfilter(exp[2], context)
return (runfilter, (func, data, filt))
def runfilter(context, mapping, data):
func, data, filt = data
Neil Kodner
templater: abort when a template filter raises an exception (issue2987)
r17383 try:
return filt(func(context, mapping, data))
except (ValueError, AttributeError, TypeError):
if isinstance(data, tuple):
dt = data[1]
else:
dt = data
raise util.Abort(_("template filter '%s' is not compatible with "
"keyword '%s'") % (filt.func_name, dt))
Matt Mackall
templater: use the parser.py parser to extend the templater syntax
r13176
def buildmap(exp, context):
func, data = compileexp(exp[1], context)
ctmpl = gettemplate(exp[2], context)
return (runmap, (func, data, ctmpl))
def runmap(context, mapping, data):
func, data, ctmpl = data
d = func(context, mapping, data)
lm = mapping.copy()
for i in d:
if isinstance(i, dict):
lm.update(i)
for f, d in ctmpl:
yield f(context, lm, d)
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.
yield i
def buildfunc(exp, context):
n = getsymbol(exp[1])
args = [compileexp(x, context) for x in getlist(exp[2])]
Matt Mackall
templater: use a global funcs table
r14925 if n in funcs:
f = funcs[n]
return (f, args)
Matt Mackall
templater: use the parser.py parser to extend the templater syntax
r13176 if n in context._filters:
if len(args) != 1:
raise error.ParseError(_("filter %s expects one argument") % n)
f = context._filters[n]
return (runfilter, (args[0][0], args[0][1], f))
methods = {
"string": lambda e, c: (runstring, e[1]),
"symbol": lambda e, c: (runsymbol, e[1]),
"group": lambda e, c: compileexp(e[1], c),
# ".": buildmember,
"|": buildfilter,
"%": buildmap,
"func": buildfunc,
}
Matt Mackall
templater: use a global funcs table
r14925 funcs = {
}
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
Brendan Cully
Allow hgweb to search for templates in more than one path....
r7107 path = ['templates', '../templates']
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'''
if isinstance(thing, str):
yield thing
Augie Fackler
globally: use safehasattr(x, '__iter__') instead of hasattr(x, '__iter__')
r14944 elif not util.safehasattr(thing, '__iter__'):
Dirkjan Ochtman
cleanups: undefined variables
r11305 if thing is not None:
Matt Mackall
templater: raise nested functions
r10852 yield str(thing)
else:
Matt Mackall
templater: use recursive flattening...
r10850 for i in thing:
if isinstance(i, str):
yield i
Augie Fackler
globally: use safehasattr(x, '__iter__') instead of hasattr(x, '__iter__')
r14944 elif not util.safehasattr(i, '__iter__'):
Matt Mackall
templater: raise nested functions
r10852 if i is not None:
yield str(i)
Matt Mackall
templater: use recursive flattening...
r10850 elif i is not None:
for j in _flatten(i):
yield j
Vadim Gelfer
make parsestring work with strings that do not have quotes.
r1902 def parsestring(s, quoted=True):
Vadim Gelfer
add doc comments to template code.
r1909 '''parse a string using simple c-like syntax.
string must be in quotes if quoted is True.'''
Vadim Gelfer
make parsestring work with strings that do not have quotes.
r1902 if quoted:
Matt Mackall
templater: simplify parsestring
r3639 if len(s) < 2 or s[0] != s[-1]:
raise SyntaxError(_('unmatched quotes'))
return s[1:-1].decode('string_escape')
Matt Mackall
templater: use str.decode in parse_string
r3632
return s.decode('string_escape')
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|...}.'''
Dirkjan Ochtman
templater: separate template management and actual string processing
r8218 def __init__(self, loader, filters={}, defaults={}):
Matt Mackall
templater: privatize class variables
r10848 self._loader = loader
self._filters = filters
self._defaults = defaults
self._cache = {}
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:
self._cache[t] = compiletemplate(self._loader(t), self)
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.'''
Matt Mackall
templater: use the parser.py parser to extend the templater syntax
r13176 return _flatten(func(self, mapping, data) for func, data in
self._load(t))
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}
Dirkjan Ochtman
templater: separate template management and actual string processing
r8218 class templater(object):
Brendan Cully
templater: return data in increasing chunk sizes...
r7396 def __init__(self, mapfile, filters={}, defaults={}, cache={},
minchunk=1024, maxchunk=65536):
Vadim Gelfer
add doc comments to template code.
r1909 '''set up template engine.
mapfile is name of file to read map definitions from.
filters is dict of functions. each transforms a value into another.
defaults is dict of default map definitions.'''
Vadim Gelfer
improve template errors when something is wrong.
r1905 self.mapfile = mapfile or 'template'
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 = {}
Vadim Gelfer
improve template errors when something is wrong.
r1905 self.base = (mapfile and os.path.dirname(mapfile)) or ''
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
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
Vadim Gelfer
improve template errors when something is wrong.
r1905 if not mapfile:
return
Dirkjan Ochtman
give better error message on non-existent mapfile (issue813)
r6337 if not os.path.exists(mapfile):
raise util.Abort(_('style not found: %s') % mapfile)
Matt Mackall
templater: use new config parser...
r8194 conf = config.config()
conf.read(mapfile)
for key, val in conf[''].items():
Ross Lagerwall
templater: handle a missing value correctly...
r17334 if not val:
raise SyntaxError(_('%s: missing value') % conf.source('', key))
Matt Mackall
templater: use new config parser...
r8194 if val[0] in "'\"":
try:
self.cache[key] = parsestring(val)
except SyntaxError, inst:
raise SyntaxError('%s: %s' %
Matt Mackall
config: getsource -> source
r8198 (conf.source('', key), inst.args[0]))
Vadim Gelfer
move hgweb template code out to templater
r1896 else:
Dirkjan Ochtman
templater: make the templating engine pluggable to some extent
r8361 val = 'default', val
if ':' in val[1]:
val = val[1].split(':', 1)
self.map[key] = val[0], os.path.join(self.base, val[1])
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])
Matt Mackall
templater: give slightly nicer error for unknown map entries
r13175 except KeyError, inst:
raise util.Abort(_('"%s" not in template map') % inst.args[0])
Vadim Gelfer
improve template errors when something is wrong.
r1905 except IOError, inst:
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:
self.ecache[ttype] = engines[ttype](self.load,
self.filters, self.defaults)
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
Vadim Gelfer
many small changes to templater....
r1899 def templatepath(name=None):
Vadim Gelfer
add doc comments to template code.
r1909 '''return location of template file or directory (if no name).
returns None if not found.'''
Brendan Cully
Allow hgweb to search for templates in more than one path....
r7107 normpaths = []
Vadim Gelfer
move changeset_templater into templater module.
r2189
Lee Cantey
Windows py2exe version didn't handle names given to templatepath() correctly
r2001 # executable version (py2exe) doesn't support __file__
Augie Fackler
windows: check util.mainfrozen() instead of ad-hoc checks everywhere
r14941 if util.mainfrozen():
Lee Cantey
Windows py2exe version didn't handle names given to templatepath() correctly
r2001 module = sys.executable
else:
module = __file__
Brendan Cully
Allow hgweb to search for templates in more than one path....
r7107 for f in path:
if f.startswith('/'):
p = f
else:
fl = f.split('/')
p = os.path.join(os.path.dirname(module), *fl)
if name:
p = os.path.join(p, name)
if name and os.path.exists(p):
Vadim Gelfer
move hgweb.templatepath into templater
r1897 return os.path.normpath(p)
Brendan Cully
Allow hgweb to search for templates in more than one path....
r7107 elif os.path.isdir(p):
normpaths.append(os.path.normpath(p))
return normpaths
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:
paths = templatepath()
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:
if not style:
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)