Commit eb29ca64 by Chris Jerdonek

Merge branch 'issue_29' into development

Closing issue #29 "Cleanups": https://github.com/defunkt/pystache/pull/29
parents d2eab0f5 b1823b37
History
=======
Next Release (version TBD)
--------------------------
* Added some docstrings [kennethreitz].
0.4.0 (2011-01-12)
------------------
* Add support for nested contexts (within template and view)
......
from pystache.template import Template
from pystache.view import View
from pystache.loader import Loader
def render(template, context=None, **kwargs):
context = context and context.copy() or {}
context.update(kwargs)
return Template(template, context).render()
# We keep all initialization code in a separate module.
from init import *
# encoding: utf-8
"""
This module contains the initialization logic called by __init__.py.
"""
from .template import Template
from .view import View
from .loader import Loader
__all__ = ['Template', 'View', 'Loader', 'render']
def render(template, context=None, **kwargs):
"""
Return the given template string rendered using the given context.
"""
context = context and context.copy() or {}
context.update(kwargs)
return Template(template, context).render()
# coding: utf-8
"""
This module provides a Loader class.
"""
import os
class Loader(object):
template_extension = 'mustache'
template_path = '.'
template_encoding = None
def load_template(self, template_name, template_dirs=None, encoding=None, extension=None):
'''Returns the template string from a file or throws IOError if it non existent'''
"""
Find and load the given template, and return it as a string.
Raises an IOError if the template cannot be found.
"""
if None == template_dirs:
template_dirs = self.template_path
if encoding is not None:
self.template_encoding = encoding
if extension is not None:
self.template_extension = extension
file_name = template_name + '.' + self.template_extension
# Given a single directory we'll load from it
# Given a single directory, we'll load from it.
if isinstance(template_dirs, basestring):
file_path = os.path.join(template_dirs, file_name)
return self._load_template_file(file_path)
# Given a list of directories we'll check each for our file
# Given a list of directories, we'll check each for our file.
for path in template_dirs:
file_path = os.path.join(path, file_name)
if os.path.exists(file_path):
return self._load_template_file(file_path)
# TODO: we should probably raise an exception of our own type.
raise IOError('"%s" not found in "%s"' % (template_name, ':'.join(template_dirs),))
def _load_template_file(self, file_path):
'''Loads and returns the template file from disk'''
"""
Read a template file, and return it as a string.
"""
f = open(file_path, 'r')
try:
template = f.read()
if self.template_encoding:
template = unicode(template, self.template_encoding)
finally:
f.close()
return template
\ No newline at end of file
return template
# coding: utf-8
"""
This module provides a Template class.
"""
import re
import cgi
import collections
import os
import copy
from .loader import Loader
try:
import markupsafe
escape = markupsafe.escape
literal = markupsafe.Markup
except ImportError:
escape = lambda x: cgi.escape(unicode(x))
literal = unicode
......@@ -25,38 +32,37 @@ except ImportError:
class Modifiers(dict):
"""Dictionary with a decorator for assigning functions to keys."""
def set(self, key):
"""
Decorator function to set the given key to the decorated function.
>>> modifiers = {}
>>> @modifiers.set('P')
... def render_tongue(self, tag_name=None, context=None):
... return ":P %s" % tag_name
>>> modifiers
{'P': <function render_tongue at 0x...>}
"""
Return a decorator that assigns the given function to the given key.
>>> modifiers = {}
>>> @modifiers.set('P')
... def render_tongue(self, tag_name=None, context=None):
... return ":P %s" % tag_name
>>> modifiers
{'P': <function render_tongue at 0x...>}
def setter(func):
"""
def decorate(func):
self[key] = func
return func
return setter
return decorate
class Template(object):
tag_re = None
otag = '{{'
ctag = '}}'
modifiers = Modifiers()
def __init__(self, template=None, context=None, **kwargs):
from view import View
from .view import View
self.template = template
......@@ -67,6 +73,12 @@ class Template(object):
self._compile_regexps()
def _compile_regexps(self):
"""
Compile and set the regular expression attributes.
This method uses the current values for the otag and ctag attributes.
"""
tags = {
'otag': re.escape(self.otag),
'ctag': re.escape(self.ctag)
......@@ -94,18 +106,22 @@ class Template(object):
# Callable
if it and check_callable(it):
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 = self._render_dictionary(inner, it)
......@@ -130,9 +146,12 @@ class Template(object):
def _render_dictionary(self, template, context):
self.view.context_list.insert(0, context)
template = Template(template, self.view)
out = template.render()
self.view.context_list.pop(0)
return out
def _render_list(self, template, listing):
......@@ -167,18 +186,29 @@ class Template(object):
@modifiers.set('=')
def _change_delimiter(self, tag_name):
"""Changes the Mustache delimiter."""
"""
Change the current delimiter.
"""
self.otag, self.ctag = tag_name.split(' ')
self._compile_regexps()
return ''
@modifiers.set('{')
@modifiers.set('&')
def render_unescaped(self, tag_name):
"""Render a tag without escaping it."""
"""
Render a tag without escaping it.
"""
return literal(self.view.get(tag_name, ''))
def render(self, encoding=None):
"""
Return the template rendered using the current view context.
"""
template = self._render_sections(self.template, self.view)
result = self._render_tags(template)
......
from pystache import Template
import os.path
# coding: utf-8
"""
This module provides a View class.
"""
import re
from types import *
from types import UnboundMethodType
from .loader import Loader
from .template import Template
def get_or_attr(context_list, name, default=None):
"""
Find and return an attribute from the given context.
"""
if not context_list:
return default
......@@ -17,8 +30,10 @@ def get_or_attr(context_list, name, default=None):
return getattr(obj, name)
except AttributeError:
pass
return default
class View(object):
template_name = None
......@@ -29,13 +44,19 @@ class View(object):
def __init__(self, template=None, context=None, **kwargs):
self.template = template
context = context or {}
context.update(**kwargs)
self.context_list = [context]
def get(self, attr, default=None):
"""
Return the value for the given attribute.
"""
attr = get_or_attr(self.context_list, attr, getattr(self, attr, default))
if hasattr(attr, '__call__') and type(attr) is UnboundMethodType:
return attr()
else:
......@@ -47,16 +68,29 @@ class View(object):
encoding=self.template_encoding, extension=self.template_extension)
def get_template(self, template_name):
"""
Return the current template after setting it, if necessary.
"""
if not self.template:
template_name = self._get_template_name(template_name)
self.template = self.load_template(template_name)
return self.template
# TODO: consider removing the template_name parameter and using
# self.template_name instead.
def _get_template_name(self, template_name=None):
"""TemplatePartial => template_partial
Takes a string but defaults to using the current class' name or
the `template_name` attribute
"""
Return the name of this Template instance.
If no template_name parameter is provided, this method returns the
class name modified as follows, for example:
TemplatePartial => template_partial
Otherwise, it returns the given template_name.
"""
if template_name:
return template_name
......@@ -76,7 +110,12 @@ class View(object):
return context
def render(self, encoding=None):
return Template(self.get_template(self.template_name), self).render(encoding=encoding)
"""
Return the view rendered using the current context.
"""
template = Template(self.get_template(self.template_name), self)
return template.render(encoding=encoding)
def __contains__(self, needle):
return needle in self.context or hasattr(self, needle)
......@@ -95,4 +134,4 @@ class View(object):
raise AttributeError("Attribute '%s' does not exist in View" % attr)
def __str__(self):
return self.render()
\ No newline at end of file
return self.render()
#!/usr/bin/env python
# coding: utf-8
import os
import sys
try:
from setuptools import setup
except ImportError:
from distutils.core import setup
def publish():
"""Publish to Pypi"""
os.system("python setup.py sdist upload")
"""
Publish this package to PyPI (aka "the Cheeseshop").
"""
os.system('python setup.py sdist upload')
if sys.argv[-1] == "publish":
if sys.argv[-1] == 'publish':
publish()
sys.exit()
......@@ -25,12 +32,12 @@ setup(name='pystache',
url='http://github.com/defunkt/pystache',
packages=['pystache'],
license='MIT',
classifiers = (
"Development Status :: 4 - Beta",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python",
"Programming Language :: Python :: 2.5",
"Programming Language :: Python :: 2.6",
)
)
classifiers = (
'Development Status :: 4 - Beta',
'License :: OSI Approved :: MIT License',
'Programming Language :: Python',
'Programming Language :: Python :: 2.5',
'Programming Language :: Python :: 2.6',
)
)
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