Collecting my attempts to improve at tech, art, and life

PageTemplate for Site Generation Part 2

Tags: pagetemplate ruby blogspot

I’ve got my Ruby 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.


Added to vault 2024-01-15. Updated on 2024-01-26