Creating a Python-based template engine involves building a system that can take a template string containing placeholders or tags and replace those placeholders dynamically with provided data. Below is a detailed example of a simple yet functional template engine supporting variable substitution, basic loops, and conditionals.
pythonimport re
class TemplateEngine:
VARIABLE_PATTERN = re.compile(r'{{s*(w+)s*}}')
BLOCK_PATTERN = re.compile(r'{%(.+?)%}')
def __init__(self, template_str):
self.template_str = template_str.strip()
def render(self, context):
tokens = self._tokenize(self.template_str)
output = self._render_tokens(tokens, context)
return output
def _tokenize(self, template):
"""
Break template into tokens of text, variables, and blocks
"""
tokens = []
pos = 0
while pos < len(template):
var_match = self.VARIABLE_PATTERN.search(template, pos)
block_match = self.BLOCK_PATTERN.search(template, pos)
# Find closest match
nearest = None
if var_match and block_match:
if var_match.start() < block_match.start():
nearest = ('var', var_match)
else:
nearest = ('block', block_match)
elif var_match:
nearest = ('var', var_match)
elif block_match:
nearest = ('block', block_match)
else:
break
start = nearest[1].start()
end = nearest[1].end()
# Add preceding text token
if start > pos:
tokens.append(('text', template[pos:start]))
if nearest[0] == 'var':
tokens.append(('var', nearest[1].group(1).strip()))
else:
tokens.append(('block', nearest[1].group(1).strip()))
pos = end
# Add remaining text
if pos < len(template):
tokens.append(('text', template[pos:]))
return tokens
def _render_tokens(self, tokens, context):
output = ''
tokens = tokens.copy()
while tokens:
token_type, token_value = tokens.pop(0)
if token_type == 'text':
output += token_value
elif token_type == 'var':
output += str(self._resolve_variable(token_value, context))
elif token_type == 'block':
parts = token_value.split()
if parts[0] == 'if':
condition = ' '.join(parts[1:])
inner_tokens, tokens = self._collect_until(tokens, 'endif')
if self._eval_condition(condition, context):
output += self._render_tokens(inner_tokens, context)
elif parts[0] == 'for':
# {% for item in items %}
var_name = parts[1]
iterable_name = parts[3]
inner_tokens, tokens = self._collect_until(tokens, 'endfor')
iterable = self._resolve_variable(iterable_name, context)
if iterable:
for item in iterable:
new_context = context.copy()
new_context[var_name] = item
output += self._render_tokens(inner_tokens, new_context)
# Can extend for elif, else etc.
return output
def _collect_until(self, tokens, end_tag):
collected = []
depth = 0
while tokens:
token_type, token_value = tokens.pop(0)
if token_type == 'block':
parts = token_value.split()
if parts[0] == end_tag and depth == 0:
return collected, tokens
elif parts[0] in ['if', 'for']:
depth += 1
elif parts[0] in ['endif', 'endfor']:
depth -= 1
collected.append((token_type, token_value))
raise ValueError(f"Missing {% {end_tag} %} tag")
def _resolve_variable(self, var_name, context):
# Support dot notation: user.name
parts = var_name.split('.')
val = context
try:
for part in parts:
if isinstance(val, dict):
val = val[part]
else:
val = getattr(val, part)
return val
except (KeyError, AttributeError):
return ''
def _eval_condition(self, condition, context):
# VERY basic condition evaluator (supports variable truthiness and equality)
# Example conditions: "user.is_active", "count == 5"
tokens = condition.split()
if len(tokens) == 1:
# Single variable truthiness
val = self._resolve_variable(tokens[0], context)
return bool(val)
elif len(tokens) == 3:
left = self._resolve_variable(tokens[0], context)
op = tokens[1]
right = tokens[2].strip("'"")
if op == '==':
return str(left) == right
elif op == '!=':
return str(left) != right
# Extend with more operators if needed
else:
return False
# Example usage:
template = """
Hello, {{ user.name }}!
{% if user.is_active %}
Your account is active.
{% endif %}
Your favorite colors are:
{% for color in user.favorites.colors %}
- {{ color }}
{% endfor %}
"""
context = {
'user': {
'name': 'Alice',
'is_active': True,
'favorites': {
'colors': ['red', 'green', 'blue']
}
}
}
engine = TemplateEngine(template)
print(engine.render(context))
This engine features:
-
Variable substitution with
{{ var }}
, supporting dot notation likeuser.name
. -
Basic
{% if condition %} ... {% endif %}
blocks. -
Simple
{% for var in iterable %} ... {% endfor %}
loops. -
Context variable lookups including nested dictionaries and object attributes.
-
Very basic condition evaluation (truthiness and simple equality).
You can extend this by adding support for else
, elif
, more operators, filters, and caching parsed templates for performance. This serves as a solid foundational template engine in pure Python.
Leave a Reply