Building a Blog

This tutorial will showcase some of the ways to use Blot to build a personal blog. This blog will be quite simple but it should have enough features to convey how site generation works.

Getting Setup

Firstly, you’ll need to install Blot.

One that’s done, we recommend installing Fabric which will be useful for writing your site’s build script. You can read more about Fabric at their official overview but essentially it is a Python library that helps you write scripts that perform local or remote operations. It then exposes those operations with a nifty cli tool. Brace yourself for the shortest Fabric tutorial in the world:

Step 1: Write a Python file called fabfile.py and put functions in it:

# fabfile.py
def hello(name):
    print "Hello, {}!".format(name)

Step 2: Use the fab cli tool to invoke individual functions from the fabfile:

$ fab -l
Available commands:

hello

$ fab hello:world
Hello, world!

Done.

Listing the Goals

For this tutorial we want to build just enough functionality into the blog site to showcase how Blot is used. Let’s stick some typical blog features:

Chronological Posts
  • Each post is a markdown file in a posts folder.
  • There should be an index of the posts sorted chronologically
  • There should be a page for each post where we can read it
Post Categories
  • Each post has an assigned category
  • There should be an index of the available categories
  • There should be a detail page for each category listing the posts it contains
Static Assets
  • There should be a logo image which we’re able to integrate into our theme.
  • There should be a stylesheet which styles our pages

Getting Started

Create a new directory for your site and within it create a fabfile.py file, a posts directory and a theme directory:

|-- fabfile.py
|-- posts/
`-- theme/

In the theme directory, create the file style.css. This will be our first content source. The goal with static files is simple: copy the file from the source to the destination.

Open your fabfile.py and enter the following contents:

base_context = {}

The base_context is like the starting-point for data in the build process. Anything you add here will be subject to modification by anything in the pipeline. Settings placed here can also affect the behavior of components in the pipeline. For now, we’ll leave it empty.

Content Types

Add the following to fabfile.py:

content_types = {
    'stylesheets': {
        'loader': blot.loaders.BasicLoader('theme', extensions=['css']),
        'reader': blot.readers.StaticReader(),
        'processors': [
            blot.assets.PathMetadata(),
        ],
    }
}

The content_types dictionary holds all of the content type definitions for our site. Currently, there is only a single type stylesheets. Let’s take a look at each of its keys:

loader

Loaders tell Blot where to find the sources of stylesheets. blot.loaders.BasicLoader supports some basic inclusion and exclusion rules including filtering by file extension. In this case we’re loading all of the files with a css extension in the theme directory. This should target the style.css file.

reader

The reader is responsible for parsing the content of sources to produce content assets with metadata. However, blot.readers.StaticReader doesn’t perform any parsing and returns source content as-is.

processors

A list of asset processors that should get a chance to modify any stylesheets returned by the loader and reader. In this case, a single asset processor blot.assets.PathMetadata will extract various details of the source file path and add it to the asset metadata. Things like basename, :code`dirname`, :code`filename`, :code`extension` and so on.

From this definition we have all we need in order to find stylesheets and load them as content assets.

Reading

Now that we have a base context and our content type definitions, reading can be performed with blog.read(). This will load our sole stylesheet, add some metadata to it. Finally, an updated version of the base context will be returned. Add the following to fabfile.py:

def build():
   context = blot.read(base_context, content_types)

We now have a function in the fabfile.py that we can invoke from the commandline. However if we do, we wont see anything meaningful just by loading content. Let’s add a quick debugging print to see the results of the reading process:

def build():
   context = blot.read(base_context, content_types)
   print context

Now we can invoke the build function with the fab command-line utility:

fab build
{'stylesheets': {'assets': [<blot.assets.base.ContentAsset object at 0x7fc3d9041c50>]}}

Done.

We can see that the context now contains a stylesheets key that maps to the context for that content type. Within that context is an assets key that contains the actual processed content assets.

If we change the print statement to print context['stylesheets']['assets'][0].metadata we can get a closer look at the asset itself and evidence of blot.assets.PathMetadata processor at work:

fab build
{'ancestry': '', 'parent': 'theme', 'extension': '.css', 'basename': 'style.css', 'dirname': 'theme', 'filename': 'style'}

Done.

For more information on loaders, readers and asset processors visit the Reading Content section of the User Guide.

Writing

Writing involves taking some assets and passing them to a writer. In our case we don’t need to render anything so we can use the blot.writers.StaticWriter to simply write our stylesheet asset contents to disk:

def build():
    context = blot.read(base_context, content_types)

    stylesheets = context['stylesheets']['assets']
    blot.write(context, [
        blot.writers.StaticWriter(stylesheets, 'static/{basename}'),
    ])

First, we grab the stylesheet assets out of the context. Then we call blot.write() while passing it the context and a list of writers. In this case, the only writer involved.

blot.writers.StaticWriter takes two arguments:

  • The assets to write
  • A path pattern describing destinations

As StaticWriter goes to write each asset it will determine where to write it by interpolating the asset’s metadata into the provided path pattern. The path pattern we’re using static/{basename} requires that the assets being written contain this metadata property. Luckily, basename, among others, is provided by the PathMetadata asset processor we used during reading. For our style.css file, we should expect its destination path to be static/style.css.

If we invoke the build function from the commandline that is exactly what we should see in the newly created output directory:

$ fab build

Done.

$ tree output
output
`-- static
    `-- style.css

1 directory, 1 file

For more information on writing visit the Writing Assets section of the User Guide.

Intermission

By now you should be developing a clearer idea of how you can bring together loaders, readers, processors and writers to form a pipeline for your content sources with Blot. The process for handling the rest of our content types works just like it does for our stylesheets. Really!

At this point the tutorial will start moving a lot faster.

Before moving on, let’s add small helper function to fabfile.py to clean our output directory by deleting it. The whole script should appear as follows:

from fabric.api import local

import blot

base_context = {}

content_types = {
    'stylesheets': {
        'loader': blot.loaders.BasicLoader('theme', extensions=['css']),
        'reader': blot.readers.StaticReader(),
        'processors': [
            blot.assets.PathMetadata(),
        ],
    }
}


def clean():
    local("rm -r output")

def build():
    clean()
    context = blot.read(base_context, content_types)
    stylesheets = context['stylesheets']['assets']
    blot.write(context, [
        blot.writers.StaticWriter(stylesheets, 'static/{basename}'),
    ])

We’ve added a clean function which removes the output directory which allows us to invoke it from the commandline. It uses a function from the Fabric api, local, which we’ve imported at the top. It makes running local shell commands very convienent. As a final note, we’re calling clean first thing from build which gives a clean target path each time you build.

Introducing Posts

To introduce our actual blog posts, we’ll add a new posts content type:

content_types = {
    'stylesheets': {
        'loader': blot.loaders.BasicLoader('theme', extensions=['css']),
        'reader': blot.readers.StaticReader(),
        'processors': [
            blot.assets.PathMetadata(),
        ],
    },
    'posts': {
        'loader': blot.loaders.BasicLoader('posts', extensions=['md']),
        'reader': blot.readers.MarkdownReader(),
        'processors': [
            blot.assets.PathMetadata(),
        ],
    }
}

This is very similar to the stylesheets content type definition, but with a few changes. First, we want to load files from the posts directory this time and only files with a .md file extension. Any such files will then be parsed as markdown by the blot.readers.MarkdownReader.