Skip to content

Commit

Permalink
First pass at implementing the rendering functions. No tests as of ye…
Browse files Browse the repository at this point in the history
…t, would like to try to get nikola rendering a theme so we can verify what it actually provides.
  • Loading branch information
jamadden committed Oct 2, 2017
1 parent eaaa3c5 commit 4638169
Show file tree
Hide file tree
Showing 7 changed files with 185 additions and 4 deletions.
4 changes: 4 additions & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,13 @@ include tox.ini
include .travis.yml
include *.txt
include .isort.cfg


exclude .nti_cover_package

recursive-include docs *.py
recursive-include docs *.rst
recursive-include docs Makefile

recursive-include src *.zcml
recursive-include src *.plugin
18 changes: 17 additions & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,23 @@
:target: https://ntinikola_chameleon.readthedocs.io/en/latest/
:alt: Documentation Status
A template system for the `nikola
A template system for the `Nikola
<https://pypi.python.org/pypi/Nikola>`_ static blog system using
`Chameleon <https://pypi.python.org/pypi/Chameleon>`_ and `z3c.pt
<https://pypi.python.org/pypi/z3c.pt>`_.

Nikola uses a `custom mechanism <https://pypi.python.org/pypi/yapsy>`_
to find plugins instead of using the usual ``pkg_resources`` system.
That makes it difficult to install plugins; it's not enough just to
``pip install`` a package from PyPI. Instead, you must *also* copy a
``.plugin`` file to a particular location on disk. This can be:

- ~/.nikola/plugins/
- The ``plugins`` directory of your Nikola site.

..
Rendering the plugin file must be last, it is done by
setup.py (since PyPI does not support the ..include directive.)
The contents of a correct ``nti.nikola_chameleon.plugin`` file are
delivered with this package. They are also rendered here::
19 changes: 16 additions & 3 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@
]


def _read(fname):
with codecs.open(fname, encoding='utf-8') as f:
def _read(*fname):
import os.path
with codecs.open(os.path.join(*fname), encoding='utf-8') as f:
return f.read()


Expand All @@ -23,7 +24,13 @@ def _read(fname):
author='Jason Madden',
author_email='jason@nextthought.com',
description="NTI Nikola Chameleon",
long_description=(_read('README.rst') + '\n\n' + _read('CHANGES.rst')),
long_description=(
_read('README.rst')
+ '\n\n'
+ '\n'.join(' ' + line for line in
_read('src/nti/nikola_chameleon/nti.nikola_chameleon.plugin').splitlines())
+ '\n\n'
+ _read('CHANGES.rst')),
license='Apache',
keywords='nikola chameleon template',
classifiers=[
Expand All @@ -49,6 +56,7 @@ def _read(fname):
'setuptools',
'Chameleon',
'z3c.pt',
'zope.dottedname',
'nikola >= 7.8',
],
extras_require={
Expand All @@ -59,5 +67,10 @@ def _read(fname):
'sphinx_rtd_theme',
],
},
# See the thread at https://github.com/pypa/pip/issues/2874#issuecomment-109429489
# for why we don't try do use data_files.
#data_files=[
# ('nikola', ['nti.nikola_chameleon.plugin',]),
#],
entry_points=entry_points,
)
6 changes: 6 additions & 0 deletions src/nti/nikola_chameleon/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
"""
The nti.nikola_chameleon Nikola plugin.
"""

from .plugin import ChameleonTemplates
ChameleonTemplates = ChameleonTemplates # Must export from the top-level namespace.
11 changes: 11 additions & 0 deletions src/nti/nikola_chameleon/configure.zcml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<!-- -*- mode: nxml -*- -->
<configure xmlns="http://namespaces.zope.org/zope"
xmlns:i18n="http://namespaces.zope.org/i18n"
xmlns:zcml="http://namespaces.zope.org/zcml">

<include package="zope.component" file="meta.zcml" />
<include package="zope.component" />

<include package="z3c.pt" />

</configure>
13 changes: 13 additions & 0 deletions src/nti/nikola_chameleon/nti.nikola_chameleon.plugin
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# -*- mode: conf; -*-
[Core]
Name = nti.nikola_chameleon
Module = nti.nikola_chameleon

[Documentation]
Author = NextThought
Version = 1.0
Website = https://github.com/NextThought/nti.nikola_chameleon
Description = Support for Chameleon ZPT templates.

[Nikola]
PluginCategory = Template
118 changes: 118 additions & 0 deletions src/nti/nikola_chameleon/plugin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
# -*- coding: utf-8 -*-
"""
The Chameleon ZPT plugin for nikola.
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

# stdlib imports
import os
import os.path

from chameleon.zpt.template import PageTemplateFile
from nikola.plugin_categories import TemplateSystem
from nikola.utils import makedirs
import z3c.pt.pagetemplate
from zope.dottedname import resolve as dottedname

logger = __import__('logging').getLogger(__name__)

class _PageTemplateFileWithLoad(z3c.pt.pagetemplate.PageTemplateFile):
"""
Enables the load: expression type for convenience.
"""
# NOTE: We cannot do the rational thing and copy this
# and modify our local value. This is because
# certain packages, notably z3c.macro,
# modify the superclass's value; depending on the order
# of import, we may or may not get that change.
# So we do the bad thing too and modify the superclass also

@property
def builtins(self):
d = super(_PageTemplateFileWithLoad, self).builtins
d['__loader'] = self._loader
return d

z3c.pt.pagetemplate.BaseTemplate.expression_types['load'] = PageTemplateFile.expression_types['load']


class ChameleonTemplates(TemplateSystem):

name = 'nti.nikola_chameleon'


def set_directories(self, directories, cache_folder):
"""Sets the list of folders where templates are located and cache."""
# A list of directories where the templates will be
# located. Most template systems have some sort of
# template loading tool that can use this.

# Does nothing, chameleon doesn't have the direct concept of a path,
# only relative to the file with the load: expression.

# We do set up the cache, though.

cache_dir = None
if not 'CHAMELEON_CACHE' in os.environ or \
not os.path.isdir(os.path.expanduser(os.environ['CHAMELEON_CACHE'])):
os.environ['CHAMELEON_CACHE'] = cache_dir = os.path.join(cache_folder, 'chameleon_cache')
else:
cache_dir = os.environ['CHAMELEON_CACHE']

logger.debug("Configuring chamelean to cache at %s", cache_dir)
conf_mod = dottedname.resolve('chameleon.config')
if conf_mod.CACHE_DIRECTORY != cache_dir: # previously imported before we set the environment
conf_mod.CACHE_DIRECTORY = cache_dir
# Which, snarf, means the template is probably also screwed up.
# It imports all of this stuff statically, and BaseTemplate
# statically creates a default loader at import time
template_mod = dottedname.resolve('chameleon.template')
if template_mod.CACHE_DIRECTORY != conf_mod.CACHE_DIRECTORY:
template_mod.CACHE_DIRECTORY = conf_mod.CACHE_DIRECTORY
template_mod.BaseTemplate.loader = template_mod._make_module_loader()

# Creating these guys with debug or autoreload, as Pyramid does when its
# debug flags are set, will override this setting
return

def template_deps(self, template_name):
"""Returns filenames which are dependencies for a template."""
# You *must* implement this, even if to return []
# It should return a list of all the files that,
# when changed, may affect the template's output.
# usually this involves template inheritance and
# inclusion.
#
# XXX: I don't know how to implement this for Chameleon,
# especially not if we enable macros.
return []

def render_template(self, template_name, output_name, context):
"""Renders template to a file using context.
This must save the data to output_name *and* return it
so that the caller may do additional processing.
"""
result = self.render_template_to_string(template_name)
if output_name is not None:
makedirs(os.path.dirname(output_name))
with open(output_name, 'wb') as f:
f.write(result.encode('utf-8'))
return result

def render_template_to_string(self, template, context):
"""Renders template to a string using context. """
# The method that does the actual rendering.
# template_name is the name of the template file,
# context is a dictionary containing the data the template
# uses for rendering.
template = _PageTemplateFileWithLoad(template)
return template(**context)

def inject_directory(self, directory):
"""Injects the directory with the lowest priority in the
template search mechanism."""
pass

0 comments on commit 4638169

Please sign in to comment.