Skip to Main Content

<code>blocks

My blog is built using Ghost as a headless CMS and Eleventy as a static site generator. My main reason for switching the content side to Ghost was to gain access to a nice editing experience. And while I thoroughly enjoy writing again, adding code snippets into my blog posts is bit of a pain.

As an example, take a look at the following code snippet:

<nav class="menu">
  <ul>
    <li><a href="#">Home</a></li>
    <li><a href="#">About</a></li>
    <li><a href="#">Contact</a></li>
  </ul>
</nav>

On a surface, it looks simple and easy. But under the hood, there are a couple of things I need to do to make it look like that for you as a reader.

Adding classes for Prism

I use Prism.js on my website to make these code blocks nicer looking. It adds syntax highlighting and line numbers. It is a major improvement for readability and requires me to add pre and code tags with specific class names in the HTML block in Ghost.

For a single snippet, it's not much. But having multiple snippets like I had in my recent Python tuple blog post, manually crafting these blocks becomes a bit of a chore - a one that I'm not specifically excited about. It introduces friction to my writing process and as I'm not a great writer to begin with, any added friction makes writing a less enjoyable experience and I end up making less technical blog posts.

So to make the above snippet nice, my script adds <pre class="language-html"><code class="language-html"> in the beginning and matching closing tags at the end of the script to trigger Prism.

Converting special characters to HTML entities

To make sure that special characters used in HTML are displayed correctly, I need to convert them to HTML entities. That's even more of a chore than adding the surrounding tags and classes.

In the above example, there are 16 < characters and 16 > characters that need to be converted. Converting those manually is horrible, especially if you notice later you need to make a change on the code.

Adding non-visible characters

For a reason I haven't been able to debug yet, if I have empty lines in my code snippets, Prism.js isn't able to parse it correctly and the output gets all messed up. To fix this, I check for empty lines and add the Unicode U+200BZERO WIDTH SPACE character to force Prism.js to parse them correctly.

If you have an idea what's this one about, let me know in Twitter.

Introducing <code>blocks

So after mentioning this pain point to a friend in Slack as we discussed using Ghost, I figured that it's not that difficult to solve with my programming abilities. As a pythonista, I started a new Python project and wrote a script that gets the language name (used in class names) as a positional argument and reads the code snippet from standard input.

import sys
​
try:
    language = sys.argv[1]
except IndexError:
    print('Usage: codeblock.py [language] < [input file]')
    sys.exit(1)
​
ENTITIES = {
    '&': '&amp;',
    '<': '&lt;',
    '>': '&gt;',
    '"': '&quot;'
}
​
codeblock = f'<pre class="language-{language}"><code class="language-{language}">'
​
for line in sys.stdin:
    if line.strip() == '':
        # This includes a zero-width space (U+200B) before the line break
        # because my blog's code blocks break if there are empty lines.
        line = '​\n'
    for character, encoded in ENTITIES.items():
        line = line.replace(character, encoded)
    codeblock = f'{codeblock}{line}'
​
codeblock = f'{codeblock}</code></pre>'
​
print(codeblock)

I took the entity list from this MDN page of reserved HTML characters. In essence, the script just reads from standard input, goes through line-by-line and replaces all instances of HTML entities, wraps it in HTML tags and prints the result to standard output where it can be copied and pasted into the blog post in Ghost.

There are a couple of ways to use it. If your code is already in a file, you can do python codeblock.py python < code.py | pbcopy (leave out pbcopy if you're not using a MacOS) and you have the modified thing in your clipboard. If your code isn't in a file, you can just do python codeblock.py python | pbcopy and write or paste the code snippet into standard input and use CTRL-D to signify end of input.

You can find the code in hamatti/codeblocks. It's MIT licensed so if you want to use or modify it, go ahead.