new ext package for modules depending on an option third party package tls-sprint
authorsylvain.thenault@logilab.fr
Tue, 17 Feb 2009 22:34:18 +0100
branchtls-sprint
changeset 704 0c2c8f0a6ded
parent 703 413675c2d46c
child 705 937dd226f72a
new ext package for modules depending on an option third party package
common/html4zope.py
common/mttransforms.py
common/rest.py
common/tal.py
ext/html4zope.py
ext/rest.py
ext/tal.py
--- a/common/html4zope.py	Tue Feb 17 22:05:39 2009 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,153 +0,0 @@
-# Author: David Goodger
-# Contact: goodger@users.sourceforge.net
-# Revision: $Revision: 1.2 $
-# Date: $Date: 2005-07-04 16:36:50 $
-# Copyright: This module has been placed in the public domain.
-
-"""
-Simple HyperText Markup Language document tree Writer.
-
-The output conforms to the HTML 4.01 Transitional DTD and to the Extensible
-HTML version 1.0 Transitional DTD (*almost* strict).  The output contains a
-minimum of formatting information.  A cascading style sheet ("default.css" by
-default) is required for proper viewing with a modern graphical browser.
-
-http://cvs.zope.org/Zope/lib/python/docutils/writers/Attic/html4zope.py?rev=1.1.2.2&only_with_tag=ajung-restructuredtext-integration-branch&content-type=text/vnd.viewcvs-markup
-"""
-
-__docformat__ = 'reStructuredText'
-
-from logilab.mtconverter import html_escape
-
-from docutils import nodes
-from docutils.writers.html4css1 import Writer as CSS1Writer
-from docutils.writers.html4css1 import HTMLTranslator as CSS1HTMLTranslator
-import os
-
-default_level = int(os.environ.get('STX_DEFAULT_LEVEL', 3))
-
-class Writer(CSS1Writer):
-    """css writer using our html translator"""
-    def __init__(self, base_url):
-        CSS1Writer.__init__(self)
-        self.translator_class = URLBinder(base_url, HTMLTranslator)
-
-    def apply_template(self):
-        """overriding this is necessary with docutils >= 0.5"""
-        return self.visitor.astext()
-
-class URLBinder:
-    def __init__(self, url, klass):
-        self.base_url = url
-        self.translator_class = HTMLTranslator
-        
-    def __call__(self, document):
-        translator = self.translator_class(document)
-        translator.base_url = self.base_url
-        return translator
-    
-class HTMLTranslator(CSS1HTMLTranslator):
-    """ReST tree to html translator"""
-
-    def astext(self):
-        """return the extracted html"""
-        return ''.join(self.body)
-    
-    def visit_title(self, node):
-        """Only 6 section levels are supported by HTML."""
-        if isinstance(node.parent, nodes.topic):
-            self.body.append(
-                  self.starttag(node, 'p', '', CLASS='topic-title'))
-            if node.parent.hasattr('id'):
-                self.body.append(
-                    self.starttag({}, 'a', '', name=node.parent['id']))
-                self.context.append('</a></p>\n')
-            else:
-                self.context.append('</p>\n')
-        elif self.section_level == 0:
-            # document title
-            self.head.append('<title>%s</title>\n'
-                             % self.encode(node.astext()))
-            self.body.append(self.starttag(node, 'h%d' % default_level, '',
-                                           CLASS='title'))
-            self.context.append('</h%d>\n' % default_level)
-        else:
-            self.body.append(
-                  self.starttag(node, 'h%s' % (
-                default_level+self.section_level-1), ''))
-            atts = {}
-            if node.hasattr('refid'):
-                atts['class'] = 'toc-backref'
-                atts['href'] = '%s#%s' % (self.base_url, node['refid'])
-            self.body.append(self.starttag({}, 'a', '', **atts))
-            self.context.append('</a></h%s>\n' % (
-                default_level+self.section_level-1))
-
-    def visit_subtitle(self, node):
-        """format a subtitle"""
-        if isinstance(node.parent, nodes.sidebar):
-            self.body.append(self.starttag(node, 'p', '',
-                                           CLASS='sidebar-subtitle'))
-            self.context.append('</p>\n')
-        else:
-            self.body.append(
-                  self.starttag(node, 'h%s' % (default_level+1), '',
-                                CLASS='subtitle'))
-            self.context.append('</h%s>\n' % (default_level+1))
-
-    def visit_document(self, node):
-        """syt: i don't want the enclosing <div class="document">"""
-    def depart_document(self, node):
-        """syt: i don't want the enclosing <div class="document">"""
-
-    def visit_reference(self, node):
-        """syt: i want absolute urls"""
-        if node.has_key('refuri'):
-            href = node['refuri']
-            if ( self.settings.cloak_email_addresses
-                 and href.startswith('mailto:')):
-                href = self.cloak_mailto(href)
-                self.in_mailto = 1
-        else:
-            assert node.has_key('refid'), \
-                   'References must have "refuri" or "refid" attribute.'
-            href = '%s#%s' % (self.base_url, node['refid'])
-        atts = {'href': href, 'class': 'reference'}
-        if not isinstance(node.parent, nodes.TextElement):
-            assert len(node) == 1 and isinstance(node[0], nodes.image)
-            atts['class'] += ' image-reference'
-        self.body.append(self.starttag(node, 'a', '', **atts))
-
-    ## override error messages to avoid XHTML problems ########################
-    def visit_problematic(self, node):
-        pass
-
-    def depart_problematic(self, node):
-        pass
-    
-    def visit_system_message(self, node):
-        backref_text = ''
-        if len(node['backrefs']):
-            backrefs = node['backrefs']
-            if len(backrefs) == 1:
-                backref_text = '; <em>backlink</em>'
-            else:
-                i = 1
-                backlinks = []
-                for backref in backrefs:
-                    backlinks.append(str(i))
-                    i += 1
-                backref_text = ('; <em>backlinks: %s</em>'
-                                % ', '.join(backlinks))
-        if node.hasattr('line'):
-            line = ', line %s' % node['line']
-        else:
-            line = ''
-        a_start = a_end = ''
-        error = u'System Message: %s%s/%s%s (%s %s)%s</p>\n' % (
-            a_start, node['type'], node['level'], a_end,
-            self.encode(node['source']), line, backref_text)
-        self.body.append(u'<div class="system-message"><b>ReST / HTML errors:</b>%s</div>' % html_escape(error))
-
-    def depart_system_message(self, node):
-        pass
--- a/common/mttransforms.py	Tue Feb 17 22:05:39 2009 +0100
+++ b/common/mttransforms.py	Tue Feb 17 22:34:18 2009 +0100
@@ -31,16 +31,7 @@
     output = 'text/html'
     def _convert(self, trdata):
         return html_publish(trdata.appobject, trdata.data)
-
-class ept_to_html(Transform):
-    inputs = ('text/cubicweb-page-template',)
-    output = 'text/html'
-    output_encoding = 'utf-8'
-    def _convert(self, trdata):
-        from cubicweb.common.tal import compile_template
-        value = trdata.encode(self.output_encoding)
-        return trdata.appobject.tal_render(compile_template(value), {})
-
+    
 
 # Instantiate and configure the transformation engine
 
@@ -49,7 +40,23 @@
 ENGINE = TransformEngine()
 ENGINE.add_transform(rest_to_html())
 ENGINE.add_transform(html_to_html())
-ENGINE.add_transform(ept_to_html())
+
+try:
+    from cubicweb.common.tal import compile_template
+except ImportError:
+    HAS_TAL = False
+else:
+    HAS_TAL = True
+    
+    class ept_to_html(Transform):
+        inputs = ('text/cubicweb-page-template',)
+        output = 'text/html'
+        output_encoding = 'utf-8'
+        def _convert(self, trdata):
+            value = trdata.encode(self.output_encoding)
+            return trdata.appobject.tal_render(compile_template(value), {})
+
+    ENGINE.add_transform(ept_to_html())
 
 if register_pil_transforms(ENGINE, verb=False):
     HAS_PIL_TRANSFORMS = True
--- a/common/rest.py	Tue Feb 17 22:05:39 2009 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,223 +0,0 @@
-"""rest publishing functions
-
-contains some functions and setup of docutils for cubicweb
-
-:organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-"""
-__docformat__ = "restructuredtext en"
-
-from cStringIO import StringIO
-from itertools import chain
-from logging import getLogger
-from os.path import join
-
-from docutils import statemachine, nodes, utils, io
-from docutils.core import publish_string
-from docutils.parsers.rst import Parser, states, directives
-from docutils.parsers.rst.roles import register_canonical_role, set_classes
-
-from logilab.mtconverter import html_escape
-
-from cubicweb.common.html4zope import Writer
-
-# We provide our own parser as an attempt to get rid of
-# state machine reinstanciation
-
-import re
-# compile states.Body patterns
-for k, v in states.Body.patterns.items():
-    if isinstance(v, str):
-        states.Body.patterns[k] = re.compile(v)
-
-# register ReStructured Text mimetype / extensions
-import mimetypes
-mimetypes.add_type('text/rest', '.rest')
-mimetypes.add_type('text/rest', '.rst')
-
-
-LOGGER = getLogger('cubicweb.rest')
-
-def eid_reference_role(role, rawtext, text, lineno, inliner,
-                       options={}, content=[]):
-    try:
-        try:
-            eid_num, rest = text.split(u':', 1)
-        except:
-            eid_num, rest = text, '#'+text
-        eid_num = int(eid_num)
-        if eid_num < 0:
-            raise ValueError
-    except ValueError:
-        msg = inliner.reporter.error(
-            'EID number must be a positive number; "%s" is invalid.'
-            % text, line=lineno)
-        prb = inliner.problematic(rawtext, rawtext, msg)
-        return [prb], [msg]
-    # Base URL mainly used by inliner.pep_reference; so this is correct:
-    context = inliner.document.settings.context
-    refedentity = context.req.eid_rset(eid_num).get_entity(0, 0)
-    ref = refedentity.absolute_url()
-    set_classes(options)
-    return [nodes.reference(rawtext, utils.unescape(rest), refuri=ref,
-                            **options)], []
-
-register_canonical_role('eid', eid_reference_role)
-
-
-def card_reference_role(role, rawtext, text, lineno, inliner,
-                       options={}, content=[]):
-    text = text.strip()
-    try:
-        wikiid, rest = text.split(u':', 1)
-    except:
-        wikiid, rest = text, text
-    context = inliner.document.settings.context
-    cardrset = context.req.execute('Card X WHERE X wikiid %(id)s',
-                                   {'id': wikiid})
-    if cardrset:
-        ref = cardrset.get_entity(0, 0).absolute_url()
-    else:
-        schema = context.schema
-        if schema.eschema('Card').has_perm(context.req, 'add'):
-            ref = context.req.build_url('view', vid='creation', etype='Card', wikiid=wikiid)
-        else:
-            ref = '#'
-    set_classes(options)
-    return [nodes.reference(rawtext, utils.unescape(rest), refuri=ref,
-                            **options)], []
-
-register_canonical_role('card', card_reference_role)
-
-
-def winclude_directive(name, arguments, options, content, lineno,
-                       content_offset, block_text, state, state_machine):
-    """Include a reST file as part of the content of this reST file.
-
-    same as standard include directive but using config.locate_doc_resource to
-    get actual file to include.
-
-    Most part of this implementation is copied from `include` directive defined
-    in `docutils.parsers.rst.directives.misc`
-    """
-    context = state.document.settings.context
-    source = state_machine.input_lines.source(
-        lineno - state_machine.input_offset - 1)
-    #source_dir = os.path.dirname(os.path.abspath(source))
-    fid = arguments[0]
-    for lang in chain((context.req.lang, context.vreg.property_value('ui.language')),
-                      context.config.available_languages()):
-        rid = '%s_%s.rst' % (fid, lang)
-        resourcedir = context.config.locate_doc_file(rid)
-        if resourcedir:
-            break
-    else:
-        severe = state_machine.reporter.severe(
-              'Problems with "%s" directive path:\nno resource matching %s.'
-              % (name, fid),
-              nodes.literal_block(block_text, block_text), line=lineno)
-        return [severe]
-    path = join(resourcedir, rid)
-    encoding = options.get('encoding', state.document.settings.input_encoding)
-    try:
-        state.document.settings.record_dependencies.add(path)
-        include_file = io.FileInput(
-            source_path=path, encoding=encoding,
-            error_handler=state.document.settings.input_encoding_error_handler,
-            handle_io_errors=None)
-    except IOError, error:
-        severe = state_machine.reporter.severe(
-              'Problems with "%s" directive path:\n%s: %s.'
-              % (name, error.__class__.__name__, error),
-              nodes.literal_block(block_text, block_text), line=lineno)
-        return [severe]
-    try:
-        include_text = include_file.read()
-    except UnicodeError, error:
-        severe = state_machine.reporter.severe(
-              'Problem with "%s" directive:\n%s: %s'
-              % (name, error.__class__.__name__, error),
-              nodes.literal_block(block_text, block_text), line=lineno)
-        return [severe]
-    if options.has_key('literal'):
-        literal_block = nodes.literal_block(include_text, include_text,
-                                            source=path)
-        literal_block.line = 1
-        return literal_block
-    else:
-        include_lines = statemachine.string2lines(include_text,
-                                                  convert_whitespace=1)
-        state_machine.insert_input(include_lines, path)
-        return []
-
-winclude_directive.arguments = (1, 0, 1)
-winclude_directive.options = {'literal': directives.flag,
-                              'encoding': directives.encoding}
-directives.register_directive('winclude', winclude_directive)
-
-class CubicWebReSTParser(Parser):
-    """The (customized) reStructuredText parser."""
-
-    def __init__(self):
-        self.initial_state = 'Body'
-        self.state_classes = states.state_classes
-        self.inliner = states.Inliner()
-        self.statemachine = states.RSTStateMachine(
-              state_classes=self.state_classes,
-              initial_state=self.initial_state,
-              debug=0)
-
-    def parse(self, inputstring, document):
-        """Parse `inputstring` and populate `document`, a document tree."""
-        self.setup_parse(inputstring, document)
-        inputlines = statemachine.string2lines(inputstring,
-                                               convert_whitespace=1)
-        self.statemachine.run(inputlines, document, inliner=self.inliner)
-        self.finish_parse()
-
-
-_REST_PARSER = CubicWebReSTParser()
-
-def rest_publish(context, data):
-    """publish a string formatted as ReStructured Text to HTML
-    
-    :type context: a cubicweb application object
-
-    :type data: str
-    :param data: some ReST text
-
-    :rtype: unicode
-    :return:
-      the data formatted as HTML or the original data if an error occured
-    """
-    req = context.req
-    if isinstance(data, unicode):
-        encoding = 'unicode'
-    else:
-        encoding = req.encoding
-    settings = {'input_encoding': encoding, 'output_encoding': 'unicode',
-                'warning_stream': StringIO(), 'context': context,
-                # dunno what's the max, severe is 4, and we never want a crash
-                # (though try/except may be a better option...)
-                'halt_level': 10, 
-                }
-    if context:
-        if hasattr(req, 'url'):
-            base_url = req.url()
-        elif hasattr(context, 'absolute_url'):
-            base_url = context.absolute_url()
-        else:
-            base_url = req.base_url()
-    else:
-        base_url = None
-    try:
-        return publish_string(writer=Writer(base_url=base_url),
-                              parser=_REST_PARSER, source=data,
-                              settings_overrides=settings)
-    except Exception:
-        LOGGER.exception('error while publishing ReST text')
-        if not isinstance(data, unicode):
-            data = unicode(data, encoding, 'replace')
-        return html_escape(req._('error while publishing ReST text')
-                           + '\n\n' + data)
--- a/common/tal.py	Tue Feb 17 22:05:39 2009 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,256 +0,0 @@
-"""provides simpleTAL extensions for CubicWeb
-
-:organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-"""
-
-__docformat__ = "restructuredtext en"
-
-import sys
-import re
-from os.path import exists, isdir, join
-from logging import getLogger
-from StringIO import StringIO
-        
-from simpletal import simpleTAL, simpleTALES
-
-from logilab.common.decorators import cached
-
-LOGGER = getLogger('cubicweb.tal')
-
-
-class LoggerAdapter(object):
-    def __init__(self, tal_logger):
-        self.tal_logger = tal_logger
-        
-    def debug(self, msg):
-        LOGGER.debug(msg)
-
-    def warn(self, msg):
-        LOGGER.warning(msg)
-
-    def __getattr__(self, attrname):
-        return getattr(self.tal_logger, attrname)
-
-
-class CubicWebContext(simpleTALES.Context):
-    """add facilities to access entity / resultset"""
-
-    def __init__(self, options=None, allowPythonPath=1):
-        simpleTALES.Context.__init__(self, options, allowPythonPath)
-        self.log = LoggerAdapter(self.log)
-
-    def update(self, context):
-        for varname, value in context.items():
-            self.addGlobal(varname, value)
-
-    def addRepeat(self, name, var, initialValue):
-        simpleTALES.Context.addRepeat(self, name, var, initialValue)
-
-# XXX FIXME need to find a clean to define OPCODE values for extensions
-I18N_CONTENT = 18  
-I18N_REPLACE = 19
-RQL_EXECUTE  = 20
-# simpleTAL uses the OPCODE values to define priority over commands.
-# TAL_ITER should have the same priority than TAL_REPEAT (i.e. 3), but
-# we can't use the same OPCODE for two different commands without changing
-# the simpleTAL implementation. Another solution would be to totally override
-# the REPEAT implementation with the ITER one, but some specific operations
-# (involving len() for instance) are not implemented for ITER, so we prefer
-# to keep both implementations for now, and to fool simpleTAL by using a float
-# number between 3 and 4
-TAL_ITER     = 3.1
-
-
-# FIX simpleTAL HTML 4.01 stupidity
-# (simpleTAL never closes tags like INPUT, IMG, HR ...)
-simpleTAL.HTML_FORBIDDEN_ENDTAG.clear()
-
-class CubicWebTemplateCompiler(simpleTAL.HTMLTemplateCompiler):
-    """extends default compiler by adding i18n:content commands"""
-
-    def __init__(self):
-        simpleTAL.HTMLTemplateCompiler.__init__(self)
-        self.commandHandler[I18N_CONTENT] = self.compile_cmd_i18n_content
-        self.commandHandler[I18N_REPLACE] = self.compile_cmd_i18n_replace
-        self.commandHandler[RQL_EXECUTE] = self.compile_cmd_rql
-        self.commandHandler[TAL_ITER] = self.compile_cmd_tal_iter
-
-    def setTALPrefix(self, prefix):
-        simpleTAL.TemplateCompiler.setTALPrefix(self, prefix)
-        self.tal_attribute_map['i18n:content'] = I18N_CONTENT
-        self.tal_attribute_map['i18n:replace'] = I18N_REPLACE
-        self.tal_attribute_map['rql:execute'] = RQL_EXECUTE
-        self.tal_attribute_map['tal:iter'] = TAL_ITER
-
-    def compile_cmd_i18n_content(self, argument):
-        # XXX tal:content structure=, text= should we support this ?
-        structure_flag = 0
-        return (I18N_CONTENT, (argument, False, structure_flag, self.endTagSymbol))
-
-    def compile_cmd_i18n_replace(self, argument):
-        # XXX tal:content structure=, text= should we support this ?
-        structure_flag = 0
-        return (I18N_CONTENT, (argument, True, structure_flag, self.endTagSymbol))
-
-    def compile_cmd_rql(self, argument):
-        return (RQL_EXECUTE, (argument, self.endTagSymbol))
-
-    def compile_cmd_tal_iter(self, argument):
-        original_id, (var_name, expression, end_tag_symbol) = \
-                     simpleTAL.HTMLTemplateCompiler.compileCmdRepeat(self, argument)
-        return (TAL_ITER, (var_name, expression, self.endTagSymbol))
-
-    def getTemplate(self):
-        return CubicWebTemplate(self.commandList, self.macroMap, self.symbolLocationTable)
-
-    def compileCmdAttributes (self, argument):
-        """XXX modified to support single attribute
-        definition ending by a ';'
-
-        backport this to simpleTAL
-        """
-        # Compile tal:attributes into attribute command
-        # Argument: [(attributeName, expression)]
-        
-        # Break up the list of attribute settings first
-        commandArgs = []
-        # We only want to match semi-colons that are not escaped
-        argumentSplitter =  re.compile(r'(?<!;);(?!;)')
-        for attributeStmt in argumentSplitter.split(argument):
-            if not attributeStmt.strip():
-                continue
-            #  remove any leading space and un-escape any semi-colons
-            attributeStmt = attributeStmt.lstrip().replace(';;', ';')
-            # Break each attributeStmt into name and expression
-            stmtBits = attributeStmt.split(' ')
-            if (len (stmtBits) < 2):
-                # Error, badly formed attributes command
-                msg = "Badly formed attributes command '%s'.  Attributes commands must be of the form: 'name expression[;name expression]'" % argument
-                self.log.error(msg)
-                raise simpleTAL.TemplateParseException(self.tagAsText(self.currentStartTag), msg)
-            attName = stmtBits[0]
-            attExpr = " ".join(stmtBits[1:])
-            commandArgs.append((attName, attExpr))
-        return (simpleTAL.TAL_ATTRIBUTES, commandArgs)
-
-
-class CubicWebTemplateInterpreter(simpleTAL.TemplateInterpreter):
-    """provides implementation for interpreting cubicweb extensions"""
-    def __init__(self):
-        simpleTAL.TemplateInterpreter.__init__(self)
-        self.commandHandler[I18N_CONTENT] = self.cmd_i18n
-        self.commandHandler[TAL_ITER] = self.cmdRepeat
-        # self.commandHandler[RQL_EXECUTE] = self.cmd_rql
-
-    def cmd_i18n(self, command, args):
-        """i18n:content and i18n:replace implementation"""
-        string, replace_flag, structure_flag, end_symbol = args
-        if replace_flag:
-            self.outputTag = 0
-        result = self.context.globals['_'](string)
-        self.tagContent = (0, result)
-        self.movePCForward = self.symbolTable[end_symbol]
-        self.programCounter += 1
-
-
-class CubicWebTemplate(simpleTAL.HTMLTemplate):
-    """overrides HTMLTemplate.expand() to systematically use CubicWebInterpreter
-    """
-    def expand(self, context, outputFile):
-        interpreter = CubicWebTemplateInterpreter()
-        interpreter.initialise(context, outputFile)
-        simpleTAL.HTMLTemplate.expand(self, context, outputFile,# outputEncoding='unicode',
-                                      interpreter=interpreter)
-
-    def expandInline(self, context, outputFile, interpreter):
-        """ Internally used when expanding a template that is part of a context."""
-        try:
-            interpreter.execute(self)
-        except UnicodeError, unierror:
-            LOGGER.exception(str(unierror))
-            raise simpleTALES.ContextContentException("found non-unicode %r string in Context!" % unierror.args[1]), None, sys.exc_info()[-1]
-
-
-def compile_template(template):
-    """compiles a TAL template string
-    :type template: unicode
-    :param template: a TAL-compliant template string
-    """
-    string_buffer = StringIO(template)
-    compiler = CubicWebTemplateCompiler()
-    compiler.parseTemplate(string_buffer) # , inputEncoding='unicode')
-    return compiler.getTemplate()
-
-
-def compile_template_file(filepath):
-    """compiles a TAL template file
-    :type filepath: str
-    :param template: path of the file to compile 
-    """
-    fp = file(filepath)
-    file_content = unicode(fp.read()) # template file should be pure ASCII
-    fp.close()
-    return compile_template(file_content)
-
-
-def evaluatePython (self, expr):
-    if not self.allowPythonPath:
-        return self.false
-    globals = {}
-    for name, value in self.globals.items():
-        if isinstance (value, simpleTALES.ContextVariable):
-            value = value.rawValue()
-        globals[name] = value
-    globals['path'] = self.pythonPathFuncs.path
-    globals['string'] = self.pythonPathFuncs.string
-    globals['exists'] = self.pythonPathFuncs.exists
-    globals['nocall'] = self.pythonPathFuncs.nocall
-    globals['test'] = self.pythonPathFuncs.test
-    locals = {}
-    for name, value in self.locals.items():
-        if (isinstance (value, simpleTALES.ContextVariable)):
-            value = value.rawValue()
-        locals[name] = value
-    # XXX precompile expr will avoid late syntax error
-    try:
-        result = eval(expr, globals, locals)
-    except Exception, ex:
-        ex = ex.__class__('in %r: %s' % (expr, ex))
-        raise ex, None, sys.exc_info()[-1]
-    if (isinstance (result, simpleTALES.ContextVariable)):
-        return result.value()
-    return result
-
-simpleTALES.Context.evaluatePython = evaluatePython
-
-
-class talbased(object):
-    def __init__(self, filename, write=True):
-##         if not osp.isfile(filepath):
-##             # print "[tal.py] just for tests..."
-##             # get parent frame
-##             directory = osp.abspath(osp.dirname(sys._getframe(1).f_globals['__file__']))
-##             filepath = osp.join(directory, filepath)
-        self.filename = filename
-        self.write = write
-
-    def __call__(self, viewfunc):
-        def wrapped(instance, *args, **kwargs):
-            variables = viewfunc(instance, *args, **kwargs)
-            html = instance.tal_render(self._compiled_template(instance), variables)
-            if self.write:
-                instance.w(html)
-            else:
-                return html
-        return wrapped
-
-    def _compiled_template(self, instance):
-        for fileordirectory in instance.config.vregistry_path():
-            filepath = join(fileordirectory, self.filename)
-            if isdir(fileordirectory) and exists(filepath):
-                return compile_template_file(filepath)
-        raise Exception('no such template %s' % self.filename)
-    _compiled_template = cached(_compiled_template, 0)
-    
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ext/html4zope.py	Tue Feb 17 22:34:18 2009 +0100
@@ -0,0 +1,153 @@
+# Author: David Goodger
+# Contact: goodger@users.sourceforge.net
+# Revision: $Revision: 1.2 $
+# Date: $Date: 2005-07-04 16:36:50 $
+# Copyright: This module has been placed in the public domain.
+
+"""
+Simple HyperText Markup Language document tree Writer.
+
+The output conforms to the HTML 4.01 Transitional DTD and to the Extensible
+HTML version 1.0 Transitional DTD (*almost* strict).  The output contains a
+minimum of formatting information.  A cascading style sheet ("default.css" by
+default) is required for proper viewing with a modern graphical browser.
+
+http://cvs.zope.org/Zope/lib/python/docutils/writers/Attic/html4zope.py?rev=1.1.2.2&only_with_tag=ajung-restructuredtext-integration-branch&content-type=text/vnd.viewcvs-markup
+"""
+
+__docformat__ = 'reStructuredText'
+
+from logilab.mtconverter import html_escape
+
+from docutils import nodes
+from docutils.writers.html4css1 import Writer as CSS1Writer
+from docutils.writers.html4css1 import HTMLTranslator as CSS1HTMLTranslator
+import os
+
+default_level = int(os.environ.get('STX_DEFAULT_LEVEL', 3))
+
+class Writer(CSS1Writer):
+    """css writer using our html translator"""
+    def __init__(self, base_url):
+        CSS1Writer.__init__(self)
+        self.translator_class = URLBinder(base_url, HTMLTranslator)
+
+    def apply_template(self):
+        """overriding this is necessary with docutils >= 0.5"""
+        return self.visitor.astext()
+
+class URLBinder:
+    def __init__(self, url, klass):
+        self.base_url = url
+        self.translator_class = HTMLTranslator
+        
+    def __call__(self, document):
+        translator = self.translator_class(document)
+        translator.base_url = self.base_url
+        return translator
+    
+class HTMLTranslator(CSS1HTMLTranslator):
+    """ReST tree to html translator"""
+
+    def astext(self):
+        """return the extracted html"""
+        return ''.join(self.body)
+    
+    def visit_title(self, node):
+        """Only 6 section levels are supported by HTML."""
+        if isinstance(node.parent, nodes.topic):
+            self.body.append(
+                  self.starttag(node, 'p', '', CLASS='topic-title'))
+            if node.parent.hasattr('id'):
+                self.body.append(
+                    self.starttag({}, 'a', '', name=node.parent['id']))
+                self.context.append('</a></p>\n')
+            else:
+                self.context.append('</p>\n')
+        elif self.section_level == 0:
+            # document title
+            self.head.append('<title>%s</title>\n'
+                             % self.encode(node.astext()))
+            self.body.append(self.starttag(node, 'h%d' % default_level, '',
+                                           CLASS='title'))
+            self.context.append('</h%d>\n' % default_level)
+        else:
+            self.body.append(
+                  self.starttag(node, 'h%s' % (
+                default_level+self.section_level-1), ''))
+            atts = {}
+            if node.hasattr('refid'):
+                atts['class'] = 'toc-backref'
+                atts['href'] = '%s#%s' % (self.base_url, node['refid'])
+            self.body.append(self.starttag({}, 'a', '', **atts))
+            self.context.append('</a></h%s>\n' % (
+                default_level+self.section_level-1))
+
+    def visit_subtitle(self, node):
+        """format a subtitle"""
+        if isinstance(node.parent, nodes.sidebar):
+            self.body.append(self.starttag(node, 'p', '',
+                                           CLASS='sidebar-subtitle'))
+            self.context.append('</p>\n')
+        else:
+            self.body.append(
+                  self.starttag(node, 'h%s' % (default_level+1), '',
+                                CLASS='subtitle'))
+            self.context.append('</h%s>\n' % (default_level+1))
+
+    def visit_document(self, node):
+        """syt: i don't want the enclosing <div class="document">"""
+    def depart_document(self, node):
+        """syt: i don't want the enclosing <div class="document">"""
+
+    def visit_reference(self, node):
+        """syt: i want absolute urls"""
+        if node.has_key('refuri'):
+            href = node['refuri']
+            if ( self.settings.cloak_email_addresses
+                 and href.startswith('mailto:')):
+                href = self.cloak_mailto(href)
+                self.in_mailto = 1
+        else:
+            assert node.has_key('refid'), \
+                   'References must have "refuri" or "refid" attribute.'
+            href = '%s#%s' % (self.base_url, node['refid'])
+        atts = {'href': href, 'class': 'reference'}
+        if not isinstance(node.parent, nodes.TextElement):
+            assert len(node) == 1 and isinstance(node[0], nodes.image)
+            atts['class'] += ' image-reference'
+        self.body.append(self.starttag(node, 'a', '', **atts))
+
+    ## override error messages to avoid XHTML problems ########################
+    def visit_problematic(self, node):
+        pass
+
+    def depart_problematic(self, node):
+        pass
+    
+    def visit_system_message(self, node):
+        backref_text = ''
+        if len(node['backrefs']):
+            backrefs = node['backrefs']
+            if len(backrefs) == 1:
+                backref_text = '; <em>backlink</em>'
+            else:
+                i = 1
+                backlinks = []
+                for backref in backrefs:
+                    backlinks.append(str(i))
+                    i += 1
+                backref_text = ('; <em>backlinks: %s</em>'
+                                % ', '.join(backlinks))
+        if node.hasattr('line'):
+            line = ', line %s' % node['line']
+        else:
+            line = ''
+        a_start = a_end = ''
+        error = u'System Message: %s%s/%s%s (%s %s)%s</p>\n' % (
+            a_start, node['type'], node['level'], a_end,
+            self.encode(node['source']), line, backref_text)
+        self.body.append(u'<div class="system-message"><b>ReST / HTML errors:</b>%s</div>' % html_escape(error))
+
+    def depart_system_message(self, node):
+        pass
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ext/rest.py	Tue Feb 17 22:34:18 2009 +0100
@@ -0,0 +1,223 @@
+"""rest publishing functions
+
+contains some functions and setup of docutils for cubicweb
+
+:organization: Logilab
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+"""
+__docformat__ = "restructuredtext en"
+
+from cStringIO import StringIO
+from itertools import chain
+from logging import getLogger
+from os.path import join
+
+from docutils import statemachine, nodes, utils, io
+from docutils.core import publish_string
+from docutils.parsers.rst import Parser, states, directives
+from docutils.parsers.rst.roles import register_canonical_role, set_classes
+
+from logilab.mtconverter import html_escape
+
+from cubicweb.ext.html4zope import Writer
+
+# We provide our own parser as an attempt to get rid of
+# state machine reinstanciation
+
+import re
+# compile states.Body patterns
+for k, v in states.Body.patterns.items():
+    if isinstance(v, str):
+        states.Body.patterns[k] = re.compile(v)
+
+# register ReStructured Text mimetype / extensions
+import mimetypes
+mimetypes.add_type('text/rest', '.rest')
+mimetypes.add_type('text/rest', '.rst')
+
+
+LOGGER = getLogger('cubicweb.rest')
+
+def eid_reference_role(role, rawtext, text, lineno, inliner,
+                       options={}, content=[]):
+    try:
+        try:
+            eid_num, rest = text.split(u':', 1)
+        except:
+            eid_num, rest = text, '#'+text
+        eid_num = int(eid_num)
+        if eid_num < 0:
+            raise ValueError
+    except ValueError:
+        msg = inliner.reporter.error(
+            'EID number must be a positive number; "%s" is invalid.'
+            % text, line=lineno)
+        prb = inliner.problematic(rawtext, rawtext, msg)
+        return [prb], [msg]
+    # Base URL mainly used by inliner.pep_reference; so this is correct:
+    context = inliner.document.settings.context
+    refedentity = context.req.eid_rset(eid_num).get_entity(0, 0)
+    ref = refedentity.absolute_url()
+    set_classes(options)
+    return [nodes.reference(rawtext, utils.unescape(rest), refuri=ref,
+                            **options)], []
+
+register_canonical_role('eid', eid_reference_role)
+
+
+def card_reference_role(role, rawtext, text, lineno, inliner,
+                       options={}, content=[]):
+    text = text.strip()
+    try:
+        wikiid, rest = text.split(u':', 1)
+    except:
+        wikiid, rest = text, text
+    context = inliner.document.settings.context
+    cardrset = context.req.execute('Card X WHERE X wikiid %(id)s',
+                                   {'id': wikiid})
+    if cardrset:
+        ref = cardrset.get_entity(0, 0).absolute_url()
+    else:
+        schema = context.schema
+        if schema.eschema('Card').has_perm(context.req, 'add'):
+            ref = context.req.build_url('view', vid='creation', etype='Card', wikiid=wikiid)
+        else:
+            ref = '#'
+    set_classes(options)
+    return [nodes.reference(rawtext, utils.unescape(rest), refuri=ref,
+                            **options)], []
+
+register_canonical_role('card', card_reference_role)
+
+
+def winclude_directive(name, arguments, options, content, lineno,
+                       content_offset, block_text, state, state_machine):
+    """Include a reST file as part of the content of this reST file.
+
+    same as standard include directive but using config.locate_doc_resource to
+    get actual file to include.
+
+    Most part of this implementation is copied from `include` directive defined
+    in `docutils.parsers.rst.directives.misc`
+    """
+    context = state.document.settings.context
+    source = state_machine.input_lines.source(
+        lineno - state_machine.input_offset - 1)
+    #source_dir = os.path.dirname(os.path.abspath(source))
+    fid = arguments[0]
+    for lang in chain((context.req.lang, context.vreg.property_value('ui.language')),
+                      context.config.available_languages()):
+        rid = '%s_%s.rst' % (fid, lang)
+        resourcedir = context.config.locate_doc_file(rid)
+        if resourcedir:
+            break
+    else:
+        severe = state_machine.reporter.severe(
+              'Problems with "%s" directive path:\nno resource matching %s.'
+              % (name, fid),
+              nodes.literal_block(block_text, block_text), line=lineno)
+        return [severe]
+    path = join(resourcedir, rid)
+    encoding = options.get('encoding', state.document.settings.input_encoding)
+    try:
+        state.document.settings.record_dependencies.add(path)
+        include_file = io.FileInput(
+            source_path=path, encoding=encoding,
+            error_handler=state.document.settings.input_encoding_error_handler,
+            handle_io_errors=None)
+    except IOError, error:
+        severe = state_machine.reporter.severe(
+              'Problems with "%s" directive path:\n%s: %s.'
+              % (name, error.__class__.__name__, error),
+              nodes.literal_block(block_text, block_text), line=lineno)
+        return [severe]
+    try:
+        include_text = include_file.read()
+    except UnicodeError, error:
+        severe = state_machine.reporter.severe(
+              'Problem with "%s" directive:\n%s: %s'
+              % (name, error.__class__.__name__, error),
+              nodes.literal_block(block_text, block_text), line=lineno)
+        return [severe]
+    if options.has_key('literal'):
+        literal_block = nodes.literal_block(include_text, include_text,
+                                            source=path)
+        literal_block.line = 1
+        return literal_block
+    else:
+        include_lines = statemachine.string2lines(include_text,
+                                                  convert_whitespace=1)
+        state_machine.insert_input(include_lines, path)
+        return []
+
+winclude_directive.arguments = (1, 0, 1)
+winclude_directive.options = {'literal': directives.flag,
+                              'encoding': directives.encoding}
+directives.register_directive('winclude', winclude_directive)
+
+class CubicWebReSTParser(Parser):
+    """The (customized) reStructuredText parser."""
+
+    def __init__(self):
+        self.initial_state = 'Body'
+        self.state_classes = states.state_classes
+        self.inliner = states.Inliner()
+        self.statemachine = states.RSTStateMachine(
+              state_classes=self.state_classes,
+              initial_state=self.initial_state,
+              debug=0)
+
+    def parse(self, inputstring, document):
+        """Parse `inputstring` and populate `document`, a document tree."""
+        self.setup_parse(inputstring, document)
+        inputlines = statemachine.string2lines(inputstring,
+                                               convert_whitespace=1)
+        self.statemachine.run(inputlines, document, inliner=self.inliner)
+        self.finish_parse()
+
+
+_REST_PARSER = CubicWebReSTParser()
+
+def rest_publish(context, data):
+    """publish a string formatted as ReStructured Text to HTML
+    
+    :type context: a cubicweb application object
+
+    :type data: str
+    :param data: some ReST text
+
+    :rtype: unicode
+    :return:
+      the data formatted as HTML or the original data if an error occured
+    """
+    req = context.req
+    if isinstance(data, unicode):
+        encoding = 'unicode'
+    else:
+        encoding = req.encoding
+    settings = {'input_encoding': encoding, 'output_encoding': 'unicode',
+                'warning_stream': StringIO(), 'context': context,
+                # dunno what's the max, severe is 4, and we never want a crash
+                # (though try/except may be a better option...)
+                'halt_level': 10, 
+                }
+    if context:
+        if hasattr(req, 'url'):
+            base_url = req.url()
+        elif hasattr(context, 'absolute_url'):
+            base_url = context.absolute_url()
+        else:
+            base_url = req.base_url()
+    else:
+        base_url = None
+    try:
+        return publish_string(writer=Writer(base_url=base_url),
+                              parser=_REST_PARSER, source=data,
+                              settings_overrides=settings)
+    except Exception:
+        LOGGER.exception('error while publishing ReST text')
+        if not isinstance(data, unicode):
+            data = unicode(data, encoding, 'replace')
+        return html_escape(req._('error while publishing ReST text')
+                           + '\n\n' + data)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ext/tal.py	Tue Feb 17 22:34:18 2009 +0100
@@ -0,0 +1,256 @@
+"""provides simpleTAL extensions for CubicWeb
+
+:organization: Logilab
+:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+"""
+
+__docformat__ = "restructuredtext en"
+
+import sys
+import re
+from os.path import exists, isdir, join
+from logging import getLogger
+from StringIO import StringIO
+        
+from simpletal import simpleTAL, simpleTALES
+
+from logilab.common.decorators import cached
+
+LOGGER = getLogger('cubicweb.tal')
+
+
+class LoggerAdapter(object):
+    def __init__(self, tal_logger):
+        self.tal_logger = tal_logger
+        
+    def debug(self, msg):
+        LOGGER.debug(msg)
+
+    def warn(self, msg):
+        LOGGER.warning(msg)
+
+    def __getattr__(self, attrname):
+        return getattr(self.tal_logger, attrname)
+
+
+class CubicWebContext(simpleTALES.Context):
+    """add facilities to access entity / resultset"""
+
+    def __init__(self, options=None, allowPythonPath=1):
+        simpleTALES.Context.__init__(self, options, allowPythonPath)
+        self.log = LoggerAdapter(self.log)
+
+    def update(self, context):
+        for varname, value in context.items():
+            self.addGlobal(varname, value)
+
+    def addRepeat(self, name, var, initialValue):
+        simpleTALES.Context.addRepeat(self, name, var, initialValue)
+
+# XXX FIXME need to find a clean to define OPCODE values for extensions
+I18N_CONTENT = 18  
+I18N_REPLACE = 19
+RQL_EXECUTE  = 20
+# simpleTAL uses the OPCODE values to define priority over commands.
+# TAL_ITER should have the same priority than TAL_REPEAT (i.e. 3), but
+# we can't use the same OPCODE for two different commands without changing
+# the simpleTAL implementation. Another solution would be to totally override
+# the REPEAT implementation with the ITER one, but some specific operations
+# (involving len() for instance) are not implemented for ITER, so we prefer
+# to keep both implementations for now, and to fool simpleTAL by using a float
+# number between 3 and 4
+TAL_ITER     = 3.1
+
+
+# FIX simpleTAL HTML 4.01 stupidity
+# (simpleTAL never closes tags like INPUT, IMG, HR ...)
+simpleTAL.HTML_FORBIDDEN_ENDTAG.clear()
+
+class CubicWebTemplateCompiler(simpleTAL.HTMLTemplateCompiler):
+    """extends default compiler by adding i18n:content commands"""
+
+    def __init__(self):
+        simpleTAL.HTMLTemplateCompiler.__init__(self)
+        self.commandHandler[I18N_CONTENT] = self.compile_cmd_i18n_content
+        self.commandHandler[I18N_REPLACE] = self.compile_cmd_i18n_replace
+        self.commandHandler[RQL_EXECUTE] = self.compile_cmd_rql
+        self.commandHandler[TAL_ITER] = self.compile_cmd_tal_iter
+
+    def setTALPrefix(self, prefix):
+        simpleTAL.TemplateCompiler.setTALPrefix(self, prefix)
+        self.tal_attribute_map['i18n:content'] = I18N_CONTENT
+        self.tal_attribute_map['i18n:replace'] = I18N_REPLACE
+        self.tal_attribute_map['rql:execute'] = RQL_EXECUTE
+        self.tal_attribute_map['tal:iter'] = TAL_ITER
+
+    def compile_cmd_i18n_content(self, argument):
+        # XXX tal:content structure=, text= should we support this ?
+        structure_flag = 0
+        return (I18N_CONTENT, (argument, False, structure_flag, self.endTagSymbol))
+
+    def compile_cmd_i18n_replace(self, argument):
+        # XXX tal:content structure=, text= should we support this ?
+        structure_flag = 0
+        return (I18N_CONTENT, (argument, True, structure_flag, self.endTagSymbol))
+
+    def compile_cmd_rql(self, argument):
+        return (RQL_EXECUTE, (argument, self.endTagSymbol))
+
+    def compile_cmd_tal_iter(self, argument):
+        original_id, (var_name, expression, end_tag_symbol) = \
+                     simpleTAL.HTMLTemplateCompiler.compileCmdRepeat(self, argument)
+        return (TAL_ITER, (var_name, expression, self.endTagSymbol))
+
+    def getTemplate(self):
+        return CubicWebTemplate(self.commandList, self.macroMap, self.symbolLocationTable)
+
+    def compileCmdAttributes (self, argument):
+        """XXX modified to support single attribute
+        definition ending by a ';'
+
+        backport this to simpleTAL
+        """
+        # Compile tal:attributes into attribute command
+        # Argument: [(attributeName, expression)]
+        
+        # Break up the list of attribute settings first
+        commandArgs = []
+        # We only want to match semi-colons that are not escaped
+        argumentSplitter =  re.compile(r'(?<!;);(?!;)')
+        for attributeStmt in argumentSplitter.split(argument):
+            if not attributeStmt.strip():
+                continue
+            #  remove any leading space and un-escape any semi-colons
+            attributeStmt = attributeStmt.lstrip().replace(';;', ';')
+            # Break each attributeStmt into name and expression
+            stmtBits = attributeStmt.split(' ')
+            if (len (stmtBits) < 2):
+                # Error, badly formed attributes command
+                msg = "Badly formed attributes command '%s'.  Attributes commands must be of the form: 'name expression[;name expression]'" % argument
+                self.log.error(msg)
+                raise simpleTAL.TemplateParseException(self.tagAsText(self.currentStartTag), msg)
+            attName = stmtBits[0]
+            attExpr = " ".join(stmtBits[1:])
+            commandArgs.append((attName, attExpr))
+        return (simpleTAL.TAL_ATTRIBUTES, commandArgs)
+
+
+class CubicWebTemplateInterpreter(simpleTAL.TemplateInterpreter):
+    """provides implementation for interpreting cubicweb extensions"""
+    def __init__(self):
+        simpleTAL.TemplateInterpreter.__init__(self)
+        self.commandHandler[I18N_CONTENT] = self.cmd_i18n
+        self.commandHandler[TAL_ITER] = self.cmdRepeat
+        # self.commandHandler[RQL_EXECUTE] = self.cmd_rql
+
+    def cmd_i18n(self, command, args):
+        """i18n:content and i18n:replace implementation"""
+        string, replace_flag, structure_flag, end_symbol = args
+        if replace_flag:
+            self.outputTag = 0
+        result = self.context.globals['_'](string)
+        self.tagContent = (0, result)
+        self.movePCForward = self.symbolTable[end_symbol]
+        self.programCounter += 1
+
+
+class CubicWebTemplate(simpleTAL.HTMLTemplate):
+    """overrides HTMLTemplate.expand() to systematically use CubicWebInterpreter
+    """
+    def expand(self, context, outputFile):
+        interpreter = CubicWebTemplateInterpreter()
+        interpreter.initialise(context, outputFile)
+        simpleTAL.HTMLTemplate.expand(self, context, outputFile,# outputEncoding='unicode',
+                                      interpreter=interpreter)
+
+    def expandInline(self, context, outputFile, interpreter):
+        """ Internally used when expanding a template that is part of a context."""
+        try:
+            interpreter.execute(self)
+        except UnicodeError, unierror:
+            LOGGER.exception(str(unierror))
+            raise simpleTALES.ContextContentException("found non-unicode %r string in Context!" % unierror.args[1]), None, sys.exc_info()[-1]
+
+
+def compile_template(template):
+    """compiles a TAL template string
+    :type template: unicode
+    :param template: a TAL-compliant template string
+    """
+    string_buffer = StringIO(template)
+    compiler = CubicWebTemplateCompiler()
+    compiler.parseTemplate(string_buffer) # , inputEncoding='unicode')
+    return compiler.getTemplate()
+
+
+def compile_template_file(filepath):
+    """compiles a TAL template file
+    :type filepath: str
+    :param template: path of the file to compile 
+    """
+    fp = file(filepath)
+    file_content = unicode(fp.read()) # template file should be pure ASCII
+    fp.close()
+    return compile_template(file_content)
+
+
+def evaluatePython (self, expr):
+    if not self.allowPythonPath:
+        return self.false
+    globals = {}
+    for name, value in self.globals.items():
+        if isinstance (value, simpleTALES.ContextVariable):
+            value = value.rawValue()
+        globals[name] = value
+    globals['path'] = self.pythonPathFuncs.path
+    globals['string'] = self.pythonPathFuncs.string
+    globals['exists'] = self.pythonPathFuncs.exists
+    globals['nocall'] = self.pythonPathFuncs.nocall
+    globals['test'] = self.pythonPathFuncs.test
+    locals = {}
+    for name, value in self.locals.items():
+        if (isinstance (value, simpleTALES.ContextVariable)):
+            value = value.rawValue()
+        locals[name] = value
+    # XXX precompile expr will avoid late syntax error
+    try:
+        result = eval(expr, globals, locals)
+    except Exception, ex:
+        ex = ex.__class__('in %r: %s' % (expr, ex))
+        raise ex, None, sys.exc_info()[-1]
+    if (isinstance (result, simpleTALES.ContextVariable)):
+        return result.value()
+    return result
+
+simpleTALES.Context.evaluatePython = evaluatePython
+
+
+class talbased(object):
+    def __init__(self, filename, write=True):
+##         if not osp.isfile(filepath):
+##             # print "[tal.py] just for tests..."
+##             # get parent frame
+##             directory = osp.abspath(osp.dirname(sys._getframe(1).f_globals['__file__']))
+##             filepath = osp.join(directory, filepath)
+        self.filename = filename
+        self.write = write
+
+    def __call__(self, viewfunc):
+        def wrapped(instance, *args, **kwargs):
+            variables = viewfunc(instance, *args, **kwargs)
+            html = instance.tal_render(self._compiled_template(instance), variables)
+            if self.write:
+                instance.w(html)
+            else:
+                return html
+        return wrapped
+
+    def _compiled_template(self, instance):
+        for fileordirectory in instance.config.vregistry_path():
+            filepath = join(fileordirectory, self.filename)
+            if isdir(fileordirectory) and exists(filepath):
+                return compile_template_file(filepath)
+        raise Exception('no such template %s' % self.filename)
+    _compiled_template = cached(_compiled_template, 0)
+