fix bug in entity.related_rql(): fetch_attr list / fetchorder weren't computed correctly
"""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)