Created
October 28, 2010 16:07
-
-
Save gcarothers/651689 to your computer and use it in GitHub Desktop.
Template Packages
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
"""Functions for doing things with template packages. | |
Template packages are collections of templates and code that they use for | |
support when rendering.""" | |
import os.path | |
import os | |
import urlparse | |
import sys | |
from paste.script.command import Command, BadCommand | |
from paste.script.filemaker import FileOp | |
from tempita import paste_script_template_renderer | |
class TemplateNotFoundError(Exception): | |
"""Raised when a template to be rendered is not found.""" | |
pass | |
def parent_package(package_name): | |
"""Returns the module object for the parent package of the package. | |
If the package is a top-level package, it returns None. "Parent" in this | |
case is defined as the package above it in the heirarchy. | |
Derived from the sample my_import in section 2.1 of the Python 2.5 Library | |
Reference.""" | |
segments = package_name.split('.') | |
if len(segments) == 1: | |
return None | |
del segments[-1] | |
parent_name = '.'.join(segments) | |
parent = __import__(parent_name, level=0) | |
for segment in segments[1:]: | |
parent = getattr(parent, segment) | |
return parent | |
class TemplatePackages(object): | |
"""Represents a source of template packages.""" | |
def __init__(self, base_package, render, debug): | |
self.base_package = base_package | |
self.debug = debug | |
self.render = render | |
@property | |
def template_directory(self): | |
"""Return the directory to search for templates from this package.""" | |
__import__('.'.join((self.base_package.__name__, 'templates'))) | |
return self.base_package.templates.__path__[0] | |
@property | |
def static_directory(self): | |
"""Return the directory to search for static files from this package.""" | |
return os.path.join(self.base_package.__path__[0], | |
self.base_package.static_directory) | |
def template_package(self, family, name): | |
"""Retreive the template package with the given name from family. | |
Example: template_package('widgets', 'buy_buttons')""" | |
try: | |
templates_module = __import__( | |
'.'.join((self.base_package.__name__, 'templates', family, name)), | |
level=0) | |
templates_module = reload(self.base_package.templates) | |
family_module = reload(getattr(templates_module, family)) | |
return reload(getattr(family_module, name)) | |
except ImportError: | |
return None | |
def execute_prerender(self, template_package, **kwargs): | |
"""The prerender function is called to perform any necessary work before | |
a template can be rendered. | |
If no prerender is found on the template package, the next package up the | |
heirarchy is checked, and so on. If a template package implements a | |
prerender and wishes to continue using its parent's functionality, it should | |
do from . import prerender and call it manually.""" | |
while template_package is not None: | |
if hasattr(template_package, 'prerender'): | |
return template_package.prerender(**kwargs) | |
else: | |
template_package = parent_package(template_package.__name__) | |
return {} | |
def choose_template(self, template_package, **kwargs): | |
"""Pick a template to render for template_package based on kwargs. | |
Should return a name relative to the location of template_package.""" | |
raise NotImplementedError('Must subclass TemplatePackages and define a way to select templates.') | |
def valid_template(self, template_package): | |
"""Tests whether or not the given template_package is 'valid', IE, can | |
be chosen.""" | |
raise NotImplementedError('Must subclass TemplatePackages and define a way to select templates.') | |
def all_valid_templates(self): | |
for (dirpath, dirnames, filenames) in os.walk(self.template_directory): | |
if '__init__.py' in filenames: | |
package_name = dirpath.replace(self.template_directory, 'templates') | |
package_name = package_name.replace(os.path.sep, '.') | |
segments = package_name.split('.') | |
if len(segments) == 3: | |
template_package = self.template_package(segments[1], | |
segments[2]) | |
if self.valid_template(template_package): | |
yield template_package | |
def render_package(self, template_package, **kwargs): | |
"""Renders a template package, returning the result.""" | |
kwargs.update(self.execute_prerender(template_package, **kwargs)) | |
relative_template = self.choose_template(template_package, **kwargs) | |
template_path = template_package.__name__.split('.')[-2:] | |
template_path.append(relative_template) | |
return self.render(os.path.join(*template_path), | |
extra_vars=dict(p=template_package, **kwargs)) | |
def render_template(self, template, **kwargs): | |
"""Renders a template returning the result. | |
The template is specified in dot notation, as family.name.""" | |
family, name = template.split('.') | |
package = self.template_package(family, name) | |
if package is None: | |
raise TemplateNotFoundError("Could not find template %s" % template) | |
return self.render_package(package, **kwargs) | |
class DefaultTemplateChooser(TemplatePackages): | |
"""A refinement of TemplatePackages that implements a choose_template that | |
picks a single default template for each package.""" | |
def choose_template(self, template_package, **kwargs): | |
return template_package.default_template | |
def valid_template(self, template_package): | |
return hasattr(template_package, 'default_template') | |
class TemplatePackageCommand(Command): | |
"""Create a template package with all the features needed to render. | |
Example usage:: | |
your_templates% paster template_package widgets/buy_buttons | |
Creating your_templates/your_templates/templates/widgets/buy_buttons/__init__.py | |
Creating your_templates/your_templates/templates/widgets/buy_buttons/buy_buttons.mako | |
""" | |
summary = __doc__.splitlines()[0] | |
usage = '\n' + __doc__ | |
min_args = 1 | |
max_args = 1 | |
group_name = 'template_packages' | |
default_verbosity = 3 | |
parser = Command.standard_parser(simulate=True) | |
def command(self): | |
try: | |
file_op = FileOp(source_dir=('template_packages', 'paster_templates')) | |
try: | |
name, directory = file_op.parse_path_name_args(self.args[0]) | |
except: | |
raise BadCommand('No egg_info directory was found') | |
base_package = file_op.find_dir('templates', True)[0] | |
dest = os.path.join('templates', directory, name) | |
file_op.template_vars.update({'name': name,}); | |
file_op.copy_file(template='package_init.py_tmpl', | |
dest=dest, filename='__init__.py', | |
add_py=False, package=False, | |
template_renderer=paste_script_template_renderer) | |
file_op.copy_file(template='base_template.mako_tmpl', | |
dest=dest, filename='%s.mako' % (name), add_py=False, | |
template_renderer=paste_script_template_renderer) | |
except BadCommand, e: | |
raise BadCommand('An error occurred. %s' % e) | |
except: | |
msg = str(sys.exc_info()[1]) | |
raise BadCommand('An unknown error occurred. %s' % msg) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment