comparison markup/template.py @ 220:f4943d2babda trunk

Fix for #45 and #46: properly support assignment to nested tuples in `py:for` and `py:with` directives.
author cmlenz
date Tue, 05 Sep 2006 16:33:13 +0000
parents f150cff4da18
children 04d260487b9a
comparison
equal deleted inserted replaced
219:ebceef564b79 220:f4943d2babda
111 def __setitem__(self, key, value): 111 def __setitem__(self, key, value):
112 """Set a variable in the current scope.""" 112 """Set a variable in the current scope."""
113 self.frames[0][key] = value 113 self.frames[0][key] = value
114 114
115 def _find(self, key, default=None): 115 def _find(self, key, default=None):
116 """Retrieve a given variable's value and frame it was found in. 116 """Retrieve a given variable's value and the frame it was found in.
117 117
118 Intented for internal use by directives. 118 Intented for internal use by directives.
119 """ 119 """
120 for frame in self.frames: 120 for frame in self.frames:
121 if key in frame: 121 if key in frame:
188 def _apply_directives(stream, ctxt, directives): 188 def _apply_directives(stream, ctxt, directives):
189 """Apply the given directives to the stream.""" 189 """Apply the given directives to the stream."""
190 if directives: 190 if directives:
191 stream = directives[0](iter(stream), ctxt, directives[1:]) 191 stream = directives[0](iter(stream), ctxt, directives[1:])
192 return stream 192 return stream
193
194 def _assignment(ast):
195 """Takes the AST representation of an assignment, and returns a function
196 that applies the assignment of a given value to a dictionary.
197 """
198 def _names(node):
199 if isinstance(node, (compiler.ast.AssTuple, compiler.ast.Tuple)):
200 return tuple([_names(child) for child in node])
201 elif isinstance(node, (compiler.ast.AssName, compiler.ast.Name)):
202 return node.name
203 def _assign(data, value, names=_names(ast)):
204 if type(names) is tuple:
205 for idx in range(len(names)):
206 _assign(data, value[idx], names[idx])
207 else:
208 data[names] = value
209 return _assign
193 210
194 211
195 class AttrsDirective(Directive): 212 class AttrsDirective(Directive):
196 """Implementation of the `py:attrs` template directive. 213 """Implementation of the `py:attrs` template directive.
197 214
383 >>> print tmpl.generate(items=[1, 2, 3]) 400 >>> print tmpl.generate(items=[1, 2, 3])
384 <ul> 401 <ul>
385 <li>1</li><li>2</li><li>3</li> 402 <li>1</li><li>2</li><li>3</li>
386 </ul> 403 </ul>
387 """ 404 """
388 __slots__ = ['targets'] 405 __slots__ = ['assign']
389 406
390 ATTRIBUTE = 'each' 407 ATTRIBUTE = 'each'
391 408
392 def __init__(self, value, filename=None, lineno=-1, offset=-1): 409 def __init__(self, value, filename=None, lineno=-1, offset=-1):
393 if ' in ' not in value: 410 if ' in ' not in value:
394 raise TemplateSyntaxError('"in" keyword missing in "for" directive', 411 raise TemplateSyntaxError('"in" keyword missing in "for" directive',
395 filename, lineno, offset) 412 filename, lineno, offset)
396 targets, value = value.split(' in ', 1) 413 assign, value = value.split(' in ', 1)
397 self.targets = [str(name.strip()) for name in targets.split(',')] 414 ast = compiler.parse(assign, 'exec')
415 self.assign = _assignment(ast.node.nodes[0].expr)
398 Directive.__init__(self, value.strip(), filename, lineno, offset) 416 Directive.__init__(self, value.strip(), filename, lineno, offset)
399 417
400 def __call__(self, stream, ctxt, directives): 418 def __call__(self, stream, ctxt, directives):
401 iterable = self.expr.evaluate(ctxt) 419 iterable = self.expr.evaluate(ctxt)
402 if iterable is None: 420 if iterable is None:
403 return 421 return
404 422
423 assign = self.assign
405 scope = {} 424 scope = {}
406 stream = list(stream) 425 stream = list(stream)
407 targets = self.targets
408 single = len(targets) == 1
409 for item in iter(iterable): 426 for item in iter(iterable):
410 if single: 427 assign(scope, item)
411 scope[targets[0]] = item
412 else:
413 for idx, name in enumerate(targets):
414 scope[name] = item[idx]
415 ctxt.push(scope) 428 ctxt.push(scope)
416 for event in _apply_directives(stream, ctxt, directives): 429 for event in _apply_directives(stream, ctxt, directives):
417 yield event 430 yield event
418 ctxt.pop() 431 ctxt.pop()
419 432
689 continue 702 continue
690 elif not isinstance(node, compiler.ast.Assign): 703 elif not isinstance(node, compiler.ast.Assign):
691 raise TemplateSyntaxError('only assignment allowed in ' 704 raise TemplateSyntaxError('only assignment allowed in '
692 'value of the "with" directive', 705 'value of the "with" directive',
693 filename, lineno, offset) 706 filename, lineno, offset)
694 self.vars.append(([n.name for n in node.nodes], 707 self.vars.append(([_assignment(n) for n in node.nodes],
695 Expression(node.expr, filename, lineno))) 708 Expression(node.expr, filename, lineno)))
696 except SyntaxError, err: 709 except SyntaxError, err:
697 err.msg += ' in expression "%s" of "%s" directive' % (value, 710 err.msg += ' in expression "%s" of "%s" directive' % (value,
698 self.tagname) 711 self.tagname)
699 raise TemplateSyntaxError(err, filename, lineno, 712 raise TemplateSyntaxError(err, filename, lineno,
700 offset + (err.offset or 0)) 713 offset + (err.offset or 0))
701 714
702 def __call__(self, stream, ctxt, directives): 715 def __call__(self, stream, ctxt, directives):
703 frame = {} 716 frame = {}
704 ctxt.push(frame) 717 ctxt.push(frame)
705 for names, expr in self.vars: 718 for targets, expr in self.vars:
706 value = expr.evaluate(ctxt, nocall=True) 719 value = expr.evaluate(ctxt, nocall=True)
707 frame.update(dict([(name, value) for name in names])) 720 for assign in targets:
721 assign(frame, value)
708 for event in _apply_directives(stream, ctxt, directives): 722 for event in _apply_directives(stream, ctxt, directives):
709 yield event 723 yield event
710 ctxt.pop() 724 ctxt.pop()
711 725
712 def __repr__(self): 726 def __repr__(self):
Copyright (C) 2012-2017 Edgewall Software