Blogue — a dynamic static blog engine Subscribe to hakunin.com

written by Maxim Chernyak on 30 Oct, 13

Find Blogue at https://github.com/maxim/blogue.

Motivation

Ok, let’s be totally frank, I decided that I’m not a big fan of static site generators for personal blogging. Moreover, now that I’ve moved to Blogue, I feel like I should migrate our company blog from Jekyll as well. It would be easy, since Blogue can seamlessly become a part of any Rails app.

The thing is, I like to have an actual dynamic back-end behind my website. I am attracted to proper URI redirects, not having to wait for compilation, not having to always rely on 3rd parties for things like comments and search, and generally being able to program dynamic things that don’t rely on exponentially multiplying html files. Plus, it’s not like hosting is an issue, a free Heroku plan is all you need. That, and perhaps the free Memcachier addon. Let me use a fancy code block to demonstrate how this works out financially.

$0 + $0 = $0

Most of all, I like static files. I like my blog posts forever preserved in clean markdown for our descendants to discover a thousand years from now as the basis of their new religion. In fact I like them so much, I get angry when some static site generators restrict me from keeping markdown clean. They force me to insert custom HTML as soon as I want to do anything mildly interesting, even if I have an obvious convention going on.

My goal with Blogue was essentially to be able to have plain markdown posts rendered as a resource inside a Rails app, while having them parsed and preprocessed in any way I like, with the full power of Rails and rubygems ecosystem. I’d like to think that Blogue emphasizes making your own conventions when blogging, and gives you the tools to parse your personal style for the needs of HTML presentation. In essence, it solves some of the more annoying markdown restrictions that would force you to resort to writing straight HTML. So let me just demonstrate a few tricks.

How Blogue helps with blogging

By having your model inherit from Blogue::Post you immediately plunge into a colorful world of possibilities, which paints your otherwise boring, gray reality1.

app/models/post.rb

class Post < Blogue::Post
  # strawberry fields
end

With this in place you can do some interesting things with your markdown files.

Cascading post properties

Let’s say having followed the README and having setup the Rails app, you decided to create a first post at app/posts/first-post.md.

~/dev/blog » touch app/posts/first-post.md

This alone will be enough to get a minimal post with some properties already present on the @post object.

>> Post.all.first.title
=> "First post"

>> Post.all.first.time
=> 2013-10-29 03:07:57 -0400

>> Post.all.first.author_name
=> "max"

Not bad for an empty file, you get a legit title, time, and name. You probably can guess where they came from. Here are their definitions in Blogue::Post.

def title
  meta_title || parsed_title || filename_title
end

def time
  meta_time || file_ctime
end

def author_name
  meta_author_name || config_author_name
end

So it’s filename_title, file_ctime, and config_author_name (which actually comes from whoami by default) respectively. Notice that they all try looking up meta first. This refers to the very simple meta syntax I made up for placing directly into markdown posts. It looks like this.

<!--meta
  foo: bar
-->

This is similar to the front matter of jekyll posts, in case you’re familiar. You can stick this anywhere in your post, and it’ll be parsed as YAML and made available via Post#meta. So as you can guess from the code, having either of those values set in meta would override the default ones.

<!--meta
  date: "Tue, 02 Dec 2008 13:07:00 +0000"
-->

FYI: I made it use the key date because I think it sounds more natural.

Let me also quickly mention the parsed_title, because it’s kind of nice. If you start your blog post with an h1 level header, it will automatically become the title. Like this.

# Welcome to my blog

...
>> Post.all.first.title
=> "Welcome to my blog"

I added this bit of magic because it’s my personal convention, it only works with this exact title format (first line, starts with a single number sign). You can easily change this convention by overriding parsed_title method.

Preprocessing

Sep 27, 2015: Updated to reflect changes introduced in Blogue 0.2.0.

Take a look at the table of contents at the top of this post. It’s dynamically generated, thanks to Kramdown, which has the TOC feature built-in. However, it requires you to add a weird looking special syntax into your markdown file.

* some list, presumably your table of contents
{:toc}

The intention is probably to have the TOC written-out by hand in markdown, and turned dynamic in HTML. Well, I didn’t want to have a TOC in my markdown, so I made a preprocessor that injects it only when rendering html.

I started by defining a class method preprocess on the Post as follows.

app/models/post.rb

class Post < Blogue::Post
  class << self
    def preprocess(template)
      template.source.prepend("* toc\n{:toc}\n\n")
    end
  end
end

This takes the ActionView::Template object and prepends the table of contents declaration to the top. This method is not yet hooked up, but Blogue has a convenient config property called Blogue.markdown_template_preprocessor just for this kind of thing. An initializer is a good place for it.

config/initializers/blogue.rb

Blogue.markdown_template_preprocessor = -> template {
  Post.preprocess(template)
}

So now Blogue will run this piece of code before it processes the markdown. Suddenly, table of contents. Well, perhaps I don’t want it on every post. No biggie. I’ll just alter the preprocess method like this.

def preprocess
  if load_meta(template.source)['toc']
    template.source.prepend("* toc\n{:toc}\n\n")
  end
end

Suddenly table of contents became opt-in via meta flag toc: true. Isn’t that nice!

Format handling

It’s actually extra nice that you get a whole ActionView::Template object in the Post.preprocess, here’s why.

Let’s say you are rendering the post as part of an atom feed, and you don’t want a table of contents in there. Most likely your atom feed is generated in an .atom file similar to this.

app/views/posts/index.atom.builder

atom_feed do |feed|
  feed.title "My Awesome Blog"

  Post.all.each do |post|
    feed.entry post, published: post.time do |entry|
      entry.title post.title
      entry.content( raw render(file: post.path) )
      entry.author do |author|
        author.name post.author_name
      end
    end
  end
end

In this case, the template you get in your Post.preprocess will have :atom in its formats array, letting you change the post based on this fact.

def preprocess
  if template.formats.exclude?(:atom) && load_meta(template.source)['toc']
    template.source.prepend("* toc\n{:toc}\n\n")
  end
end

I think that is pretty great.

Make your own conventions

Now, the perceptive among you (or those who give a damn about any of this) may have noticed that prepending table of contents to the top isn’t going to work, because it would appear before the title. Well, in my blog setup I handle this by actually removing title, and rendering it together with the author and date in a separate partial. How do I remove title? In the same preprocessor.

def preprocess
  if template.source.starts_with?('# ')
    template.source.replace(template.source.lines[1..-1].join)
  end

  if template.formats.exclude?(:atom) && load_meta(template.source)['toc']
    template.source.prepend("* toc\n{:toc}\n\n")
  end
end

Now hopefully it all makes sense. This preprocessing trick really gives you infinite possibilities on the shape of your final html, while keeping your markdown posts intact.

  1. Subject to psychedelic drug use. 

comments powered by Disqus