‘Iacta alea est’
I wrote about building a static site generator, Sew, with Mote as a templating library. Now that I have reached a templating API with Hypertext DSL that I am happy with I’d like to have a go at replacing Mote with Hypertext in Sew.
Sew is itself a single 97 SLOC file. It contains a
Context class which handles the rendering of templates with layouts and partials using Mote. I have reduced the snippet below to just the rendering aspects. (
site) is an
OpenStruct object containing an array of pages and an array of data files.)
class Context … def render(page) @site.page = page @site.content = mote(page.body) partial("_layout") end def mote(content) Mote.parse(content, self, site.each_pair.map(&:first))[site] end def partial(template) localized = sprintf("%s.%s.mote", template, site.page.locale) if File.exist?(localized) mote(File.read(localized)) else mote(File.read(sprintf("%s.mote", template))) end end end
As a short refresher, the
Context takes the site struct on initialization and then each page is rendered one by one with this site object as the params. The site object is modified slightly for each page with
@site.page (the current page details) and
@site.content (the rendered template from that page for inserting into the layout).
This is how the context is used as part of the
context = Context.new(site) … site.pages.each do |page| File.open(page.destination, "w") do |file| file.write context.render(page) end end
So basically one context object is used and each page uses the same object but with a different page. With Hypertext we’ll need one object per page so we can rejig things around a little:
- context = Context.new(site) … + site.layout = File.read("_layout.ht") site.pages.each do |page| + site.page = page File.open(page.destination, "w") do |file| - file.write context.render(page) + file.write ??? end end
We set the layout globally, assuming one layout for the whole site. We know that with Hypertext we won’t need a double pass, so we can remove the assignment of
site.content and rely on
site.page. We assign
site.page to the current page (defined by the loop).
And now we need to dovetail our Hypertext DSL Template class into our rendering flow. For now, we can ditch the
Sew::Context and replace it with
We know we’re going to need the fragment rendering versus the file rendering, since we have already loaded the file contents into memory.
site is an OpenStruct we’ll need to use
OpenStruct#each_pair instead of
Hash#each to do the variable assignment.
(As an aside, this is a perfect example of knowing your tools well and knowing the problem space well since you can match the two easily. The alternative is to write a hodge-podge of decision logic to cater for all cases.)
class Sew … class Template < Hypertext::DSL def initialize(site) site.each_pair do |key, val| instance_variable_set(sprintf("@%s", key), val) end @ht = Hypertext.new render @layout end def render(string) instance_eval string end end end
I’ve opted for
render rather than
partial here because I think it reads better in this context.
If we want partials then we’ll have to read from file explicitly (which we’re not doing with layout and template) so I’ll introduce the
partial method to do just that
def partial(filename) render File.read(filename) end
As you may have noticed I’m still not enamoured by Frankenstein’s method which switches on (non-)existence of a file.
And for those familiar with Sew I’m leaving localisation out for now to keep things simple.
If we now return to the
Sew::build command then we can insert our
Template class for rendering pages, replacing our
site.layout = File.read("_layout.ht") site.pages.each do |page| site.page = page File.open(page.destination, "w") do |file| file.write Template.new(site).to_s end end
Given a layout and template and a partial in Hypertext DSL (and Sew) form:
# _layout.ht html lang: "en-GB" do head do title do text @page.title end end body do render @page.body end partial "_footer.ht" end # index.ht --- title: Sew Me --- h1 do text "Welcome" end p do text "A paragraph sewn together." end # _footer.ht footer do text "You put your foot-er in it." end
$ sew build, lo and behold, produces the following output:
<html lang="en-GB"> <head> <title> Sew Me </title> </head> <body> <h1> Welcome </h1> <p> A paragraph sewn together. </p> </body> <footer> You put your foot-er in it. </footer> </html>
Wonderful. A full gist shows all of the code together. Sew is down to 72 SLOC. I can live with that.
—Thursday 8th April 2021.