November 26, 2014

Updates and Architecture

It's been about two weeks since I released Cryogen to the public and the feedback has been great so I'd like to go over some of the changes that have been made thus far and a how everything fits together behind Cryogen.


Someone pointed out to me that posts or pages that were deleted from resources/templates would have to be manually deleted from resources/public in order for the sitemap and RSS feed to be updated correctly since these two files are generated based on the contents of the public folder.

To remedy this, I updated the compiler to wipe out the public folder and then generate all of its contents again with each compile. I was actually worried that this would increase compile time but Yogthos's blog has actually moved to Cryogen and found no problems with performance even with his 60+ existing posts.

Previously, any asset folders like css and javascript were stored under public but then have now been moved to templates. Everything in the public folder is now generated by the compiler. I've added the :resources key to the config that lets you specify which asset folders you would like to copy over from templates to public.

:resources ["css" "js" "img"]

The copying is done by this simple function in src/cryogen/io.clj using fs.

(defn copy-resources [{:keys [blog-prefix resources]}]
  (doseq [resource resources]
      (str "resources/templates/" resource)
      (str public blog-prefix "/" resource))))

Cryogen also has Disqus support now. Simply register your blog on Disqus, add your disqus-shortname to the config and set disqus? to true and Selmer will inject the info into the following script in the post template.

{% if disqus-shortname %}
<div id="disqus_thread"></div>
<script type="text/javascript">
    (function() {
        var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true;
        dsq.src = '//{{disqus-shortname}}';
        (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq);
{% endif %}

For long posts or pages with numerous headings, you can now generate a table of contents. Just add :toc true to the metadata of the post or page where you want to generate a toc. For example:

{:title "Updates and Architecture"
 :layout :post
 :tags [:cryogen :clojure]
 :toc true}

Some other updates:

  • Can now customize the number of recent posts shown
    • Sass support by turbopape
    • Archive sorting fixed for users with a locale that isn't "en"


Sometimes things are obvious to you when you design them but not necessarily to others. I've received a few questions regarding templating on the Cryogen repo so I thought I'd go over the basic architecture here.

The reason I created Cryogen was because I wanted a site generator (in Clojure!) that had a clear separation between content and layout. The idea is that you create HTML templates under resources/templates/html/layouts with whatever layout/theme you want and the content of your posts and pages from resources/templates/md gets injected by the compiler through Selmer.

Post/Page Layouts

Each markdown file representing a post or page must contain metadata about the layout in the :layout key that corresponds to an HTML file under html/layouts. For example, this post's metadata is as follows:

{:title "Updates and Architecture"
 :layout :post
 :tags [:cryogen :clojure]}

When the site gets built, this is what puts the post together:

(spit (str public (:uri post))
            (render-file (str "templates/html/layouts/" (:layout post))
                         (merge default-params
                                {:servlet-context  "../"
                                 :post             post
                                 :disqus-shortname disqus-shortname})))

Selmer will render the post and the compiler will spit it out into the public folder.

The second argument passed into render-file is a map of items that Selmer will inject into the specified layout. In this case, that will be post.html.

<div id="post">

Here, post is another map of items so {{post.title}} and {{post.content}} will render the values of :title and :content from post. Pages and tags are compiled in a similar fashion.


This segues into how inheritance works in the templates. In the layouts folder, base.html contains the header, sidebar and footer of the site. These are the things that remain constant no matter what page or post you are viewing. The page/post content gets injected into the main reading pane with {% block content %}:

    <div id="content">
    {% block content %}
    {% endblock %}

To inherit the base template, all the other html files in the layouts folder start with a line of code that specifies the path of the base layout it should inherit followed by the layout for that page or post wrapped in {% block content %}.

A short example is the layout for pages.

{% extends "templates/html/layouts/base.html" %}
{% block content %}
<div id="custom-page">
    <div id="page-header">
    {% if page.toc %}{{page.toc|safe}}{% endif %}
{% endblock %}

And that's it! If you want to change the header, sidebar or footer - change it in base.html. If you want to add more post or page layouts, create a new html file under resources/templates/html/layouts and follow the above structure. If you want to add different filters to your content, please check out the Selmer docs.

Tags: cryogen clojure