PageTemplate for Site Generation Part 2
I’ve got my code filtering Markdown and now I want to stuff that filtered content into an HTML page. I could just use
maruku#to_html_document
, but I need the ability to add details like a title and site-related links.
I could use a format similar to my Python blog files. I won’t really need PageTemplate if I do that, though. Not for the content file, anyways. That’s okay, though. The Maruku filter was more of a proof-of-concept, anyways. PageTemplate will be useful for fitting the generated content into an actual template, though.
That means I’m starting over on my content files.
Given a content file that looks like this:
title: A Simple Page
--
This page intentionally left blank.
I want an object that makes the title available in some way (simple Hash style access is fine), and makes the HTML-formatted content available. After a few minutes of fiddling and poking around, I end up with tests and application code.
Article Test Code
#!/usr/local/bin/ruby
require 'test/unit'
require 'SiteTemplate'
class TC_Article < Test::Unit::TestCase
def test_article_file()
article_file = 'simple.txt'
assert(article = Article.new(article_file))
assert_equal(article_file, article.source_file)
assert_equal('A Simple Page', article.metadata['title'])
assert(article.content =~ %r{<p>This page intentionally left blank.</p>})
end
end
The Application Code
#!/usr/local/bin/ruby
require 'rubygems'
require 'maruku'
# A single HTML page generated by a content file
#
# Content files usually look like this:
#
# title: My Title
# --
# Article contents
class Article
attr_reader :source_file, :metadata, :content
def initialize(filename)
@metadata = {}
parse!(filename)
end
def parse!(filename)
@source_file = filename
content = ''
in_content = false
File.open(filename).each_line do |line|
if in_content then
content << line
else
if line =~ /^--$/ then
in_content = true
next
end
if line =~ /^(\w+?):\s*(.+)$/ then
key = $1
value = $2
@metadata[key] = value
end
end
end
@content = Maruku.new(content).to_html
end
end
It’s a really simple, slow parser, but it works. I won’t try to optimize it before I’ve actually figured out what it’s supposed to be doing.
The Template
The next target is stuffing this content into a template. That’s easy. Here’s the template:
simple.html Template File
<html>
<head>
<title>[%var title%]</title>
</head>
<body>
<h1>[%var title%]</h1>
[%var content%]
</body>
</html>
I could assemble my page manually if I felt like it. As a matter of fact, let’s do that in one of the tests.
Manual Page Generation Test
require 'PageTemplate'
class TC_HTML_Page < Test::Unit::TestCase
def test_manual_page_generation()
article_file = 'simple.txt'
template = PageTemplate.new()
template.load('simple.html')
article = Article.new(article_file)
template['title'] = article.metadata['title']
template['content'] = article.content
assert_match(%r{<title>A Simple Page</title>}, template.output)
end
end
Do I really want to manually apply even that little bit of code, though? No, I don’t.
Automatic Page Generation Test
class TC_HTML_Page < Test::Unit::TestCase
def test_standard_page_generation
article_file = 'simple.txt'
template_file = 'simple.html'
assert(html_page = HTML_Page.new(:article => article_file, :template => template_file))
assert_match(%r{<title>A Simple Page</title>}, html_page.to_html)
assert_match(%r{<p>This page}, html_page.to_html)
end
end
Automatic Page Generation Code
require 'PageTemplate'
class HTML_Page
def initialize(opts = {})
@article = Article.new(opts[:article])
@template = PageTemplate.new()
@template.load(opts[:template])
end
def to_html()
@template['title'] = @article.metadata['title']
@template['content'] = @article.content
return @template.output
end
end
Saving a File
Okay, now I have article files with content and metadata being consumed, formatted, and handed off to PageTemplate for wrapping into a pretty HTML page. The only thing remaining at this stage is to actually write the file.
Test Writes
require 'fileutils'
class TC_HTML_Page < Test::Unit::TestCase
def test_standard_page_generation
article_file = 'simple.txt'
template_file = 'simple.html'
output_file = 'test/out.simple.html'
FileUtils::rm_rf(output_file)
assert(html_page = HTML_Page.new(
:article => article_file,
:template => template_file,
:output_file => output_file))
assert_match(%r{<title>A Simple Page</title>}, html_page.to_html)
assert_match(%r{<p>This page}, html_page.to_html)
html_page.write_to_file
assert(saved_html = File.open(output_file).read())
assert_match(%r{<title>A Simple Page</title>}, saved_html)
assert_match(%r{<p>This page}, saved_html)
FileUtils::rm_rf(output_file)
end
end
Code to Make the Writes Happen
Oh heck, just take the whole thing. This is what my SiteTemplate.rb
file looks like right now.
#!/usr/local/bin/ruby
require 'rubygems'
require 'fileutils'
require 'maruku'
require 'PageTemplate'
# A single HTML page generated by a content file
#
# Content files usually look like this:
#
# title: My Title
# --
# Article contents
class Article
attr_reader :source_file, :metadata, :content
def initialize(filename)
@metadata = {}
parse!(filename)
end
def parse!(filename)
@source_file = filename
content = ''
in_content = false
File.open(filename).each_line do |line|
if in_content then
content << line
else
if line =~ /^--$/ then
in_content = true
next
end
if line =~ /^(\w+?):\s*(.+)$/ then
key = $1
value = $2
@metadata[key] = value
end
end
end
@content = Maruku.new(content).to_html
end
end
# Takes an Article and a PageTemplate and mushes them together
# Note: This version still assumes that needed metadata will
# be available. This is not a safe assumption.
class HTML_Page
def initialize(opts = {})
@article = Article.new(opts[:article])
@template = PageTemplate.new()
@template.load(opts[:template])
@output_file = opts[:output_file]
end
def to_html()
@template['title'] = @article.metadata['title']
@template['content'] = @article.content
return @template.output
end
def write_to_file()
FileUtils::mkdir_p(File.dirname(@output_file))
File.open(@output_file, 'w') { |f| f.print to_html }
end
end
Wrapup
This stage is done. We’ve taken some article files that look a lot like my blog files and turned them into fully-fleshed HTML files. They will fit into a PageTemplate that’s been defined by the site maintainer, guaranteeing a standard look for the site.
My next post on this topic will deal with putting an HTML_Page
into the context of a larger site.