# HG changeset patch # User cmlenz # Date 1183398550 0 # Node ID 578a19ed8bb0c6a49134642d3d9e7ee3463cdfc3 # Parent 161aa8a43b1fcf68546c8f5d1e74be9a5404961d Initial code for newctxt branch. diff --git a/genshi/template/base.py b/genshi/template/base.py --- a/genshi/template/base.py +++ b/genshi/template/base.py @@ -13,12 +13,6 @@ """Basic templating functionality.""" -try: - from collections import deque -except ImportError: - class deque(list): - def appendleft(self, x): self.insert(0, x) - def popleft(self): return self.pop(0) import os from StringIO import StringIO @@ -116,7 +110,6 @@ >>> ctxt.get('other') 1 >>> ctxt.pop() - {'one': 'frost'} >>> ctxt.get('one') 'foo' """ @@ -125,17 +118,16 @@ """Initialize the template context with the given keyword arguments as data. """ - self.frames = deque([data]) - self.pop = self.frames.popleft - self.push = self.frames.appendleft - self._match_templates = [] - self._choice_stack = [] + self.data = data + self._restorers = [] # functions that reverse stack changes + self._match_templates = [] # for match directives + self._choice_stack = [] # for choose/when/otherwise # Helper functions for use in expressions def defined(name): """Return whether a variable with the specified name exists in the expression scope.""" - return name in self + return name in self.data def value_of(name, default=None): """If a variable of the specified name is defined, return its value. Otherwise, return the provided default value, or ``None``.""" @@ -143,106 +135,39 @@ data.setdefault('defined', defined) data.setdefault('value_of', value_of) - def __repr__(self): - return repr(list(self.frames)) - - def __contains__(self, key): - """Return whether a variable exists in any of the scopes. - - :param key: the name of the variable - """ - return self._find(key)[1] is not None - - def __delitem__(self, key): - """Remove a variable from all scopes. - - :param key: the name of the variable - """ - for frame in self.frames: - if key in frame: - del frame[key] - - def __getitem__(self, key): - """Get a variables's value, starting at the current scope and going - upward. - - :param key: the name of the variable - :return: the variable value - :raises KeyError: if the requested variable wasn't found in any scope - """ - value, frame = self._find(key) - if frame is None: - raise KeyError(key) - return value - - def __len__(self): - """Return the number of distinctly named variables in the context. - - :return: the number of variables in the context - """ - return len(self.items()) + self.get = self.data.get - def __setitem__(self, key, value): - """Set a variable in the current scope. - - :param key: the name of the variable - :param value: the variable value - """ - self.frames[0][key] = value - - def _find(self, key, default=None): - """Retrieve a given variable's value and the frame it was found in. - - Intended primarily for internal use by directives. - - :param key: the name of the variable - :param default: the default value to return when the variable is not - found - """ - for frame in self.frames: - if key in frame: - return frame[key], frame - return default, None - - def get(self, key, default=None): - """Get a variable's value, starting at the current scope and going - upward. - - :param key: the name of the variable - :param default: the default value to return when the variable is not - found - """ - for frame in self.frames: - if key in frame: - return frame[key] - return default - - def keys(self): - """Return the name of all variables in the context. - - :return: a list of variable names - """ - keys = [] - for frame in self.frames: - keys += [key for key in frame if key not in keys] - return keys - - def items(self): - """Return a list of ``(name, value)`` tuples for all variables in the - context. - - :return: a list of variables - """ - return [(key, self.get(key)) for key in self.keys()] + def __repr__(self): + return '<%s %r>' % (type(self).__name__, self.data) def push(self, data): """Push a new scope on the stack. :param data: the data dictionary to push on the context stack. """ + def _restorer(key, d=self.data): + if key in d: + def f(d=d, val=d.get(key)): + d[key] = val + else: + def f(d=d): + if key in d: del d[key] + return f + self._restorers.append([_restorer(k) for k in data]) + self.data.update(data) def pop(self): """Pop the top-most scope from the stack.""" + for restore in self._restorers.pop(): + restore() + + def replace(self, data): + """Replace the top-most scope with the given data. + + :param data: the data dictionary to replace the top-most frame with + """ + self.pop() + self.push(data) def _apply_directives(stream, ctxt, directives): @@ -453,7 +378,7 @@ yield kind, (tag, Attrs(new_attrs)), pos elif kind is EXPR: - result = data.evaluate(ctxt) + result = data.evaluate(ctxt.data) if result is not None: # First check for a string, otherwise the iterable test below # succeeds, and the string will be chopped up into individual diff --git a/genshi/template/directives.py b/genshi/template/directives.py --- a/genshi/template/directives.py +++ b/genshi/template/directives.py @@ -165,7 +165,7 @@ def __call__(self, stream, ctxt, directives): def _generate(): kind, (tag, attrib), pos = stream.next() - attrs = self.expr.evaluate(ctxt) + attrs = self.expr.evaluate(ctxt.data) if attrs: if isinstance(attrs, Stream): try: @@ -295,7 +295,7 @@ if name in kwargs: val = kwargs.pop(name) else: - val = self.defaults.get(name).evaluate(ctxt) + val = self.defaults.get(name).evaluate(ctxt.data) scope[name] = val if not self.star_args is None: scope[self.star_args] = args @@ -314,7 +314,7 @@ # Store the function reference in the bottom context frame so that it # doesn't get popped off before processing the template has finished # FIXME: this makes context data mutable as a side-effect - ctxt.frames[-1][self.name] = function + ctxt.data[self.name] = function return [] @@ -356,7 +356,7 @@ attach = classmethod(attach) def __call__(self, stream, ctxt, directives): - iterable = self.expr.evaluate(ctxt) + iterable = self.expr.evaluate(ctxt.data) if iterable is None: return @@ -401,7 +401,7 @@ attach = classmethod(attach) def __call__(self, stream, ctxt, directives): - if self.expr.evaluate(ctxt): + if self.expr.evaluate(ctxt.data): return _apply_directives(stream, ctxt, directives) return [] @@ -519,7 +519,7 @@ def __call__(self, stream, ctxt, directives): def _generate(): - if self.expr.evaluate(ctxt): + if self.expr.evaluate(ctxt.data): stream.next() # skip start tag previous = stream.next() for event in stream: @@ -589,7 +589,7 @@ def __call__(self, stream, ctxt, directives): info = [False, None] if self.expr: - info[1] = self.expr.evaluate(ctxt) + info[1] = self.expr.evaluate(ctxt.data) ctxt._choice_stack.append(info) for event in _apply_directives(stream, ctxt, directives): yield event @@ -630,11 +630,11 @@ if info[1]: value = info[1] if self.expr: - matched = value == self.expr.evaluate(ctxt) + matched = value == self.expr.evaluate(ctxt.data) else: matched = bool(value) else: - matched = bool(self.expr.evaluate(ctxt)) + matched = bool(self.expr.evaluate(ctxt.data)) info[0] = matched if not matched: return [] @@ -715,9 +715,10 @@ frame = {} ctxt.push(frame) for targets, expr in self.vars: - value = expr.evaluate(ctxt) + value = expr.evaluate(ctxt.data) for assign in targets: assign(frame, value) + ctxt.replace(frame) for event in _apply_directives(stream, ctxt, directives): yield event ctxt.pop() diff --git a/genshi/template/eval.py b/genshi/template/eval.py --- a/genshi/template/eval.py +++ b/genshi/template/eval.py @@ -133,7 +133,7 @@ __traceback_hide__ = 'before_and_this' _globals = self._globals _globals['data'] = data - return eval(self.code, _globals, {'data': data}) + return eval(self.code, _globals, data) class Suite(Code): @@ -261,7 +261,7 @@ return val lookup_name = classmethod(lookup_name) - def lookup_attr(cls, data, obj, key): + def lookup_attr(cls, obj, key): __traceback_hide__ = True if hasattr(obj, key): return getattr(obj, key) @@ -271,7 +271,7 @@ return cls.undefined(key, owner=obj) lookup_attr = classmethod(lookup_attr) - def lookup_item(cls, data, obj, key): + def lookup_item(cls, obj, key): __traceback_hide__ = True if len(key) == 1: key = key[0] @@ -738,12 +738,12 @@ def visitGetattr(self, node): return ast.CallFunc(ast.Name('_lookup_attr'), [ - ast.Name('data'), self.visit(node.expr), + self.visit(node.expr), ast.Const(node.attrname) ]) def visitSubscript(self, node): return ast.CallFunc(ast.Name('_lookup_item'), [ - ast.Name('data'), self.visit(node.expr), + self.visit(node.expr), ast.Tuple([self.visit(sub) for sub in node.subs]) ]) diff --git a/genshi/template/markup.py b/genshi/template/markup.py --- a/genshi/template/markup.py +++ b/genshi/template/markup.py @@ -233,7 +233,7 @@ """ for event in stream: if event[0] is EXEC: - event[1].execute(_ctxt2dict(ctxt)) + event[1].execute(ctxt.data) else: yield event