diff --git a/docs/settings.rst b/docs/settings.rst index 79878755..6bcc7292 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -92,6 +92,8 @@ Setting name (default value) What doe will not be generated with properly-formed URLs. You should include ``http://`` and your domain, with no trailing slash at the end. Example: ``SITEURL = 'http://mydomain.com'`` +`TEMPLATE_PAGES` (``None``) A mapping containing template pages that will be rendered with + the blog entries. See :ref:`template_pages`. `STATIC_PATHS` (``['images']``) The static paths you want to have accessible on the output path "static". By default, Pelican will copy the 'images' folder to the @@ -266,6 +268,23 @@ can get a list of available locales via the ``locale -a`` command; see manpage .. _locale(1): http://linux.die.net/man/1/locale + +.. _template_pages: + +Template pages +============== + +If you want to generate custom pages besides your blog entries, you can point +any Jinja2 template file with a path pointing to the file and the destination +path for the generated file. + +For instance, if you have a blog with three static pages, for a list of books, +your resume and a contact page, you could have:: + + TEMPLATE_PAGES = {'src/books.html': 'dest/books.html', + 'src/resume.html': 'dest/resume.html', + 'src/contact.html': 'dest/contact.html'} + Feed settings ============= diff --git a/pelican/__init__.py b/pelican/__init__.py index 9809b19b..804fe5b4 100644 --- a/pelican/__init__.py +++ b/pelican/__init__.py @@ -9,7 +9,8 @@ from pelican import signals from pelican.generators import (ArticlesGenerator, PagesGenerator, StaticGenerator, PdfGenerator, - LessCSSGenerator, SourceFileGenerator) + LessCSSGenerator, SourceFileGenerator, + TemplatePagesGenerator) from pelican.log import init from pelican.settings import read_settings from pelican.utils import (clean_output_dir, files_changed, file_changed, @@ -171,6 +172,9 @@ class Pelican(object): def get_generator_classes(self): generators = [StaticGenerator, ArticlesGenerator, PagesGenerator] + + if self.settings['TEMPLATE_PAGES']: + generators.append(TemplatePagesGenerator) if self.settings['PDF_GENERATOR']: generators.append(PdfGenerator) if self.settings['LESS_GENERATOR']: # can be True or PATH to lessc diff --git a/pelican/generators.py b/pelican/generators.py index c38409a1..f056484a 100644 --- a/pelican/generators.py +++ b/pelican/generators.py @@ -12,8 +12,8 @@ from functools import partial from itertools import chain from operator import attrgetter, itemgetter -from jinja2 import Environment, FileSystemLoader, PrefixLoader, ChoiceLoader -from jinja2.exceptions import TemplateNotFound +from jinja2 import (Environment, FileSystemLoader, PrefixLoader, ChoiceLoader, + BaseLoader, TemplateNotFound) from pelican.contents import Article, Page, Category, is_valid_content from pelican.readers import read_file @@ -110,6 +110,35 @@ class Generator(object): self.context[item] = value +class _FileLoader(BaseLoader): + + def __init__(self, path, basedir): + self.path = path + self.fullpath = os.path.join(basedir, path) + + def get_source(self, environment, template): + if template != self.path or not os.path.exists(self.fullpath): + raise TemplateNotFound(template) + mtime = os.path.getmtime(self.fullpath) + with file(self.fullpath) as f: + source = f.read().decode('utf-8') + return source, self.fullpath, \ + lambda: mtime == os.path.getmtime(self.fullpath) + + +class TemplatePagesGenerator(Generator): + + def generate_output(self, writer): + for source, dest in self.settings['TEMPLATE_PAGES'].items(): + self.env.loader.loaders.insert(0, _FileLoader(source, self.path)) + try: + template = self.env.get_template(source) + rurls = self.settings.get('RELATIVE_URLS') + writer.write_file(dest, template, self.context, rurls) + finally: + del self.env.loader.loaders[0] + + class ArticlesGenerator(Generator): """Generate blog articles""" diff --git a/pelican/settings.py b/pelican/settings.py index 610d0733..35752fee 100644 --- a/pelican/settings.py +++ b/pelican/settings.py @@ -80,6 +80,7 @@ _DEFAULT_CONFIG = {'PATH': '.', 'WEBASSETS': False, 'PLUGINS': [], 'MARKDOWN_EXTENSIONS': ['toc', ], + 'TEMPLATE_PAGES': {} } diff --git a/tests/test_generators.py b/tests/test_generators.py index 8caa7213..accdb699 100644 --- a/tests/test_generators.py +++ b/tests/test_generators.py @@ -6,7 +6,9 @@ import re from tempfile import mkdtemp from shutil import rmtree -from pelican.generators import ArticlesGenerator, LessCSSGenerator, PagesGenerator +from pelican.generators import ArticlesGenerator, LessCSSGenerator, \ + PagesGenerator, TemplatePagesGenerator +from pelican.writers import Writer from pelican.settings import _DEFAULT_CONFIG from .support import unittest, skipIfNoExecutable @@ -194,6 +196,50 @@ class TestPageGenerator(unittest.TestCase): self.assertItemsEqual(hidden_pages_expected,hidden_pages) +class TestTemplatePagesGenerator(unittest.TestCase): + + TEMPLATE_CONTENT = "foo: {{ foo }}" + + def setUp(self): + self.temp_content = mkdtemp() + self.temp_output = mkdtemp() + + def tearDown(self): + rmtree(self.temp_content) + rmtree(self.temp_output) + + def test_generate_output(self): + + settings = _DEFAULT_CONFIG.copy() + settings['STATIC_PATHS'] = ['static'] + settings['TEMPLATE_PAGES'] = { + 'template/source.html': 'generated/file.html' + } + + generator = TemplatePagesGenerator({'foo': 'bar'}, settings, + self.temp_content, '', self.temp_output, None) + + # create a dummy template file + template_dir = os.path.join(self.temp_content, 'template') + template_filename = os.path.join(template_dir, 'source.html') + os.makedirs(template_dir) + with open(template_filename, 'w') as template_file: + template_file.write(self.TEMPLATE_CONTENT) + + writer = Writer(self.temp_output, settings=settings) + generator.generate_output(writer) + + output_filename = os.path.join( + self.temp_output, 'generated', 'file.html') + + # output file has been generated + self.assertTrue(os.path.exists(output_filename)) + + # output content is correct + with open(output_filename, 'r') as output_file: + self.assertEquals(output_file.read(), 'foo: bar') + + class TestLessCSSGenerator(unittest.TestCase): LESS_CONTENT = """