Helping myself write more in the new year

Happy New Year! I’m implementing a Rulin’Blog post on this to come, I hope.

this year to write something every day, and publish it here. It’s something I did for part of last year, but then I decided to change up my website, I got busy, and I fell way off. So here I am, renewed in my quest to post up an essay, poem, recipe, review, or something else every single day this year.

To help myself be more writerly, I decided to write out a script to automate the basic task of beginning a new blog post. I’ve done this before, but new blog, new script, so I began againPlus, I don’t really know where I put the dang original script.

. I decided to try writing it in Python to get practice.To jump to the script itself, click here.

The dark times

My usual workflow is this:

  1. I type vim ~/acdw.net/posts/2019-01-03-the-title.essay in my shell.

  2. I manually type in the YAML frontmatter:

  3. I write up my post.

    Usually in writing up my post, I decide I want to change my title. So I change the title in the YAML block … but wait! The filename is wrong now too!

  4. So I have to
    • :wq from vim
    • run mv ~/acdw.net/posts/2019-01-03-the-title.essay ~/acdw.net/posts/2019-01-03-the-new-title.essay
    • vim ~/acdw.net/posts/2019-01-03-the-new-title.essay
    • oh my god.
  5. Rinse, repeat for who knows how many times, and for every post.

This is untenable.

Beginning again

I decided to implement a draft system in HakyllI did something very similar to Jorge Israel Peña’s method as outlined in his blog post.

, so I can put posts I’m working on in a drafts/ subfolder of my site directory and they won’t be published until they’re ready. But I thought, I can do one better — let’s work on a temporary file!

Python has a library for thatIs this a meme?

, called tempfile. You can create a temporary file using tempfile.mkstemp that sticks around for a while, so I used that. It can also add the suffix .md, so vim knows it’s a markdown file, and a prefix so the user can know what they’re working on at a glance.

I just pop that into a function, and I’m good to go.

Frontmatter

Hakyll uses YAML frontmatter to define metadata about each postIt looks something like the YAML above.

, so my next step is to get that frontmatter in there automatically when I start the script. I just use a constant defined at the top of my scriptI use the {space} variable because I have Vim automatically automatically truncate end-of-line spaces on a save.

:

and enter it with a quick

Editing

Okay, now comes the hard part: actually writing the post. I just use a subprocess.run for that, passing the $EDITOR variable from the environment (with a sane default, of course):

Saving

Remember, I’ve done all this as a tempfile. Now I need to actually save the file in the drafts/ folder. For that, I’ll need the date of the post, the slug, and the group, which is basically my version of a category. I just pass the group in as a parameter to new_post, so that’s taken care of. The date and slug need to be pulled from the YAML frontmatter of the file.

I used the re library instead of PyYAML, because pulling in a whole YAML dependency is silly for two fields and because PyYAML complained about multiple documents when I used the two ---s to delineate the metadataYou’re supposed to use --- for the beginning and ... for the end, but seriously, who cares? Hakyll doesn’t, I don’t, so there.

.

So I find the title and date by searching for their definitions in the file:

and then do a couple of sanity checks:

To generate the slug from the title, I just use my handy-dandy slugify function:

And then I write the file and remove the tempfile:

Quibbles

There are still a few problems. The first is how to tell my script what group the post should be in. I usually know what group I’m going to write in, but I’m not so sure where it’ll go from there, so that makes sense to pass as a script argument. Thus:

The second problem is that, sometimes I realize I jumped the gun. Like today, at first I was like, I’ll write a poem, then I decided to write this essay about writing essays. So I had to rm ~/acdw.net/drafts/2019-01-03-some-poem.poem so I didn’t clog up my drafts directory, which was very frustrating.

The solution is just to split the file’s contents along the ---s, then see if the body section is empty. If it is, you can bail:

Packaging

So my script is all done, but it’s a real pain to use. I set it up in a virtualenv, so if I want to use it I have to source the virtualenv and then run it with python and then deactivate the virtualenv. Luckily, we have bash for that! I wrote up a quick bash script that does all that for me and passes all the arguments to my python script and put it in my $PATH. You can see it below, after my python script.

Future thoughts

Even though my script is good enough to use today, I still want to add some features:

  • Check if you’re already working on something today, and ask if you’d like to continue on that or start something new
  • Maybe a draft command that allows you to choose a draft to work on
  • A publish command to move a draft into the posts/ folderI’m not sure whether this is a better command for this script or for my Hakyll site script. This requires more thought.

  • A kind of search function so I can quit and come back to a post without finding it in my drafts folder

I’ll work on these features later, though; too many times I’ve let tinkering get in the way of writing!

Appendix

The script

#!/usr/bin/env python3

import os
import re
import subprocess
import sys
import tempfile
from datetime import date

EDITOR = "editor"
DRAFT_DIR = os.getenv("HOME") + "/acdw.net/drafts/"
FRONTMATTER = """\
---
title:{space}
date: {date}
tags:{space}
---


""".format(
    space=' ', date=date.today())


def slugify(title):
    """Turn a title into a slug."""
    words = [str.lower(word) for word in re.split(r"\W+", title) if word != ""]
    return '-'.join(words)


def new_post(group, output_dir):
    # Make a new tempfile
    thandle, tname = tempfile.mkstemp(
        suffix='.md', prefix=f'acdw-{group}-', text=True)

    # Populate it with YAML frontmatter
    with open(thandle, mode='w') as f:
        f.write(FRONTMATTER)

    # Edit the file in user's EDITOR
    subprocess.run([os.getenv("EDITOR", EDITOR), tname, '+'], check=True)

    # Let's see the new file
    with open(tname, mode='r') as f:
        contents = f.read()

    _, metadata, body = contents.split('---')
    # If nothing has been written, bail
    if body.strip() == '':
        print("acdw: Post empty.")
        return None

    # Get metadata for filename
    title = re.search(r"^title:\s*(.*)$", metadata, re.MULTILINE)
    if title is not None:
        title = title.group(1)
    else:
        title = ""

    slug = slugify(title)

    date = re.search(r"^date:\s*(.*)$", metadata, re.MULTILINE)
    if date is not None:
        date = date.group(1)
    else:
        date = date.today()

    # Write the file to the directory
    fname = date + "-" + slug + "." + group

    with open(output_dir + fname, 'w+') as f:
        f.write(contents)

    # Delete tmp file
    os.remove(tname)

    print("acdw:", fname + " saved.")


if __name__ == "__main__":
    try:
        group = sys.argv[1]
    except IndexError:
        print("Usage: acdw <group>")

    new_post(group, DRAFT_DIR)

The wrapper