changeset 555:8f7ce3b9ab4f experimental-newctxt

Initial code for newctxt branch.
author cmlenz
date Mon, 02 Jul 2007 17:49:10 +0000
parents 0cdbd6e5831e
children c50f29474a0a
files genshi/template/base.py genshi/template/directives.py genshi/template/eval.py genshi/template/markup.py
diffstat 4 files changed, 46 insertions(+), 120 deletions(-) [+]
line wrap: on
line diff
--- 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
--- 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()
--- 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])
         ])
--- 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
 
Copyright (C) 2012-2017 Edgewall Software