175 |
175 |
176 if quote: |
176 if quote: |
177 raise error.ParseError(_("unterminated string"), start) |
177 raise error.ParseError(_("unterminated string"), start) |
178 return parsed, pos |
178 return parsed, pos |
179 |
179 |
|
180 def parse(tmpl): |
|
181 """Parse template string into tree""" |
|
182 parsed, pos = _parsetemplate(tmpl, 0, len(tmpl)) |
|
183 assert pos == len(tmpl), 'unquoted template should be consumed' |
|
184 return ('template', parsed) |
|
185 |
180 def compiletemplate(tmpl, context): |
186 def compiletemplate(tmpl, context): |
181 parsed, pos = _parsetemplate(tmpl, 0, len(tmpl)) |
187 """Parse and compile template string to (func, data) pair""" |
182 return [compileexp(e, context, methods) for e in parsed] |
188 return compileexp(parse(tmpl), context, methods) |
183 |
189 |
184 def compileexp(exp, context, curmethods): |
190 def compileexp(exp, context, curmethods): |
185 t = exp[0] |
191 t = exp[0] |
186 if t in curmethods: |
192 if t in curmethods: |
187 return curmethods[t](exp, context) |
193 return curmethods[t](exp, context) |
200 if x[0] == 'list': |
206 if x[0] == 'list': |
201 return getlist(x[1]) + [x[2]] |
207 return getlist(x[1]) + [x[2]] |
202 return [x] |
208 return [x] |
203 |
209 |
204 def gettemplate(exp, context): |
210 def gettemplate(exp, context): |
|
211 """Compile given template tree or load named template from map file; |
|
212 returns (func, data) pair""" |
205 if exp[0] == 'template': |
213 if exp[0] == 'template': |
206 return [compileexp(e, context, methods) for e in exp[1]] |
214 return compileexp(exp, context, methods) |
207 if exp[0] == 'symbol': |
215 if exp[0] == 'symbol': |
208 # unlike runsymbol(), here 'symbol' is always taken as template name |
216 # unlike runsymbol(), here 'symbol' is always taken as template name |
209 # even if it exists in mapping. this allows us to override mapping |
217 # even if it exists in mapping. this allows us to override mapping |
210 # by web templates, e.g. 'changelogtag' is redefined in map file. |
218 # by web templates, e.g. 'changelogtag' is redefined in map file. |
211 return context._load(exp[1]) |
219 return context._load(exp[1]) |
306 raise error.Abort(_("template filter '%s' is not compatible with " |
314 raise error.Abort(_("template filter '%s' is not compatible with " |
307 "keyword '%s'") % (filt.func_name, dt)) |
315 "keyword '%s'") % (filt.func_name, dt)) |
308 |
316 |
309 def buildmap(exp, context): |
317 def buildmap(exp, context): |
310 func, data = compileexp(exp[1], context, methods) |
318 func, data = compileexp(exp[1], context, methods) |
311 ctmpl = gettemplate(exp[2], context) |
319 tfunc, tdata = gettemplate(exp[2], context) |
312 return (runmap, (func, data, ctmpl)) |
320 return (runmap, (func, data, tfunc, tdata)) |
313 |
321 |
314 def runmap(context, mapping, data): |
322 def runmap(context, mapping, data): |
315 func, data, ctmpl = data |
323 func, data, tfunc, tdata = data |
316 d = func(context, mapping, data) |
324 d = func(context, mapping, data) |
317 if util.safehasattr(d, 'itermaps'): |
325 if util.safehasattr(d, 'itermaps'): |
318 diter = d.itermaps() |
326 diter = d.itermaps() |
319 else: |
327 else: |
320 try: |
328 try: |
328 for i in diter: |
336 for i in diter: |
329 lm = mapping.copy() |
337 lm = mapping.copy() |
330 if isinstance(i, dict): |
338 if isinstance(i, dict): |
331 lm.update(i) |
339 lm.update(i) |
332 lm['originalnode'] = mapping.get('node') |
340 lm['originalnode'] = mapping.get('node') |
333 yield runtemplate(context, lm, ctmpl) |
341 yield tfunc(context, lm, tdata) |
334 else: |
342 else: |
335 # v is not an iterable of dicts, this happen when 'key' |
343 # v is not an iterable of dicts, this happen when 'key' |
336 # has been fully expanded already and format is useless. |
344 # has been fully expanded already and format is useless. |
337 # If so, return the expanded value. |
345 # If so, return the expanded value. |
338 yield i |
346 yield i |
855 filters = {} |
863 filters = {} |
856 self._filters = filters |
864 self._filters = filters |
857 if defaults is None: |
865 if defaults is None: |
858 defaults = {} |
866 defaults = {} |
859 self._defaults = defaults |
867 self._defaults = defaults |
860 self._cache = {} |
868 self._cache = {} # key: (func, data) |
861 |
869 |
862 def _load(self, t): |
870 def _load(self, t): |
863 '''load, parse, and cache a template''' |
871 '''load, parse, and cache a template''' |
864 if t not in self._cache: |
872 if t not in self._cache: |
865 # put poison to cut recursion while compiling 't' |
873 # put poison to cut recursion while compiling 't' |
866 self._cache[t] = [(_runrecursivesymbol, t)] |
874 self._cache[t] = (_runrecursivesymbol, t) |
867 try: |
875 try: |
868 self._cache[t] = compiletemplate(self._loader(t), self) |
876 self._cache[t] = compiletemplate(self._loader(t), self) |
869 except: # re-raises |
877 except: # re-raises |
870 del self._cache[t] |
878 del self._cache[t] |
871 raise |
879 raise |
873 |
881 |
874 def process(self, t, mapping): |
882 def process(self, t, mapping): |
875 '''Perform expansion. t is name of map element to expand. |
883 '''Perform expansion. t is name of map element to expand. |
876 mapping contains added elements for use during expansion. Is a |
884 mapping contains added elements for use during expansion. Is a |
877 generator.''' |
885 generator.''' |
878 return _flatten(runtemplate(self, mapping, self._load(t))) |
886 func, data = self._load(t) |
|
887 return _flatten(func(self, mapping, data)) |
879 |
888 |
880 engines = {'default': engine} |
889 engines = {'default': engine} |
881 |
890 |
882 def stylelist(): |
891 def stylelist(): |
883 paths = templatepaths() |
892 paths = templatepaths() |