Getting Cozy with Jekyll

The Problem

I’ve been hav­ing lots of fun with this blog, and Jekyll makes it easy to write posts. I love the sim­plic­ity in­volved in cre­at­ing a post and pub­lish­ing it. How­ever, there were some ir­ri­ta­tions in­volved when start­ing to write a post: I had to write the front mat­ter man­u­ally every sin­gle time. As it turns out, I was ba­si­cally copy­ing and past­ing the front mat­ter and chang­ing val­ues every time. There must be a bet­ter way!

There is­n’t an easy fix for this. Since the front mat­ter is spe­cific to every post, there’s noth­ing I can spec­ify in the Jekyll con­fig­u­ra­tion. So the ques­tion was: what do?

The Solution

After a lit­tle bit of re­search into pre­ex­ist­ing so­lu­tions, I de­cided it would be eas­i­est to just write my own lit­tle script. I have a spe­cial set up, where, in ad­di­tion to just blog posts, I also have art and pro­ject posts that I want to semi-au­to­mate the gen­er­a­tion of also.

I ended up writ­ing a Python script that ba­si­cally walks me through the front mat­ter re­quire­ments for each post. The in­ter­face looks a lit­tle bit like this:

blog > title: Getting Cozy with Jekykll
blog > author(Naitian Zhou):
blog > cover(required): TODO
blog > desc(A blog post): A quick guide to easliy generating new Jekyll psts with Python and Mustache
Would you like to add a tag? (y/N)y
blog > tag > name: Python

This gen­er­ates a file _drafts/getting-cozy-with-jekyll.md with the fol­low­ing con­tent:

---
title: Getting Cozy with Jekyll
author: Naitian Zhou
cover: /assets/TODO
description: A quick guide to easily generating new Jekyll posts with Python and Mustache
tags:
    - Python
---

A cou­ple of in­ter­est­ing notes: I im­ple­mented very ba­sic de­fault val­ues, which al­lowed me to leave the Author: field blank, and in­cluded the abil­ity to add lists to the front mat­ter.

In fact, I even made it so that I could spec­ify more JSON schemas for posts and the script would au­to­mat­i­cally be able to han­dle those as well. Here’s the rel­e­vant di­rec­tory struc­ture:

 - //other Jekyll files
 - _templates/
   - schemas.json
   - art.mustache
   - blog.mustache
   - project.mustache
 - _utils/
   - help.py

I used the mus­tache tem­plat­ing lan­guage for easy tem­plat­ing. The art.mus­tache file looks like this:

---
title: {{title}}
src: {{img}}
{{#other}}
others:
{{/other}}
{{#other}}
   - {{file}}
{{/other}}
description: {{desc}}
---

This method gives me a lot of flex­i­bil­ity to flesh out the tem­plates in the fu­ture. I could even add con­tent into the body text, in ad­di­tion to the front mat­ter.

I used Python and the pystache li­brary to take the in­put and ren­der out the cor­rect files.

To take in­put, I wrote a re­cur­sive func­tion that looks at the schema spec­i­fied by the schemas.json file.

Here is the en­tirety of the schemas file:

{
    "blog": {
        "title": "required",
        "author": "Naitian Zhou",
        "cover": "required",
        "desc": "A blog post",
        "tag": [
            {"name": ""}
        ]
    },
    "art": {
        "title": "required",
        "img": "required",
        "desc": "An art piece",
        "other": [
            {"file": ""}
        ]
    },
    "project": {
        "title": "",
        "thumb": "",
        "desc": "A project page",
        "tech": [
            {"name": ""}
        ]
    }    
}

The func­tion looks like this:

def prompt(title, obj):
    """Prompt for input

    :obj: Empty Object
    :returns: Filled-in object

    """
    for key in obj:
        if type(obj[key]) is list:
            if input('Would you like to add a {}? (y/N)'.format(key)) == 'y':
                while True:
                    obj[key].append(prompt('{} > {}'.format(title, key), copy.deepcopy(obj[key][0])))
                    if input('Finished? (y/N)') == 'y':
                        break
            obj[key] = obj[key][1:] if len(obj[key]) >= 2 else None
        else:
            message = '{} > {}: '.format(title, key) if obj[key] == '' else '{} > {}({}): '.format(title, key, obj[key])
            val = input(message)
            if val != '':
                obj[key] = val
    return obj

It prompts you for the req­ui­site val­ues (and keeps the de­fault if you don’t en­ter any­thing), and re­cur­sively goes into lists if lists are pre­sent. It will then re­turn an OrderedDict that we can feed into pystache.render().

Now when­ever I want to write a new blog post, I can just call

$ _utils/help.py new blog

and it will hold my hand as we nav­i­gate through the front mat­ter op­tions.

You can find all of the files on the Github for my blog.

More specif­i­cally, here are the Python script and the tem­plate di­rec­tory