Skip to content

Commit

Permalink
Add a Jinja 2 composer for templates.
Browse files Browse the repository at this point in the history
  • Loading branch information
mblayman committed Dec 26, 2016
1 parent a6934e2 commit 3872a9d
Show file tree
Hide file tree
Showing 5 changed files with 137 additions and 0 deletions.
2 changes: 2 additions & 0 deletions docs/composers.rst
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ Built-in composers

.. autoclass:: handroll.composers.atom.AtomComposer

.. autoclass:: handroll.composers.j2.Jinja2Composer

.. autoclass:: handroll.composers.CopyComposer

.. autoclass:: handroll.composers.md.MarkdownComposer
Expand Down
2 changes: 2 additions & 0 deletions docs/releases.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ Releases
Version 3.1, In Development
---------------------------

* Processs Jinja 2 templates for any file with a ``.j2`` extension
with the built-in ``Jinja2Composer``.
* Add ``SitemapExtension`` to generate sitemaps.
* Move version information into the ``handroll`` package
so it is available at runtime.
Expand Down
60 changes: 60 additions & 0 deletions handroll/composers/j2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# Copyright (c) 2016, Matt Layman

import os

import jinja2

from handroll import logger
from handroll.composers import Composer
from handroll.composers.mixins import FrontmatterComposerMixin
from handroll.i18n import _


class Jinja2Composer(FrontmatterComposerMixin, Composer):
"""Compose any content from a Jinja 2 template files (``.j2``).
The ``Jinja2Composer`` takes a template file and processes it
through the Jinja 2 renderer. The site configuration is provided
to the context for access to global data.
The output file uses the same name as the source file with
the ``.j2`` extension removed.
"""

def compose(self, catalog, source_file, out_dir):
filename = os.path.basename(source_file.rstrip('.j2'))
output_file = os.path.join(out_dir, filename)
if self._needs_update(source_file, output_file):
logger.info(_('Generating from template {source_file} ...').format(
source_file=source_file))
data, source = self.get_data(source_file)
data['config'] = self._config
template = jinja2.Template(source)
with open(output_file, 'wb') as out:
out.write(template.render(data).encode('utf-8'))
else:
logger.debug(_('Skipping {filename} ... It is up to date.').format(
filename=filename))

def _needs_update(self, source_file, output_file):
"""Check if the output file needs to be updated.
Look at the modified times of the source file and output file.
"""
if self._config.force:
return True

if not os.path.exists(output_file):
return True

return os.path.getmtime(source_file) > os.path.getmtime(output_file)

def get_output_extension(self, filename):
"""Get the output extension.
The composer treats the last found extension before ``.j2``
as the file's output extension (e.g., ``source.txt.j2``
results in ``source.txt``).
"""
root, ext = os.path.splitext(filename.rstrip('.j2'))
return ext
72 changes: 72 additions & 0 deletions handroll/tests/test_composers.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from handroll.composers.mixins import FrontmatterComposerMixin
from handroll.composers.atom import AtomComposer
from handroll.composers.generic import GenericHTMLComposer
from handroll.composers.j2 import Jinja2Composer
from handroll.composers.md import MarkdownComposer
from handroll.composers.rst import ReStructuredTextComposer
from handroll.composers.sass import SassComposer
Expand Down Expand Up @@ -444,3 +445,74 @@ def test_malformed_document_with_frontmatter(self):
f.write(source.encode('utf-8'))
mixin = FrontmatterComposerMixin()
self.assertRaises(AbortError, mixin.get_data, f.name)


class TestJinja2Composer(TestCase):

def _make_one(self):
config = self.factory.make_configuration()
config.outdir = tempfile.mkdtemp()
return Jinja2Composer(config)

def test_get_output_extension(self):
composer = self._make_one()
extension = composer.get_output_extension('source.xyz.j2')
self.assertEqual('.xyz', extension)

def test_composes(self):
source = inspect.cleandoc("""%YAML 1.1
---
title: A Fake Title
---
title: {{ title }}
domain: {{ config.domain }}
""")
with tempfile.NamedTemporaryFile(delete=False, suffix='.txt.j2') as f:
f.write(source.encode('utf-8'))
composer = self._make_one()
output_file = os.path.join(
composer._config.outdir, os.path.basename(f.name.rstrip('.j2')))
composer.compose(None, f.name, composer._config.outdir)
content = open(output_file, 'r').read()
self.assertEqual(
'title: A Fake Title\ndomain: http://www.example.com',
content)

def test_needs_update(self):
site = tempfile.mkdtemp()
output_file = os.path.join(site, 'output.md')
open(output_file, 'w').close()
future = os.path.getmtime(output_file) + 1
source_file = os.path.join(site, 'test.md')
open(source_file, 'w').close()
os.utime(source_file, (future, future))

composer = self._make_one()
self.assertTrue(composer._needs_update(source_file, output_file))

past = future - 10
os.utime(source_file, (past, past))
self.assertFalse(composer._needs_update(source_file, output_file))

def test_forces_update(self):
site = tempfile.mkdtemp()
output_file = os.path.join(site, 'output.md')
open(output_file, 'w').close()
past = os.path.getmtime(output_file) - 10
source_file = os.path.join(site, 'test.md')
open(source_file, 'w').close()
os.utime(source_file, (past, past))
composer = self._make_one()
composer._config.force = True
self.assertTrue(composer._needs_update(source_file, output_file))

@mock.patch('handroll.composers.j2.jinja2.Template.render')
def test_skips_up_to_date(self, render):
site = tempfile.mkdtemp()
source_file = os.path.join(site, 'source.txt.j2')
open(source_file, 'w').close()
output_file = os.path.join(site, 'source.txt')
open(output_file, 'w').close()
composer = self._make_one()
composer.compose(None, source_file, site)
self.assertFalse(render.called)
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ def run(self):
'console_scripts': ['handroll = handroll.entry:main'],
'handroll.composers': [
'.atom = handroll.composers.atom:AtomComposer',
'.j2 = handroll.composers.j2:Jinja2Composer',
'.md = handroll.composers.md:MarkdownComposer',
'.rst = handroll.composers.rst:ReStructuredTextComposer',
'.sass = handroll.composers.sass:SassComposer',
Expand Down

0 comments on commit 3872a9d

Please sign in to comment.