SVN: r25659 - in trunk/quixote: . test
David Binger <dbinger-fVcApmY9cLvQ3/1i3zOLAti2O/[email protected]> Mon, 22 Nov 2004 11:32:28 -0500
| Newsgroups | gmane.comp.web.quixote.cvs |
|---|---|
| Message-ID | <[email protected]> |
Author: dbinger
Date: 2004-11-22 11:29:27 -0500 (Mon, 22 Nov 2004)
New Revision: 25659
Modified:
trunk/quixote/ptl_compile.py
trunk/quixote/test/utest_ptl.py
Log:
Support $-substitution in templates, as in python 2.4's string.Template class.
$-substitution is applied to every '$'-containing literal in a template.
Templates written before this change, if they contain literals containing '$',
*must* be converted by replacing each '$' with '$$'.
Drop support for old-style ("template f()") template definitions.
Modified: trunk/quixote/ptl_compile.py
===================================================================
--- trunk/quixote/ptl_compile.py 2004-11-22 14:16:20 UTC (rev 25658)
+++ trunk/quixote/ptl_compile.py 2004-11-22 16:29:27 UTC (rev 25659)
@@ -1,8 +1,8 @@
#!/www/python/bin/python
-#$HeadURL$
-#$Id$
+"""
+$URL$
+$Id$
-"""
Compile a PTL template.
First template function names are mangled, noting the template type.
@@ -30,18 +30,8 @@
from compiler.consts import OP_ASSIGN
from compiler import misc, syntax
-
-# magic names inserted into the code
-IO_MODULE = "quixote.html"
-IO_CLASS = "TemplateIO"
-IO_MANGLED_CLASS = "_q_TemplateIO"
-IO_INSTANCE = "_q_output"
HTML_TEMPLATE_PREFIX = "_q_html_template_"
PLAIN_TEMPLATE_PREFIX = "_q_plain_template_"
-TEMPLATE_PREFIX = "_q_template_"
-MARKUP_MODULE = "quixote.html"
-MARKUP_CLASS = "htmltext"
-MARKUP_MANGLED_CLASS = "_q_htmltext"
class TemplateTransformer(transformer.Transformer):
@@ -49,23 +39,22 @@
transformer.Transformer.__init__(self, *args, **kwargs)
self.__template_type = [] # stack, "html", "plain" or None
+ def _get_template_type(self):
+ """Return the type of the function being compiled ("html", "plain"
+ or None)."""
+ if self.__template_type:
+ return self.__template_type[-1]
+ else:
+ return None
+
def file_input(self, nodelist):
- # Add a "from IO_MODULE import IO_CLASS" statement to the
- # beginning of the module.
doc = None # self.get_docstring(nodelist, symbol.file_input)
- io_imp = ast.From(IO_MODULE, [(IO_CLASS, IO_MANGLED_CLASS)])
- markup_imp = ast.From(MARKUP_MODULE,
- [(MARKUP_CLASS, MARKUP_MANGLED_CLASS)])
+ html_imp = ast.From('quixote.html', [('TemplateIO', '_q_TemplateIO'),
+ ('htmltext', '_q_htmltext')])
+ vars_imp = ast.From("__builtin__", [("vars", "_q_vars")])
+ stmts = [ vars_imp, html_imp ]
- # Add an IO_INSTANCE binding for module level expressions (like
- # doc strings). This instance will not be returned.
- io_instance = ast.CallFunc(ast.Name(IO_MANGLED_CLASS), [])
- io_assign_name = ast.AssName(IO_INSTANCE, OP_ASSIGN)
- io_assign = ast.Assign([io_assign_name], io_instance)
-
- stmts = [ io_imp, io_assign, markup_imp ]
-
for node in nodelist:
if node[0] != token.ENDMARKER and node[0] != token.NEWLINE:
self.com_append_stmt(stmts, node)
@@ -96,16 +85,11 @@
elif name.startswith(HTML_TEMPLATE_PREFIX):
name = name[len(HTML_TEMPLATE_PREFIX):]
template_type = "html"
- elif name.startswith(TEMPLATE_PREFIX):
- name = name[len(TEMPLATE_PREFIX):]
- template_type = "plain"
else:
raise RuntimeError, 'unknown prefix on %s' % name
self.__template_type.append(template_type)
- # Add "IO_INSTANCE = IO_CLASS()" statement at the beginning of
- # the function and a "return IO_INSTANCE" at the end.
if args[0] == symbol.varargslist:
names, defaults, flags = self.com_arglist(args[1:])
else:
@@ -116,15 +100,15 @@
# code for function
code = self.com_node(nodelist[-1])
- # create an instance, assign to IO_INSTANCE
- klass = ast.Name(IO_MANGLED_CLASS)
+ # _q_output = _q_TemplateIO()
+ klass = ast.Name('_q_TemplateIO')
args = [ast.Const(template_type == "html")]
instance = ast.CallFunc(klass, args)
- assign_name = ast.AssName(IO_INSTANCE, OP_ASSIGN)
+ assign_name = ast.AssName('_q_output', OP_ASSIGN)
assign = ast.Assign([assign_name], instance)
- # return the IO_INSTANCE.getvalue(...)
- func = ast.Getattr(ast.Name(IO_INSTANCE), "getvalue")
+ # return _q_output.getvalue()
+ func = ast.Getattr(ast.Name('_q_output'), "getvalue")
ret = ast.Return(ast.CallFunc(func, []))
# wrap original function code
@@ -141,14 +125,14 @@
return n
def expr_stmt(self, nodelist):
- if not self.__template_type or not self.__template_type[-1]:
+ if self._get_template_type() is None:
return transformer.Transformer.expr_stmt(self, nodelist)
# Instead of discarding objects on the stack, call
- # "IO_INSTANCE += obj".
+ # "_q_output += obj".
exprNode = self.com_node(nodelist[-1])
if len(nodelist) == 1:
- lval = ast.Name(IO_INSTANCE)
+ lval = ast.Name('_q_output')
n = ast.AugAssign(lval, '+=', exprNode)
if hasattr(exprNode, 'lineno'):
n.lineno = exprNode.lineno
@@ -169,13 +153,45 @@
k = ''
for node in nodelist:
k = k + eval(node[1])
- n = ast.Const(k)
- if self.__template_type and self.__template_type[-1] == "html":
- # change "foo" to _q_htmltext("foo")
- n = ast.CallFunc(ast.Name(MARKUP_MANGLED_CLASS), [n])
- return n
+ lineno = node[2]
+ if self._get_template_type() is not None and '$' in k:
+ try:
+ k = _convert_string(k)
+ except ValueError, e:
+ raise SyntaxError(str(e), (None, lineno, None, None))
+ return ast.Mod((self._get_text_node(k),
+ ast.CallFunc(ast.Name('_q_vars'), [])))
+ else:
+ return self._get_text_node(k)
+ def _get_text_node(self, k):
+ if self._get_template_type() == "html":
+ return ast.CallFunc(ast.Name('_q_htmltext'), [ast.Const(k)])
+ else:
+ return ast.Const(k)
+_substitution_pattern = r"""
+ \$(?:
+ (?P<escaped>\$) |
+ (?P<named>%(idpat)s) |
+ {(?P<braced>%(idpat)s)} |
+ (?P<invalid>.*)
+ )""" % dict(idpat='[_a-z][_a-z0-9]*')
+
+_substitution_re = re.compile(_substitution_pattern, re.I|re.VERBOSE)
+
+def _convert_string(s):
+ def convert(mo):
+ name = mo.group('named') or mo.group('braced')
+ if name is not None:
+ return '%(' + name + ')s'
+ elif mo.group('escaped') is not None:
+ return '$'
+ elif mo.group('invalid') is not None:
+ invalid = '$' + mo.group('invalid')
+ raise ValueError('invalid substitution %r' % invalid)
+ return _substitution_re.sub(convert, s.replace('%', '%%'))
+
_template_re = re.compile(r"^([ \t]*) def (?:[ \t]+)" # def
r" ([a-zA-Z_][a-zA-Z_0-9]*)" # <name>
r" (?:[ \t]*) \[(plain|html)\] (?:[ \t]*)" # <type>
@@ -241,7 +257,7 @@
return template.code
def compile(inputname, outputname):
- """compile(inputname, outputname)
+ """(inputname, outputname)
Compile a template file. The new template is writen to outputname.
"""
Modified: trunk/quixote/test/utest_ptl.py
===================================================================
--- trunk/quixote/test/utest_ptl.py 2004-11-22 14:16:20 UTC (rev 25658)
+++ trunk/quixote/test/utest_ptl.py 2004-11-22 16:29:27 UTC (rev 25659)
@@ -53,6 +53,35 @@
except SyntaxError, e:
assert e.lineno == 1
+ def check_dollar(self):
+ run_ptl('def f(a):',
+ ' "yes $a"',
+ 'assert f(1) == None')
+ run_ptl('def f [plain] (a):',
+ ' "yes $a"',
+ 'assert f(1) == "yes 1"')
+ run_ptl('def f [html] (a):',
+ ' "yes ${a} $a $$"',
+ 'assert f(1) == "yes 1 1 $"')
+ try:
+ run_ptl('def f [plain] (a):',
+ ' "yes ${a} $a $"')
+ assert 0
+ except SyntaxError, e:
+ assert str(e) == "invalid substitution '$' (line 2) (test, line 2)"
+ try:
+ run_ptl('def f [plain] (a):',
+ ' "$-"')
+ assert 0
+ except SyntaxError, e:
+ assert str(e) == (
+ "invalid substitution '$-' (line 2) (test, line 2)")
+ run_ptl('def f [html] (a):',
+ ' "yes %s $a" % "odd"',
+ 'assert f(1) == "yes odd 1"')
+ run_ptl('def f [plain] (a):',
+ ' "yes %(a)s $a" % locals()',
+ 'assert f(1) == "yes 1 1"')
if __name__ == "__main__":
Test()