Skip to content

Commit

Permalink
Merge pull request #65 from handroll/sitemap-ext
Browse files Browse the repository at this point in the history
Add a sitemap extension.
  • Loading branch information
mblayman committed Dec 20, 2016
2 parents d6ccd17 + b3539b9 commit dde167d
Show file tree
Hide file tree
Showing 9 changed files with 161 additions and 5 deletions.
15 changes: 15 additions & 0 deletions docs/extensions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -137,3 +137,18 @@ Required fields:
Optional fields:

* ``summary`` - A summary of the post.

.. _sitemapextension:

Sitemap extension
-----------------

The sitemap extension generates a sitemap
of your site's HTML content.
The generated file will be stored
in the root
of the output directory
as ``sitemap.txt``.

Enable the sitemap extension by adding ``with_sitemap = True`` to
the ``site`` section of your configuration file.
1 change: 1 addition & 0 deletions docs/releases.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ Releases
Version 3.1, In Development
---------------------------

* Add ``SitemapExtension`` to generate sitemaps.
* Move version information into the ``handroll`` package
so it is available at runtime.
* Perform continuous integration testing on OS X.
Expand Down
11 changes: 7 additions & 4 deletions handroll/composers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,10 @@ def compose(self, catalog, source_file, out_dir):

@property
def output_extension(self):
raise AttributeError(
'The output extension of the CopyComposer is dependent '
'on the source file extension. This property should not '
'be invoked for this composer.')
"""No-op for copied files.
The output extension of the CopyComposer is dependent on the source
file extension. To avoid breaking the interface, the most meaningful
'extension' to report is nothing. But let's not propagate None.
"""
return ''
45 changes: 45 additions & 0 deletions handroll/extensions/sitemap.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Copyright (c) 2016, Matt Layman

import os

from handroll import logger
from handroll.extensions.base import Extension
from handroll.i18n import _


class SitemapExtension(Extension):
"""Generate a sitemap from the HTML pages of the site."""

handle_frontmatter_loaded = True
handle_pre_composition = True
handle_post_composition = True

def __init__(self, config):
super(SitemapExtension, self).__init__(config)
self.urls = set()
self._composers = None
self._resolver = None
# Assume that the sitemap needs to be generated at startup.
self._dirty = True

def on_pre_composition(self, director):
self._composers = director.composers
self._resolver = director.resolver

def on_frontmatter_loaded(self, source_file, frontmatter):
composer = self._composers.select_composer_for(source_file)
if composer.output_extension == '.html':
url = self._resolver.as_url(source_file)
if url not in self.urls:
self.urls.add(url)
self._dirty = True

def on_post_composition(self, director):
if not self._dirty:
return
logger.info(_('Generating sitemap ...'))
sitemap_path = os.path.join(director.outdir, 'sitemap.txt')
with open(sitemap_path, 'w') as sitemap:
for url in sorted(self.urls):
sitemap.write(url + '\n')
self._dirty = False
2 changes: 1 addition & 1 deletion handroll/tests/test_composers.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ def test_output_extension(self):
never rely on this composer's output extension.
"""
composer = self._make_one()
self.assertRaises(AttributeError, lambda: composer.output_extension)
self.assertEqual('', composer.output_extension)

@mock.patch('handroll.composers.shutil')
def test_copies_when_forced(self, shutil):
Expand Down
86 changes: 86 additions & 0 deletions handroll/tests/test_sitemap_extension.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# Copyright (c) 2016, Matt Layman

import os

from handroll import signals
from handroll.extensions.sitemap import SitemapExtension
from handroll.tests import TestCase


class TestSitemapExtention(TestCase):

def tearDown(self):
super(TestSitemapExtention, self).tearDown()
# Clean up any attached extension instance.
signals.frontmatter_loaded.receivers.clear()
signals.pre_composition.receivers.clear()
signals.post_composition.receivers.clear()

def _make_one(self, director):
director.config.parser.add_section('sitemap')
extension = SitemapExtension(director.config)
extension.on_pre_composition(director)
return extension

def test_handles_frontmatter_loaded(self):
extension = SitemapExtension(None)
self.assertTrue(extension.handle_frontmatter_loaded)

def test_handles_pre_composition(self):
extension = SitemapExtension(None)
self.assertTrue(extension.handle_pre_composition)

def test_handles_post_composition(self):
extension = SitemapExtension(None)
self.assertTrue(extension.handle_post_composition)

def test_records_html_url(self):
director = self.factory.make_director()
extension = self._make_one(director)
extension._dirty = False
path = os.path.join(director.site.path, 'path/to/sample.md')
extension.on_frontmatter_loaded(path, {})
self.assertIn(
'http://www.example.com/path/to/sample.html', extension.urls)
self.assertTrue(extension._dirty)

def test_ignores_non_html_url(self):
director = self.factory.make_director()
extension = self._make_one(director)
path = os.path.join(director.site.path, 'path/to/sample.png')
extension.on_frontmatter_loaded(path, {})
self.assertFalse(extension.urls)

def test_generates_sitemap_output(self):
director = self.factory.make_director()
os.mkdir(director.outdir)
extension = self._make_one(director)
path_a = os.path.join(director.site.path, 'path/to/a.md')
path_b = os.path.join(director.site.path, 'path/to/b.md')
extension.on_frontmatter_loaded(path_b, {})
extension.on_frontmatter_loaded(path_a, {})
extension.on_post_composition(director)
content = open(os.path.join(director.outdir, 'sitemap.txt')).read()
self.assertEqual(
'http://www.example.com/path/to/a.html\n'
'http://www.example.com/path/to/b.html\n',
content)
self.assertFalse(extension._dirty)

def test_not_dirty_when_url_already_recorded(self):
director = self.factory.make_director()
extension = self._make_one(director)
path_a = os.path.join(director.site.path, 'path/to/a.md')
extension.on_frontmatter_loaded(path_a, {})
extension._dirty = False
extension.on_frontmatter_loaded(path_a, {})
self.assertFalse(extension._dirty)

def test_no_sitemap_when_not_dirty(self):
director = self.factory.make_director()
os.mkdir(director.outdir)
extension = self._make_one(director)
extension._dirty = False
extension.on_post_composition(director)
self.assertFalse(
os.path.exists(os.path.join(director.outdir, 'sitemap.txt')))
4 changes: 4 additions & 0 deletions handroll/tests/testcase.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ def assertIn(self, a, b):
"""Backport for Python 2.6."""
self.assertTrue(a in b)

def assertNotIn(self, a, b):
"""Backport for Python 2.6."""
self.assertTrue(a not in b)

def assertIsNone(self, x):
"""Backport for Python 2.6."""
self.assertTrue(x is None)
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ def run(self):
],
'handroll.extensions': [
'blog = handroll.extensions.blog:BlogExtension',
'sitemap = handroll.extensions.sitemap:SitemapExtension',
]
},
include_package_data=True,
Expand Down
1 change: 1 addition & 0 deletions transifex.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ def get_auth_from_conf(here):

return username, password


if __name__ == '__main__':
here = os.path.abspath(os.path.dirname(__file__))
username, password = get_auth_from_conf(here)
Expand Down

0 comments on commit dde167d

Please sign in to comment.