Skip to content

Commit

Permalink
Make public/private clearer; add a check for bad loop var name
Browse files Browse the repository at this point in the history
  • Loading branch information
nedbat committed May 5, 2014
1 parent 6ee4117 commit 6cb4fbd
Show file tree
Hide file tree
Showing 2 changed files with 41 additions and 23 deletions.
62 changes: 39 additions & 23 deletions template-engine/templite.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,20 @@ class Templite(object):
{# This will be ignored #}
Construct a Templite with the template text, then use `render` against a
dictionary context to create a finished string.
dictionary context to create a finished string::
templite = Templite('''
<h1>Hello {{name|upper}}!</h1>
{% for topic in topics %}
<p>You are interested in {{topic}}.</p>
{% endif %}
''',
{'upper': str.upper},
)
text = templite.render({
'name': "Ned",
'topics': ['Python', 'Geometry', 'Juggling'],
})
"""
def __init__(self, text, *contexts):
Expand Down Expand Up @@ -116,7 +129,7 @@ def flush_output():
for token in tokens:
if token.startswith('{{'):
# An expression to evaluate.
buffered.append("s(%s)" % self.expr_code(token[2:-2].strip()))
buffered.append("s(%s)" % self._expr_code(token[2:-2].strip()))
elif token.startswith('{#'):
# Comment: ignore it and move on.
continue
Expand All @@ -127,20 +140,20 @@ def flush_output():
if words[0] == 'if':
# An if statement: evaluate the expression to determine if.
if len(words) != 2:
self.syntax_error("Don't understand if", token)
self._syntax_error("Don't understand if", token)
ops_stack.append('if')
code.add_line("if %s:" % self.expr_code(words[1]))
code.add_line("if %s:" % self._expr_code(words[1]))
code.indent()
elif words[0] == 'for':
# A loop: iterate over expression result.
if len(words) != 4 or words[2] != 'in':
self.syntax_error("Don't understand for", token)
self._syntax_error("Don't understand for", token)
ops_stack.append('for')
self.loop_vars.add(words[1])
self._variable(words[1], self.loop_vars)
code.add_line(
"for c_%s in %s:" % (
words[1],
self.expr_code(words[3])
self._expr_code(words[3])
)
)
code.indent()
Expand All @@ -149,10 +162,10 @@ def flush_output():
end_what = words[0][3:]
start_what = ops_stack.pop()
if start_what != end_what:
self.syntax_error("Mismatched end tag", end_what)
self._syntax_error("Mismatched end tag", end_what)
code.dedent()
else:
self.syntax_error("Don't understand tag", words[0])
self._syntax_error("Don't understand tag", words[0])
else:
# Literal content. If it isn't empty, output it.
if token:
Expand All @@ -164,40 +177,43 @@ def flush_output():
vars_code.add_line("c_%s = ctx[%r]" % (var_name, var_name))

if ops_stack:
self.syntax_error("Unmatched action tag", ops_stack[-1])
self._syntax_error("Unmatched action tag", ops_stack[-1])

code.add_line("return ''.join(result)")
code.dedent()
self.render_function = code.get_globals()['render']
self._render_function = code.get_globals()['render']

def syntax_error(self, msg, thing):
def _syntax_error(self, msg, thing):
"""Raise a syntax error using `msg`, and showing `thing`."""
raise SyntaxError("%s: %r" % (msg, thing))

def variable(self, name):
def _variable(self, name, vars_set):
"""Track that `name` is used as a variable.
Raises an exception if `name` is not a valid name.
Adds the name to `vars_set`, a set of variable names.
Raises an syntax error if `name` is not a valid name.
"""
if not re.match(r"[_a-zA-Z][_a-zA-Z0-9]*$", name):
self.syntax_error("Not a valid name", name)
self.all_vars.add(name)
self._syntax_error("Not a valid name", name)
vars_set.add(name)

def expr_code(self, expr):
def _expr_code(self, expr):
"""Generate a Python expression for `expr`."""
if "|" in expr:
pipes = expr.split("|")
code = self.expr_code(pipes[0])
code = self._expr_code(pipes[0])
for func in pipes[1:]:
self.variable(func)
self._variable(func, self.all_vars)
code = "c_%s(%s)" % (func, code)
elif "." in expr:
dots = expr.split(".")
code = self.expr_code(dots[0])
code = self._expr_code(dots[0])
args = ", ".join(repr(d) for d in dots[1:])
code = "dot(%s, %s)" % (code, args)
else:
self.variable(expr)
self._variable(expr, self.all_vars)
code = "c_%s" % expr
return code

Expand All @@ -211,9 +227,9 @@ def render(self, context=None):
ctx = dict(self.context)
if context:
ctx.update(context)
return self.render_function(ctx, self.do_dots)
return self._render_function(ctx, self._do_dots)

def do_dots(self, value, *dots):
def _do_dots(self, value, *dots):
"""Evaluate dotted expressions at runtime."""
for dot in dots:
try:
Expand Down
2 changes: 2 additions & 0 deletions template-engine/test_templite.py
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,8 @@ def test_bad_names(self):
self.try_render("Wat: {{ var%&!@ }}")
with self.assertSynErr("Not a valid name: 'filter%&!@'"):
self.try_render("Wat: {{ foo|filter%&!@ }}")
with self.assertSynErr("Not a valid name: '@'"):
self.try_render("Wat: {% for @ in x %}{% endfor %}")

def test_bogus_tag_syntax(self):
with self.assertSynErr("Don't understand tag: 'bogus'"):
Expand Down

0 comments on commit 6cb4fbd

Please sign in to comment.