Automating the creation of Blog posts

A Python solution to create blog posts from the command line
Author
Published

August 21, 2023

Code Repository

Automating the creation of Blog posts

A Python solution to create blog posts from the command line
Author
Published

August 21, 2023

1 Motivation

When putting together a blog post, there are several steps that are time consuming. For instance, creating a directory with the date for the slug, then a title name and followed a YAML header that suits the format of the article. All this can be time consuming and hence in this post I will show how I have automated the creation of template posts to write code right away rather that setting this up

  • Goal: from a simple command line i.e. mkblog notebook 'How to rock' a folder with the current date and the title-name i.e. 2023-08-21_how-to-rock will be created. Then the script should copy a pre-build post model that will modify the header’s date, title, href (for downloable Jupyter Notebooks) i.e:
title: "How to create Dynamic plots"
date: "2023-08-21"

2 Creating the script

The first thing that came to my mind when creating the script was to the type of post. On a general level I have classify them in three categories

  • post: this is a normal qmd file that can hold articles and is capable to run R, Python, JavaScript and so on.
  • poems: this is a qmd modified version that enables the formatting that you see in my poems.
  • notebook: this is a ipynb file that can be executed using my conda environments

The question is now how to set all this up. Reviewing at scripting languages and ways on how to create extension for html files I realised all this could be done with python.

Here down below the script that makes all this possible

2.1 Make a Directory

create_dir.py
import os 
import sys
from datetime import datetime
import shutil

def create_post(type_file, post_name):

    # Save a copy only name
    raw_post_name = post_name

    # Example: "how-to-rock"
    post_name = post_name.lower().replace(" ", "-")

    # Current Date: "2023-08-21"
    current_date = datetime.today().strftime('%Y-%m-%d')

    # Folder name: "2023-08-21_how-to-rock"
    folder_name = current_date + '_' + post_name

    # Current Directory path
    current_dir = os.getcwd()
    
    # Output Directory
    output_path = current_dir + "/out"

    # Folder Path: "../out/2023-08-21_how-to-rock"
    path_folder = os.path.join(output_path, folder_name)

    # Make a Directory
    os.mkdir(path_folder)
    print("Directory '% s' created!" % folder_name)

# Name of Post
# 0 is the name of the py file
# 1 is the first argument

type_file = "notebook" # sys.argv[1]
post_name = "how to rock" # sys.argv[2]             

create_post(type_file, post_name)
Directory '2023-08-21_how-to-rock' created!

2.2 Copy, move & edit a Blog’s Template

After we have created a folder, we can proceed to make a copy of a user-defined template with settings that we may want to use for that specific type of post. For instance for my notebook posts I want to modify the date of the header, its title name and the ref link to download the output of the Jupyter notebook.

For this to scale and be able to fire the script from any directory I will point to a specific folder where I will make all this changes and then move those modified files to my ../blog section.

The structure of my files where the script is stored is as follows:

Terminal
_extensions
└── mkpost
    ├── create_dir.py
    ├── out
    ├── splitWin.app
    └── templates
        ├── notebook
        │   └── index.ipynb
        ├── poems
        │   └── index.qmd
        └── post
            └── index.qmd
Note

To print the tree structure you see above I have used: brew install tree and then tree <path>

In the folder view you find mkpost as the folder that holds the script previously shown. There is also out folder which contains the folder just created 2023-08-21_how-to-rock. There is also splitWin.app which will talk in section and lastly a folder called templates where you can see index.* files that will be copied to our newly created folder.

Following the continuation of the python script that does all the moving, copying and editing.

create_dir.py

def create_file(type_file):
    # -------------- CREATE FILE --------------
    # Holds available extensions
    dic = {'post': 'qmd', 'poems':'qmd', 'notebook':'ipynb'}
    file_extension = dic[type_file]
    
    # Source path: "../template/file_type/index.ext"
    source_file = f"templates/{type_file}/index.{file_extension}"
    
    # File name "index.ext"
    file_name = f"index.{file_extension}"

    # Destination path: "../out/2023-08-21_how-to-rock/index.ext"
    destination_file = os.path.join(path_folder, file_name)

    # Creating a Copy from Template folder
    shutil.copy(source_file, destination_file)

    # -------------- UPDATE HEADER --------------
    lines = open(destination_file, 'r').readlines()
    
    if (type_file == "notebook"):
        title_row = 9
        lines[title_row] = f'    \"title: \\"{raw_post_name.capitalize()}\\"\\n\",'

        date_row = 11
        lines[date_row] = f'    \"date: \\"{current_date}\\"\\n\",'

        code_name_row = 16
        post_code_name = post_name + '.out.ipynb'
        lines[code_name_row] = f'    \"      file-name: {post_code_name}\\n\",'

    else:
        title_row = 1
        lines[title_row] = f'title: "{raw_post_name.capitalize()}"\n'

        date_row = 3
        lines[date_row] = f'date: "{current_date}"\n'

    file = open(destination_file, 'w')
    file.writelines(lines)
    file.close()
    
    # -------------- MOVE FILE --------------
    # Folder Path: "../out/2023-08-21_how-to-rock"
    src = path_folder

    # Path to Blog
    dst = "../../blog"
    shutil.move(src, dst)       
    

if (type_file == "post"):
    create_file("post")
elif (type_file == "notebook"):
    create_file("notebook")
elif (type_file == "poems"):
    create_file("poems")
else:
    print(f"Wrong type_file: {type_file}. Available: 'post', 'poems' or 'notebook'")
    sys.exit()
    

The code above does the following:

  1. When the user inputs the name and the type_file it looks at the template folder with the specified extension and does the copying
  2. Updates the header changing date to the current date at the moment of creating the directory, title with the name we input in the terminal and, lastly if is a notebook it changes the href to enable the user download the notebook.
  3. Once all changes are done, the folder with the index.* file are move to the blog section.

3 Fire it up from terminal

For convenience, no matter in which directory of your computer you are in, this section cover how to fire it up the script that we have created. For starters, if you are running macOS or Linux most likely you will be using bash, zsh or fish shells. These shells, as it comes to not surprise, can launch scripts with user-defined strings defined.

For my liking I have decided to proceed with:

Terminal
mkblog [type_file] ["name_post"]

To enable this, you have to cd to your .bash_profile or .zshrc in my case. Then we will define the alias mkblog and will do the following:

  1. cd to the path where we have stored our python script. That is _extensions
  2. Execute the script using python
  3. Open my workspace in Visual Studio
  4. Open the index.* we just created i.e. in the dir "2023-08-21_how-to-rock"
  5. Make quarto render that file. Voila start coding!
.zshrc
# Creates script to open blog post
mkblog() {
  cd ../path/more_path/_extensions/mkpost
  python create_dir.py $1 $2
  cd ..
  cd ..
  code website.code-workspace
  BACKUPDIR=$(ls -td blog/*/ | head -1)
  cd $BACKUPDIR
  FILENAME=$(ls | sort -f)
  code $FILENAME
  quarto preview $FILENAME 
}

Bonus - Create an script to split views

When working with large screen I would like to place in the left side the file I created and in the right side my browser to preview the changes I made while editing my file. If you are familiar with AppleScript then you will be at home with the following lines.

splitWin.app
activate application "Visual Studio Code"
repeat 1 times
    tell application "System Events" to key code 123 using {command down}
end repeat

activate application "Microsoft Edge"
repeat 1 times
    tell application "System Events" to key code 124 using {command down}
end repeat

Long story short, the script will send the keystrokes cmd + ←, cmd + → to each application and thus split the windows to left and right. How to accomplished this behavior? Glad you asked, I invite your to read this post where I go through how I made my life easier using Lua Shortcuts.