Mercurial > public > mercurial-scm > hg
comparison mercurial/templateutil.py @ 36913:da2977e674a3
templater: extract template evaluation utility to new module
Prepares for splitting template functions to new module.
All eval* functions were moved to templateutil.py, and run* functions had to
be moved as well due to the dependency from eval*s. eval*s were aliased as
they are commonly used in codebase. _getdictitem() had to be made public.
author | Yuya Nishihara <yuya@tcha.org> |
---|---|
date | Thu, 08 Mar 2018 22:33:24 +0900 |
parents | mercurial/templater.py@543afbdc8e59 |
children | 6ff6e1d6b5b8 |
comparison
equal
deleted
inserted
replaced
36912:543afbdc8e59 | 36913:da2977e674a3 |
---|---|
1 # templateutil.py - utility for template evaluation | |
2 # | |
3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com> | |
4 # | |
5 # This software may be used and distributed according to the terms of the | |
6 # GNU General Public License version 2 or any later version. | |
7 | |
8 from __future__ import absolute_import | |
9 | |
10 import types | |
11 | |
12 from .i18n import _ | |
13 from . import ( | |
14 error, | |
15 pycompat, | |
16 templatefilters, | |
17 templatekw, | |
18 util, | |
19 ) | |
20 | |
21 class ResourceUnavailable(error.Abort): | |
22 pass | |
23 | |
24 class TemplateNotFound(error.Abort): | |
25 pass | |
26 | |
27 def findsymbolicname(arg): | |
28 """Find symbolic name for the given compiled expression; returns None | |
29 if nothing found reliably""" | |
30 while True: | |
31 func, data = arg | |
32 if func is runsymbol: | |
33 return data | |
34 elif func is runfilter: | |
35 arg = data[0] | |
36 else: | |
37 return None | |
38 | |
39 def evalrawexp(context, mapping, arg): | |
40 """Evaluate given argument as a bare template object which may require | |
41 further processing (such as folding generator of strings)""" | |
42 func, data = arg | |
43 return func(context, mapping, data) | |
44 | |
45 def evalfuncarg(context, mapping, arg): | |
46 """Evaluate given argument as value type""" | |
47 thing = evalrawexp(context, mapping, arg) | |
48 thing = templatekw.unwrapvalue(thing) | |
49 # evalrawexp() may return string, generator of strings or arbitrary object | |
50 # such as date tuple, but filter does not want generator. | |
51 if isinstance(thing, types.GeneratorType): | |
52 thing = stringify(thing) | |
53 return thing | |
54 | |
55 def evalboolean(context, mapping, arg): | |
56 """Evaluate given argument as boolean, but also takes boolean literals""" | |
57 func, data = arg | |
58 if func is runsymbol: | |
59 thing = func(context, mapping, data, default=None) | |
60 if thing is None: | |
61 # not a template keyword, takes as a boolean literal | |
62 thing = util.parsebool(data) | |
63 else: | |
64 thing = func(context, mapping, data) | |
65 thing = templatekw.unwrapvalue(thing) | |
66 if isinstance(thing, bool): | |
67 return thing | |
68 # other objects are evaluated as strings, which means 0 is True, but | |
69 # empty dict/list should be False as they are expected to be '' | |
70 return bool(stringify(thing)) | |
71 | |
72 def evalinteger(context, mapping, arg, err=None): | |
73 v = evalfuncarg(context, mapping, arg) | |
74 try: | |
75 return int(v) | |
76 except (TypeError, ValueError): | |
77 raise error.ParseError(err or _('not an integer')) | |
78 | |
79 def evalstring(context, mapping, arg): | |
80 return stringify(evalrawexp(context, mapping, arg)) | |
81 | |
82 def evalstringliteral(context, mapping, arg): | |
83 """Evaluate given argument as string template, but returns symbol name | |
84 if it is unknown""" | |
85 func, data = arg | |
86 if func is runsymbol: | |
87 thing = func(context, mapping, data, default=data) | |
88 else: | |
89 thing = func(context, mapping, data) | |
90 return stringify(thing) | |
91 | |
92 _evalfuncbytype = { | |
93 bool: evalboolean, | |
94 bytes: evalstring, | |
95 int: evalinteger, | |
96 } | |
97 | |
98 def evalastype(context, mapping, arg, typ): | |
99 """Evaluate given argument and coerce its type""" | |
100 try: | |
101 f = _evalfuncbytype[typ] | |
102 except KeyError: | |
103 raise error.ProgrammingError('invalid type specified: %r' % typ) | |
104 return f(context, mapping, arg) | |
105 | |
106 def runinteger(context, mapping, data): | |
107 return int(data) | |
108 | |
109 def runstring(context, mapping, data): | |
110 return data | |
111 | |
112 def _recursivesymbolblocker(key): | |
113 def showrecursion(**args): | |
114 raise error.Abort(_("recursive reference '%s' in template") % key) | |
115 return showrecursion | |
116 | |
117 def runsymbol(context, mapping, key, default=''): | |
118 v = context.symbol(mapping, key) | |
119 if v is None: | |
120 # put poison to cut recursion. we can't move this to parsing phase | |
121 # because "x = {x}" is allowed if "x" is a keyword. (issue4758) | |
122 safemapping = mapping.copy() | |
123 safemapping[key] = _recursivesymbolblocker(key) | |
124 try: | |
125 v = context.process(key, safemapping) | |
126 except TemplateNotFound: | |
127 v = default | |
128 if callable(v) and getattr(v, '_requires', None) is None: | |
129 # old templatekw: expand all keywords and resources | |
130 props = context._resources.copy() | |
131 props.update(mapping) | |
132 return v(**pycompat.strkwargs(props)) | |
133 if callable(v): | |
134 # new templatekw | |
135 try: | |
136 return v(context, mapping) | |
137 except ResourceUnavailable: | |
138 # unsupported keyword is mapped to empty just like unknown keyword | |
139 return None | |
140 return v | |
141 | |
142 def runtemplate(context, mapping, template): | |
143 for arg in template: | |
144 yield evalrawexp(context, mapping, arg) | |
145 | |
146 def runfilter(context, mapping, data): | |
147 arg, filt = data | |
148 thing = evalfuncarg(context, mapping, arg) | |
149 try: | |
150 return filt(thing) | |
151 except (ValueError, AttributeError, TypeError): | |
152 sym = findsymbolicname(arg) | |
153 if sym: | |
154 msg = (_("template filter '%s' is not compatible with keyword '%s'") | |
155 % (pycompat.sysbytes(filt.__name__), sym)) | |
156 else: | |
157 msg = (_("incompatible use of template filter '%s'") | |
158 % pycompat.sysbytes(filt.__name__)) | |
159 raise error.Abort(msg) | |
160 | |
161 def runmap(context, mapping, data): | |
162 darg, targ = data | |
163 d = evalrawexp(context, mapping, darg) | |
164 if util.safehasattr(d, 'itermaps'): | |
165 diter = d.itermaps() | |
166 else: | |
167 try: | |
168 diter = iter(d) | |
169 except TypeError: | |
170 sym = findsymbolicname(darg) | |
171 if sym: | |
172 raise error.ParseError(_("keyword '%s' is not iterable") % sym) | |
173 else: | |
174 raise error.ParseError(_("%r is not iterable") % d) | |
175 | |
176 for i, v in enumerate(diter): | |
177 lm = mapping.copy() | |
178 lm['index'] = i | |
179 if isinstance(v, dict): | |
180 lm.update(v) | |
181 lm['originalnode'] = mapping.get('node') | |
182 yield evalrawexp(context, lm, targ) | |
183 else: | |
184 # v is not an iterable of dicts, this happen when 'key' | |
185 # has been fully expanded already and format is useless. | |
186 # If so, return the expanded value. | |
187 yield v | |
188 | |
189 def runmember(context, mapping, data): | |
190 darg, memb = data | |
191 d = evalrawexp(context, mapping, darg) | |
192 if util.safehasattr(d, 'tomap'): | |
193 lm = mapping.copy() | |
194 lm.update(d.tomap()) | |
195 return runsymbol(context, lm, memb) | |
196 if util.safehasattr(d, 'get'): | |
197 return getdictitem(d, memb) | |
198 | |
199 sym = findsymbolicname(darg) | |
200 if sym: | |
201 raise error.ParseError(_("keyword '%s' has no member") % sym) | |
202 else: | |
203 raise error.ParseError(_("%r has no member") % pycompat.bytestr(d)) | |
204 | |
205 def runnegate(context, mapping, data): | |
206 data = evalinteger(context, mapping, data, | |
207 _('negation needs an integer argument')) | |
208 return -data | |
209 | |
210 def runarithmetic(context, mapping, data): | |
211 func, left, right = data | |
212 left = evalinteger(context, mapping, left, | |
213 _('arithmetic only defined on integers')) | |
214 right = evalinteger(context, mapping, right, | |
215 _('arithmetic only defined on integers')) | |
216 try: | |
217 return func(left, right) | |
218 except ZeroDivisionError: | |
219 raise error.Abort(_('division by zero is not defined')) | |
220 | |
221 def getdictitem(dictarg, key): | |
222 val = dictarg.get(key) | |
223 if val is None: | |
224 return | |
225 return templatekw.wraphybridvalue(dictarg, key, val) | |
226 | |
227 stringify = templatefilters.stringify |