mirror of
https://github.com/kennethreitz-archive/pystache.git
synced 2026-06-05 23:40:16 +00:00
First test passing with {{ }}
This commit is contained in:
+37
-114
@@ -17,138 +17,61 @@ def modifier(symbol):
|
||||
return func
|
||||
return set_modifier
|
||||
|
||||
|
||||
def get_or_attr(obj, name, default=None):
|
||||
try:
|
||||
return obj[name]
|
||||
except KeyError:
|
||||
return default
|
||||
except:
|
||||
try:
|
||||
return getattr(obj, name)
|
||||
except AttributeError:
|
||||
return default
|
||||
|
||||
|
||||
class Template(object):
|
||||
# The regular expression used to find a #section
|
||||
section_re = None
|
||||
|
||||
# The regular expression used to find a tag.
|
||||
|
||||
tag_re = None
|
||||
|
||||
# Opening tag delimiter
|
||||
|
||||
otag = '{{'
|
||||
|
||||
# Closing tag delimiter
|
||||
|
||||
ctag = '}}'
|
||||
|
||||
def __init__(self, template, context=None):
|
||||
|
||||
def __init__(self, template=None, context=None, **kwargs):
|
||||
from view import View
|
||||
|
||||
self.template = template
|
||||
self.context = context or {}
|
||||
self.compile_regexps()
|
||||
|
||||
def render(self, template=None, context=None, encoding=None):
|
||||
"""Turns a Mustache template into something wonderful."""
|
||||
template = template or self.template
|
||||
context = context or self.context
|
||||
|
||||
template = self.render_sections(template, context)
|
||||
result = self.render_tags(template, context)
|
||||
if encoding is not None:
|
||||
result = result.encode(encoding)
|
||||
return result
|
||||
|
||||
def compile_regexps(self):
|
||||
"""Compiles our section and tag regular expressions."""
|
||||
tags = { 'otag': re.escape(self.otag), 'ctag': re.escape(self.ctag) }
|
||||
|
||||
section = r"%(otag)s[\#|^]([^\}]*)%(ctag)s\s*(.+?)\s*%(otag)s/\1%(ctag)s"
|
||||
self.section_re = re.compile(section % tags, re.M|re.S)
|
||||
|
||||
if kwargs:
|
||||
context.update(kwargs)
|
||||
|
||||
self.view = context if isinstance(context, View) else View(context=context)
|
||||
|
||||
self._compile_regexps()
|
||||
|
||||
def _compile_regexps(self):
|
||||
tags = {
|
||||
'otag': re.escape(self.otag),
|
||||
'ctag': re.escape(self.ctag)
|
||||
}
|
||||
|
||||
tag = r"%(otag)s(#|=|&|!|>|\{)?(.+?)\1?%(ctag)s+"
|
||||
self.tag_re = re.compile(tag % tags)
|
||||
|
||||
def render_sections(self, template, context):
|
||||
"""Expands sections."""
|
||||
while 1:
|
||||
match = self.section_re.search(template)
|
||||
if match is None:
|
||||
break
|
||||
|
||||
section, section_name, inner = match.group(0, 1, 2)
|
||||
section_name = section_name.strip()
|
||||
|
||||
it = get_or_attr(context, section_name, None)
|
||||
replacer = ''
|
||||
if it and isinstance(it, collections.Callable):
|
||||
replacer = it(inner)
|
||||
elif it and not hasattr(it, '__iter__'):
|
||||
if section[2] != '^':
|
||||
replacer = inner
|
||||
elif it and hasattr(it, 'keys') and hasattr(it, '__getitem__'):
|
||||
if section[2] != '^':
|
||||
replacer = self.render(inner, it)
|
||||
elif it:
|
||||
insides = []
|
||||
for item in it:
|
||||
insides.append(self.render(inner, item))
|
||||
replacer = ''.join(insides)
|
||||
elif not it and section[2] == '^':
|
||||
replacer = inner
|
||||
|
||||
template = template.replace(section, replacer)
|
||||
|
||||
|
||||
def _render_sections(self, template, view):
|
||||
return template
|
||||
|
||||
def render_tags(self, template, context):
|
||||
"""Renders all the tags in a template for a context."""
|
||||
while 1:
|
||||
|
||||
def _render_tags(self, template, view):
|
||||
while True:
|
||||
match = self.tag_re.search(template)
|
||||
if match is None:
|
||||
break
|
||||
|
||||
|
||||
tag, tag_type, tag_name = match.group(0, 1, 2)
|
||||
tag_name = tag_name.strip()
|
||||
func = modifiers[tag_type]
|
||||
replacement = func(self, tag_name, context)
|
||||
replacement = func(self, tag_name, view)
|
||||
template = template.replace(tag, replacement)
|
||||
|
||||
|
||||
return template
|
||||
|
||||
|
||||
@modifier(None)
|
||||
def render_tag(self, tag_name, context):
|
||||
"""Given a tag name and context, finds, escapes, and renders the tag."""
|
||||
raw = get_or_attr(context, tag_name, '')
|
||||
if not raw and raw is not 0:
|
||||
return ''
|
||||
def _render_tag(self, tag_name, view):
|
||||
raw = view.get(tag_name, '')
|
||||
|
||||
return cgi.escape(unicode(raw))
|
||||
|
||||
@modifier('!')
|
||||
def render_comment(self, tag_name=None, context=None):
|
||||
"""Rendering a comment always returns nothing."""
|
||||
return ''
|
||||
|
||||
@modifier('{')
|
||||
@modifier('&')
|
||||
def render_unescaped(self, tag_name=None, context=None):
|
||||
"""Render a tag without escaping it."""
|
||||
return unicode(get_or_attr(context, tag_name, ''))
|
||||
|
||||
@modifier('>')
|
||||
def render_partial(self, tag_name=None, context=None):
|
||||
"""Renders a partial within the current context."""
|
||||
# Import view here to avoid import loop
|
||||
from pystache.view import View
|
||||
|
||||
view = View(context=context)
|
||||
view.template_name = tag_name
|
||||
|
||||
return view.render()
|
||||
|
||||
@modifier('=')
|
||||
def render_delimiter(self, tag_name=None, context=None):
|
||||
"""Changes the Mustache delimiter."""
|
||||
self.otag, self.ctag = tag_name.split(' ')
|
||||
self.compile_regexps()
|
||||
return ''
|
||||
def render(self):
|
||||
template = self._render_sections(self.template, self.view)
|
||||
result = self._render_tags(template, self.view)
|
||||
|
||||
return result
|
||||
+3
-106
@@ -4,114 +4,11 @@ import re
|
||||
from types import *
|
||||
|
||||
class View(object):
|
||||
# Path where this view's template(s) live
|
||||
template_path = '.'
|
||||
|
||||
# Extension for templates
|
||||
template_extension = 'mustache'
|
||||
|
||||
# The name of this template. If none is given the View will try
|
||||
# to infer it based on the class name.
|
||||
template_name = None
|
||||
|
||||
# Absolute path to the template itself. Pystache will try to guess
|
||||
# if it's not provided.
|
||||
template_file = None
|
||||
|
||||
# Contents of the template.
|
||||
template = None
|
||||
|
||||
# Character encoding of the template file. If None, Pystache will not
|
||||
# do any decoding of the template.
|
||||
template_encoding = None
|
||||
|
||||
def __init__(self, template=None, context=None, **kwargs):
|
||||
self.template = template
|
||||
self.context = context or {}
|
||||
|
||||
# If the context we're handed is a View, we want to inherit
|
||||
# its settings.
|
||||
if isinstance(context, View):
|
||||
self.inherit_settings(context)
|
||||
|
||||
if kwargs:
|
||||
self.context.update(kwargs)
|
||||
|
||||
def inherit_settings(self, view):
|
||||
"""Given another View, copies its settings."""
|
||||
if view.template_path:
|
||||
self.template_path = view.template_path
|
||||
|
||||
if view.template_name:
|
||||
self.template_name = view.template_name
|
||||
|
||||
def load_template(self):
|
||||
if self.template:
|
||||
return self.template
|
||||
self.context.update(**kwargs)
|
||||
|
||||
if self.template_file:
|
||||
return self._load_template()
|
||||
|
||||
name = self.get_template_name() + '.' + self.template_extension
|
||||
|
||||
if isinstance(self.template_path, basestring):
|
||||
self.template_file = os.path.join(self.template_path, name)
|
||||
return self._load_template()
|
||||
|
||||
for path in self.template_path:
|
||||
self.template_file = os.path.join(path, name)
|
||||
if os.path.exists(self.template_file):
|
||||
return self._load_template()
|
||||
|
||||
raise IOError('"%s" not found in "%s"' % (name, ':'.join(self.template_path),))
|
||||
|
||||
|
||||
def _load_template(self):
|
||||
f = open(self.template_file, 'r')
|
||||
try:
|
||||
template = f.read()
|
||||
if self.template_encoding:
|
||||
template = unicode(template, self.template_encoding)
|
||||
finally:
|
||||
f.close()
|
||||
return template
|
||||
|
||||
def get_template_name(self, name=None):
|
||||
"""TemplatePartial => template_partial
|
||||
Takes a string but defaults to using the current class' name or
|
||||
the `template_name` attribute
|
||||
"""
|
||||
if self.template_name:
|
||||
return self.template_name
|
||||
|
||||
if not name:
|
||||
name = self.__class__.__name__
|
||||
|
||||
def repl(match):
|
||||
return '_' + match.group(0).lower()
|
||||
|
||||
return re.sub('[A-Z]', repl, name)[1:]
|
||||
|
||||
def __contains__(self, needle):
|
||||
return needle in self.context or hasattr(self, needle)
|
||||
|
||||
def __getitem__(self, attr):
|
||||
val = self.get(attr, None)
|
||||
if not val:
|
||||
raise KeyError("No such key.")
|
||||
return val
|
||||
|
||||
def get(self, attr, default):
|
||||
attr = self.context.get(attr, getattr(self, attr, default))
|
||||
|
||||
if hasattr(attr, '__call__') and type(attr) is UnboundMethodType:
|
||||
return attr()
|
||||
else:
|
||||
return attr
|
||||
|
||||
def render(self, encoding=None):
|
||||
template = self.load_template()
|
||||
return Template(template, self).render(encoding=encoding)
|
||||
|
||||
def __str__(self):
|
||||
return self.render()
|
||||
def get(self, attr, default=None):
|
||||
return self.context.get(attr, getattr(self, attr, default))
|
||||
@@ -0,0 +1,9 @@
|
||||
import unittest
|
||||
import pystache
|
||||
|
||||
class TestSimple(unittest.TestCase):
|
||||
|
||||
def test_simple_render(self):
|
||||
tmpl = '{{derp}}'
|
||||
|
||||
self.assertEqual('herp', pystache.Template(tmpl, {'derp': 'herp'}).render())
|
||||
Reference in New Issue
Block a user