Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/websdk/genshi/template
diff options
context:
space:
mode:
Diffstat (limited to 'websdk/genshi/template')
-rw-r--r--websdk/genshi/template/__init__.py23
-rw-r--r--websdk/genshi/template/_ast24.py446
-rw-r--r--websdk/genshi/template/ast24.py505
-rw-r--r--websdk/genshi/template/astutil.py784
-rw-r--r--websdk/genshi/template/base.py634
-rw-r--r--websdk/genshi/template/directives.py725
-rw-r--r--websdk/genshi/template/eval.py629
-rw-r--r--websdk/genshi/template/interpolation.py153
-rw-r--r--websdk/genshi/template/loader.py344
-rw-r--r--websdk/genshi/template/markup.py397
-rw-r--r--websdk/genshi/template/plugin.py176
-rw-r--r--websdk/genshi/template/text.py333
12 files changed, 5149 insertions, 0 deletions
diff --git a/websdk/genshi/template/__init__.py b/websdk/genshi/template/__init__.py
new file mode 100644
index 0000000..47a9310
--- /dev/null
+++ b/websdk/genshi/template/__init__.py
@@ -0,0 +1,23 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2006-2007 Edgewall Software
+# All rights reserved.
+#
+# This software is licensed as described in the file COPYING, which
+# you should have received as part of this distribution. The terms
+# are also available at http://genshi.edgewall.org/wiki/License.
+#
+# This software consists of voluntary contributions made by many
+# individuals. For the exact contribution history, see the revision
+# history and logs, available at http://genshi.edgewall.org/log/.
+
+"""Implementation of the template engine."""
+
+from genshi.template.base import Context, Template, TemplateError, \
+ TemplateRuntimeError, TemplateSyntaxError, \
+ BadDirectiveError
+from genshi.template.loader import TemplateLoader, TemplateNotFound
+from genshi.template.markup import MarkupTemplate
+from genshi.template.text import TextTemplate, OldTextTemplate, NewTextTemplate
+
+__docformat__ = 'restructuredtext en'
diff --git a/websdk/genshi/template/_ast24.py b/websdk/genshi/template/_ast24.py
new file mode 100644
index 0000000..05d241b
--- /dev/null
+++ b/websdk/genshi/template/_ast24.py
@@ -0,0 +1,446 @@
+# Generated automatically, please do not edit
+# Generator can be found in Genshi SVN, scripts/ast-generator.py
+
+__version__ = 43614
+
+class AST(object):
+ _fields = None
+ __doc__ = None
+
+class operator(AST):
+ _fields = None
+ __doc__ = None
+ _attributes = []
+class Add(operator):
+ _fields = None
+ __doc__ = None
+
+class boolop(AST):
+ _fields = None
+ __doc__ = None
+ _attributes = []
+class And(boolop):
+ _fields = None
+ __doc__ = None
+
+class stmt(AST):
+ _fields = None
+ __doc__ = None
+ _attributes = ['lineno', 'col_offset']
+class Assert(stmt):
+ _fields = ('test', 'msg')
+ __doc__ = None
+
+class Assign(stmt):
+ _fields = ('targets', 'value')
+ __doc__ = None
+
+class expr(AST):
+ _fields = None
+ __doc__ = None
+ _attributes = ['lineno', 'col_offset']
+class Attribute(expr):
+ _fields = ('value', 'attr', 'ctx')
+ __doc__ = None
+
+class AugAssign(stmt):
+ _fields = ('target', 'op', 'value')
+ __doc__ = None
+
+class expr_context(AST):
+ _fields = None
+ __doc__ = None
+ _attributes = []
+class AugLoad(expr_context):
+ _fields = None
+ __doc__ = None
+
+class AugStore(expr_context):
+ _fields = None
+ __doc__ = None
+
+class BinOp(expr):
+ _fields = ('left', 'op', 'right')
+ __doc__ = None
+
+class BitAnd(operator):
+ _fields = None
+ __doc__ = None
+
+class BitOr(operator):
+ _fields = None
+ __doc__ = None
+
+class BitXor(operator):
+ _fields = None
+ __doc__ = None
+
+class BoolOp(expr):
+ _fields = ('op', 'values')
+ __doc__ = None
+
+class Break(stmt):
+ _fields = None
+ __doc__ = None
+
+class Call(expr):
+ _fields = ('func', 'args', 'keywords', 'starargs', 'kwargs')
+ __doc__ = None
+
+class ClassDef(stmt):
+ _fields = ('name', 'bases', 'body')
+ __doc__ = None
+
+class Compare(expr):
+ _fields = ('left', 'ops', 'comparators')
+ __doc__ = None
+
+class Continue(stmt):
+ _fields = None
+ __doc__ = None
+
+class Del(expr_context):
+ _fields = None
+ __doc__ = None
+
+class Delete(stmt):
+ _fields = ('targets',)
+ __doc__ = None
+
+class Dict(expr):
+ _fields = ('keys', 'values')
+ __doc__ = None
+
+class Div(operator):
+ _fields = None
+ __doc__ = None
+
+class slice(AST):
+ _fields = None
+ __doc__ = None
+ _attributes = []
+class Ellipsis(slice):
+ _fields = None
+ __doc__ = None
+
+class cmpop(AST):
+ _fields = None
+ __doc__ = None
+ _attributes = []
+class Eq(cmpop):
+ _fields = None
+ __doc__ = None
+
+class Exec(stmt):
+ _fields = ('body', 'globals', 'locals')
+ __doc__ = None
+
+class Expr(stmt):
+ _fields = ('value',)
+ __doc__ = None
+
+class mod(AST):
+ _fields = None
+ __doc__ = None
+ _attributes = []
+class Expression(mod):
+ _fields = ('body',)
+ __doc__ = None
+
+class ExtSlice(slice):
+ _fields = ('dims',)
+ __doc__ = None
+
+class FloorDiv(operator):
+ _fields = None
+ __doc__ = None
+
+class For(stmt):
+ _fields = ('target', 'iter', 'body', 'orelse')
+ __doc__ = None
+
+class FunctionDef(stmt):
+ _fields = ('name', 'args', 'body', 'decorators')
+ __doc__ = None
+
+class GeneratorExp(expr):
+ _fields = ('elt', 'generators')
+ __doc__ = None
+
+class Global(stmt):
+ _fields = ('names',)
+ __doc__ = None
+
+class Gt(cmpop):
+ _fields = None
+ __doc__ = None
+
+class GtE(cmpop):
+ _fields = None
+ __doc__ = None
+
+class If(stmt):
+ _fields = ('test', 'body', 'orelse')
+ __doc__ = None
+
+class IfExp(expr):
+ _fields = ('test', 'body', 'orelse')
+ __doc__ = None
+
+class Import(stmt):
+ _fields = ('names',)
+ __doc__ = None
+
+class ImportFrom(stmt):
+ _fields = ('module', 'names', 'level')
+ __doc__ = None
+
+class In(cmpop):
+ _fields = None
+ __doc__ = None
+
+class Index(slice):
+ _fields = ('value',)
+ __doc__ = None
+
+class Interactive(mod):
+ _fields = ('body',)
+ __doc__ = None
+
+class unaryop(AST):
+ _fields = None
+ __doc__ = None
+ _attributes = []
+class Invert(unaryop):
+ _fields = None
+ __doc__ = None
+
+class Is(cmpop):
+ _fields = None
+ __doc__ = None
+
+class IsNot(cmpop):
+ _fields = None
+ __doc__ = None
+
+class LShift(operator):
+ _fields = None
+ __doc__ = None
+
+class Lambda(expr):
+ _fields = ('args', 'body')
+ __doc__ = None
+
+class List(expr):
+ _fields = ('elts', 'ctx')
+ __doc__ = None
+
+class ListComp(expr):
+ _fields = ('elt', 'generators')
+ __doc__ = None
+
+class Load(expr_context):
+ _fields = None
+ __doc__ = None
+
+class Lt(cmpop):
+ _fields = None
+ __doc__ = None
+
+class LtE(cmpop):
+ _fields = None
+ __doc__ = None
+
+class Mod(operator):
+ _fields = None
+ __doc__ = None
+
+class Module(mod):
+ _fields = ('body',)
+ __doc__ = None
+
+class Mult(operator):
+ _fields = None
+ __doc__ = None
+
+class Name(expr):
+ _fields = ('id', 'ctx')
+ __doc__ = None
+
+class Not(unaryop):
+ _fields = None
+ __doc__ = None
+
+class NotEq(cmpop):
+ _fields = None
+ __doc__ = None
+
+class NotIn(cmpop):
+ _fields = None
+ __doc__ = None
+
+class Num(expr):
+ _fields = ('n',)
+ __doc__ = None
+
+class Or(boolop):
+ _fields = None
+ __doc__ = None
+
+class Param(expr_context):
+ _fields = None
+ __doc__ = None
+
+class Pass(stmt):
+ _fields = None
+ __doc__ = None
+
+class Pow(operator):
+ _fields = None
+ __doc__ = None
+
+class Print(stmt):
+ _fields = ('dest', 'values', 'nl')
+ __doc__ = None
+
+class RShift(operator):
+ _fields = None
+ __doc__ = None
+
+class Raise(stmt):
+ _fields = ('type', 'inst', 'tback')
+ __doc__ = None
+
+class Repr(expr):
+ _fields = ('value',)
+ __doc__ = None
+
+class Return(stmt):
+ _fields = ('value',)
+ __doc__ = None
+
+class Slice(slice):
+ _fields = ('lower', 'upper', 'step')
+ __doc__ = None
+
+class Store(expr_context):
+ _fields = None
+ __doc__ = None
+
+class Str(expr):
+ _fields = ('s',)
+ __doc__ = None
+
+class Sub(operator):
+ _fields = None
+ __doc__ = None
+
+class Subscript(expr):
+ _fields = ('value', 'slice', 'ctx')
+ __doc__ = None
+
+class Suite(mod):
+ _fields = ('body',)
+ __doc__ = None
+
+class TryExcept(stmt):
+ _fields = ('body', 'handlers', 'orelse')
+ __doc__ = None
+
+class TryFinally(stmt):
+ _fields = ('body', 'finalbody')
+ __doc__ = None
+
+class Tuple(expr):
+ _fields = ('elts', 'ctx')
+ __doc__ = None
+
+class UAdd(unaryop):
+ _fields = None
+ __doc__ = None
+
+class USub(unaryop):
+ _fields = None
+ __doc__ = None
+
+class UnaryOp(expr):
+ _fields = ('op', 'operand')
+ __doc__ = None
+
+class While(stmt):
+ _fields = ('test', 'body', 'orelse')
+ __doc__ = None
+
+class With(stmt):
+ _fields = ('context_expr', 'optional_vars', 'body')
+ __doc__ = None
+
+class Yield(expr):
+ _fields = ('value',)
+ __doc__ = None
+
+class alias(AST):
+ _fields = ('name', 'asname')
+ __doc__ = None
+
+class arguments(AST):
+ _fields = ('args', 'vararg', 'kwarg', 'defaults')
+ __doc__ = None
+
+class boolop(AST):
+ _fields = None
+ __doc__ = None
+ _attributes = []
+
+class cmpop(AST):
+ _fields = None
+ __doc__ = None
+ _attributes = []
+
+class comprehension(AST):
+ _fields = ('target', 'iter', 'ifs')
+ __doc__ = None
+
+class excepthandler(AST):
+ _fields = ('type', 'name', 'body', 'lineno', 'col_offset')
+ __doc__ = None
+
+class expr(AST):
+ _fields = None
+ __doc__ = None
+ _attributes = ['lineno', 'col_offset']
+
+class expr_context(AST):
+ _fields = None
+ __doc__ = None
+ _attributes = []
+
+class keyword(AST):
+ _fields = ('arg', 'value')
+ __doc__ = None
+
+class mod(AST):
+ _fields = None
+ __doc__ = None
+ _attributes = []
+
+class operator(AST):
+ _fields = None
+ __doc__ = None
+ _attributes = []
+
+class slice(AST):
+ _fields = None
+ __doc__ = None
+ _attributes = []
+
+class stmt(AST):
+ _fields = None
+ __doc__ = None
+ _attributes = ['lineno', 'col_offset']
+
+class unaryop(AST):
+ _fields = None
+ __doc__ = None
+ _attributes = []
+
diff --git a/websdk/genshi/template/ast24.py b/websdk/genshi/template/ast24.py
new file mode 100644
index 0000000..af6dce9
--- /dev/null
+++ b/websdk/genshi/template/ast24.py
@@ -0,0 +1,505 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2008-2009 Edgewall Software
+# All rights reserved.
+#
+# This software is licensed as described in the file COPYING, which
+# you should have received as part of this distribution. The terms
+# are also available at http://genshi.edgewall.org/wiki/License.
+#
+# This software consists of voluntary contributions made by many
+# individuals. For the exact contribution history, see the revision
+# history and logs, available at http://genshi.edgewall.org/log/.
+
+"""Emulation of the proper abstract syntax tree API for Python 2.4."""
+
+import compiler
+import compiler.ast
+
+from genshi.template import _ast24 as _ast
+
+__all__ = ['_ast', 'parse']
+__docformat__ = 'restructuredtext en'
+
+
+def _new(cls, *args, **kwargs):
+ ret = cls()
+ if ret._fields:
+ for attr, value in zip(ret._fields, args):
+ if attr in kwargs:
+ raise ValueError('Field set both in args and kwargs')
+ setattr(ret, attr, value)
+ for attr in kwargs:
+ if (getattr(ret, '_fields', None) and attr in ret._fields) \
+ or (getattr(ret, '_attributes', None) and
+ attr in ret._attributes):
+ setattr(ret, attr, kwargs[attr])
+ return ret
+
+
+class ASTUpgrader(object):
+ """Transformer changing structure of Python 2.4 ASTs to
+ Python 2.5 ones.
+
+ Transforms ``compiler.ast`` Abstract Syntax Tree to builtin ``_ast``.
+ It can use fake`` _ast`` classes and this way allow ``_ast`` emulation
+ in Python 2.4.
+ """
+
+ def __init__(self):
+ self.out_flags = None
+ self.lines = [-1]
+
+ def _new(self, *args, **kwargs):
+ return _new(lineno = self.lines[-1], *args, **kwargs)
+
+ def visit(self, node):
+ if node is None:
+ return None
+ if type(node) is tuple:
+ return tuple([self.visit(n) for n in node])
+ lno = getattr(node, 'lineno', None)
+ if lno is not None:
+ self.lines.append(lno)
+ visitor = getattr(self, 'visit_%s' % node.__class__.__name__, None)
+ if visitor is None:
+ raise Exception('Unhandled node type %r' % type(node))
+
+ retval = visitor(node)
+ if lno is not None:
+ self.lines.pop()
+ return retval
+
+ def visit_Module(self, node):
+ body = self.visit(node.node)
+ if node.doc:
+ body = [self._new(_ast.Expr, self._new(_ast.Str, node.doc))] + body
+ return self._new(_ast.Module, body)
+
+ def visit_Expression(self, node):
+ return self._new(_ast.Expression, self.visit(node.node))
+
+ def _extract_args(self, node):
+ tab = node.argnames[:]
+ if node.flags & compiler.ast.CO_VARKEYWORDS:
+ kwarg = tab[-1]
+ tab = tab[:-1]
+ else:
+ kwarg = None
+
+ if node.flags & compiler.ast.CO_VARARGS:
+ vararg = tab[-1]
+ tab = tab[:-1]
+ else:
+ vararg = None
+
+ def _tup(t):
+ if isinstance(t, str):
+ return self._new(_ast.Name, t, _ast.Store())
+ elif isinstance(t, tuple):
+ elts = [_tup(x) for x in t]
+ return self._new(_ast.Tuple, elts, _ast.Store())
+ else:
+ raise NotImplemented
+
+ args = []
+ for arg in tab:
+ if isinstance(arg, str):
+ args.append(self._new(_ast.Name, arg, _ast.Param()))
+ elif isinstance(arg, tuple):
+ args.append(_tup(arg))
+ else:
+ assert False, node.__class__
+
+ defaults = [self.visit(d) for d in node.defaults]
+ return self._new(_ast.arguments, args, vararg, kwarg, defaults)
+
+
+ def visit_Function(self, node):
+ if getattr(node, 'decorators', ()):
+ decorators = [self.visit(d) for d in node.decorators.nodes]
+ else:
+ decorators = []
+
+ args = self._extract_args(node)
+ body = self.visit(node.code)
+ if node.doc:
+ body = [self._new(_ast.Expr, self._new(_ast.Str, node.doc))] + body
+ return self._new(_ast.FunctionDef, node.name, args, body, decorators)
+
+ def visit_Class(self, node):
+ #self.name_types.append(_ast.Load)
+ bases = [self.visit(b) for b in node.bases]
+ #self.name_types.pop()
+ body = self.visit(node.code)
+ if node.doc:
+ body = [self._new(_ast.Expr, self._new(_ast.Str, node.doc))] + body
+ return self._new(_ast.ClassDef, node.name, bases, body)
+
+ def visit_Return(self, node):
+ return self._new(_ast.Return, self.visit(node.value))
+
+ def visit_Assign(self, node):
+ #self.name_types.append(_ast.Store)
+ targets = [self.visit(t) for t in node.nodes]
+ #self.name_types.pop()
+ return self._new(_ast.Assign, targets, self.visit(node.expr))
+
+ aug_operators = {
+ '+=': _ast.Add,
+ '/=': _ast.Div,
+ '//=': _ast.FloorDiv,
+ '<<=': _ast.LShift,
+ '%=': _ast.Mod,
+ '*=': _ast.Mult,
+ '**=': _ast.Pow,
+ '>>=': _ast.RShift,
+ '-=': _ast.Sub,
+ }
+
+ def visit_AugAssign(self, node):
+ target = self.visit(node.node)
+
+ # Because it's AugAssign target can't be list nor tuple
+ # so we only have to change context of one node
+ target.ctx = _ast.Store()
+ op = self.aug_operators[node.op]()
+ return self._new(_ast.AugAssign, target, op, self.visit(node.expr))
+
+ def _visit_Print(nl):
+ def _visit(self, node):
+ values = [self.visit(v) for v in node.nodes]
+ return self._new(_ast.Print, self.visit(node.dest), values, nl)
+ return _visit
+
+ visit_Print = _visit_Print(False)
+ visit_Printnl = _visit_Print(True)
+ del _visit_Print
+
+ def visit_For(self, node):
+ return self._new(_ast.For, self.visit(node.assign), self.visit(node.list),
+ self.visit(node.body), self.visit(node.else_))
+
+ def visit_While(self, node):
+ return self._new(_ast.While, self.visit(node.test), self.visit(node.body),
+ self.visit(node.else_))
+
+ def visit_If(self, node):
+ def _level(tests, else_):
+ test = self.visit(tests[0][0])
+ body = self.visit(tests[0][1])
+ if len(tests) == 1:
+ orelse = self.visit(else_)
+ else:
+ orelse = [_level(tests[1:], else_)]
+ return self._new(_ast.If, test, body, orelse)
+ return _level(node.tests, node.else_)
+
+ def visit_With(self, node):
+ return self._new(_ast.With, self.visit(node.expr),
+ self.visit(node.vars), self.visit(node.body))
+
+ def visit_Raise(self, node):
+ return self._new(_ast.Raise, self.visit(node.expr1),
+ self.visit(node.expr2), self.visit(node.expr3))
+
+ def visit_TryExcept(self, node):
+ handlers = []
+ for type, name, body in node.handlers:
+ handlers.append(self._new(_ast.excepthandler, self.visit(type),
+ self.visit(name), self.visit(body)))
+ return self._new(_ast.TryExcept, self.visit(node.body),
+ handlers, self.visit(node.else_))
+
+ def visit_TryFinally(self, node):
+ return self._new(_ast.TryFinally, self.visit(node.body),
+ self.visit(node.final))
+
+ def visit_Assert(self, node):
+ return self._new(_ast.Assert, self.visit(node.test), self.visit(node.fail))
+
+ def visit_Import(self, node):
+ names = [self._new(_ast.alias, n[0], n[1]) for n in node.names]
+ return self._new(_ast.Import, names)
+
+ def visit_From(self, node):
+ names = [self._new(_ast.alias, n[0], n[1]) for n in node.names]
+ return self._new(_ast.ImportFrom, node.modname, names, 0)
+
+ def visit_Exec(self, node):
+ return self._new(_ast.Exec, self.visit(node.expr),
+ self.visit(node.locals), self.visit(node.globals))
+
+ def visit_Global(self, node):
+ return self._new(_ast.Global, node.names[:])
+
+ def visit_Discard(self, node):
+ return self._new(_ast.Expr, self.visit(node.expr))
+
+ def _map_class(to):
+ def _visit(self, node):
+ return self._new(to)
+ return _visit
+
+ visit_Pass = _map_class(_ast.Pass)
+ visit_Break = _map_class(_ast.Break)
+ visit_Continue = _map_class(_ast.Continue)
+
+ def _visit_BinOperator(opcls):
+ def _visit(self, node):
+ return self._new(_ast.BinOp, self.visit(node.left),
+ opcls(), self.visit(node.right))
+ return _visit
+ visit_Add = _visit_BinOperator(_ast.Add)
+ visit_Div = _visit_BinOperator(_ast.Div)
+ visit_FloorDiv = _visit_BinOperator(_ast.FloorDiv)
+ visit_LeftShift = _visit_BinOperator(_ast.LShift)
+ visit_Mod = _visit_BinOperator(_ast.Mod)
+ visit_Mul = _visit_BinOperator(_ast.Mult)
+ visit_Power = _visit_BinOperator(_ast.Pow)
+ visit_RightShift = _visit_BinOperator(_ast.RShift)
+ visit_Sub = _visit_BinOperator(_ast.Sub)
+ del _visit_BinOperator
+
+ def _visit_BitOperator(opcls):
+ def _visit(self, node):
+ def _make(nodes):
+ if len(nodes) == 1:
+ return self.visit(nodes[0])
+ left = _make(nodes[:-1])
+ right = self.visit(nodes[-1])
+ return self._new(_ast.BinOp, left, opcls(), right)
+ return _make(node.nodes)
+ return _visit
+ visit_Bitand = _visit_BitOperator(_ast.BitAnd)
+ visit_Bitor = _visit_BitOperator(_ast.BitOr)
+ visit_Bitxor = _visit_BitOperator(_ast.BitXor)
+ del _visit_BitOperator
+
+ def _visit_UnaryOperator(opcls):
+ def _visit(self, node):
+ return self._new(_ast.UnaryOp, opcls(), self.visit(node.expr))
+ return _visit
+
+ visit_Invert = _visit_UnaryOperator(_ast.Invert)
+ visit_Not = _visit_UnaryOperator(_ast.Not)
+ visit_UnaryAdd = _visit_UnaryOperator(_ast.UAdd)
+ visit_UnarySub = _visit_UnaryOperator(_ast.USub)
+ del _visit_UnaryOperator
+
+ def _visit_BoolOperator(opcls):
+ def _visit(self, node):
+ values = [self.visit(n) for n in node.nodes]
+ return self._new(_ast.BoolOp, opcls(), values)
+ return _visit
+ visit_And = _visit_BoolOperator(_ast.And)
+ visit_Or = _visit_BoolOperator(_ast.Or)
+ del _visit_BoolOperator
+
+ cmp_operators = {
+ '==': _ast.Eq,
+ '!=': _ast.NotEq,
+ '<': _ast.Lt,
+ '<=': _ast.LtE,
+ '>': _ast.Gt,
+ '>=': _ast.GtE,
+ 'is': _ast.Is,
+ 'is not': _ast.IsNot,
+ 'in': _ast.In,
+ 'not in': _ast.NotIn,
+ }
+
+ def visit_Compare(self, node):
+ left = self.visit(node.expr)
+ ops = []
+ comparators = []
+ for optype, expr in node.ops:
+ ops.append(self.cmp_operators[optype]())
+ comparators.append(self.visit(expr))
+ return self._new(_ast.Compare, left, ops, comparators)
+
+ def visit_Lambda(self, node):
+ args = self._extract_args(node)
+ body = self.visit(node.code)
+ return self._new(_ast.Lambda, args, body)
+
+ def visit_IfExp(self, node):
+ return self._new(_ast.IfExp, self.visit(node.test), self.visit(node.then),
+ self.visit(node.else_))
+
+ def visit_Dict(self, node):
+ keys = [self.visit(x[0]) for x in node.items]
+ values = [self.visit(x[1]) for x in node.items]
+ return self._new(_ast.Dict, keys, values)
+
+ def visit_ListComp(self, node):
+ generators = [self.visit(q) for q in node.quals]
+ return self._new(_ast.ListComp, self.visit(node.expr), generators)
+
+ def visit_GenExprInner(self, node):
+ generators = [self.visit(q) for q in node.quals]
+ return self._new(_ast.GeneratorExp, self.visit(node.expr), generators)
+
+ def visit_GenExpr(self, node):
+ return self.visit(node.code)
+
+ def visit_GenExprFor(self, node):
+ ifs = [self.visit(i) for i in node.ifs]
+ return self._new(_ast.comprehension, self.visit(node.assign),
+ self.visit(node.iter), ifs)
+
+ def visit_ListCompFor(self, node):
+ ifs = [self.visit(i) for i in node.ifs]
+ return self._new(_ast.comprehension, self.visit(node.assign),
+ self.visit(node.list), ifs)
+
+ def visit_GenExprIf(self, node):
+ return self.visit(node.test)
+ visit_ListCompIf = visit_GenExprIf
+
+ def visit_Yield(self, node):
+ return self._new(_ast.Yield, self.visit(node.value))
+
+ def visit_CallFunc(self, node):
+ args = []
+ keywords = []
+ for arg in node.args:
+ if isinstance(arg, compiler.ast.Keyword):
+ keywords.append(self._new(_ast.keyword, arg.name,
+ self.visit(arg.expr)))
+ else:
+ args.append(self.visit(arg))
+ return self._new(_ast.Call, self.visit(node.node), args, keywords,
+ self.visit(node.star_args), self.visit(node.dstar_args))
+
+ def visit_Backquote(self, node):
+ return self._new(_ast.Repr, self.visit(node.expr))
+
+ def visit_Const(self, node):
+ if node.value is None: # appears in slices
+ return None
+ elif isinstance(node.value, basestring):
+ return self._new(_ast.Str, node.value)
+ else:
+ return self._new(_ast.Num, node.value)
+
+ def visit_Name(self, node):
+ return self._new(_ast.Name, node.name, _ast.Load())
+
+ def visit_Getattr(self, node):
+ return self._new(_ast.Attribute, self.visit(node.expr), node.attrname,
+ _ast.Load())
+
+ def visit_Tuple(self, node):
+ nodes = [self.visit(n) for n in node.nodes]
+ return self._new(_ast.Tuple, nodes, _ast.Load())
+
+ def visit_List(self, node):
+ nodes = [self.visit(n) for n in node.nodes]
+ return self._new(_ast.List, nodes, _ast.Load())
+
+ def get_ctx(self, flags):
+ if flags == 'OP_DELETE':
+ return _ast.Del()
+ elif flags == 'OP_APPLY':
+ return _ast.Load()
+ elif flags == 'OP_ASSIGN':
+ return _ast.Store()
+ else:
+ # FIXME Exception here
+ assert False, repr(flags)
+
+ def visit_AssName(self, node):
+ self.out_flags = node.flags
+ ctx = self.get_ctx(node.flags)
+ return self._new(_ast.Name, node.name, ctx)
+
+ def visit_AssAttr(self, node):
+ self.out_flags = node.flags
+ ctx = self.get_ctx(node.flags)
+ return self._new(_ast.Attribute, self.visit(node.expr),
+ node.attrname, ctx)
+
+ def _visit_AssCollection(cls):
+ def _visit(self, node):
+ flags = None
+ elts = []
+ for n in node.nodes:
+ elts.append(self.visit(n))
+ if flags is None:
+ flags = self.out_flags
+ else:
+ assert flags == self.out_flags
+ self.out_flags = flags
+ ctx = self.get_ctx(flags)
+ return self._new(cls, elts, ctx)
+ return _visit
+
+ visit_AssList = _visit_AssCollection(_ast.List)
+ visit_AssTuple = _visit_AssCollection(_ast.Tuple)
+ del _visit_AssCollection
+
+ def visit_Slice(self, node):
+ lower = self.visit(node.lower)
+ upper = self.visit(node.upper)
+ ctx = self.get_ctx(node.flags)
+ self.out_flags = node.flags
+ return self._new(_ast.Subscript, self.visit(node.expr),
+ self._new(_ast.Slice, lower, upper, None), ctx)
+
+ def visit_Subscript(self, node):
+ ctx = self.get_ctx(node.flags)
+ subs = [self.visit(s) for s in node.subs]
+
+ advanced = (_ast.Slice, _ast.Ellipsis)
+ slices = []
+ nonindex = False
+ for sub in subs:
+ if isinstance(sub, advanced):
+ nonindex = True
+ slices.append(sub)
+ else:
+ slices.append(self._new(_ast.Index, sub))
+ if len(slices) == 1:
+ slice = slices[0]
+ elif nonindex:
+ slice = self._new(_ast.ExtSlice, slices)
+ else:
+ slice = self._new(_ast.Tuple, slices, _ast.Load())
+
+ self.out_flags = node.flags
+ return self._new(_ast.Subscript, self.visit(node.expr), slice, ctx)
+
+ def visit_Sliceobj(self, node):
+ a = [self.visit(n) for n in node.nodes + [None]*(3 - len(node.nodes))]
+ return self._new(_ast.Slice, a[0], a[1], a[2])
+
+ def visit_Ellipsis(self, node):
+ return self._new(_ast.Ellipsis)
+
+ def visit_Stmt(self, node):
+ def _check_del(n):
+ # del x is just AssName('x', 'OP_DELETE')
+ # we want to transform it to Delete([Name('x', Del())])
+ dcls = (_ast.Name, _ast.List, _ast.Subscript, _ast.Attribute)
+ if isinstance(n, dcls) and isinstance(n.ctx, _ast.Del):
+ return self._new(_ast.Delete, [n])
+ elif isinstance(n, _ast.Tuple) and isinstance(n.ctx, _ast.Del):
+ # unpack last tuple to avoid making del (x, y, z,);
+ # out of del x, y, z; (there's no difference between
+ # this two in compiler.ast)
+ return self._new(_ast.Delete, n.elts)
+ else:
+ return n
+ def _keep(n):
+ if isinstance(n, _ast.Expr) and n.value is None:
+ return False
+ else:
+ return True
+ return [s for s in [_check_del(self.visit(n)) for n in node.nodes]
+ if _keep(s)]
+
+
+def parse(source, mode):
+ node = compiler.parse(source, mode)
+ return ASTUpgrader().visit(node)
diff --git a/websdk/genshi/template/astutil.py b/websdk/genshi/template/astutil.py
new file mode 100644
index 0000000..c3ad107
--- /dev/null
+++ b/websdk/genshi/template/astutil.py
@@ -0,0 +1,784 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2008-2010 Edgewall Software
+# All rights reserved.
+#
+# This software is licensed as described in the file COPYING, which
+# you should have received as part of this distribution. The terms
+# are also available at http://genshi.edgewall.org/wiki/License.
+#
+# This software consists of voluntary contributions made by many
+# individuals. For the exact contribution history, see the revision
+# history and logs, available at http://genshi.edgewall.org/log/.
+
+"""Support classes for generating code from abstract syntax trees."""
+
+try:
+ import _ast
+except ImportError:
+ from genshi.template.ast24 import _ast, parse
+else:
+ def parse(source, mode):
+ return compile(source, '', mode, _ast.PyCF_ONLY_AST)
+
+
+__docformat__ = 'restructuredtext en'
+
+
+class ASTCodeGenerator(object):
+ """General purpose base class for AST transformations.
+
+ Every visitor method can be overridden to return an AST node that has been
+ altered or replaced in some way.
+ """
+ def __init__(self, tree):
+ self.lines_info = []
+ self.line_info = None
+ self.code = ''
+ self.line = None
+ self.last = None
+ self.indent = 0
+ self.blame_stack = []
+ self.visit(tree)
+ if self.line.strip():
+ self.code += self.line + '\n'
+ self.lines_info.append(self.line_info)
+ self.line = None
+ self.line_info = None
+
+ def _change_indent(self, delta):
+ self.indent += delta
+
+ def _new_line(self):
+ if self.line is not None:
+ self.code += self.line + '\n'
+ self.lines_info.append(self.line_info)
+ self.line = ' '*4*self.indent
+ if len(self.blame_stack) == 0:
+ self.line_info = []
+ self.last = None
+ else:
+ self.line_info = [(0, self.blame_stack[-1],)]
+ self.last = self.blame_stack[-1]
+
+ def _write(self, s):
+ if len(s) == 0:
+ return
+ if len(self.blame_stack) == 0:
+ if self.last is not None:
+ self.last = None
+ self.line_info.append((len(self.line), self.last))
+ else:
+ if self.last != self.blame_stack[-1]:
+ self.last = self.blame_stack[-1]
+ self.line_info.append((len(self.line), self.last))
+ self.line += s
+
+ def visit(self, node):
+ if node is None:
+ return None
+ if type(node) is tuple:
+ return tuple([self.visit(n) for n in node])
+ try:
+ self.blame_stack.append((node.lineno, node.col_offset,))
+ info = True
+ except AttributeError:
+ info = False
+ visitor = getattr(self, 'visit_%s' % node.__class__.__name__, None)
+ if visitor is None:
+ raise Exception('Unhandled node type %r' % type(node))
+ ret = visitor(node)
+ if info:
+ self.blame_stack.pop()
+ return ret
+
+ def visit_Module(self, node):
+ for n in node.body:
+ self.visit(n)
+ visit_Interactive = visit_Module
+ visit_Suite = visit_Module
+
+ def visit_Expression(self, node):
+ self._new_line()
+ return self.visit(node.body)
+
+ # arguments = (expr* args, identifier? vararg,
+ # identifier? kwarg, expr* defaults)
+ def visit_arguments(self, node):
+ first = True
+ no_default_count = len(node.args) - len(node.defaults)
+ for i, arg in enumerate(node.args):
+ if not first:
+ self._write(', ')
+ else:
+ first = False
+ self.visit(arg)
+ if i >= no_default_count:
+ self._write('=')
+ self.visit(node.defaults[i - no_default_count])
+ if getattr(node, 'vararg', None):
+ if not first:
+ self._write(', ')
+ else:
+ first = False
+ self._write('*' + node.vararg)
+ if getattr(node, 'kwarg', None):
+ if not first:
+ self._write(', ')
+ else:
+ first = False
+ self._write('**' + node.kwarg)
+
+ # FunctionDef(identifier name, arguments args,
+ # stmt* body, expr* decorator_list)
+ def visit_FunctionDef(self, node):
+ decarators = ()
+ if hasattr(node, 'decorator_list'):
+ decorators = getattr(node, 'decorator_list')
+ else: # different name in earlier Python versions
+ decorators = getattr(node, 'decorators', ())
+ for decorator in decorators:
+ self._new_line()
+ self._write('@')
+ self.visit(decorator)
+ self._new_line()
+ self._write('def ' + node.name + '(')
+ self.visit(node.args)
+ self._write('):')
+ self._change_indent(1)
+ for statement in node.body:
+ self.visit(statement)
+ self._change_indent(-1)
+
+ # ClassDef(identifier name, expr* bases, stmt* body)
+ def visit_ClassDef(self, node):
+ self._new_line()
+ self._write('class ' + node.name)
+ if node.bases:
+ self._write('(')
+ self.visit(node.bases[0])
+ for base in node.bases[1:]:
+ self._write(', ')
+ self.visit(base)
+ self._write(')')
+ self._write(':')
+ self._change_indent(1)
+ for statement in node.body:
+ self.visit(statement)
+ self._change_indent(-1)
+
+ # Return(expr? value)
+ def visit_Return(self, node):
+ self._new_line()
+ self._write('return')
+ if getattr(node, 'value', None):
+ self._write(' ')
+ self.visit(node.value)
+
+ # Delete(expr* targets)
+ def visit_Delete(self, node):
+ self._new_line()
+ self._write('del ')
+ self.visit(node.targets[0])
+ for target in node.targets[1:]:
+ self._write(', ')
+ self.visit(target)
+
+ # Assign(expr* targets, expr value)
+ def visit_Assign(self, node):
+ self._new_line()
+ for target in node.targets:
+ self.visit(target)
+ self._write(' = ')
+ self.visit(node.value)
+
+ # AugAssign(expr target, operator op, expr value)
+ def visit_AugAssign(self, node):
+ self._new_line()
+ self.visit(node.target)
+ self._write(' ' + self.binary_operators[node.op.__class__] + '= ')
+ self.visit(node.value)
+
+ # Print(expr? dest, expr* values, bool nl)
+ def visit_Print(self, node):
+ self._new_line()
+ self._write('print')
+ if getattr(node, 'dest', None):
+ self._write(' >> ')
+ self.visit(node.dest)
+ if getattr(node, 'values', None):
+ self._write(', ')
+ else:
+ self._write(' ')
+ if getattr(node, 'values', None):
+ self.visit(node.values[0])
+ for value in node.values[1:]:
+ self._write(', ')
+ self.visit(value)
+ if not node.nl:
+ self._write(',')
+
+ # For(expr target, expr iter, stmt* body, stmt* orelse)
+ def visit_For(self, node):
+ self._new_line()
+ self._write('for ')
+ self.visit(node.target)
+ self._write(' in ')
+ self.visit(node.iter)
+ self._write(':')
+ self._change_indent(1)
+ for statement in node.body:
+ self.visit(statement)
+ self._change_indent(-1)
+ if getattr(node, 'orelse', None):
+ self._new_line()
+ self._write('else:')
+ self._change_indent(1)
+ for statement in node.orelse:
+ self.visit(statement)
+ self._change_indent(-1)
+
+ # While(expr test, stmt* body, stmt* orelse)
+ def visit_While(self, node):
+ self._new_line()
+ self._write('while ')
+ self.visit(node.test)
+ self._write(':')
+ self._change_indent(1)
+ for statement in node.body:
+ self.visit(statement)
+ self._change_indent(-1)
+ if getattr(node, 'orelse', None):
+ self._new_line()
+ self._write('else:')
+ self._change_indent(1)
+ for statement in node.orelse:
+ self.visit(statement)
+ self._change_indent(-1)
+
+ # If(expr test, stmt* body, stmt* orelse)
+ def visit_If(self, node):
+ self._new_line()
+ self._write('if ')
+ self.visit(node.test)
+ self._write(':')
+ self._change_indent(1)
+ for statement in node.body:
+ self.visit(statement)
+ self._change_indent(-1)
+ if getattr(node, 'orelse', None):
+ self._new_line()
+ self._write('else:')
+ self._change_indent(1)
+ for statement in node.orelse:
+ self.visit(statement)
+ self._change_indent(-1)
+
+ # With(expr context_expr, expr? optional_vars, stmt* body)
+ def visit_With(self, node):
+ self._new_line()
+ self._write('with ')
+ self.visit(node.context_expr)
+ if getattr(node, 'optional_vars', None):
+ self._write(' as ')
+ self.visit(node.optional_vars)
+ self._write(':')
+ self._change_indent(1)
+ for statement in node.body:
+ self.visit(statement)
+ self._change_indent(-1)
+
+
+ # Raise(expr? type, expr? inst, expr? tback)
+ def visit_Raise(self, node):
+ self._new_line()
+ self._write('raise')
+ if not node.type:
+ return
+ self._write(' ')
+ self.visit(node.type)
+ if not node.inst:
+ return
+ self._write(', ')
+ self.visit(node.inst)
+ if not node.tback:
+ return
+ self._write(', ')
+ self.visit(node.tback)
+
+ # TryExcept(stmt* body, excepthandler* handlers, stmt* orelse)
+ def visit_TryExcept(self, node):
+ self._new_line()
+ self._write('try:')
+ self._change_indent(1)
+ for statement in node.body:
+ self.visit(statement)
+ self._change_indent(-1)
+ if getattr(node, 'handlers', None):
+ for handler in node.handlers:
+ self.visit(handler)
+ self._new_line()
+ if getattr(node, 'orelse', None):
+ self._write('else:')
+ self._change_indent(1)
+ for statement in node.orelse:
+ self.visit(statement)
+ self._change_indent(-1)
+
+ # excepthandler = (expr? type, expr? name, stmt* body)
+ def visit_ExceptHandler(self, node):
+ self._new_line()
+ self._write('except')
+ if getattr(node, 'type', None):
+ self._write(' ')
+ self.visit(node.type)
+ if getattr(node, 'name', None):
+ self._write(', ')
+ self.visit(node.name)
+ self._write(':')
+ self._change_indent(1)
+ for statement in node.body:
+ self.visit(statement)
+ self._change_indent(-1)
+ visit_excepthandler = visit_ExceptHandler
+
+ # TryFinally(stmt* body, stmt* finalbody)
+ def visit_TryFinally(self, node):
+ self._new_line()
+ self._write('try:')
+ self._change_indent(1)
+ for statement in node.body:
+ self.visit(statement)
+ self._change_indent(-1)
+
+ if getattr(node, 'finalbody', None):
+ self._new_line()
+ self._write('finally:')
+ self._change_indent(1)
+ for statement in node.finalbody:
+ self.visit(statement)
+ self._change_indent(-1)
+
+ # Assert(expr test, expr? msg)
+ def visit_Assert(self, node):
+ self._new_line()
+ self._write('assert ')
+ self.visit(node.test)
+ if getattr(node, 'msg', None):
+ self._write(', ')
+ self.visit(node.msg)
+
+ def visit_alias(self, node):
+ self._write(node.name)
+ if getattr(node, 'asname', None):
+ self._write(' as ')
+ self._write(node.asname)
+
+ # Import(alias* names)
+ def visit_Import(self, node):
+ self._new_line()
+ self._write('import ')
+ self.visit(node.names[0])
+ for name in node.names[1:]:
+ self._write(', ')
+ self.visit(name)
+
+ # ImportFrom(identifier module, alias* names, int? level)
+ def visit_ImportFrom(self, node):
+ self._new_line()
+ self._write('from ')
+ if node.level:
+ self._write('.' * node.level)
+ self._write(node.module)
+ self._write(' import ')
+ self.visit(node.names[0])
+ for name in node.names[1:]:
+ self._write(', ')
+ self.visit(name)
+
+ # Exec(expr body, expr? globals, expr? locals)
+ def visit_Exec(self, node):
+ self._new_line()
+ self._write('exec ')
+ self.visit(node.body)
+ if not node.globals:
+ return
+ self._write(', ')
+ self.visit(node.globals)
+ if not node.locals:
+ return
+ self._write(', ')
+ self.visit(node.locals)
+
+ # Global(identifier* names)
+ def visit_Global(self, node):
+ self._new_line()
+ self._write('global ')
+ self.visit(node.names[0])
+ for name in node.names[1:]:
+ self._write(', ')
+ self.visit(name)
+
+ # Expr(expr value)
+ def visit_Expr(self, node):
+ self._new_line()
+ self.visit(node.value)
+
+ # Pass
+ def visit_Pass(self, node):
+ self._new_line()
+ self._write('pass')
+
+ # Break
+ def visit_Break(self, node):
+ self._new_line()
+ self._write('break')
+
+ # Continue
+ def visit_Continue(self, node):
+ self._new_line()
+ self._write('continue')
+
+ ### EXPRESSIONS
+ def with_parens(f):
+ def _f(self, node):
+ self._write('(')
+ f(self, node)
+ self._write(')')
+ return _f
+
+ bool_operators = {_ast.And: 'and', _ast.Or: 'or'}
+
+ # BoolOp(boolop op, expr* values)
+ @with_parens
+ def visit_BoolOp(self, node):
+ joiner = ' ' + self.bool_operators[node.op.__class__] + ' '
+ self.visit(node.values[0])
+ for value in node.values[1:]:
+ self._write(joiner)
+ self.visit(value)
+
+ binary_operators = {
+ _ast.Add: '+',
+ _ast.Sub: '-',
+ _ast.Mult: '*',
+ _ast.Div: '/',
+ _ast.Mod: '%',
+ _ast.Pow: '**',
+ _ast.LShift: '<<',
+ _ast.RShift: '>>',
+ _ast.BitOr: '|',
+ _ast.BitXor: '^',
+ _ast.BitAnd: '&',
+ _ast.FloorDiv: '//'
+ }
+
+ # BinOp(expr left, operator op, expr right)
+ @with_parens
+ def visit_BinOp(self, node):
+ self.visit(node.left)
+ self._write(' ' + self.binary_operators[node.op.__class__] + ' ')
+ self.visit(node.right)
+
+ unary_operators = {
+ _ast.Invert: '~',
+ _ast.Not: 'not',
+ _ast.UAdd: '+',
+ _ast.USub: '-',
+ }
+
+ # UnaryOp(unaryop op, expr operand)
+ def visit_UnaryOp(self, node):
+ self._write(self.unary_operators[node.op.__class__] + ' ')
+ self.visit(node.operand)
+
+ # Lambda(arguments args, expr body)
+ @with_parens
+ def visit_Lambda(self, node):
+ self._write('lambda ')
+ self.visit(node.args)
+ self._write(': ')
+ self.visit(node.body)
+
+ # IfExp(expr test, expr body, expr orelse)
+ @with_parens
+ def visit_IfExp(self, node):
+ self.visit(node.body)
+ self._write(' if ')
+ self.visit(node.test)
+ self._write(' else ')
+ self.visit(node.orelse)
+
+ # Dict(expr* keys, expr* values)
+ def visit_Dict(self, node):
+ self._write('{')
+ for key, value in zip(node.keys, node.values):
+ self.visit(key)
+ self._write(': ')
+ self.visit(value)
+ self._write(', ')
+ self._write('}')
+
+ # ListComp(expr elt, comprehension* generators)
+ def visit_ListComp(self, node):
+ self._write('[')
+ self.visit(node.elt)
+ for generator in node.generators:
+ # comprehension = (expr target, expr iter, expr* ifs)
+ self._write(' for ')
+ self.visit(generator.target)
+ self._write(' in ')
+ self.visit(generator.iter)
+ for ifexpr in generator.ifs:
+ self._write(' if ')
+ self.visit(ifexpr)
+ self._write(']')
+
+ # GeneratorExp(expr elt, comprehension* generators)
+ def visit_GeneratorExp(self, node):
+ self._write('(')
+ self.visit(node.elt)
+ for generator in node.generators:
+ # comprehension = (expr target, expr iter, expr* ifs)
+ self._write(' for ')
+ self.visit(generator.target)
+ self._write(' in ')
+ self.visit(generator.iter)
+ for ifexpr in generator.ifs:
+ self._write(' if ')
+ self.visit(ifexpr)
+ self._write(')')
+
+ # Yield(expr? value)
+ def visit_Yield(self, node):
+ self._write('yield')
+ if getattr(node, 'value', None):
+ self._write(' ')
+ self.visit(node.value)
+
+ comparision_operators = {
+ _ast.Eq: '==',
+ _ast.NotEq: '!=',
+ _ast.Lt: '<',
+ _ast.LtE: '<=',
+ _ast.Gt: '>',
+ _ast.GtE: '>=',
+ _ast.Is: 'is',
+ _ast.IsNot: 'is not',
+ _ast.In: 'in',
+ _ast.NotIn: 'not in',
+ }
+
+ # Compare(expr left, cmpop* ops, expr* comparators)
+ @with_parens
+ def visit_Compare(self, node):
+ self.visit(node.left)
+ for op, comparator in zip(node.ops, node.comparators):
+ self._write(' ' + self.comparision_operators[op.__class__] + ' ')
+ self.visit(comparator)
+
+ # Call(expr func, expr* args, keyword* keywords,
+ # expr? starargs, expr? kwargs)
+ def visit_Call(self, node):
+ self.visit(node.func)
+ self._write('(')
+ first = True
+ for arg in node.args:
+ if not first:
+ self._write(', ')
+ first = False
+ self.visit(arg)
+
+ for keyword in node.keywords:
+ if not first:
+ self._write(', ')
+ first = False
+ # keyword = (identifier arg, expr value)
+ self._write(keyword.arg)
+ self._write('=')
+ self.visit(keyword.value)
+ if getattr(node, 'starargs', None):
+ if not first:
+ self._write(', ')
+ first = False
+ self._write('*')
+ self.visit(node.starargs)
+
+ if getattr(node, 'kwargs', None):
+ if not first:
+ self._write(', ')
+ first = False
+ self._write('**')
+ self.visit(node.kwargs)
+ self._write(')')
+
+ # Repr(expr value)
+ def visit_Repr(self, node):
+ self._write('`')
+ self.visit(node.value)
+ self._write('`')
+
+ # Num(object n)
+ def visit_Num(self, node):
+ self._write(repr(node.n))
+
+ # Str(string s)
+ def visit_Str(self, node):
+ self._write(repr(node.s))
+
+ # Attribute(expr value, identifier attr, expr_context ctx)
+ def visit_Attribute(self, node):
+ self.visit(node.value)
+ self._write('.')
+ self._write(node.attr)
+
+ # Subscript(expr value, slice slice, expr_context ctx)
+ def visit_Subscript(self, node):
+ self.visit(node.value)
+ self._write('[')
+ def _process_slice(node):
+ if isinstance(node, _ast.Ellipsis):
+ self._write('...')
+ elif isinstance(node, _ast.Slice):
+ if getattr(node, 'lower', 'None'):
+ self.visit(node.lower)
+ self._write(':')
+ if getattr(node, 'upper', None):
+ self.visit(node.upper)
+ if getattr(node, 'step', None):
+ self._write(':')
+ self.visit(node.step)
+ elif isinstance(node, _ast.Index):
+ self.visit(node.value)
+ elif isinstance(node, _ast.ExtSlice):
+ self.visit(node.dims[0])
+ for dim in node.dims[1:]:
+ self._write(', ')
+ self.visit(dim)
+ else:
+ raise NotImplemented('Slice type not implemented')
+ _process_slice(node.slice)
+ self._write(']')
+
+ # Name(identifier id, expr_context ctx)
+ def visit_Name(self, node):
+ self._write(node.id)
+
+ # List(expr* elts, expr_context ctx)
+ def visit_List(self, node):
+ self._write('[')
+ for elt in node.elts:
+ self.visit(elt)
+ self._write(', ')
+ self._write(']')
+
+ # Tuple(expr *elts, expr_context ctx)
+ def visit_Tuple(self, node):
+ self._write('(')
+ for elt in node.elts:
+ self.visit(elt)
+ self._write(', ')
+ self._write(')')
+
+
+class ASTTransformer(object):
+ """General purpose base class for AST transformations.
+
+ Every visitor method can be overridden to return an AST node that has been
+ altered or replaced in some way.
+ """
+
+ def visit(self, node):
+ if node is None:
+ return None
+ if type(node) is tuple:
+ return tuple([self.visit(n) for n in node])
+ visitor = getattr(self, 'visit_%s' % node.__class__.__name__, None)
+ if visitor is None:
+ return node
+ return visitor(node)
+
+ def _clone(self, node):
+ clone = node.__class__()
+ for name in getattr(clone, '_attributes', ()):
+ try:
+ setattr(clone, 'name', getattr(node, name))
+ except AttributeError:
+ pass
+ for name in clone._fields:
+ try:
+ value = getattr(node, name)
+ except AttributeError:
+ pass
+ else:
+ if value is None:
+ pass
+ elif isinstance(value, list):
+ value = [self.visit(x) for x in value]
+ elif isinstance(value, tuple):
+ value = tuple(self.visit(x) for x in value)
+ else:
+ value = self.visit(value)
+ setattr(clone, name, value)
+ return clone
+
+ visit_Module = _clone
+ visit_Interactive = _clone
+ visit_Expression = _clone
+ visit_Suite = _clone
+
+ visit_FunctionDef = _clone
+ visit_ClassDef = _clone
+ visit_Return = _clone
+ visit_Delete = _clone
+ visit_Assign = _clone
+ visit_AugAssign = _clone
+ visit_Print = _clone
+ visit_For = _clone
+ visit_While = _clone
+ visit_If = _clone
+ visit_With = _clone
+ visit_Raise = _clone
+ visit_TryExcept = _clone
+ visit_TryFinally = _clone
+ visit_Assert = _clone
+ visit_ExceptHandler = _clone
+
+ visit_Import = _clone
+ visit_ImportFrom = _clone
+ visit_Exec = _clone
+ visit_Global = _clone
+ visit_Expr = _clone
+ # Pass, Break, Continue don't need to be copied
+
+ visit_BoolOp = _clone
+ visit_BinOp = _clone
+ visit_UnaryOp = _clone
+ visit_Lambda = _clone
+ visit_IfExp = _clone
+ visit_Dict = _clone
+ visit_ListComp = _clone
+ visit_GeneratorExp = _clone
+ visit_Yield = _clone
+ visit_Compare = _clone
+ visit_Call = _clone
+ visit_Repr = _clone
+ # Num, Str don't need to be copied
+
+ visit_Attribute = _clone
+ visit_Subscript = _clone
+ visit_Name = _clone
+ visit_List = _clone
+ visit_Tuple = _clone
+
+ visit_comprehension = _clone
+ visit_excepthandler = _clone
+ visit_arguments = _clone
+ visit_keyword = _clone
+ visit_alias = _clone
+
+ visit_Slice = _clone
+ visit_ExtSlice = _clone
+ visit_Index = _clone
+
+ del _clone
diff --git a/websdk/genshi/template/base.py b/websdk/genshi/template/base.py
new file mode 100644
index 0000000..202faae
--- /dev/null
+++ b/websdk/genshi/template/base.py
@@ -0,0 +1,634 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2006-2010 Edgewall Software
+# All rights reserved.
+#
+# This software is licensed as described in the file COPYING, which
+# you should have received as part of this distribution. The terms
+# are also available at http://genshi.edgewall.org/wiki/License.
+#
+# This software consists of voluntary contributions made by many
+# individuals. For the exact contribution history, see the revision
+# history and logs, available at http://genshi.edgewall.org/log/.
+
+"""Basic templating functionality."""
+
+from collections import deque
+import os
+from StringIO import StringIO
+import sys
+
+from genshi.core import Attrs, Stream, StreamEventKind, START, TEXT, _ensure
+from genshi.input import ParseError
+
+__all__ = ['Context', 'DirectiveFactory', 'Template', 'TemplateError',
+ 'TemplateRuntimeError', 'TemplateSyntaxError', 'BadDirectiveError']
+__docformat__ = 'restructuredtext en'
+
+
+class TemplateError(Exception):
+ """Base exception class for errors related to template processing."""
+
+ def __init__(self, message, filename=None, lineno=-1, offset=-1):
+ """Create the exception.
+
+ :param message: the error message
+ :param filename: the filename of the template
+ :param lineno: the number of line in the template at which the error
+ occurred
+ :param offset: the column number at which the error occurred
+ """
+ if filename is None:
+ filename = '<string>'
+ self.msg = message #: the error message string
+ if filename != '<string>' or lineno >= 0:
+ message = '%s (%s, line %d)' % (self.msg, filename, lineno)
+ Exception.__init__(self, message)
+ self.filename = filename #: the name of the template file
+ self.lineno = lineno #: the number of the line containing the error
+ self.offset = offset #: the offset on the line
+
+
+class TemplateSyntaxError(TemplateError):
+ """Exception raised when an expression in a template causes a Python syntax
+ error, or the template is not well-formed.
+ """
+
+ def __init__(self, message, filename=None, lineno=-1, offset=-1):
+ """Create the exception
+
+ :param message: the error message
+ :param filename: the filename of the template
+ :param lineno: the number of line in the template at which the error
+ occurred
+ :param offset: the column number at which the error occurred
+ """
+ if isinstance(message, SyntaxError) and message.lineno is not None:
+ message = str(message).replace(' (line %d)' % message.lineno, '')
+ TemplateError.__init__(self, message, filename, lineno)
+
+
+class BadDirectiveError(TemplateSyntaxError):
+ """Exception raised when an unknown directive is encountered when parsing
+ a template.
+
+ An unknown directive is any attribute using the namespace for directives,
+ with a local name that doesn't match any registered directive.
+ """
+
+ def __init__(self, name, filename=None, lineno=-1):
+ """Create the exception
+
+ :param name: the name of the directive
+ :param filename: the filename of the template
+ :param lineno: the number of line in the template at which the error
+ occurred
+ """
+ TemplateSyntaxError.__init__(self, 'bad directive "%s"' % name,
+ filename, lineno)
+
+
+class TemplateRuntimeError(TemplateError):
+ """Exception raised when an the evaluation of a Python expression in a
+ template causes an error.
+ """
+
+
+class Context(object):
+ """Container for template input data.
+
+ A context provides a stack of scopes (represented by dictionaries).
+
+ Template directives such as loops can push a new scope on the stack with
+ data that should only be available inside the loop. When the loop
+ terminates, that scope can get popped off the stack again.
+
+ >>> ctxt = Context(one='foo', other=1)
+ >>> ctxt.get('one')
+ 'foo'
+ >>> ctxt.get('other')
+ 1
+ >>> ctxt.push(dict(one='frost'))
+ >>> ctxt.get('one')
+ 'frost'
+ >>> ctxt.get('other')
+ 1
+ >>> ctxt.pop()
+ {'one': 'frost'}
+ >>> ctxt.get('one')
+ 'foo'
+ """
+
+ def __init__(self, **data):
+ """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 = []
+
+ # 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
+ 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``."""
+ return self.get(name, default)
+ 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
+ has_key = __contains__
+
+ 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())
+
+ 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 update(self, mapping):
+ """Update the context from the mapping provided."""
+ self.frames[0].update(mapping)
+
+ def push(self, data):
+ """Push a new scope on the stack.
+
+ :param data: the data dictionary to push on the context stack.
+ """
+
+ def pop(self):
+ """Pop the top-most scope from the stack."""
+
+
+def _apply_directives(stream, directives, ctxt, vars):
+ """Apply the given directives to the stream.
+
+ :param stream: the stream the directives should be applied to
+ :param directives: the list of directives to apply
+ :param ctxt: the `Context`
+ :param vars: additional variables that should be available when Python
+ code is executed
+ :return: the stream with the given directives applied
+ """
+ if directives:
+ stream = directives[0](iter(stream), directives[1:], ctxt, **vars)
+ return stream
+
+
+def _eval_expr(expr, ctxt, vars=None):
+ """Evaluate the given `Expression` object.
+
+ :param expr: the expression to evaluate
+ :param ctxt: the `Context`
+ :param vars: additional variables that should be available to the
+ expression
+ :return: the result of the evaluation
+ """
+ if vars:
+ ctxt.push(vars)
+ retval = expr.evaluate(ctxt)
+ if vars:
+ ctxt.pop()
+ return retval
+
+
+def _exec_suite(suite, ctxt, vars=None):
+ """Execute the given `Suite` object.
+
+ :param suite: the code suite to execute
+ :param ctxt: the `Context`
+ :param vars: additional variables that should be available to the
+ code
+ """
+ if vars:
+ ctxt.push(vars)
+ ctxt.push({})
+ suite.execute(ctxt)
+ if vars:
+ top = ctxt.pop()
+ ctxt.pop()
+ ctxt.frames[0].update(top)
+
+
+class DirectiveFactoryMeta(type):
+ """Meta class for directive factories."""
+
+ def __new__(cls, name, bases, d):
+ if 'directives' in d:
+ d['_dir_by_name'] = dict(d['directives'])
+ d['_dir_order'] = [directive[1] for directive in d['directives']]
+
+ return type.__new__(cls, name, bases, d)
+
+
+class DirectiveFactory(object):
+ """Base for classes that provide a set of template directives.
+
+ :since: version 0.6
+ """
+ __metaclass__ = DirectiveFactoryMeta
+
+ directives = []
+ """A list of ``(name, cls)`` tuples that define the set of directives
+ provided by this factory.
+ """
+
+ def get_directive(self, name):
+ """Return the directive class for the given name.
+
+ :param name: the directive name as used in the template
+ :return: the directive class
+ :see: `Directive`
+ """
+ return self._dir_by_name.get(name)
+
+ def get_directive_index(self, dir_cls):
+ """Return a key for the given directive class that should be used to
+ sort it among other directives on the same `SUB` event.
+
+ The default implementation simply returns the index of the directive in
+ the `directives` list.
+
+ :param dir_cls: the directive class
+ :return: the sort key
+ """
+ if dir_cls in self._dir_order:
+ return self._dir_order.index(dir_cls)
+ return len(self._dir_order)
+
+
+class Template(DirectiveFactory):
+ """Abstract template base class.
+
+ This class implements most of the template processing model, but does not
+ specify the syntax of templates.
+ """
+
+ EXEC = StreamEventKind('EXEC')
+ """Stream event kind representing a Python code suite to execute."""
+
+ EXPR = StreamEventKind('EXPR')
+ """Stream event kind representing a Python expression."""
+
+ INCLUDE = StreamEventKind('INCLUDE')
+ """Stream event kind representing the inclusion of another template."""
+
+ SUB = StreamEventKind('SUB')
+ """Stream event kind representing a nested stream to which one or more
+ directives should be applied.
+ """
+
+ serializer = None
+ _number_conv = unicode # function used to convert numbers to event data
+
+ def __init__(self, source, filepath=None, filename=None, loader=None,
+ encoding=None, lookup='strict', allow_exec=True):
+ """Initialize a template from either a string, a file-like object, or
+ an already parsed markup stream.
+
+ :param source: a string, file-like object, or markup stream to read the
+ template from
+ :param filepath: the absolute path to the template file
+ :param filename: the path to the template file relative to the search
+ path
+ :param loader: the `TemplateLoader` to use for loading included
+ templates
+ :param encoding: the encoding of the `source`
+ :param lookup: the variable lookup mechanism; either "strict" (the
+ default), "lenient", or a custom lookup class
+ :param allow_exec: whether Python code blocks in templates should be
+ allowed
+
+ :note: Changed in 0.5: Added the `allow_exec` argument
+ """
+ self.filepath = filepath or filename
+ self.filename = filename
+ self.loader = loader
+ self.lookup = lookup
+ self.allow_exec = allow_exec
+ self._init_filters()
+ self._init_loader()
+ self._prepared = False
+
+ if isinstance(source, basestring):
+ source = StringIO(source)
+ else:
+ source = source
+ try:
+ self._stream = self._parse(source, encoding)
+ except ParseError, e:
+ raise TemplateSyntaxError(e.msg, self.filepath, e.lineno, e.offset)
+
+ def __getstate__(self):
+ state = self.__dict__.copy()
+ state['filters'] = []
+ return state
+
+ def __setstate__(self, state):
+ self.__dict__ = state
+ self._init_filters()
+
+ def __repr__(self):
+ return '<%s "%s">' % (type(self).__name__, self.filename)
+
+ def _init_filters(self):
+ self.filters = [self._flatten, self._include]
+
+ def _init_loader(self):
+ if self.loader is None:
+ from genshi.template.loader import TemplateLoader
+ if self.filename:
+ if self.filepath != self.filename:
+ basedir = os.path.normpath(self.filepath)[:-len(
+ os.path.normpath(self.filename))
+ ]
+ else:
+ basedir = os.path.dirname(self.filename)
+ else:
+ basedir = '.'
+ self.loader = TemplateLoader([os.path.abspath(basedir)])
+
+ @property
+ def stream(self):
+ if not self._prepared:
+ self._stream = list(self._prepare(self._stream))
+ self._prepared = True
+ return self._stream
+
+ def _parse(self, source, encoding):
+ """Parse the template.
+
+ The parsing stage parses the template and constructs a list of
+ directives that will be executed in the render stage. The input is
+ split up into literal output (text that does not depend on the context
+ data) and directives or expressions.
+
+ :param source: a file-like object containing the XML source of the
+ template, or an XML event stream
+ :param encoding: the encoding of the `source`
+ """
+ raise NotImplementedError
+
+ def _prepare(self, stream):
+ """Call the `attach` method of every directive found in the template.
+
+ :param stream: the event stream of the template
+ """
+ from genshi.template.loader import TemplateNotFound
+
+ for kind, data, pos in stream:
+ if kind is SUB:
+ directives = []
+ substream = data[1]
+ for _, cls, value, namespaces, pos in sorted(data[0]):
+ directive, substream = cls.attach(self, substream, value,
+ namespaces, pos)
+ if directive:
+ directives.append(directive)
+ substream = self._prepare(substream)
+ if directives:
+ yield kind, (directives, list(substream)), pos
+ else:
+ for event in substream:
+ yield event
+ else:
+ if kind is INCLUDE:
+ href, cls, fallback = data
+ if isinstance(href, basestring) and \
+ not getattr(self.loader, 'auto_reload', True):
+ # If the path to the included template is static, and
+ # auto-reloading is disabled on the template loader,
+ # the template is inlined into the stream
+ try:
+ tmpl = self.loader.load(href, relative_to=pos[0],
+ cls=cls or self.__class__)
+ for event in tmpl.stream:
+ yield event
+ except TemplateNotFound:
+ if fallback is None:
+ raise
+ for event in self._prepare(fallback):
+ yield event
+ continue
+ elif fallback:
+ # Otherwise the include is performed at run time
+ data = href, cls, list(self._prepare(fallback))
+
+ yield kind, data, pos
+
+ def generate(self, *args, **kwargs):
+ """Apply the template to the given context data.
+
+ Any keyword arguments are made available to the template as context
+ data.
+
+ Only one positional argument is accepted: if it is provided, it must be
+ an instance of the `Context` class, and keyword arguments are ignored.
+ This calling style is used for internal processing.
+
+ :return: a markup event stream representing the result of applying
+ the template to the context data.
+ """
+ vars = {}
+ if args:
+ assert len(args) == 1
+ ctxt = args[0]
+ if ctxt is None:
+ ctxt = Context(**kwargs)
+ else:
+ vars = kwargs
+ assert isinstance(ctxt, Context)
+ else:
+ ctxt = Context(**kwargs)
+
+ stream = self.stream
+ for filter_ in self.filters:
+ stream = filter_(iter(stream), ctxt, **vars)
+ return Stream(stream, self.serializer)
+
+ def _flatten(self, stream, ctxt, **vars):
+ number_conv = self._number_conv
+ stack = []
+ push = stack.append
+ pop = stack.pop
+ stream = iter(stream)
+
+ while 1:
+ for kind, data, pos in stream:
+
+ if kind is START and data[1]:
+ # Attributes may still contain expressions in start tags at
+ # this point, so do some evaluation
+ tag, attrs = data
+ new_attrs = []
+ for name, value in attrs:
+ if type(value) is list: # this is an interpolated string
+ values = [event[1]
+ for event in self._flatten(value, ctxt, **vars)
+ if event[0] is TEXT and event[1] is not None
+ ]
+ if not values:
+ continue
+ value = ''.join(values)
+ new_attrs.append((name, value))
+ yield kind, (tag, Attrs(new_attrs)), pos
+
+ elif kind is EXPR:
+ result = _eval_expr(data, ctxt, vars)
+ 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 characters
+ if isinstance(result, basestring):
+ yield TEXT, result, pos
+ elif isinstance(result, (int, float, long)):
+ yield TEXT, number_conv(result), pos
+ elif hasattr(result, '__iter__'):
+ push(stream)
+ stream = _ensure(result)
+ break
+ else:
+ yield TEXT, unicode(result), pos
+
+ elif kind is SUB:
+ # This event is a list of directives and a list of nested
+ # events to which those directives should be applied
+ push(stream)
+ stream = _apply_directives(data[1], data[0], ctxt, vars)
+ break
+
+ elif kind is EXEC:
+ _exec_suite(data, ctxt, vars)
+
+ else:
+ yield kind, data, pos
+
+ else:
+ if not stack:
+ break
+ stream = pop()
+
+ def _include(self, stream, ctxt, **vars):
+ """Internal stream filter that performs inclusion of external
+ template files.
+ """
+ from genshi.template.loader import TemplateNotFound
+
+ for event in stream:
+ if event[0] is INCLUDE:
+ href, cls, fallback = event[1]
+ if not isinstance(href, basestring):
+ parts = []
+ for subkind, subdata, subpos in self._flatten(href, ctxt,
+ **vars):
+ if subkind is TEXT:
+ parts.append(subdata)
+ href = ''.join([x for x in parts if x is not None])
+ try:
+ tmpl = self.loader.load(href, relative_to=event[2][0],
+ cls=cls or self.__class__)
+ for event in tmpl.generate(ctxt, **vars):
+ yield event
+ except TemplateNotFound:
+ if fallback is None:
+ raise
+ for filter_ in self.filters:
+ fallback = filter_(iter(fallback), ctxt, **vars)
+ for event in fallback:
+ yield event
+ else:
+ yield event
+
+
+EXEC = Template.EXEC
+EXPR = Template.EXPR
+INCLUDE = Template.INCLUDE
+SUB = Template.SUB
diff --git a/websdk/genshi/template/directives.py b/websdk/genshi/template/directives.py
new file mode 100644
index 0000000..e2c9424
--- /dev/null
+++ b/websdk/genshi/template/directives.py
@@ -0,0 +1,725 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2006-2009 Edgewall Software
+# All rights reserved.
+#
+# This software is licensed as described in the file COPYING, which
+# you should have received as part of this distribution. The terms
+# are also available at http://genshi.edgewall.org/wiki/License.
+#
+# This software consists of voluntary contributions made by many
+# individuals. For the exact contribution history, see the revision
+# history and logs, available at http://genshi.edgewall.org/log/.
+
+"""Implementation of the various template directives."""
+
+from genshi.core import QName, Stream
+from genshi.path import Path
+from genshi.template.base import TemplateRuntimeError, TemplateSyntaxError, \
+ EXPR, _apply_directives, _eval_expr
+from genshi.template.eval import Expression, ExpressionASTTransformer, \
+ _ast, _parse
+
+__all__ = ['AttrsDirective', 'ChooseDirective', 'ContentDirective',
+ 'DefDirective', 'ForDirective', 'IfDirective', 'MatchDirective',
+ 'OtherwiseDirective', 'ReplaceDirective', 'StripDirective',
+ 'WhenDirective', 'WithDirective']
+__docformat__ = 'restructuredtext en'
+
+
+class DirectiveMeta(type):
+ """Meta class for template directives."""
+
+ def __new__(cls, name, bases, d):
+ d['tagname'] = name.lower().replace('directive', '')
+ return type.__new__(cls, name, bases, d)
+
+
+class Directive(object):
+ """Abstract base class for template directives.
+
+ A directive is basically a callable that takes three positional arguments:
+ ``ctxt`` is the template data context, ``stream`` is an iterable over the
+ events that the directive applies to, and ``directives`` is is a list of
+ other directives on the same stream that need to be applied.
+
+ Directives can be "anonymous" or "registered". Registered directives can be
+ applied by the template author using an XML attribute with the
+ corresponding name in the template. Such directives should be subclasses of
+ this base class that can be instantiated with the value of the directive
+ attribute as parameter.
+
+ Anonymous directives are simply functions conforming to the protocol
+ described above, and can only be applied programmatically (for example by
+ template filters).
+ """
+ __metaclass__ = DirectiveMeta
+ __slots__ = ['expr']
+
+ def __init__(self, value, template=None, namespaces=None, lineno=-1,
+ offset=-1):
+ self.expr = self._parse_expr(value, template, lineno, offset)
+
+ @classmethod
+ def attach(cls, template, stream, value, namespaces, pos):
+ """Called after the template stream has been completely parsed.
+
+ :param template: the `Template` object
+ :param stream: the event stream associated with the directive
+ :param value: the argument value for the directive; if the directive was
+ specified as an element, this will be an `Attrs` instance
+ with all specified attributes, otherwise it will be a
+ `unicode` object with just the attribute value
+ :param namespaces: a mapping of namespace URIs to prefixes
+ :param pos: a ``(filename, lineno, offset)`` tuple describing the
+ location where the directive was found in the source
+
+ This class method should return a ``(directive, stream)`` tuple. If
+ ``directive`` is not ``None``, it should be an instance of the `Directive`
+ class, and gets added to the list of directives applied to the substream
+ at runtime. `stream` is an event stream that replaces the original
+ stream associated with the directive.
+ """
+ return cls(value, template, namespaces, *pos[1:]), stream
+
+ def __call__(self, stream, directives, ctxt, **vars):
+ """Apply the directive to the given stream.
+
+ :param stream: the event stream
+ :param directives: a list of the remaining directives that should
+ process the stream
+ :param ctxt: the context data
+ :param vars: additional variables that should be made available when
+ Python code is executed
+ """
+ raise NotImplementedError
+
+ def __repr__(self):
+ expr = ''
+ if getattr(self, 'expr', None) is not None:
+ expr = ' "%s"' % self.expr.source
+ return '<%s%s>' % (type(self).__name__, expr)
+
+ @classmethod
+ def _parse_expr(cls, expr, template, lineno=-1, offset=-1):
+ """Parses the given expression, raising a useful error message when a
+ syntax error is encountered.
+ """
+ try:
+ return expr and Expression(expr, template.filepath, lineno,
+ lookup=template.lookup) or None
+ except SyntaxError, err:
+ err.msg += ' in expression "%s" of "%s" directive' % (expr,
+ cls.tagname)
+ raise TemplateSyntaxError(err, template.filepath, lineno,
+ offset + (err.offset or 0))
+
+
+def _assignment(ast):
+ """Takes the AST representation of an assignment, and returns a
+ function that applies the assignment of a given value to a dictionary.
+ """
+ def _names(node):
+ if isinstance(node, _ast.Tuple):
+ return tuple([_names(child) for child in node.elts])
+ elif isinstance(node, _ast.Name):
+ return node.id
+ def _assign(data, value, names=_names(ast)):
+ if type(names) is tuple:
+ for idx in range(len(names)):
+ _assign(data, value[idx], names[idx])
+ else:
+ data[names] = value
+ return _assign
+
+
+class AttrsDirective(Directive):
+ """Implementation of the ``py:attrs`` template directive.
+
+ The value of the ``py:attrs`` attribute should be a dictionary or a sequence
+ of ``(name, value)`` tuples. The items in that dictionary or sequence are
+ added as attributes to the element:
+
+ >>> from genshi.template import MarkupTemplate
+ >>> tmpl = MarkupTemplate('''<ul xmlns:py="http://genshi.edgewall.org/">
+ ... <li py:attrs="foo">Bar</li>
+ ... </ul>''')
+ >>> print(tmpl.generate(foo={'class': 'collapse'}))
+ <ul>
+ <li class="collapse">Bar</li>
+ </ul>
+ >>> print(tmpl.generate(foo=[('class', 'collapse')]))
+ <ul>
+ <li class="collapse">Bar</li>
+ </ul>
+
+ If the value evaluates to ``None`` (or any other non-truth value), no
+ attributes are added:
+
+ >>> print(tmpl.generate(foo=None))
+ <ul>
+ <li>Bar</li>
+ </ul>
+ """
+ __slots__ = []
+
+ def __call__(self, stream, directives, ctxt, **vars):
+ def _generate():
+ kind, (tag, attrib), pos = stream.next()
+ attrs = _eval_expr(self.expr, ctxt, vars)
+ if attrs:
+ if isinstance(attrs, Stream):
+ try:
+ attrs = iter(attrs).next()
+ except StopIteration:
+ attrs = []
+ elif not isinstance(attrs, list): # assume it's a dict
+ attrs = attrs.items()
+ attrib -= [name for name, val in attrs if val is None]
+ attrib |= [(QName(name), unicode(val).strip()) for name, val
+ in attrs if val is not None]
+ yield kind, (tag, attrib), pos
+ for event in stream:
+ yield event
+
+ return _apply_directives(_generate(), directives, ctxt, vars)
+
+
+class ContentDirective(Directive):
+ """Implementation of the ``py:content`` template directive.
+
+ This directive replaces the content of the element with the result of
+ evaluating the value of the ``py:content`` attribute:
+
+ >>> from genshi.template import MarkupTemplate
+ >>> tmpl = MarkupTemplate('''<ul xmlns:py="http://genshi.edgewall.org/">
+ ... <li py:content="bar">Hello</li>
+ ... </ul>''')
+ >>> print(tmpl.generate(bar='Bye'))
+ <ul>
+ <li>Bye</li>
+ </ul>
+ """
+ __slots__ = []
+
+ @classmethod
+ def attach(cls, template, stream, value, namespaces, pos):
+ if type(value) is dict:
+ raise TemplateSyntaxError('The content directive can not be used '
+ 'as an element', template.filepath,
+ *pos[1:])
+ expr = cls._parse_expr(value, template, *pos[1:])
+ return None, [stream[0], (EXPR, expr, pos), stream[-1]]
+
+
+class DefDirective(Directive):
+ """Implementation of the ``py:def`` template directive.
+
+ This directive can be used to create "Named Template Functions", which
+ are template snippets that are not actually output during normal
+ processing, but rather can be expanded from expressions in other places
+ in the template.
+
+ A named template function can be used just like a normal Python function
+ from template expressions:
+
+ >>> from genshi.template import MarkupTemplate
+ >>> tmpl = MarkupTemplate('''<div xmlns:py="http://genshi.edgewall.org/">
+ ... <p py:def="echo(greeting, name='world')" class="message">
+ ... ${greeting}, ${name}!
+ ... </p>
+ ... ${echo('Hi', name='you')}
+ ... </div>''')
+ >>> print(tmpl.generate(bar='Bye'))
+ <div>
+ <p class="message">
+ Hi, you!
+ </p>
+ </div>
+
+ If a function does not require parameters, the parenthesis can be omitted
+ in the definition:
+
+ >>> tmpl = MarkupTemplate('''<div xmlns:py="http://genshi.edgewall.org/">
+ ... <p py:def="helloworld" class="message">
+ ... Hello, world!
+ ... </p>
+ ... ${helloworld()}
+ ... </div>''')
+ >>> print(tmpl.generate(bar='Bye'))
+ <div>
+ <p class="message">
+ Hello, world!
+ </p>
+ </div>
+ """
+ __slots__ = ['name', 'args', 'star_args', 'dstar_args', 'defaults']
+
+ def __init__(self, args, template, namespaces=None, lineno=-1, offset=-1):
+ Directive.__init__(self, None, template, namespaces, lineno, offset)
+ ast = _parse(args).body
+ self.args = []
+ self.star_args = None
+ self.dstar_args = None
+ self.defaults = {}
+ if isinstance(ast, _ast.Call):
+ self.name = ast.func.id
+ for arg in ast.args:
+ # only names
+ self.args.append(arg.id)
+ for kwd in ast.keywords:
+ self.args.append(kwd.arg)
+ exp = Expression(kwd.value, template.filepath,
+ lineno, lookup=template.lookup)
+ self.defaults[kwd.arg] = exp
+ if getattr(ast, 'starargs', None):
+ self.star_args = ast.starargs.id
+ if getattr(ast, 'kwargs', None):
+ self.dstar_args = ast.kwargs.id
+ else:
+ self.name = ast.id
+
+ @classmethod
+ def attach(cls, template, stream, value, namespaces, pos):
+ if type(value) is dict:
+ value = value.get('function')
+ return super(DefDirective, cls).attach(template, stream, value,
+ namespaces, pos)
+
+ def __call__(self, stream, directives, ctxt, **vars):
+ stream = list(stream)
+
+ def function(*args, **kwargs):
+ scope = {}
+ args = list(args) # make mutable
+ for name in self.args:
+ if args:
+ scope[name] = args.pop(0)
+ else:
+ if name in kwargs:
+ val = kwargs.pop(name)
+ else:
+ val = _eval_expr(self.defaults.get(name), ctxt, vars)
+ scope[name] = val
+ if not self.star_args is None:
+ scope[self.star_args] = args
+ if not self.dstar_args is None:
+ scope[self.dstar_args] = kwargs
+ ctxt.push(scope)
+ for event in _apply_directives(stream, directives, ctxt, vars):
+ yield event
+ ctxt.pop()
+ function.__name__ = self.name
+
+ # 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
+
+ return []
+
+ def __repr__(self):
+ return '<%s "%s">' % (type(self).__name__, self.name)
+
+
+class ForDirective(Directive):
+ """Implementation of the ``py:for`` template directive for repeating an
+ element based on an iterable in the context data.
+
+ >>> from genshi.template import MarkupTemplate
+ >>> tmpl = MarkupTemplate('''<ul xmlns:py="http://genshi.edgewall.org/">
+ ... <li py:for="item in items">${item}</li>
+ ... </ul>''')
+ >>> print(tmpl.generate(items=[1, 2, 3]))
+ <ul>
+ <li>1</li><li>2</li><li>3</li>
+ </ul>
+ """
+ __slots__ = ['assign', 'filename']
+
+ def __init__(self, value, template, namespaces=None, lineno=-1, offset=-1):
+ if ' in ' not in value:
+ raise TemplateSyntaxError('"in" keyword missing in "for" directive',
+ template.filepath, lineno, offset)
+ assign, value = value.split(' in ', 1)
+ ast = _parse(assign, 'exec')
+ value = 'iter(%s)' % value.strip()
+ self.assign = _assignment(ast.body[0].value)
+ self.filename = template.filepath
+ Directive.__init__(self, value, template, namespaces, lineno, offset)
+
+ @classmethod
+ def attach(cls, template, stream, value, namespaces, pos):
+ if type(value) is dict:
+ value = value.get('each')
+ return super(ForDirective, cls).attach(template, stream, value,
+ namespaces, pos)
+
+ def __call__(self, stream, directives, ctxt, **vars):
+ iterable = _eval_expr(self.expr, ctxt, vars)
+ if iterable is None:
+ return
+
+ assign = self.assign
+ scope = {}
+ stream = list(stream)
+ for item in iterable:
+ assign(scope, item)
+ ctxt.push(scope)
+ for event in _apply_directives(stream, directives, ctxt, vars):
+ yield event
+ ctxt.pop()
+
+ def __repr__(self):
+ return '<%s>' % type(self).__name__
+
+
+class IfDirective(Directive):
+ """Implementation of the ``py:if`` template directive for conditionally
+ excluding elements from being output.
+
+ >>> from genshi.template import MarkupTemplate
+ >>> tmpl = MarkupTemplate('''<div xmlns:py="http://genshi.edgewall.org/">
+ ... <b py:if="foo">${bar}</b>
+ ... </div>''')
+ >>> print(tmpl.generate(foo=True, bar='Hello'))
+ <div>
+ <b>Hello</b>
+ </div>
+ """
+ __slots__ = []
+
+ @classmethod
+ def attach(cls, template, stream, value, namespaces, pos):
+ if type(value) is dict:
+ value = value.get('test')
+ return super(IfDirective, cls).attach(template, stream, value,
+ namespaces, pos)
+
+ def __call__(self, stream, directives, ctxt, **vars):
+ value = _eval_expr(self.expr, ctxt, vars)
+ if value:
+ return _apply_directives(stream, directives, ctxt, vars)
+ return []
+
+
+class MatchDirective(Directive):
+ """Implementation of the ``py:match`` template directive.
+
+ >>> from genshi.template import MarkupTemplate
+ >>> tmpl = MarkupTemplate('''<div xmlns:py="http://genshi.edgewall.org/">
+ ... <span py:match="greeting">
+ ... Hello ${select('@name')}
+ ... </span>
+ ... <greeting name="Dude" />
+ ... </div>''')
+ >>> print(tmpl.generate())
+ <div>
+ <span>
+ Hello Dude
+ </span>
+ </div>
+ """
+ __slots__ = ['path', 'namespaces', 'hints']
+
+ def __init__(self, value, template, hints=None, namespaces=None,
+ lineno=-1, offset=-1):
+ Directive.__init__(self, None, template, namespaces, lineno, offset)
+ self.path = Path(value, template.filepath, lineno)
+ self.namespaces = namespaces or {}
+ self.hints = hints or ()
+
+ @classmethod
+ def attach(cls, template, stream, value, namespaces, pos):
+ hints = []
+ if type(value) is dict:
+ if value.get('buffer', '').lower() == 'false':
+ hints.append('not_buffered')
+ if value.get('once', '').lower() == 'true':
+ hints.append('match_once')
+ if value.get('recursive', '').lower() == 'false':
+ hints.append('not_recursive')
+ value = value.get('path')
+ return cls(value, template, frozenset(hints), namespaces, *pos[1:]), \
+ stream
+
+ def __call__(self, stream, directives, ctxt, **vars):
+ ctxt._match_templates.append((self.path.test(ignore_context=True),
+ self.path, list(stream), self.hints,
+ self.namespaces, directives))
+ return []
+
+ def __repr__(self):
+ return '<%s "%s">' % (type(self).__name__, self.path.source)
+
+
+class ReplaceDirective(Directive):
+ """Implementation of the ``py:replace`` template directive.
+
+ This directive replaces the element with the result of evaluating the
+ value of the ``py:replace`` attribute:
+
+ >>> from genshi.template import MarkupTemplate
+ >>> tmpl = MarkupTemplate('''<div xmlns:py="http://genshi.edgewall.org/">
+ ... <span py:replace="bar">Hello</span>
+ ... </div>''')
+ >>> print(tmpl.generate(bar='Bye'))
+ <div>
+ Bye
+ </div>
+
+ This directive is equivalent to ``py:content`` combined with ``py:strip``,
+ providing a less verbose way to achieve the same effect:
+
+ >>> tmpl = MarkupTemplate('''<div xmlns:py="http://genshi.edgewall.org/">
+ ... <span py:content="bar" py:strip="">Hello</span>
+ ... </div>''')
+ >>> print(tmpl.generate(bar='Bye'))
+ <div>
+ Bye
+ </div>
+ """
+ __slots__ = []
+
+ @classmethod
+ def attach(cls, template, stream, value, namespaces, pos):
+ if type(value) is dict:
+ value = value.get('value')
+ if not value:
+ raise TemplateSyntaxError('missing value for "replace" directive',
+ template.filepath, *pos[1:])
+ expr = cls._parse_expr(value, template, *pos[1:])
+ return None, [(EXPR, expr, pos)]
+
+
+class StripDirective(Directive):
+ """Implementation of the ``py:strip`` template directive.
+
+ When the value of the ``py:strip`` attribute evaluates to ``True``, the
+ element is stripped from the output
+
+ >>> from genshi.template import MarkupTemplate
+ >>> tmpl = MarkupTemplate('''<div xmlns:py="http://genshi.edgewall.org/">
+ ... <div py:strip="True"><b>foo</b></div>
+ ... </div>''')
+ >>> print(tmpl.generate())
+ <div>
+ <b>foo</b>
+ </div>
+
+ Leaving the attribute value empty is equivalent to a truth value.
+
+ This directive is particulary interesting for named template functions or
+ match templates that do not generate a top-level element:
+
+ >>> tmpl = MarkupTemplate('''<div xmlns:py="http://genshi.edgewall.org/">
+ ... <div py:def="echo(what)" py:strip="">
+ ... <b>${what}</b>
+ ... </div>
+ ... ${echo('foo')}
+ ... </div>''')
+ >>> print(tmpl.generate())
+ <div>
+ <b>foo</b>
+ </div>
+ """
+ __slots__ = []
+
+ def __call__(self, stream, directives, ctxt, **vars):
+ def _generate():
+ if not self.expr or _eval_expr(self.expr, ctxt, vars):
+ stream.next() # skip start tag
+ previous = stream.next()
+ for event in stream:
+ yield previous
+ previous = event
+ else:
+ for event in stream:
+ yield event
+ return _apply_directives(_generate(), directives, ctxt, vars)
+
+
+class ChooseDirective(Directive):
+ """Implementation of the ``py:choose`` directive for conditionally selecting
+ one of several body elements to display.
+
+ If the ``py:choose`` expression is empty the expressions of nested
+ ``py:when`` directives are tested for truth. The first true ``py:when``
+ body is output. If no ``py:when`` directive is matched then the fallback
+ directive ``py:otherwise`` will be used.
+
+ >>> from genshi.template import MarkupTemplate
+ >>> tmpl = MarkupTemplate('''<div xmlns:py="http://genshi.edgewall.org/"
+ ... py:choose="">
+ ... <span py:when="0 == 1">0</span>
+ ... <span py:when="1 == 1">1</span>
+ ... <span py:otherwise="">2</span>
+ ... </div>''')
+ >>> print(tmpl.generate())
+ <div>
+ <span>1</span>
+ </div>
+
+ If the ``py:choose`` directive contains an expression, the nested
+ ``py:when`` directives are tested for equality to the ``py:choose``
+ expression:
+
+ >>> tmpl = MarkupTemplate('''<div xmlns:py="http://genshi.edgewall.org/"
+ ... py:choose="2">
+ ... <span py:when="1">1</span>
+ ... <span py:when="2">2</span>
+ ... </div>''')
+ >>> print(tmpl.generate())
+ <div>
+ <span>2</span>
+ </div>
+
+ Behavior is undefined if a ``py:choose`` block contains content outside a
+ ``py:when`` or ``py:otherwise`` block. Behavior is also undefined if a
+ ``py:otherwise`` occurs before ``py:when`` blocks.
+ """
+ __slots__ = ['matched', 'value']
+
+ @classmethod
+ def attach(cls, template, stream, value, namespaces, pos):
+ if type(value) is dict:
+ value = value.get('test')
+ return super(ChooseDirective, cls).attach(template, stream, value,
+ namespaces, pos)
+
+ def __call__(self, stream, directives, ctxt, **vars):
+ info = [False, bool(self.expr), None]
+ if self.expr:
+ info[2] = _eval_expr(self.expr, ctxt, vars)
+ ctxt._choice_stack.append(info)
+ for event in _apply_directives(stream, directives, ctxt, vars):
+ yield event
+ ctxt._choice_stack.pop()
+
+
+class WhenDirective(Directive):
+ """Implementation of the ``py:when`` directive for nesting in a parent with
+ the ``py:choose`` directive.
+
+ See the documentation of the `ChooseDirective` for usage.
+ """
+ __slots__ = ['filename']
+
+ def __init__(self, value, template, namespaces=None, lineno=-1, offset=-1):
+ Directive.__init__(self, value, template, namespaces, lineno, offset)
+ self.filename = template.filepath
+
+ @classmethod
+ def attach(cls, template, stream, value, namespaces, pos):
+ if type(value) is dict:
+ value = value.get('test')
+ return super(WhenDirective, cls).attach(template, stream, value,
+ namespaces, pos)
+
+ def __call__(self, stream, directives, ctxt, **vars):
+ info = ctxt._choice_stack and ctxt._choice_stack[-1]
+ if not info:
+ raise TemplateRuntimeError('"when" directives can only be used '
+ 'inside a "choose" directive',
+ self.filename, *stream.next()[2][1:])
+ if info[0]:
+ return []
+ if not self.expr and not info[1]:
+ raise TemplateRuntimeError('either "choose" or "when" directive '
+ 'must have a test expression',
+ self.filename, *stream.next()[2][1:])
+ if info[1]:
+ value = info[2]
+ if self.expr:
+ matched = value == _eval_expr(self.expr, ctxt, vars)
+ else:
+ matched = bool(value)
+ else:
+ matched = bool(_eval_expr(self.expr, ctxt, vars))
+ info[0] = matched
+ if not matched:
+ return []
+
+ return _apply_directives(stream, directives, ctxt, vars)
+
+
+class OtherwiseDirective(Directive):
+ """Implementation of the ``py:otherwise`` directive for nesting in a parent
+ with the ``py:choose`` directive.
+
+ See the documentation of `ChooseDirective` for usage.
+ """
+ __slots__ = ['filename']
+
+ def __init__(self, value, template, namespaces=None, lineno=-1, offset=-1):
+ Directive.__init__(self, None, template, namespaces, lineno, offset)
+ self.filename = template.filepath
+
+ def __call__(self, stream, directives, ctxt, **vars):
+ info = ctxt._choice_stack and ctxt._choice_stack[-1]
+ if not info:
+ raise TemplateRuntimeError('an "otherwise" directive can only be '
+ 'used inside a "choose" directive',
+ self.filename, *stream.next()[2][1:])
+ if info[0]:
+ return []
+ info[0] = True
+
+ return _apply_directives(stream, directives, ctxt, vars)
+
+
+class WithDirective(Directive):
+ """Implementation of the ``py:with`` template directive, which allows
+ shorthand access to variables and expressions.
+
+ >>> from genshi.template import MarkupTemplate
+ >>> tmpl = MarkupTemplate('''<div xmlns:py="http://genshi.edgewall.org/">
+ ... <span py:with="y=7; z=x+10">$x $y $z</span>
+ ... </div>''')
+ >>> print(tmpl.generate(x=42))
+ <div>
+ <span>42 7 52</span>
+ </div>
+ """
+ __slots__ = ['vars']
+
+ def __init__(self, value, template, namespaces=None, lineno=-1, offset=-1):
+ Directive.__init__(self, None, template, namespaces, lineno, offset)
+ self.vars = []
+ value = value.strip()
+ try:
+ ast = _parse(value, 'exec')
+ for node in ast.body:
+ if not isinstance(node, _ast.Assign):
+ raise TemplateSyntaxError('only assignment allowed in '
+ 'value of the "with" directive',
+ template.filepath, lineno, offset)
+ self.vars.append(([_assignment(n) for n in node.targets],
+ Expression(node.value, template.filepath,
+ lineno, lookup=template.lookup)))
+ except SyntaxError, err:
+ err.msg += ' in expression "%s" of "%s" directive' % (value,
+ self.tagname)
+ raise TemplateSyntaxError(err, template.filepath, lineno,
+ offset + (err.offset or 0))
+
+ @classmethod
+ def attach(cls, template, stream, value, namespaces, pos):
+ if type(value) is dict:
+ value = value.get('vars')
+ return super(WithDirective, cls).attach(template, stream, value,
+ namespaces, pos)
+
+ def __call__(self, stream, directives, ctxt, **vars):
+ frame = {}
+ ctxt.push(frame)
+ for targets, expr in self.vars:
+ value = _eval_expr(expr, ctxt, vars)
+ for assign in targets:
+ assign(frame, value)
+ for event in _apply_directives(stream, directives, ctxt, vars):
+ yield event
+ ctxt.pop()
+
+ def __repr__(self):
+ return '<%s>' % (type(self).__name__)
diff --git a/websdk/genshi/template/eval.py b/websdk/genshi/template/eval.py
new file mode 100644
index 0000000..8593aaa
--- /dev/null
+++ b/websdk/genshi/template/eval.py
@@ -0,0 +1,629 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2006-2010 Edgewall Software
+# All rights reserved.
+#
+# This software is licensed as described in the file COPYING, which
+# you should have received as part of this distribution. The terms
+# are also available at http://genshi.edgewall.org/wiki/License.
+#
+# This software consists of voluntary contributions made by many
+# individuals. For the exact contribution history, see the revision
+# history and logs, available at http://genshi.edgewall.org/log/.
+
+"""Support for "safe" evaluation of Python expressions."""
+
+import __builtin__
+
+from textwrap import dedent
+from types import CodeType
+
+from genshi.core import Markup
+from genshi.template.astutil import ASTTransformer, ASTCodeGenerator, \
+ _ast, parse
+from genshi.template.base import TemplateRuntimeError
+from genshi.util import flatten
+
+__all__ = ['Code', 'Expression', 'Suite', 'LenientLookup', 'StrictLookup',
+ 'Undefined', 'UndefinedError']
+__docformat__ = 'restructuredtext en'
+
+
+# Check for a Python 2.4 bug in the eval loop
+has_star_import_bug = False
+try:
+ class _FakeMapping(object):
+ __getitem__ = __setitem__ = lambda *a: None
+ exec 'from sys import *' in {}, _FakeMapping()
+except SystemError:
+ has_star_import_bug = True
+del _FakeMapping
+
+
+def _star_import_patch(mapping, modname):
+ """This function is used as helper if a Python version with a broken
+ star-import opcode is in use.
+ """
+ module = __import__(modname, None, None, ['__all__'])
+ if hasattr(module, '__all__'):
+ members = module.__all__
+ else:
+ members = [x for x in module.__dict__ if not x.startswith('_')]
+ mapping.update([(name, getattr(module, name)) for name in members])
+
+
+class Code(object):
+ """Abstract base class for the `Expression` and `Suite` classes."""
+ __slots__ = ['source', 'code', 'ast', '_globals']
+
+ def __init__(self, source, filename=None, lineno=-1, lookup='strict',
+ xform=None):
+ """Create the code object, either from a string, or from an AST node.
+
+ :param source: either a string containing the source code, or an AST
+ node
+ :param filename: the (preferably absolute) name of the file containing
+ the code
+ :param lineno: the number of the line on which the code was found
+ :param lookup: the lookup class that defines how variables are looked
+ up in the context; can be either "strict" (the default),
+ "lenient", or a custom lookup class
+ :param xform: the AST transformer that should be applied to the code;
+ if `None`, the appropriate transformation is chosen
+ depending on the mode
+ """
+ if isinstance(source, basestring):
+ self.source = source
+ node = _parse(source, mode=self.mode)
+ else:
+ assert isinstance(source, _ast.AST), \
+ 'Expected string or AST node, but got %r' % source
+ self.source = '?'
+ if self.mode == 'eval':
+ node = _ast.Expression()
+ node.body = source
+ else:
+ node = _ast.Module()
+ node.body = [source]
+
+ self.ast = node
+ self.code = _compile(node, self.source, mode=self.mode,
+ filename=filename, lineno=lineno, xform=xform)
+ if lookup is None:
+ lookup = LenientLookup
+ elif isinstance(lookup, basestring):
+ lookup = {'lenient': LenientLookup, 'strict': StrictLookup}[lookup]
+ self._globals = lookup.globals
+
+ def __getstate__(self):
+ state = {'source': self.source, 'ast': self.ast,
+ 'lookup': self._globals.im_self}
+ c = self.code
+ state['code'] = (c.co_nlocals, c.co_stacksize, c.co_flags, c.co_code,
+ c.co_consts, c.co_names, c.co_varnames, c.co_filename,
+ c.co_name, c.co_firstlineno, c.co_lnotab, (), ())
+ return state
+
+ def __setstate__(self, state):
+ self.source = state['source']
+ self.ast = state['ast']
+ self.code = CodeType(0, *state['code'])
+ self._globals = state['lookup'].globals
+
+ def __eq__(self, other):
+ return (type(other) == type(self)) and (self.code == other.code)
+
+ def __hash__(self):
+ return hash(self.code)
+
+ def __ne__(self, other):
+ return not self == other
+
+ def __repr__(self):
+ return '%s(%r)' % (type(self).__name__, self.source)
+
+
+class Expression(Code):
+ """Evaluates Python expressions used in templates.
+
+ >>> data = dict(test='Foo', items=[1, 2, 3], dict={'some': 'thing'})
+ >>> Expression('test').evaluate(data)
+ 'Foo'
+
+ >>> Expression('items[0]').evaluate(data)
+ 1
+ >>> Expression('items[-1]').evaluate(data)
+ 3
+ >>> Expression('dict["some"]').evaluate(data)
+ 'thing'
+
+ Similar to e.g. Javascript, expressions in templates can use the dot
+ notation for attribute access to access items in mappings:
+
+ >>> Expression('dict.some').evaluate(data)
+ 'thing'
+
+ This also works the other way around: item access can be used to access
+ any object attribute:
+
+ >>> class MyClass(object):
+ ... myattr = 'Bar'
+ >>> data = dict(mine=MyClass(), key='myattr')
+ >>> Expression('mine.myattr').evaluate(data)
+ 'Bar'
+ >>> Expression('mine["myattr"]').evaluate(data)
+ 'Bar'
+ >>> Expression('mine[key]').evaluate(data)
+ 'Bar'
+
+ All of the standard Python operators are available to template expressions.
+ Built-in functions such as ``len()`` are also available in template
+ expressions:
+
+ >>> data = dict(items=[1, 2, 3])
+ >>> Expression('len(items)').evaluate(data)
+ 3
+ """
+ __slots__ = []
+ mode = 'eval'
+
+ def evaluate(self, data):
+ """Evaluate the expression against the given data dictionary.
+
+ :param data: a mapping containing the data to evaluate against
+ :return: the result of the evaluation
+ """
+ __traceback_hide__ = 'before_and_this'
+ _globals = self._globals(data)
+ return eval(self.code, _globals, {'__data__': data})
+
+
+class Suite(Code):
+ """Executes Python statements used in templates.
+
+ >>> data = dict(test='Foo', items=[1, 2, 3], dict={'some': 'thing'})
+ >>> Suite("foo = dict['some']").execute(data)
+ >>> data['foo']
+ 'thing'
+ """
+ __slots__ = []
+ mode = 'exec'
+
+ def execute(self, data):
+ """Execute the suite in the given data dictionary.
+
+ :param data: a mapping containing the data to execute in
+ """
+ __traceback_hide__ = 'before_and_this'
+ _globals = self._globals(data)
+ exec self.code in _globals, data
+
+
+UNDEFINED = object()
+
+
+class UndefinedError(TemplateRuntimeError):
+ """Exception thrown when a template expression attempts to access a variable
+ not defined in the context.
+
+ :see: `LenientLookup`, `StrictLookup`
+ """
+ def __init__(self, name, owner=UNDEFINED):
+ if owner is not UNDEFINED:
+ message = '%s has no member named "%s"' % (repr(owner), name)
+ else:
+ message = '"%s" not defined' % name
+ TemplateRuntimeError.__init__(self, message)
+
+
+class Undefined(object):
+ """Represents a reference to an undefined variable.
+
+ Unlike the Python runtime, template expressions can refer to an undefined
+ variable without causing a `NameError` to be raised. The result will be an
+ instance of the `Undefined` class, which is treated the same as ``False`` in
+ conditions, but raise an exception on any other operation:
+
+ >>> foo = Undefined('foo')
+ >>> bool(foo)
+ False
+ >>> list(foo)
+ []
+ >>> print(foo)
+ undefined
+
+ However, calling an undefined variable, or trying to access an attribute
+ of that variable, will raise an exception that includes the name used to
+ reference that undefined variable.
+
+ >>> foo('bar')
+ Traceback (most recent call last):
+ ...
+ UndefinedError: "foo" not defined
+
+ >>> foo.bar
+ Traceback (most recent call last):
+ ...
+ UndefinedError: "foo" not defined
+
+ :see: `LenientLookup`
+ """
+ __slots__ = ['_name', '_owner']
+
+ def __init__(self, name, owner=UNDEFINED):
+ """Initialize the object.
+
+ :param name: the name of the reference
+ :param owner: the owning object, if the variable is accessed as a member
+ """
+ self._name = name
+ self._owner = owner
+
+ def __iter__(self):
+ return iter([])
+
+ def __nonzero__(self):
+ return False
+
+ def __repr__(self):
+ return '<%s %r>' % (type(self).__name__, self._name)
+
+ def __str__(self):
+ return 'undefined'
+
+ def _die(self, *args, **kwargs):
+ """Raise an `UndefinedError`."""
+ __traceback_hide__ = True
+ raise UndefinedError(self._name, self._owner)
+ __call__ = __getattr__ = __getitem__ = _die
+
+ # Hack around some behavior introduced in Python 2.6.2
+ # http://genshi.edgewall.org/ticket/324
+ __length_hint__ = None
+
+
+class LookupBase(object):
+ """Abstract base class for variable lookup implementations."""
+
+ @classmethod
+ def globals(cls, data):
+ """Construct the globals dictionary to use as the execution context for
+ the expression or suite.
+ """
+ return {
+ '__data__': data,
+ '_lookup_name': cls.lookup_name,
+ '_lookup_attr': cls.lookup_attr,
+ '_lookup_item': cls.lookup_item,
+ '_star_import_patch': _star_import_patch,
+ 'UndefinedError': UndefinedError,
+ }
+
+ @classmethod
+ def lookup_name(cls, data, name):
+ __traceback_hide__ = True
+ val = data.get(name, UNDEFINED)
+ if val is UNDEFINED:
+ val = BUILTINS.get(name, val)
+ if val is UNDEFINED:
+ val = cls.undefined(name)
+ return val
+
+ @classmethod
+ def lookup_attr(cls, obj, key):
+ __traceback_hide__ = True
+ try:
+ val = getattr(obj, key)
+ except AttributeError:
+ if hasattr(obj.__class__, key):
+ raise
+ else:
+ try:
+ val = obj[key]
+ except (KeyError, TypeError):
+ val = cls.undefined(key, owner=obj)
+ return val
+
+ @classmethod
+ def lookup_item(cls, obj, key):
+ __traceback_hide__ = True
+ if len(key) == 1:
+ key = key[0]
+ try:
+ return obj[key]
+ except (AttributeError, KeyError, IndexError, TypeError), e:
+ if isinstance(key, basestring):
+ val = getattr(obj, key, UNDEFINED)
+ if val is UNDEFINED:
+ val = cls.undefined(key, owner=obj)
+ return val
+ raise
+
+ @classmethod
+ def undefined(cls, key, owner=UNDEFINED):
+ """Can be overridden by subclasses to specify behavior when undefined
+ variables are accessed.
+
+ :param key: the name of the variable
+ :param owner: the owning object, if the variable is accessed as a member
+ """
+ raise NotImplementedError
+
+
+class LenientLookup(LookupBase):
+ """Default variable lookup mechanism for expressions.
+
+ When an undefined variable is referenced using this lookup style, the
+ reference evaluates to an instance of the `Undefined` class:
+
+ >>> expr = Expression('nothing', lookup='lenient')
+ >>> undef = expr.evaluate({})
+ >>> undef
+ <Undefined 'nothing'>
+
+ The same will happen when a non-existing attribute or item is accessed on
+ an existing object:
+
+ >>> expr = Expression('something.nil', lookup='lenient')
+ >>> expr.evaluate({'something': dict()})
+ <Undefined 'nil'>
+
+ See the documentation of the `Undefined` class for details on the behavior
+ of such objects.
+
+ :see: `StrictLookup`
+ """
+
+ @classmethod
+ def undefined(cls, key, owner=UNDEFINED):
+ """Return an ``Undefined`` object."""
+ __traceback_hide__ = True
+ return Undefined(key, owner=owner)
+
+
+class StrictLookup(LookupBase):
+ """Strict variable lookup mechanism for expressions.
+
+ Referencing an undefined variable using this lookup style will immediately
+ raise an ``UndefinedError``:
+
+ >>> expr = Expression('nothing', lookup='strict')
+ >>> expr.evaluate({})
+ Traceback (most recent call last):
+ ...
+ UndefinedError: "nothing" not defined
+
+ The same happens when a non-existing attribute or item is accessed on an
+ existing object:
+
+ >>> expr = Expression('something.nil', lookup='strict')
+ >>> expr.evaluate({'something': dict()})
+ Traceback (most recent call last):
+ ...
+ UndefinedError: {} has no member named "nil"
+ """
+
+ @classmethod
+ def undefined(cls, key, owner=UNDEFINED):
+ """Raise an ``UndefinedError`` immediately."""
+ __traceback_hide__ = True
+ raise UndefinedError(key, owner=owner)
+
+
+def _parse(source, mode='eval'):
+ source = source.strip()
+ if mode == 'exec':
+ lines = [line.expandtabs() for line in source.splitlines()]
+ if lines:
+ first = lines[0]
+ rest = dedent('\n'.join(lines[1:])).rstrip()
+ if first.rstrip().endswith(':') and not rest[0].isspace():
+ rest = '\n'.join([' %s' % line for line in rest.splitlines()])
+ source = '\n'.join([first, rest])
+ if isinstance(source, unicode):
+ source = '\xef\xbb\xbf' + source.encode('utf-8')
+ return parse(source, mode)
+
+
+def _compile(node, source=None, mode='eval', filename=None, lineno=-1,
+ xform=None):
+ if isinstance(filename, unicode):
+ # unicode file names not allowed for code objects
+ filename = filename.encode('utf-8', 'replace')
+ elif not filename:
+ filename = '<string>'
+ if lineno <= 0:
+ lineno = 1
+
+ if xform is None:
+ xform = {
+ 'eval': ExpressionASTTransformer
+ }.get(mode, TemplateASTTransformer)
+ tree = xform().visit(node)
+
+ if mode == 'eval':
+ name = '<Expression %r>' % (source or '?')
+ else:
+ lines = source.splitlines()
+ if not lines:
+ extract = ''
+ else:
+ extract = lines[0]
+ if len(lines) > 1:
+ extract += ' ...'
+ name = '<Suite %r>' % (extract)
+ new_source = ASTCodeGenerator(tree).code
+ code = compile(new_source, filename, mode)
+
+ try:
+ # We'd like to just set co_firstlineno, but it's readonly. So we need
+ # to clone the code object while adjusting the line number
+ return CodeType(0, code.co_nlocals, code.co_stacksize,
+ code.co_flags | 0x0040, code.co_code, code.co_consts,
+ code.co_names, code.co_varnames, filename, name,
+ lineno, code.co_lnotab, (), ())
+ except RuntimeError:
+ return code
+
+
+def _new(class_, *args, **kwargs):
+ ret = class_()
+ for attr, value in zip(ret._fields, args):
+ if attr in kwargs:
+ raise ValueError('Field set both in args and kwargs')
+ setattr(ret, attr, value)
+ for attr, value in kwargs:
+ setattr(ret, attr, value)
+ return ret
+
+
+BUILTINS = __builtin__.__dict__.copy()
+BUILTINS.update({'Markup': Markup, 'Undefined': Undefined})
+CONSTANTS = frozenset(['False', 'True', 'None', 'NotImplemented', 'Ellipsis'])
+
+
+class TemplateASTTransformer(ASTTransformer):
+ """Concrete AST transformer that implements the AST transformations needed
+ for code embedded in templates.
+ """
+
+ def __init__(self):
+ self.locals = [CONSTANTS]
+
+ def _extract_names(self, node):
+ names = set()
+ def _process(node):
+ if isinstance(node, _ast.Name):
+ names.add(node.id)
+ elif isinstance(node, _ast.alias):
+ names.add(node.asname or node.name)
+ elif isinstance(node, _ast.Tuple):
+ for elt in node.elts:
+ _process(elt)
+ if hasattr(node, 'args'):
+ for arg in node.args:
+ _process(arg)
+ if hasattr(node, 'vararg'):
+ names.add(node.vararg)
+ if hasattr(node, 'kwarg'):
+ names.add(node.kwarg)
+ elif hasattr(node, 'names'):
+ for elt in node.names:
+ _process(elt)
+ return names
+
+ def visit_Str(self, node):
+ if isinstance(node.s, str):
+ try: # If the string is ASCII, return a `str` object
+ node.s.decode('ascii')
+ except ValueError: # Otherwise return a `unicode` object
+ return _new(_ast.Str, node.s.decode('utf-8'))
+ return node
+
+ def visit_ClassDef(self, node):
+ if len(self.locals) > 1:
+ self.locals[-1].add(node.name)
+ self.locals.append(set())
+ try:
+ return ASTTransformer.visit_ClassDef(self, node)
+ finally:
+ self.locals.pop()
+
+ def visit_Import(self, node):
+ if len(self.locals) > 1:
+ self.locals[-1].update(self._extract_names(node))
+ return ASTTransformer.visit_Import(self, node)
+
+ def visit_ImportFrom(self, node):
+ if [a.name for a in node.names] == ['*']:
+ if has_star_import_bug:
+ # This is a Python 2.4 bug. Only if we have a broken Python
+ # version do we need to apply this hack
+ node = _new(_ast.Expr, _new(_ast.Call,
+ _new(_ast.Name, '_star_import_patch'), [
+ _new(_ast.Name, '__data__'),
+ _new(_ast.Str, node.module)
+ ], (), ()))
+ return node
+ if len(self.locals) > 1:
+ self.locals[-1].update(self._extract_names(node))
+ return ASTTransformer.visit_ImportFrom(self, node)
+
+ def visit_FunctionDef(self, node):
+ if len(self.locals) > 1:
+ self.locals[-1].add(node.name)
+
+ self.locals.append(self._extract_names(node.args))
+ try:
+ return ASTTransformer.visit_FunctionDef(self, node)
+ finally:
+ self.locals.pop()
+
+ # GeneratorExp(expr elt, comprehension* generators)
+ def visit_GeneratorExp(self, node):
+ gens = []
+ for generator in node.generators:
+ # comprehension = (expr target, expr iter, expr* ifs)
+ self.locals.append(set())
+ gen = _new(_ast.comprehension, self.visit(generator.target),
+ self.visit(generator.iter),
+ [self.visit(if_) for if_ in generator.ifs])
+ gens.append(gen)
+
+ # use node.__class__ to make it reusable as ListComp
+ ret = _new(node.__class__, self.visit(node.elt), gens)
+ #delete inserted locals
+ del self.locals[-len(node.generators):]
+ return ret
+
+ # ListComp(expr elt, comprehension* generators)
+ visit_ListComp = visit_GeneratorExp
+
+ def visit_Lambda(self, node):
+ self.locals.append(self._extract_names(node.args))
+ try:
+ return ASTTransformer.visit_Lambda(self, node)
+ finally:
+ self.locals.pop()
+
+ def visit_Name(self, node):
+ # If the name refers to a local inside a lambda, list comprehension, or
+ # generator expression, leave it alone
+ if isinstance(node.ctx, _ast.Load) and \
+ node.id not in flatten(self.locals):
+ # Otherwise, translate the name ref into a context lookup
+ name = _new(_ast.Name, '_lookup_name', _ast.Load())
+ namearg = _new(_ast.Name, '__data__', _ast.Load())
+ strarg = _new(_ast.Str, node.id)
+ node = _new(_ast.Call, name, [namearg, strarg], [])
+ elif isinstance(node.ctx, _ast.Store):
+ if len(self.locals) > 1:
+ self.locals[-1].add(node.id)
+
+ return node
+
+
+class ExpressionASTTransformer(TemplateASTTransformer):
+ """Concrete AST transformer that implements the AST transformations needed
+ for code embedded in templates.
+ """
+
+ def visit_Attribute(self, node):
+ if not isinstance(node.ctx, _ast.Load):
+ return ASTTransformer.visit_Attribute(self, node)
+
+ func = _new(_ast.Name, '_lookup_attr', _ast.Load())
+ args = [self.visit(node.value), _new(_ast.Str, node.attr)]
+ return _new(_ast.Call, func, args, [])
+
+ def visit_Subscript(self, node):
+ if not isinstance(node.ctx, _ast.Load) or \
+ not isinstance(node.slice, _ast.Index):
+ return ASTTransformer.visit_Subscript(self, node)
+
+ func = _new(_ast.Name, '_lookup_item', _ast.Load())
+ args = [
+ self.visit(node.value),
+ _new(_ast.Tuple, (self.visit(node.slice.value),), _ast.Load())
+ ]
+ return _new(_ast.Call, func, args, [])
diff --git a/websdk/genshi/template/interpolation.py b/websdk/genshi/template/interpolation.py
new file mode 100644
index 0000000..1e1a385
--- /dev/null
+++ b/websdk/genshi/template/interpolation.py
@@ -0,0 +1,153 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2007-2009 Edgewall Software
+# All rights reserved.
+#
+# This software is licensed as described in the file COPYING, which
+# you should have received as part of this distribution. The terms
+# are also available at http://genshi.edgewall.org/wiki/License.
+#
+# This software consists of voluntary contributions made by many
+# individuals. For the exact contribution history, see the revision
+# history and logs, available at http://genshi.edgewall.org/log/.
+
+"""String interpolation routines, i.e. the splitting up a given text into some
+parts that are literal strings, and others that are Python expressions.
+"""
+
+from itertools import chain
+import os
+import re
+from tokenize import PseudoToken
+
+from genshi.core import TEXT
+from genshi.template.base import TemplateSyntaxError, EXPR
+from genshi.template.eval import Expression
+
+__all__ = ['interpolate']
+__docformat__ = 'restructuredtext en'
+
+NAMESTART = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_'
+NAMECHARS = NAMESTART + '.0123456789'
+PREFIX = '$'
+
+token_re = re.compile('%s|%s(?s)' % (
+ r'[uU]?[rR]?("""|\'\'\')((?<!\\)\\\1|.)*?\1',
+ PseudoToken
+))
+
+
+def interpolate(text, filepath=None, lineno=-1, offset=0, lookup='strict'):
+ """Parse the given string and extract expressions.
+
+ This function is a generator that yields `TEXT` events for literal strings,
+ and `EXPR` events for expressions, depending on the results of parsing the
+ string.
+
+ >>> for kind, data, pos in interpolate("hey ${foo}bar"):
+ ... print('%s %r' % (kind, data))
+ TEXT 'hey '
+ EXPR Expression('foo')
+ TEXT 'bar'
+
+ :param text: the text to parse
+ :param filepath: absolute path to the file in which the text was found
+ (optional)
+ :param lineno: the line number at which the text was found (optional)
+ :param offset: the column number at which the text starts in the source
+ (optional)
+ :param lookup: the variable lookup mechanism; either "lenient" (the
+ default), "strict", or a custom lookup class
+ :return: a list of `TEXT` and `EXPR` events
+ :raise TemplateSyntaxError: when a syntax error in an expression is
+ encountered
+ """
+ pos = [filepath, lineno, offset]
+
+ textbuf = []
+ textpos = None
+ for is_expr, chunk in chain(lex(text, pos, filepath), [(True, '')]):
+ if is_expr:
+ if textbuf:
+ yield TEXT, ''.join(textbuf), textpos
+ del textbuf[:]
+ textpos = None
+ if chunk:
+ try:
+ expr = Expression(chunk.strip(), pos[0], pos[1],
+ lookup=lookup)
+ yield EXPR, expr, tuple(pos)
+ except SyntaxError, err:
+ raise TemplateSyntaxError(err, filepath, pos[1],
+ pos[2] + (err.offset or 0))
+ else:
+ textbuf.append(chunk)
+ if textpos is None:
+ textpos = tuple(pos)
+
+ if '\n' in chunk:
+ lines = chunk.splitlines()
+ pos[1] += len(lines) - 1
+ pos[2] += len(lines[-1])
+ else:
+ pos[2] += len(chunk)
+
+
+def lex(text, textpos, filepath):
+ offset = pos = 0
+ end = len(text)
+ escaped = False
+
+ while 1:
+ if escaped:
+ offset = text.find(PREFIX, offset + 2)
+ escaped = False
+ else:
+ offset = text.find(PREFIX, pos)
+ if offset < 0 or offset == end - 1:
+ break
+ next = text[offset + 1]
+
+ if next == '{':
+ if offset > pos:
+ yield False, text[pos:offset]
+ pos = offset + 2
+ level = 1
+ while level:
+ match = token_re.match(text, pos)
+ if match is None:
+ raise TemplateSyntaxError('invalid syntax', filepath,
+ *textpos[1:])
+ pos = match.end()
+ tstart, tend = match.regs[3]
+ token = text[tstart:tend]
+ if token == '{':
+ level += 1
+ elif token == '}':
+ level -= 1
+ yield True, text[offset + 2:pos - 1]
+
+ elif next in NAMESTART:
+ if offset > pos:
+ yield False, text[pos:offset]
+ pos = offset
+ pos += 1
+ while pos < end:
+ char = text[pos]
+ if char not in NAMECHARS:
+ break
+ pos += 1
+ yield True, text[offset + 1:pos].strip()
+
+ elif not escaped and next == PREFIX:
+ if offset > pos:
+ yield False, text[pos:offset]
+ escaped = True
+ pos = offset + 1
+
+ else:
+ yield False, text[pos:offset + 1]
+ pos = offset + 1
+
+ if pos < end:
+ yield False, text[pos:]
diff --git a/websdk/genshi/template/loader.py b/websdk/genshi/template/loader.py
new file mode 100644
index 0000000..0e7cda7
--- /dev/null
+++ b/websdk/genshi/template/loader.py
@@ -0,0 +1,344 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2006-2010 Edgewall Software
+# All rights reserved.
+#
+# This software is licensed as described in the file COPYING, which
+# you should have received as part of this distribution. The terms
+# are also available at http://genshi.edgewall.org/wiki/License.
+#
+# This software consists of voluntary contributions made by many
+# individuals. For the exact contribution history, see the revision
+# history and logs, available at http://genshi.edgewall.org/log/.
+
+"""Template loading and caching."""
+
+import os
+try:
+ import threading
+except ImportError:
+ import dummy_threading as threading
+
+from genshi.template.base import TemplateError
+from genshi.util import LRUCache
+
+__all__ = ['TemplateLoader', 'TemplateNotFound', 'directory', 'package',
+ 'prefixed']
+__docformat__ = 'restructuredtext en'
+
+
+class TemplateNotFound(TemplateError):
+ """Exception raised when a specific template file could not be found."""
+
+ def __init__(self, name, search_path):
+ """Create the exception.
+
+ :param name: the filename of the template
+ :param search_path: the search path used to lookup the template
+ """
+ TemplateError.__init__(self, 'Template "%s" not found' % name)
+ self.search_path = search_path
+
+
+class TemplateLoader(object):
+ """Responsible for loading templates from files on the specified search
+ path.
+
+ >>> import tempfile
+ >>> fd, path = tempfile.mkstemp(suffix='.html', prefix='template')
+ >>> os.write(fd, '<p>$var</p>')
+ 11
+ >>> os.close(fd)
+
+ The template loader accepts a list of directory paths that are then used
+ when searching for template files, in the given order:
+
+ >>> loader = TemplateLoader([os.path.dirname(path)])
+
+ The `load()` method first checks the template cache whether the requested
+ template has already been loaded. If not, it attempts to locate the
+ template file, and returns the corresponding `Template` object:
+
+ >>> from genshi.template import MarkupTemplate
+ >>> template = loader.load(os.path.basename(path))
+ >>> isinstance(template, MarkupTemplate)
+ True
+
+ Template instances are cached: requesting a template with the same name
+ results in the same instance being returned:
+
+ >>> loader.load(os.path.basename(path)) is template
+ True
+
+ The `auto_reload` option can be used to control whether a template should
+ be automatically reloaded when the file it was loaded from has been
+ changed. Disable this automatic reloading to improve performance.
+
+ >>> os.remove(path)
+ """
+ def __init__(self, search_path=None, auto_reload=False,
+ default_encoding=None, max_cache_size=25, default_class=None,
+ variable_lookup='strict', allow_exec=True, callback=None):
+ """Create the template laoder.
+
+ :param search_path: a list of absolute path names that should be
+ searched for template files, or a string containing
+ a single absolute path; alternatively, any item on
+ the list may be a ''load function'' that is passed
+ a filename and returns a file-like object and some
+ metadata
+ :param auto_reload: whether to check the last modification time of
+ template files, and reload them if they have changed
+ :param default_encoding: the default encoding to assume when loading
+ templates; defaults to UTF-8
+ :param max_cache_size: the maximum number of templates to keep in the
+ cache
+ :param default_class: the default `Template` subclass to use when
+ instantiating templates
+ :param variable_lookup: the variable lookup mechanism; either "strict"
+ (the default), "lenient", or a custom lookup
+ class
+ :param allow_exec: whether to allow Python code blocks in templates
+ :param callback: (optional) a callback function that is invoked after a
+ template was initialized by this loader; the function
+ is passed the template object as only argument. This
+ callback can be used for example to add any desired
+ filters to the template
+ :see: `LenientLookup`, `StrictLookup`
+
+ :note: Changed in 0.5: Added the `allow_exec` argument
+ """
+ from genshi.template.markup import MarkupTemplate
+
+ self.search_path = search_path
+ if self.search_path is None:
+ self.search_path = []
+ elif not isinstance(self.search_path, (list, tuple)):
+ self.search_path = [self.search_path]
+
+ self.auto_reload = auto_reload
+ """Whether templates should be reloaded when the underlying file is
+ changed"""
+
+ self.default_encoding = default_encoding
+ self.default_class = default_class or MarkupTemplate
+ self.variable_lookup = variable_lookup
+ self.allow_exec = allow_exec
+ if callback is not None and not hasattr(callback, '__call__'):
+ raise TypeError('The "callback" parameter needs to be callable')
+ self.callback = callback
+ self._cache = LRUCache(max_cache_size)
+ self._uptodate = {}
+ self._lock = threading.RLock()
+
+ def __getstate__(self):
+ state = self.__dict__.copy()
+ state['_lock'] = None
+ return state
+
+ def __setstate__(self, state):
+ self.__dict__ = state
+ self._lock = threading.RLock()
+
+ def load(self, filename, relative_to=None, cls=None, encoding=None):
+ """Load the template with the given name.
+
+ If the `filename` parameter is relative, this method searches the
+ search path trying to locate a template matching the given name. If the
+ file name is an absolute path, the search path is ignored.
+
+ If the requested template is not found, a `TemplateNotFound` exception
+ is raised. Otherwise, a `Template` object is returned that represents
+ the parsed template.
+
+ Template instances are cached to avoid having to parse the same
+ template file more than once. Thus, subsequent calls of this method
+ with the same template file name will return the same `Template`
+ object (unless the ``auto_reload`` option is enabled and the file was
+ changed since the last parse.)
+
+ If the `relative_to` parameter is provided, the `filename` is
+ interpreted as being relative to that path.
+
+ :param filename: the relative path of the template file to load
+ :param relative_to: the filename of the template from which the new
+ template is being loaded, or ``None`` if the
+ template is being loaded directly
+ :param cls: the class of the template object to instantiate
+ :param encoding: the encoding of the template to load; defaults to the
+ ``default_encoding`` of the loader instance
+ :return: the loaded `Template` instance
+ :raises TemplateNotFound: if a template with the given name could not
+ be found
+ """
+ if cls is None:
+ cls = self.default_class
+ search_path = self.search_path
+
+ # Make the filename relative to the template file its being loaded
+ # from, but only if that file is specified as a relative path, or no
+ # search path has been set up
+ if relative_to and (not search_path or not os.path.isabs(relative_to)):
+ filename = os.path.join(os.path.dirname(relative_to), filename)
+
+ filename = os.path.normpath(filename)
+ cachekey = filename
+
+ self._lock.acquire()
+ try:
+ # First check the cache to avoid reparsing the same file
+ try:
+ tmpl = self._cache[cachekey]
+ if not self.auto_reload:
+ return tmpl
+ uptodate = self._uptodate[cachekey]
+ if uptodate is not None and uptodate():
+ return tmpl
+ except (KeyError, OSError):
+ pass
+
+ isabs = False
+
+ if os.path.isabs(filename):
+ # Bypass the search path if the requested filename is absolute
+ search_path = [os.path.dirname(filename)]
+ isabs = True
+
+ elif relative_to and os.path.isabs(relative_to):
+ # Make sure that the directory containing the including
+ # template is on the search path
+ dirname = os.path.dirname(relative_to)
+ if dirname not in search_path:
+ search_path = list(search_path) + [dirname]
+ isabs = True
+
+ elif not search_path:
+ # Uh oh, don't know where to look for the template
+ raise TemplateError('Search path for templates not configured')
+
+ for loadfunc in search_path:
+ if isinstance(loadfunc, basestring):
+ loadfunc = directory(loadfunc)
+ try:
+ filepath, filename, fileobj, uptodate = loadfunc(filename)
+ except IOError:
+ continue
+ else:
+ try:
+ if isabs:
+ # If the filename of either the included or the
+ # including template is absolute, make sure the
+ # included template gets an absolute path, too,
+ # so that nested includes work properly without a
+ # search path
+ filename = filepath
+ tmpl = self._instantiate(cls, fileobj, filepath,
+ filename, encoding=encoding)
+ if self.callback:
+ self.callback(tmpl)
+ self._cache[cachekey] = tmpl
+ self._uptodate[cachekey] = uptodate
+ finally:
+ if hasattr(fileobj, 'close'):
+ fileobj.close()
+ return tmpl
+
+ raise TemplateNotFound(filename, search_path)
+
+ finally:
+ self._lock.release()
+
+ def _instantiate(self, cls, fileobj, filepath, filename, encoding=None):
+ """Instantiate and return the `Template` object based on the given
+ class and parameters.
+
+ This function is intended for subclasses to override if they need to
+ implement special template instantiation logic. Code that just uses
+ the `TemplateLoader` should use the `load` method instead.
+
+ :param cls: the class of the template object to instantiate
+ :param fileobj: a readable file-like object containing the template
+ source
+ :param filepath: the absolute path to the template file
+ :param filename: the path to the template file relative to the search
+ path
+ :param encoding: the encoding of the template to load; defaults to the
+ ``default_encoding`` of the loader instance
+ :return: the loaded `Template` instance
+ :rtype: `Template`
+ """
+ if encoding is None:
+ encoding = self.default_encoding
+ return cls(fileobj, filepath=filepath, filename=filename, loader=self,
+ encoding=encoding, lookup=self.variable_lookup,
+ allow_exec=self.allow_exec)
+
+ @staticmethod
+ def directory(path):
+ """Loader factory for loading templates from a local directory.
+
+ :param path: the path to the local directory containing the templates
+ :return: the loader function to load templates from the given directory
+ :rtype: ``function``
+ """
+ def _load_from_directory(filename):
+ filepath = os.path.join(path, filename)
+ fileobj = open(filepath, 'U')
+ mtime = os.path.getmtime(filepath)
+ def _uptodate():
+ return mtime == os.path.getmtime(filepath)
+ return filepath, filename, fileobj, _uptodate
+ return _load_from_directory
+
+ @staticmethod
+ def package(name, path):
+ """Loader factory for loading templates from egg package data.
+
+ :param name: the name of the package containing the resources
+ :param path: the path inside the package data
+ :return: the loader function to load templates from the given package
+ :rtype: ``function``
+ """
+ from pkg_resources import resource_stream
+ def _load_from_package(filename):
+ filepath = os.path.join(path, filename)
+ return filepath, filename, resource_stream(name, filepath), None
+ return _load_from_package
+
+ @staticmethod
+ def prefixed(**delegates):
+ """Factory for a load function that delegates to other loaders
+ depending on the prefix of the requested template path.
+
+ The prefix is stripped from the filename when passing on the load
+ request to the delegate.
+
+ >>> load = prefixed(
+ ... app1 = lambda filename: ('app1', filename, None, None),
+ ... app2 = lambda filename: ('app2', filename, None, None)
+ ... )
+ >>> print(load('app1/foo.html'))
+ ('app1', 'app1/foo.html', None, None)
+ >>> print(load('app2/bar.html'))
+ ('app2', 'app2/bar.html', None, None)
+
+ :param delegates: mapping of path prefixes to loader functions
+ :return: the loader function
+ :rtype: ``function``
+ """
+ def _dispatch_by_prefix(filename):
+ for prefix, delegate in delegates.items():
+ if filename.startswith(prefix):
+ if isinstance(delegate, basestring):
+ delegate = directory(delegate)
+ filepath, _, fileobj, uptodate = delegate(
+ filename[len(prefix):].lstrip('/\\')
+ )
+ return filepath, filename, fileobj, uptodate
+ raise TemplateNotFound(filename, list(delegates.keys()))
+ return _dispatch_by_prefix
+
+
+directory = TemplateLoader.directory
+package = TemplateLoader.package
+prefixed = TemplateLoader.prefixed
diff --git a/websdk/genshi/template/markup.py b/websdk/genshi/template/markup.py
new file mode 100644
index 0000000..0e31632
--- /dev/null
+++ b/websdk/genshi/template/markup.py
@@ -0,0 +1,397 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2006-2010 Edgewall Software
+# All rights reserved.
+#
+# This software is licensed as described in the file COPYING, which
+# you should have received as part of this distribution. The terms
+# are also available at http://genshi.edgewall.org/wiki/License.
+#
+# This software consists of voluntary contributions made by many
+# individuals. For the exact contribution history, see the revision
+# history and logs, available at http://genshi.edgewall.org/log/.
+
+"""Markup templating engine."""
+
+from itertools import chain
+
+from genshi.core import Attrs, Markup, Namespace, Stream, StreamEventKind
+from genshi.core import START, END, START_NS, END_NS, TEXT, PI, COMMENT
+from genshi.input import XMLParser
+from genshi.template.base import BadDirectiveError, Template, \
+ TemplateSyntaxError, _apply_directives, \
+ EXEC, INCLUDE, SUB
+from genshi.template.eval import Suite
+from genshi.template.interpolation import interpolate
+from genshi.template.directives import *
+from genshi.template.text import NewTextTemplate
+
+__all__ = ['MarkupTemplate']
+__docformat__ = 'restructuredtext en'
+
+
+class MarkupTemplate(Template):
+ """Implementation of the template language for XML-based templates.
+
+ >>> tmpl = MarkupTemplate('''<ul xmlns:py="http://genshi.edgewall.org/">
+ ... <li py:for="item in items">${item}</li>
+ ... </ul>''')
+ >>> print(tmpl.generate(items=[1, 2, 3]))
+ <ul>
+ <li>1</li><li>2</li><li>3</li>
+ </ul>
+ """
+
+ DIRECTIVE_NAMESPACE = 'http://genshi.edgewall.org/'
+ XINCLUDE_NAMESPACE = 'http://www.w3.org/2001/XInclude'
+
+ directives = [('def', DefDirective),
+ ('match', MatchDirective),
+ ('when', WhenDirective),
+ ('otherwise', OtherwiseDirective),
+ ('for', ForDirective),
+ ('if', IfDirective),
+ ('choose', ChooseDirective),
+ ('with', WithDirective),
+ ('replace', ReplaceDirective),
+ ('content', ContentDirective),
+ ('attrs', AttrsDirective),
+ ('strip', StripDirective)]
+ serializer = 'xml'
+ _number_conv = Markup
+
+ def __init__(self, source, filepath=None, filename=None, loader=None,
+ encoding=None, lookup='strict', allow_exec=True):
+ Template.__init__(self, source, filepath=filepath, filename=filename,
+ loader=loader, encoding=encoding, lookup=lookup,
+ allow_exec=allow_exec)
+ self.add_directives(self.DIRECTIVE_NAMESPACE, self)
+
+ def _init_filters(self):
+ Template._init_filters(self)
+ # Make sure the include filter comes after the match filter
+ self.filters.remove(self._include)
+ self.filters += [self._match, self._include]
+
+ def _parse(self, source, encoding):
+ if not isinstance(source, Stream):
+ source = XMLParser(source, filename=self.filename,
+ encoding=encoding)
+ stream = []
+
+ for kind, data, pos in source:
+
+ if kind is TEXT:
+ for kind, data, pos in interpolate(data, self.filepath, pos[1],
+ pos[2], lookup=self.lookup):
+ stream.append((kind, data, pos))
+
+ elif kind is PI and data[0] == 'python':
+ if not self.allow_exec:
+ raise TemplateSyntaxError('Python code blocks not allowed',
+ self.filepath, *pos[1:])
+ try:
+ suite = Suite(data[1], self.filepath, pos[1],
+ lookup=self.lookup)
+ except SyntaxError, err:
+ raise TemplateSyntaxError(err, self.filepath,
+ pos[1] + (err.lineno or 1) - 1,
+ pos[2] + (err.offset or 0))
+ stream.append((EXEC, suite, pos))
+
+ elif kind is COMMENT:
+ if not data.lstrip().startswith('!'):
+ stream.append((kind, data, pos))
+
+ else:
+ stream.append((kind, data, pos))
+
+ return stream
+
+ def _extract_directives(self, stream, namespace, factory):
+ depth = 0
+ dirmap = {} # temporary mapping of directives to elements
+ new_stream = []
+ ns_prefix = {} # namespace prefixes in use
+
+ for kind, data, pos in stream:
+
+ if kind is START:
+ tag, attrs = data
+ directives = []
+ strip = False
+
+ if tag.namespace == namespace:
+ cls = factory.get_directive(tag.localname)
+ if cls is None:
+ raise BadDirectiveError(tag.localname,
+ self.filepath, pos[1])
+ args = dict([(name.localname, value) for name, value
+ in attrs if not name.namespace])
+ directives.append((factory.get_directive_index(cls), cls,
+ args, ns_prefix.copy(), pos))
+ strip = True
+
+ new_attrs = []
+ for name, value in attrs:
+ if name.namespace == namespace:
+ cls = factory.get_directive(name.localname)
+ if cls is None:
+ raise BadDirectiveError(name.localname,
+ self.filepath, pos[1])
+ if type(value) is list and len(value) == 1:
+ value = value[0][1]
+ directives.append((factory.get_directive_index(cls),
+ cls, value, ns_prefix.copy(), pos))
+ else:
+ new_attrs.append((name, value))
+ new_attrs = Attrs(new_attrs)
+
+ if directives:
+ directives.sort()
+ dirmap[(depth, tag)] = (directives, len(new_stream),
+ strip)
+
+ new_stream.append((kind, (tag, new_attrs), pos))
+ depth += 1
+
+ elif kind is END:
+ depth -= 1
+ new_stream.append((kind, data, pos))
+
+ # If there have have directive attributes with the
+ # corresponding start tag, move the events inbetween into
+ # a "subprogram"
+ if (depth, data) in dirmap:
+ directives, offset, strip = dirmap.pop((depth, data))
+ substream = new_stream[offset:]
+ if strip:
+ substream = substream[1:-1]
+ new_stream[offset:] = [
+ (SUB, (directives, substream), pos)
+ ]
+
+ elif kind is SUB:
+ directives, substream = data
+ substream = self._extract_directives(substream, namespace,
+ factory)
+
+ if len(substream) == 1 and substream[0][0] is SUB:
+ added_directives, substream = substream[0][1]
+ directives += added_directives
+
+ new_stream.append((kind, (directives, substream), pos))
+
+ elif kind is START_NS:
+ # Strip out the namespace declaration for template
+ # directives
+ prefix, uri = data
+ ns_prefix[prefix] = uri
+ if uri != namespace:
+ new_stream.append((kind, data, pos))
+
+ elif kind is END_NS:
+ uri = ns_prefix.pop(data, None)
+ if uri and uri != namespace:
+ new_stream.append((kind, data, pos))
+
+ else:
+ new_stream.append((kind, data, pos))
+
+ return new_stream
+
+ def _extract_includes(self, stream):
+ streams = [[]] # stacked lists of events of the "compiled" template
+ prefixes = {}
+ fallbacks = []
+ includes = []
+ xinclude_ns = Namespace(self.XINCLUDE_NAMESPACE)
+
+ for kind, data, pos in stream:
+ stream = streams[-1]
+
+ if kind is START:
+ # Record any directive attributes in start tags
+ tag, attrs = data
+ if tag in xinclude_ns:
+ if tag.localname == 'include':
+ include_href = attrs.get('href')
+ if not include_href:
+ raise TemplateSyntaxError('Include misses required '
+ 'attribute "href"',
+ self.filepath, *pos[1:])
+ includes.append((include_href, attrs.get('parse')))
+ streams.append([])
+ elif tag.localname == 'fallback':
+ streams.append([])
+ fallbacks.append(streams[-1])
+ else:
+ stream.append((kind, (tag, attrs), pos))
+
+ elif kind is END:
+ if fallbacks and data == xinclude_ns['fallback']:
+ assert streams.pop() is fallbacks[-1]
+ elif data == xinclude_ns['include']:
+ fallback = None
+ if len(fallbacks) == len(includes):
+ fallback = fallbacks.pop()
+ streams.pop() # discard anything between the include tags
+ # and the fallback element
+ stream = streams[-1]
+ href, parse = includes.pop()
+ try:
+ cls = {
+ 'xml': MarkupTemplate,
+ 'text': NewTextTemplate
+ }.get(parse) or self.__class__
+ except KeyError:
+ raise TemplateSyntaxError('Invalid value for "parse" '
+ 'attribute of include',
+ self.filepath, *pos[1:])
+ stream.append((INCLUDE, (href, cls, fallback), pos))
+ else:
+ stream.append((kind, data, pos))
+
+ elif kind is START_NS and data[1] == xinclude_ns:
+ # Strip out the XInclude namespace
+ prefixes[data[0]] = data[1]
+
+ elif kind is END_NS and data in prefixes:
+ prefixes.pop(data)
+
+ else:
+ stream.append((kind, data, pos))
+
+ assert len(streams) == 1
+ return streams[0]
+
+ def _interpolate_attrs(self, stream):
+ for kind, data, pos in stream:
+
+ if kind is START:
+ # Record any directive attributes in start tags
+ tag, attrs = data
+ new_attrs = []
+ for name, value in attrs:
+ if value:
+ value = list(interpolate(value, self.filepath, pos[1],
+ pos[2], lookup=self.lookup))
+ if len(value) == 1 and value[0][0] is TEXT:
+ value = value[0][1]
+ new_attrs.append((name, value))
+ data = tag, Attrs(new_attrs)
+
+ yield kind, data, pos
+
+ def _prepare(self, stream):
+ return Template._prepare(self,
+ self._extract_includes(self._interpolate_attrs(stream))
+ )
+
+ def add_directives(self, namespace, factory):
+ """Register a custom `DirectiveFactory` for a given namespace.
+
+ :param namespace: the namespace URI
+ :type namespace: `basestring`
+ :param factory: the directive factory to register
+ :type factory: `DirectiveFactory`
+ :since: version 0.6
+ """
+ assert not self._prepared, 'Too late for adding directives, ' \
+ 'template already prepared'
+ self._stream = self._extract_directives(self._stream, namespace,
+ factory)
+
+ def _match(self, stream, ctxt, start=0, end=None, **vars):
+ """Internal stream filter that applies any defined match templates
+ to the stream.
+ """
+ match_templates = ctxt._match_templates
+
+ tail = []
+ def _strip(stream, append=tail.append):
+ depth = 1
+ next = stream.next
+ while 1:
+ event = next()
+ if event[0] is START:
+ depth += 1
+ elif event[0] is END:
+ depth -= 1
+ if depth > 0:
+ yield event
+ else:
+ append(event)
+ break
+
+ for event in stream:
+
+ # We (currently) only care about start and end events for matching
+ # We might care about namespace events in the future, though
+ if not match_templates or (event[0] is not START and
+ event[0] is not END):
+ yield event
+ continue
+
+ for idx, (test, path, template, hints, namespaces, directives) \
+ in enumerate(match_templates):
+ if idx < start or end is not None and idx >= end:
+ continue
+
+ if test(event, namespaces, ctxt) is True:
+ if 'match_once' in hints:
+ del match_templates[idx]
+ idx -= 1
+
+ # Let the remaining match templates know about the event so
+ # they get a chance to update their internal state
+ for test in [mt[0] for mt in match_templates[idx + 1:]]:
+ test(event, namespaces, ctxt, updateonly=True)
+
+ # Consume and store all events until an end event
+ # corresponding to this start event is encountered
+ pre_end = idx + 1
+ if 'match_once' not in hints and 'not_recursive' in hints:
+ pre_end -= 1
+ inner = _strip(stream)
+ if pre_end > 0:
+ inner = self._match(inner, ctxt, start=start,
+ end=pre_end, **vars)
+ content = self._include(chain([event], inner, tail), ctxt)
+ if 'not_buffered' not in hints:
+ content = list(content)
+ content = Stream(content)
+
+ # Make the select() function available in the body of the
+ # match template
+ selected = [False]
+ def select(path):
+ selected[0] = True
+ return content.select(path, namespaces, ctxt)
+ vars = dict(select=select)
+
+ # Recursively process the output
+ template = _apply_directives(template, directives, ctxt,
+ vars)
+ for event in self._match(self._flatten(template, ctxt,
+ **vars),
+ ctxt, start=idx + 1, **vars):
+ yield event
+
+ # If the match template did not actually call select to
+ # consume the matched stream, the original events need to
+ # be consumed here or they'll get appended to the output
+ if not selected[0]:
+ for event in content:
+ pass
+
+ # Let the remaining match templates know about the last
+ # event in the matched content, so they can update their
+ # internal state accordingly
+ for test in [mt[0] for mt in match_templates[idx + 1:]]:
+ test(tail[0], namespaces, ctxt, updateonly=True)
+
+ break
+
+ else: # no matches
+ yield event
diff --git a/websdk/genshi/template/plugin.py b/websdk/genshi/template/plugin.py
new file mode 100644
index 0000000..70d56af
--- /dev/null
+++ b/websdk/genshi/template/plugin.py
@@ -0,0 +1,176 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2006-2009 Edgewall Software
+# Copyright (C) 2006 Matthew Good
+# All rights reserved.
+#
+# This software is licensed as described in the file COPYING, which
+# you should have received as part of this distribution. The terms
+# are also available at http://genshi.edgewall.org/wiki/License.
+#
+# This software consists of voluntary contributions made by many
+# individuals. For the exact contribution history, see the revision
+# history and logs, available at http://genshi.edgewall.org/log/.
+
+"""Basic support for the template engine plugin API used by TurboGears and
+CherryPy/Buffet.
+"""
+
+from genshi.input import ET, HTML, XML
+from genshi.output import DocType
+from genshi.template.base import Template
+from genshi.template.loader import TemplateLoader
+from genshi.template.markup import MarkupTemplate
+from genshi.template.text import TextTemplate, NewTextTemplate
+
+__all__ = ['ConfigurationError', 'AbstractTemplateEnginePlugin',
+ 'MarkupTemplateEnginePlugin', 'TextTemplateEnginePlugin']
+__docformat__ = 'restructuredtext en'
+
+
+class ConfigurationError(ValueError):
+ """Exception raised when invalid plugin options are encountered."""
+
+
+class AbstractTemplateEnginePlugin(object):
+ """Implementation of the plugin API."""
+
+ template_class = None
+ extension = None
+
+ def __init__(self, extra_vars_func=None, options=None):
+ self.get_extra_vars = extra_vars_func
+ if options is None:
+ options = {}
+ self.options = options
+
+ self.default_encoding = options.get('genshi.default_encoding', 'utf-8')
+ auto_reload = options.get('genshi.auto_reload', '1')
+ if isinstance(auto_reload, basestring):
+ auto_reload = auto_reload.lower() in ('1', 'on', 'yes', 'true')
+ search_path = [p for p in
+ options.get('genshi.search_path', '').split(':') if p]
+ self.use_package_naming = not search_path
+ try:
+ max_cache_size = int(options.get('genshi.max_cache_size', 25))
+ except ValueError:
+ raise ConfigurationError('Invalid value for max_cache_size: "%s"' %
+ options.get('genshi.max_cache_size'))
+
+ loader_callback = options.get('genshi.loader_callback', None)
+ if loader_callback and not hasattr(loader_callback, '__call__'):
+ raise ConfigurationError('loader callback must be a function')
+
+ lookup_errors = options.get('genshi.lookup_errors', 'strict')
+ if lookup_errors not in ('lenient', 'strict'):
+ raise ConfigurationError('Unknown lookup errors mode "%s"' %
+ lookup_errors)
+
+ try:
+ allow_exec = bool(options.get('genshi.allow_exec', True))
+ except ValueError:
+ raise ConfigurationError('Invalid value for allow_exec "%s"' %
+ options.get('genshi.allow_exec'))
+
+ self.loader = TemplateLoader([p for p in search_path if p],
+ auto_reload=auto_reload,
+ max_cache_size=max_cache_size,
+ default_class=self.template_class,
+ variable_lookup=lookup_errors,
+ allow_exec=allow_exec,
+ callback=loader_callback)
+
+ def load_template(self, templatename, template_string=None):
+ """Find a template specified in python 'dot' notation, or load one from
+ a string.
+ """
+ if template_string is not None:
+ return self.template_class(template_string)
+
+ if self.use_package_naming:
+ divider = templatename.rfind('.')
+ if divider >= 0:
+ from pkg_resources import resource_filename
+ package = templatename[:divider]
+ basename = templatename[divider + 1:] + self.extension
+ templatename = resource_filename(package, basename)
+
+ return self.loader.load(templatename)
+
+ def _get_render_options(self, format=None, fragment=False):
+ if format is None:
+ format = self.default_format
+ kwargs = {'method': format}
+ if self.default_encoding:
+ kwargs['encoding'] = self.default_encoding
+ return kwargs
+
+ def render(self, info, format=None, fragment=False, template=None):
+ """Render the template to a string using the provided info."""
+ kwargs = self._get_render_options(format=format, fragment=fragment)
+ return self.transform(info, template).render(**kwargs)
+
+ def transform(self, info, template):
+ """Render the output to an event stream."""
+ if not isinstance(template, Template):
+ template = self.load_template(template)
+ return template.generate(**info)
+
+
+class MarkupTemplateEnginePlugin(AbstractTemplateEnginePlugin):
+ """Implementation of the plugin API for markup templates."""
+
+ template_class = MarkupTemplate
+ extension = '.html'
+
+ def __init__(self, extra_vars_func=None, options=None):
+ AbstractTemplateEnginePlugin.__init__(self, extra_vars_func, options)
+
+ default_doctype = self.options.get('genshi.default_doctype')
+ if default_doctype:
+ doctype = DocType.get(default_doctype)
+ if doctype is None:
+ raise ConfigurationError('Unknown doctype %r' % default_doctype)
+ self.default_doctype = doctype
+ else:
+ self.default_doctype = None
+
+ format = self.options.get('genshi.default_format', 'html').lower()
+ if format not in ('html', 'xhtml', 'xml', 'text'):
+ raise ConfigurationError('Unknown output format %r' % format)
+ self.default_format = format
+
+ def _get_render_options(self, format=None, fragment=False):
+ kwargs = super(MarkupTemplateEnginePlugin,
+ self)._get_render_options(format, fragment)
+ if self.default_doctype and not fragment:
+ kwargs['doctype'] = self.default_doctype
+ return kwargs
+
+ def transform(self, info, template):
+ """Render the output to an event stream."""
+ data = {'ET': ET, 'HTML': HTML, 'XML': XML}
+ if self.get_extra_vars:
+ data.update(self.get_extra_vars())
+ data.update(info)
+ return super(MarkupTemplateEnginePlugin, self).transform(data, template)
+
+
+class TextTemplateEnginePlugin(AbstractTemplateEnginePlugin):
+ """Implementation of the plugin API for text templates."""
+
+ template_class = TextTemplate
+ extension = '.txt'
+ default_format = 'text'
+
+ def __init__(self, extra_vars_func=None, options=None):
+ if options is None:
+ options = {}
+
+ new_syntax = options.get('genshi.new_text_syntax')
+ if isinstance(new_syntax, basestring):
+ new_syntax = new_syntax.lower() in ('1', 'on', 'yes', 'true')
+ if new_syntax:
+ self.template_class = NewTextTemplate
+
+ AbstractTemplateEnginePlugin.__init__(self, extra_vars_func, options)
diff --git a/websdk/genshi/template/text.py b/websdk/genshi/template/text.py
new file mode 100644
index 0000000..746226c
--- /dev/null
+++ b/websdk/genshi/template/text.py
@@ -0,0 +1,333 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2006-2009 Edgewall Software
+# All rights reserved.
+#
+# This software is licensed as described in the file COPYING, which
+# you should have received as part of this distribution. The terms
+# are also available at http://genshi.edgewall.org/wiki/License.
+#
+# This software consists of voluntary contributions made by many
+# individuals. For the exact contribution history, see the revision
+# history and logs, available at http://genshi.edgewall.org/log/.
+
+"""Plain text templating engine.
+
+This module implements two template language syntaxes, at least for a certain
+transitional period. `OldTextTemplate` (aliased to just `TextTemplate`) defines
+a syntax that was inspired by Cheetah/Velocity. `NewTextTemplate` on the other
+hand is inspired by the syntax of the Django template language, which has more
+explicit delimiting of directives, and is more flexible with regards to
+white space and line breaks.
+
+In a future release, `OldTextTemplate` will be phased out in favor of
+`NewTextTemplate`, as the names imply. Therefore the new syntax is strongly
+recommended for new projects, and existing projects may want to migrate to the
+new syntax to remain compatible with future Genshi releases.
+"""
+
+import re
+
+from genshi.core import TEXT
+from genshi.template.base import BadDirectiveError, Template, \
+ TemplateSyntaxError, EXEC, INCLUDE, SUB
+from genshi.template.eval import Suite
+from genshi.template.directives import *
+from genshi.template.directives import Directive
+from genshi.template.interpolation import interpolate
+
+__all__ = ['NewTextTemplate', 'OldTextTemplate', 'TextTemplate']
+__docformat__ = 'restructuredtext en'
+
+
+class NewTextTemplate(Template):
+ r"""Implementation of a simple text-based template engine. This class will
+ replace `OldTextTemplate` in a future release.
+
+ It uses a more explicit delimiting style for directives: instead of the old
+ style which required putting directives on separate lines that were prefixed
+ with a ``#`` sign, directives and commenbtsr are enclosed in delimiter pairs
+ (by default ``{% ... %}`` and ``{# ... #}``, respectively).
+
+ Variable substitution uses the same interpolation syntax as for markup
+ languages: simple references are prefixed with a dollar sign, more complex
+ expression enclosed in curly braces.
+
+ >>> tmpl = NewTextTemplate('''Dear $name,
+ ...
+ ... {# This is a comment #}
+ ... We have the following items for you:
+ ... {% for item in items %}
+ ... * ${'Item %d' % item}
+ ... {% end %}
+ ... ''')
+ >>> print(tmpl.generate(name='Joe', items=[1, 2, 3]).render(encoding=None))
+ Dear Joe,
+ <BLANKLINE>
+ <BLANKLINE>
+ We have the following items for you:
+ <BLANKLINE>
+ * Item 1
+ <BLANKLINE>
+ * Item 2
+ <BLANKLINE>
+ * Item 3
+ <BLANKLINE>
+ <BLANKLINE>
+
+ By default, no spaces or line breaks are removed. If a line break should
+ not be included in the output, prefix it with a backslash:
+
+ >>> tmpl = NewTextTemplate('''Dear $name,
+ ...
+ ... {# This is a comment #}\
+ ... We have the following items for you:
+ ... {% for item in items %}\
+ ... * $item
+ ... {% end %}\
+ ... ''')
+ >>> print(tmpl.generate(name='Joe', items=[1, 2, 3]).render(encoding=None))
+ Dear Joe,
+ <BLANKLINE>
+ We have the following items for you:
+ * 1
+ * 2
+ * 3
+ <BLANKLINE>
+
+ Backslashes are also used to escape the start delimiter of directives and
+ comments:
+
+ >>> tmpl = NewTextTemplate('''Dear $name,
+ ...
+ ... \{# This is a comment #}
+ ... We have the following items for you:
+ ... {% for item in items %}\
+ ... * $item
+ ... {% end %}\
+ ... ''')
+ >>> print(tmpl.generate(name='Joe', items=[1, 2, 3]).render(encoding=None))
+ Dear Joe,
+ <BLANKLINE>
+ {# This is a comment #}
+ We have the following items for you:
+ * 1
+ * 2
+ * 3
+ <BLANKLINE>
+
+ :since: version 0.5
+ """
+ directives = [('def', DefDirective),
+ ('when', WhenDirective),
+ ('otherwise', OtherwiseDirective),
+ ('for', ForDirective),
+ ('if', IfDirective),
+ ('choose', ChooseDirective),
+ ('with', WithDirective)]
+ serializer = 'text'
+
+ _DIRECTIVE_RE = r'((?<!\\)%s\s*(\w+)\s*(.*?)\s*%s|(?<!\\)%s.*?%s)'
+ _ESCAPE_RE = r'\\\n|\\(\\)|\\(%s)|\\(%s)'
+
+ def __init__(self, source, filepath=None, filename=None, loader=None,
+ encoding=None, lookup='strict', allow_exec=False,
+ delims=('{%', '%}', '{#', '#}')):
+ self.delimiters = delims
+ Template.__init__(self, source, filepath=filepath, filename=filename,
+ loader=loader, encoding=encoding, lookup=lookup)
+
+ def _get_delims(self):
+ return self._delims
+ def _set_delims(self, delims):
+ if len(delims) != 4:
+ raise ValueError('delimiers tuple must have exactly four elements')
+ self._delims = delims
+ self._directive_re = re.compile(self._DIRECTIVE_RE % tuple(
+ [re.escape(d) for d in delims]
+ ), re.DOTALL)
+ self._escape_re = re.compile(self._ESCAPE_RE % tuple(
+ [re.escape(d) for d in delims[::2]]
+ ))
+ delimiters = property(_get_delims, _set_delims, """\
+ The delimiters for directives and comments. This should be a four item tuple
+ of the form ``(directive_start, directive_end, comment_start,
+ comment_end)``, where each item is a string.
+ """)
+
+ def _parse(self, source, encoding):
+ """Parse the template from text input."""
+ stream = [] # list of events of the "compiled" template
+ dirmap = {} # temporary mapping of directives to elements
+ depth = 0
+
+ source = source.read()
+ if isinstance(source, str):
+ source = source.decode(encoding or 'utf-8', 'replace')
+ offset = 0
+ lineno = 1
+
+ _escape_sub = self._escape_re.sub
+ def _escape_repl(mo):
+ groups = [g for g in mo.groups() if g]
+ if not groups:
+ return ''
+ return groups[0]
+
+ for idx, mo in enumerate(self._directive_re.finditer(source)):
+ start, end = mo.span(1)
+ if start > offset:
+ text = _escape_sub(_escape_repl, source[offset:start])
+ for kind, data, pos in interpolate(text, self.filepath, lineno,
+ lookup=self.lookup):
+ stream.append((kind, data, pos))
+ lineno += len(text.splitlines())
+
+ lineno += len(source[start:end].splitlines())
+ command, value = mo.group(2, 3)
+
+ if command == 'include':
+ pos = (self.filename, lineno, 0)
+ value = list(interpolate(value, self.filepath, lineno, 0,
+ lookup=self.lookup))
+ if len(value) == 1 and value[0][0] is TEXT:
+ value = value[0][1]
+ stream.append((INCLUDE, (value, None, []), pos))
+
+ elif command == 'python':
+ if not self.allow_exec:
+ raise TemplateSyntaxError('Python code blocks not allowed',
+ self.filepath, lineno)
+ try:
+ suite = Suite(value, self.filepath, lineno,
+ lookup=self.lookup)
+ except SyntaxError, err:
+ raise TemplateSyntaxError(err, self.filepath,
+ lineno + (err.lineno or 1) - 1)
+ pos = (self.filename, lineno, 0)
+ stream.append((EXEC, suite, pos))
+
+ elif command == 'end':
+ depth -= 1
+ if depth in dirmap:
+ directive, start_offset = dirmap.pop(depth)
+ substream = stream[start_offset:]
+ stream[start_offset:] = [(SUB, ([directive], substream),
+ (self.filepath, lineno, 0))]
+
+ elif command:
+ cls = self.get_directive(command)
+ if cls is None:
+ raise BadDirectiveError(command)
+ directive = 0, cls, value, None, (self.filepath, lineno, 0)
+ dirmap[depth] = (directive, len(stream))
+ depth += 1
+
+ offset = end
+
+ if offset < len(source):
+ text = _escape_sub(_escape_repl, source[offset:])
+ for kind, data, pos in interpolate(text, self.filepath, lineno,
+ lookup=self.lookup):
+ stream.append((kind, data, pos))
+
+ return stream
+
+
+class OldTextTemplate(Template):
+ """Legacy implementation of the old syntax text-based templates. This class
+ is provided in a transition phase for backwards compatibility. New code
+ should use the `NewTextTemplate` class and the improved syntax it provides.
+
+ >>> tmpl = OldTextTemplate('''Dear $name,
+ ...
+ ... We have the following items for you:
+ ... #for item in items
+ ... * $item
+ ... #end
+ ...
+ ... All the best,
+ ... Foobar''')
+ >>> print(tmpl.generate(name='Joe', items=[1, 2, 3]).render(encoding=None))
+ Dear Joe,
+ <BLANKLINE>
+ We have the following items for you:
+ * 1
+ * 2
+ * 3
+ <BLANKLINE>
+ All the best,
+ Foobar
+ """
+ directives = [('def', DefDirective),
+ ('when', WhenDirective),
+ ('otherwise', OtherwiseDirective),
+ ('for', ForDirective),
+ ('if', IfDirective),
+ ('choose', ChooseDirective),
+ ('with', WithDirective)]
+ serializer = 'text'
+
+ _DIRECTIVE_RE = re.compile(r'(?:^[ \t]*(?<!\\)#(end).*\n?)|'
+ r'(?:^[ \t]*(?<!\\)#((?:\w+|#).*)\n?)',
+ re.MULTILINE)
+
+ def _parse(self, source, encoding):
+ """Parse the template from text input."""
+ stream = [] # list of events of the "compiled" template
+ dirmap = {} # temporary mapping of directives to elements
+ depth = 0
+
+ source = source.read()
+ if isinstance(source, str):
+ source = source.decode(encoding or 'utf-8', 'replace')
+ offset = 0
+ lineno = 1
+
+ for idx, mo in enumerate(self._DIRECTIVE_RE.finditer(source)):
+ start, end = mo.span()
+ if start > offset:
+ text = source[offset:start]
+ for kind, data, pos in interpolate(text, self.filepath, lineno,
+ lookup=self.lookup):
+ stream.append((kind, data, pos))
+ lineno += len(text.splitlines())
+
+ text = source[start:end].lstrip()[1:]
+ lineno += len(text.splitlines())
+ directive = text.split(None, 1)
+ if len(directive) > 1:
+ command, value = directive
+ else:
+ command, value = directive[0], None
+
+ if command == 'end':
+ depth -= 1
+ if depth in dirmap:
+ directive, start_offset = dirmap.pop(depth)
+ substream = stream[start_offset:]
+ stream[start_offset:] = [(SUB, ([directive], substream),
+ (self.filepath, lineno, 0))]
+ elif command == 'include':
+ pos = (self.filename, lineno, 0)
+ stream.append((INCLUDE, (value.strip(), None, []), pos))
+ elif command != '#':
+ cls = self.get_directive(command)
+ if cls is None:
+ raise BadDirectiveError(command)
+ directive = 0, cls, value, None, (self.filepath, lineno, 0)
+ dirmap[depth] = (directive, len(stream))
+ depth += 1
+
+ offset = end
+
+ if offset < len(source):
+ text = source[offset:].replace('\\#', '#')
+ for kind, data, pos in interpolate(text, self.filepath, lineno,
+ lookup=self.lookup):
+ stream.append((kind, data, pos))
+
+ return stream
+
+
+TextTemplate = OldTextTemplate