Skip to content

Commit

Permalink
Start the basics of the list page builder.
Browse files Browse the repository at this point in the history
  • Loading branch information
mblayman committed Jul 8, 2015
1 parent 0e3ca2f commit f74d09d
Show file tree
Hide file tree
Showing 2 changed files with 102 additions and 34 deletions.
79 changes: 60 additions & 19 deletions handroll/extensions/blog.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ def on_frontmatter_loaded(self, source_file, frontmatter):
_('Invalid blog frontmatter (expects True or False): '
'{blog_value}').format(blog_value=is_post))
# TODO: Validate that the post has the required fields.
# TODO: add dirty checking so the output won't write all the time.
if is_post:
post = BlogPost(
date=frontmatter['date'],
Expand All @@ -88,14 +89,27 @@ def on_frontmatter_loaded(self, source_file, frontmatter):
self.posts[source_file] = post

def on_post_composition(self, director):
"""Generate blog output."""
blog_posts = sorted(self.posts.values(), key=lambda p: p.date)
self._generate_atom_feed(director, blog_posts)
if self.list_template is not None:
self._generate_list_page(director, blog_posts)

def _generate_atom_feed(self, director, blog_posts):
"""Generate the atom feed."""
builder = FeedBuilder(self.atom_metadata)
blog_posts = self.posts.values()
for post in sorted(blog_posts, key=lambda p: p.date, reverse=True):
builder.add(post)
# The feed expects oldest entries first.
builder.add(reversed(blog_posts))
output_file = os.path.join(director.outdir, self.atom_output)
builder.write_to(output_file)

def _generate_list_page(self, director, blog_posts):
"""Generate the list page."""
builder = ListPageBuilder()
builder.add(blog_posts)
output_file = os.path.join(director.outdir, self.list_output)
builder.write_to(output_file)

def _add_atom_metadata(self, name, option):
"""Add atom metadata from the config parser."""
self.atom_metadata[name] = self._get_option(option)
Expand All @@ -110,26 +124,53 @@ def _get_option(self, option):
option=option))


class FeedBuilder(object):
class BlogBuilder(object):
"""A template pattern class for generating output related to a blog."""

def _generate_output(self):
"""Generate output that belongs in the destination file.
Subclasses must implement this method.
"""
raise NotImplementedError()

def write_to(self, filepath):
"""Write the output to the provided filepath."""
output = self._generate_output()
with open(filepath, 'wb') as out:
out.write(output.encode('utf-8'))
out.write(b'<!-- handrolled for excellence -->\n')


class FeedBuilder(BlogBuilder):
"""Transform blog metadata and posts into an Atom feed."""

def __init__(self, metadata):
self.metadata = metadata
self._feed = AtomFeed(**metadata)

def add(self, post):
"""Add a blog post to the feed."""
entry = FeedEntry(
summary=post.summary,
title=post.title,
title_type='html',
url=post.url,
updated=post.date,
)
self._feed.add(entry)
def add(self, posts):
"""Add blog posts to the feed."""
for post in posts:
self._feed.add(FeedEntry(
summary=post.summary,
title=post.title,
title_type='html',
url=post.url,
updated=post.date,
))

def write_to(self, filepath):
"""Write the feed to the provided filepath."""
with open(filepath, 'wb') as out:
out.write(self._feed.to_string().encode('utf-8'))
out.write(b'<!-- handrolled for excellence -->\n')
def _generate_output(self):
return self._feed.to_string()


class ListPageBuilder(BlogBuilder):
"""Transform blog posts into a list page."""

def add(self, posts):
"""Add the posts and generate a blog list."""
# TODO: Generate the HTML.

def _generate_output(self):
# TODO: Fetch the template and render it.
return ''
57 changes: 42 additions & 15 deletions handroll/tests/test_extensions.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
from handroll.configuration import Configuration
from handroll.exceptions import AbortError
from handroll.extensions.base import Extension
from handroll.extensions.blog import BlogExtension, FeedBuilder
from handroll.extensions.blog import (
BlogExtension, BlogBuilder, FeedBuilder, ListPageBuilder)
from handroll.extensions.loader import ExtensionLoader
from handroll.resolver import FileResolver
from handroll.tests import TestCase
Expand Down Expand Up @@ -114,11 +115,11 @@ def _add_blog_section(self, parser, exclude=None):
continue
parser.set('blog', option, value)

def _make_preprocessed_one(self, director=None):
def _make_preprocessed_one(self, director=None, exclude=None):
"""Make an instance that has all default metadata already parsed."""
if director is None:
director = self.factory.make_director()
self._add_blog_section(director.config.parser)
self._add_blog_section(director.config.parser, exclude=exclude)
extension = BlogExtension(director.config)
extension.on_pre_composition(director)
return extension
Expand Down Expand Up @@ -271,7 +272,8 @@ def test_adds_post(self, builder_add):
post = self.factory.make_blog_post()
extension.posts[post.source_file] = post
extension.on_post_composition(director)
builder_add.assert_called_once_with(post)
received_post = next(builder_add.call_args[0][0])
self.assertEqual(post, received_post)

def test_date_in_post(self):
extension = self._make_preprocessed_one()
Expand All @@ -295,19 +297,15 @@ def test_posts_added_to_builder_by_date(self, builder_add):
older = self.factory.make_blog_post()
older.source_file = 'older.md'
older.date = older.date - datetime.timedelta(days=-1)
oldest = self.factory.make_blog_post()
oldest.source_file = 'oldest.md'
oldest.date = oldest.date - datetime.timedelta(days=-2)
extension = self._make_preprocessed_one()
extension.posts['current.md'] = current
extension.posts['older.md'] = older
extension.posts['oldest.md'] = oldest
director = self.factory.make_director()
os.mkdir(director.outdir)
extension.on_post_composition(director)
self.assertEqual(oldest, builder_add.call_args_list[0][0][0])
self.assertEqual(older, builder_add.call_args_list[1][0][0])
self.assertEqual(current, builder_add.call_args_list[2][0][0])
posts = builder_add.call_args[0][0]
self.assertEqual(older, next(posts))
self.assertEqual(current, next(posts))

def test_list_template_not_required(self):
director = self.factory.make_director()
Expand Down Expand Up @@ -340,6 +338,35 @@ def test_list_output_required_with_list_template(self):
except AbortError as ae:
self.assertTrue('list_output' in str(ae))

@mock.patch.object(ListPageBuilder, 'write_to')
def test_builds_list_page(self, write_to):
director = self.factory.make_director()
os.mkdir(director.outdir)
extension = self._make_preprocessed_one(director=director)
extension.on_post_composition(director)
expected_output = os.path.join(director.outdir, 'archive.html')
write_to.assert_called_once_with(expected_output)

@mock.patch.object(ListPageBuilder, 'write_to')
def test_skip_list_page_building_when_no_template_exists(self, write_to):
director = self.factory.make_director()
os.mkdir(director.outdir)
extension = self._make_preprocessed_one(
director=director, exclude='list_template')
extension.on_post_composition(director)
self.assertFalse(write_to.called)


class TestBlogBuilder(TestCase):

def test_generate_output_not_implemented(self):
builder = BlogBuilder()
try:
builder.write_to('doesnotmatter.html')
self.fail()
except NotImplementedError:
pass


class TestFeedBuilder(TestCase):

Expand All @@ -357,19 +384,19 @@ def test_has_metadata(self):
def test_post_creates_feed_entry(self):
builder = self._make_one()
post = self.factory.make_blog_post()
builder.add(post)
builder.add([post])
self.assertEqual(1, len(builder._feed.entries))

def test_feed_entry_has_post_url(self):
builder = self._make_one()
post = self.factory.make_blog_post()
builder.add(post)
builder.add([post])
self.assertEqual(post.url, builder._feed.entries[0].url)

def test_feed_entry_has_summary(self):
builder = self._make_one()
post = self.factory.make_blog_post()
builder.add(post)
builder.add([post])
self.assertEqual(post.summary, builder._feed.entries[0].summary)

def test_title_type_is_html(self):
Expand All @@ -380,5 +407,5 @@ def test_title_type_is_html(self):
"""
builder = self._make_one()
post = self.factory.make_blog_post()
builder.add(post)
builder.add([post])
self.assertEqual('html', builder._feed.entries[0].title_type)

0 comments on commit f74d09d

Please sign in to comment.