Commit 11b156c2 by Pieter van de Bruggen

Beginning a rework of the parser.

parent c19e58c8
import re import re
import cgi
import collections
import os
import copy
modifiers = {}
def modifier(symbol):
"""Decorator for associating a function with a Mustache tag modifier.
@modifier('P')
def render_tongue(self, tag_name=None, context=None):
return ":P %s" % tag_name
{{P yo }} => :P yo
"""
def set_modifier(func):
modifiers[symbol] = func
return func
return set_modifier
class Template(object): class Template(object):
tag_re = None tag_re = None
otag, ctag = '{{', '}}'
otag = '{{'
def __init__(self, template=None, context={}, **kwargs):
ctag = '}}'
def __init__(self, template=None, context=None, **kwargs):
from view import View from view import View
self.template = template self.template = template
if kwargs: if kwargs:
context.update(kwargs) context.update(kwargs)
self.view = context if isinstance(context, View) else View(context=context) self.view = context if isinstance(context, View) else View(context=context)
self._compile_regexps() self._compile_regexps()
def _compile_regexps(self): def _compile_regexps(self):
tags = { tags = {'otag': re.escape(self.otag), 'ctag': re.escape(self.ctag)}
'otag': re.escape(self.otag), tag = r"""
'ctag': re.escape(self.ctag) (?P<content>[\s\S]*?)
} (?P<whitespace>[\ \t]*)
%(otag)s \s*
section = r"%(otag)s[\#|^]([^\}]*)%(ctag)s\s*(.+?\s*)%(otag)s/\1%(ctag)s" (?:
self.section_re = re.compile(section % tags, re.M|re.S) (?P<change>=) \s* (?P<delims>.+?) \s* = |
(?P<raw>{) \s* (?P<raw_name>.+?) \s* } |
tag = r"%(otag)s(#|=|&|!|>|\{)?(.+?)\1?%(ctag)s+" (?P<tag>\W?) \s* (?P<name>[\s\S]+?)
self.tag_re = re.compile(tag % tags) )
\s* %(ctag)s
def _render_sections(self, template, view): """
while True: self.tag_re = re.compile(tag % tags, re.M | re.X)
match = self.section_re.search(template)
if match is None: def _parse(self, template, section=None, index=0):
break """Parse a template into a syntax tree."""
buffer = []
pos = index
section, section_name, inner = match.group(0, 1, 2)
section_name = section_name.strip()
it = self.view.get(section_name, None)
replacer = ''
# Callable
if it and isinstance(it, collections.Callable):
replacer = it(inner)
# Dictionary
elif it and hasattr(it, 'keys') and hasattr(it, '__getitem__'):
if section[2] != '^':
replacer = self._render_dictionary(inner, it)
# Lists
elif it and hasattr(it, '__iter__'):
if section[2] != '^':
replacer = self._render_list(inner, it)
# Other objects
elif it and isinstance(it, object):
if section[2] != '^':
replacer = self._render_dictionary(inner, it)
# Falsey and Negated or Truthy and Not Negated
elif (not it and section[2] == '^') or (it and section[2] != '^'):
replacer = inner
template = template.replace(section, replacer)
return template
def _render_tags(self, template):
while True: while True:
match = self.tag_re.search(template) match = self.tag_re.search(template, pos)
if match is None: if match is None:
break break
tag, tag_type, tag_name = match.group(0, 1, 2) # Normalize the captures dictionary.
tag_name = tag_name.strip() captures = match.groupdict()
func = modifiers[tag_type] if captures['change'] is not None:
replacement = func(self, tag_name) captures.update(tag='=', name=captures['delims'])
template = template.replace(tag, replacement) elif captures['raw'] is not None:
captures.update(tag='{', name=captures['raw_name'])
return template
# Save the literal text content.
def _render_dictionary(self, template, context): buffer.append(captures['content'])
self.view.context_list.insert(0, context) pos = match.end()
template = Template(template, self.view)
out = template.render() # Save the whitespace following the text content.
self.view.context_list.pop(0) # TODO: Standalone tags should consume this.
return out buffer.append(captures['whitespace'])
def _render_list(self, template, listing): # TODO: Process the remaining tag types.
insides = [] if captures['tag'] is '!':
for item in listing: pass
insides.append(self._render_dictionary(template, item))
# Save the rest of the template.
return ''.join(insides) buffer.append(template[pos:])
@modifier(None) return buffer
def _render_tag(self, tag_name):
raw = self.view.get(tag_name, '') def _generate(self, parsed, view):
"""Convert a parse tree into a fully evaluated template."""
# For methods with no return value
if not raw and raw is not 0: # TODO: Handle non-trivial cases.
return '' return ''.join(parsed)
return cgi.escape(unicode(raw))
@modifier('!')
def _render_comment(self, tag_name):
return ''
@modifier('>')
def _render_partial(self, template_name):
from pystache import Loader
markup = Loader().load_template(template_name, self.view.template_path, encoding=self.view.template_encoding)
template = Template(markup, self.view)
return template.render()
@modifier('=')
def _change_delimiter(self, tag_name):
"""Changes the Mustache delimiter."""
self.otag, self.ctag = tag_name.split(' ')
self._compile_regexps()
return ''
@modifier('{')
@modifier('&')
def render_unescaped(self, tag_name):
"""Render a tag without escaping it."""
return unicode(self.view.get(tag_name, ''))
def render(self, encoding=None): def render(self, encoding=None):
template = self._render_sections(self.template, self.view) parsed = self._parse(self.template)
result = self._render_tags(template) result = self._generate(parsed, self.view)
if encoding is not None: if encoding is not None:
result = result.encode(encoding) result = result.encode(encoding)
return result return result
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment