# HG changeset patch # User Yuya Nishihara # Date 2017-12-21 12:29:06 # Node ID 32c278eb876f826b50457887daad72123ddace03 # Parent d6cfa722b04466ebc645e7ccc3a2b5b75873cff0 templater: keep default resources per template engine (API) This allows us to register a repo object as a resource in hgweb template, without loosing '{repo}' symbol: symbol('repo') -> mapping['repo'] (n/a) -> defaults['repo'] resource('repo') -> mapping['repo'] (n/a) -> resources['repo'] I'm thinking of redesigning the templatekw API to take (context, mapping) in place of **(context._resources + mapping), but that will be a big change and not implemented yet. props['templ'] is ported to the resources dict as an example. .. api:: mapping does not contain all template resources. use context.resource() in template functions. diff --git a/mercurial/cmdutil.py b/mercurial/cmdutil.py --- a/mercurial/cmdutil.py +++ b/mercurial/cmdutil.py @@ -1886,7 +1886,6 @@ class changeset_templater(changeset_prin '''show a single changeset or file revision''' props = props.copy() props.update(templatekw.keywords) - props['templ'] = self.t props['ctx'] = ctx props['repo'] = self.repo props['ui'] = self.repo.ui @@ -2663,7 +2662,6 @@ def _graphnodeformatter(ui, displayer): if isinstance(displayer, changeset_templater): cache = displayer.cache # reuse cache of slow templates props = templatekw.keywords.copy() - props['templ'] = templ props['cache'] = cache def formatnode(repo, ctx): props['ctx'] = ctx diff --git a/mercurial/formatter.py b/mercurial/formatter.py --- a/mercurial/formatter.py +++ b/mercurial/formatter.py @@ -392,7 +392,6 @@ class templateformatter(baseformatter): props.update(item) if 'ctx' in item: # but template resources must be always available - props['templ'] = self._t props['repo'] = props['ctx'].repo() props['revcache'] = {} props = pycompat.strkwargs(props) @@ -468,18 +467,19 @@ def templatepartsmap(spec, t, partnames) partsmap[part] = ref return partsmap -def loadtemplater(ui, spec, cache=None): +def loadtemplater(ui, spec, resources=None, cache=None): """Create a templater from either a literal template or loading from a map file""" assert not (spec.tmpl and spec.mapfile) if spec.mapfile: - return templater.templater.frommapfile(spec.mapfile, cache=cache) - return maketemplater(ui, spec.tmpl, cache=cache) + frommapfile = templater.templater.frommapfile + return frommapfile(spec.mapfile, resources=resources, cache=cache) + return maketemplater(ui, spec.tmpl, resources=resources, cache=cache) -def maketemplater(ui, tmpl, cache=None): +def maketemplater(ui, tmpl, resources=None, cache=None): """Create a templater from a string template 'tmpl'""" aliases = ui.configitems('templatealias') - t = templater.templater(cache=cache, aliases=aliases) + t = templater.templater(resources=resources, cache=cache, aliases=aliases) t.cache.update((k, templater.unquotestring(v)) for k, v in ui.configitems('templates')) if tmpl: diff --git a/mercurial/templater.py b/mercurial/templater.py --- a/mercurial/templater.py +++ b/mercurial/templater.py @@ -393,7 +393,11 @@ def runsymbol(context, mapping, key, def except TemplateNotFound: v = default if callable(v): - return v(**pycompat.strkwargs(mapping)) + # TODO: templatekw functions will be updated to take (context, mapping) + # pair instead of **props + props = context._resources.copy() + props.update(mapping) + return v(**props) return v def buildtemplate(exp, context): @@ -657,7 +661,10 @@ def files(context, mapping, args): ctx = context.resource(mapping, 'ctx') m = ctx.match([raw]) files = list(ctx.matches(m)) - return templatekw.showlist("file", files, mapping) + # TODO: pass (context, mapping) pair to keyword function + props = context._resources.copy() + props.update(mapping) + return templatekw.showlist("file", files, props) @templatefunc('fill(text[, width[, initialident[, hangindent]]])') def fill(context, mapping, args): @@ -878,7 +885,10 @@ def latesttag(context, mapping, args): if len(args) == 1: pattern = evalstring(context, mapping, args[0]) - return templatekw.showlatesttags(pattern, **pycompat.strkwargs(mapping)) + # TODO: pass (context, mapping) pair to keyword function + props = context._resources.copy() + props.update(mapping) + return templatekw.showlatesttags(pattern, **pycompat.strkwargs(props)) @templatefunc('localdate(date[, tz])') def localdate(context, mapping, args): @@ -1062,8 +1072,11 @@ def revset(context, mapping, args): revs = list(revs) revsetcache[raw] = revs + # TODO: pass (context, mapping) pair to keyword function + props = context._resources.copy() + props.update(mapping) return templatekw.showrevslist("revision", revs, - **pycompat.strkwargs(mapping)) + **pycompat.strkwargs(props)) @templatefunc('rstdoc(text, style)') def rstdoc(context, mapping, args): @@ -1290,14 +1303,18 @@ class engine(object): filter uses function to transform value. syntax is {key|filter1|filter2|...}.''' - def __init__(self, loader, filters=None, defaults=None, aliases=()): + def __init__(self, loader, filters=None, defaults=None, resources=None, + aliases=()): self._loader = loader if filters is None: filters = {} self._filters = filters if defaults is None: defaults = {} + if resources is None: + resources = {} self._defaults = defaults + self._resources = resources self._aliasmap = _aliasrules.buildmap(aliases) self._cache = {} # key: (func, data) @@ -1311,7 +1328,12 @@ class engine(object): def resource(self, mapping, key): """Return internal data (e.g. cache) used for keyword/function evaluation""" - return mapping[key] + v = mapping.get(key) + if v is None: + v = self._resources.get(key) + if v is None: + raise KeyError + return v def _load(self, t): '''load, parse, and cache a template''' @@ -1406,17 +1428,21 @@ class TemplateNotFound(error.Abort): class templater(object): - def __init__(self, filters=None, defaults=None, cache=None, aliases=(), - minchunk=1024, maxchunk=65536): + def __init__(self, filters=None, defaults=None, resources=None, + cache=None, aliases=(), minchunk=1024, maxchunk=65536): '''set up template engine. filters is dict of functions. each transforms a value into another. defaults is dict of default map definitions. + resources is dict of internal data (e.g. cache), which are inaccessible + from user template. aliases is list of alias (name, replacement) pairs. ''' if filters is None: filters = {} if defaults is None: defaults = {} + if resources is None: + resources = {} if cache is None: cache = {} self.cache = cache.copy() @@ -1424,15 +1450,17 @@ class templater(object): self.filters = templatefilters.filters.copy() self.filters.update(filters) self.defaults = defaults + self._resources = {'templ': self} + self._resources.update(resources) self._aliases = aliases self.minchunk, self.maxchunk = minchunk, maxchunk self.ecache = {} @classmethod - def frommapfile(cls, mapfile, filters=None, defaults=None, cache=None, - minchunk=1024, maxchunk=65536): + 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, cache, [], minchunk, maxchunk) + t = cls(filters, defaults, resources, cache, [], minchunk, maxchunk) cache, tmap, aliases = _readmapfile(mapfile) t.cache.update(cache) t.map = tmap @@ -1469,7 +1497,7 @@ class templater(object): except KeyError: raise error.Abort(_('invalid template engine: %s') % ttype) self.ecache[ttype] = ecls(self.load, self.filters, self.defaults, - self._aliases) + self._resources, self._aliases) proc = self.ecache[ttype] stream = proc.process(t, mapping) diff --git a/tests/test-template-engine.t b/tests/test-template-engine.t --- a/tests/test-template-engine.t +++ b/tests/test-template-engine.t @@ -4,8 +4,9 @@ > from mercurial import templater > > class mytemplater(object): - > def __init__(self, loader, filters, defaults, aliases): + > def __init__(self, loader, filters, defaults, resources, aliases): > self.loader = loader + > self._resources = resources > > def process(self, t, map): > tmpl = self.loader(t) @@ -13,7 +14,9 @@ > if k in ('templ', 'ctx', 'repo', 'revcache', 'cache', 'troubles'): > continue > if hasattr(v, '__call__'): - > v = v(**map) + > props = self._resources.copy() + > props.update(map) + > v = v(**props) > v = templater.stringify(v) > tmpl = tmpl.replace('{{%s}}' % k, v) > yield tmpl