Mike Slinn
Mike Slinn

Bash Script to Create a New Jekyll Post

Published 2020-08-16.

This article is categorized under Jekyll, Open Source, Scripting.

I use Jekyll to build this website. Some material is published as articles, some as blog posts. I wrote a script called newPost that creates a new draft blog post with SEO considerations. SEO rankings are improved when the description and title tag are neither too long nor too short.

Sample Usage

Here is an example of how I used it on 2020-08-16:

Shell
$ _bin/newPost
Post Title (30-60 characters):
______________________________123456789012345678901234567890
This is a test of newPost for the greater good
46 characters, excellent!

Publication date: 2020-08-16
Post Description (30-60 characters):
____________________________________________________________123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890
I wish newPost used some sort of web service to generate an SEO-optimized description
85 characters, excellent!

Post Categories (comma delimited):
Post Tags (comma delimited):
Post Keywords (comma delimited): Goofiness, Silliness
Banner image (bg_ .jpg):

For the above example the generated file is called _drafts/2020-08-16-this-is-a-test-of-newpost-for-the-greater-good.html. When you are happy with the new posting, move it from _drafts to _posts like this:

Shell
$ mv _drafts/2020-08-16-new-jekyll-post.html _posts/

Generated Posting

Here is the generated file:

---
categories: []
description: I wish newPost used some sort of web service to generate an SEO-optimized description
image:
keywords: [Goofiness, Silliness]
last_modified_at: 2020-08-16
layout: blog
title: This is a test of newPost for the greater good
tags: []
---

Error Handling

The script checks the length of the title and the posting description for SEO purposes. If either of these are too long or too short, the script allows the user to edit their input over and over until they get it right. For example, here you can see that at first the user just types in xx for the title, then they provide a string that is too long, then they edit it until it has an acceptable length:

Shell
$ _bin/newPost
Post Title (30-60 characters):
______________________________123456789012345678901234567890
xx
28 characters too short, please edit
Post Title (30-60 characters):
______________________________123456789012345678901234567890
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
14 characters too long, please edit
Post Title (30-60 characters):
______________________________:123456789012345678901234567890
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
59 characters, excellent!

Source Code

This is the source code for the newPost bash script.

#!/bin/bash

# This bash script will setup a new HTML Jekyll draft blog post and open it for editing in Notepad++
#
# See https://www.mslinn.com/blog/2020/08/16/new-jekyll-post.html
#
# Copyright Original by Katie Harron - @pibby  from https://gist.github.com/pibby/6911493
# Copyright 2020 Modified by Mike Slinn - mslinn@mslinn.com
#  - added length checks for title and description
#  - added reprompt for when length check fails
#  - launch notepad instead of Mac editor
#  - modified front matter
#  - Added optional date as command line parameter
#  - Added date modification dialog
#
# SPDX-License-Identifier: Apache-2.0

DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )/.."
cd "$DIR"

function checkLength {
  # $1 - minimum length
  # $2 - maximum length
  # $3 - string to test
  length=${#3}
  if (( length < $1 )); then
    >&2 echo "$(($1 - $length)) characters too short, please edit"
    return 1
  elif (( length > $2 )); then
    >&2 echo "$(( $length - $2 )) characters too long, please edit"
    return 2
  else
    >&2 echo "$length characters, excellent!"
    return 0
  fi
}

function edit {
  if [ `which notepad` ]; then
    notepad "$filename"  # Invokes mslinn's notepad++ script
  elif [ `which gedit` ]; then
    `which gedit` "$filename"
  else
    echo "No editor defined, please edit '$filename' somehow"
    exit 1
  fi
}

function emit_array {
  if [ "$2" ]; then
    echo "$1: [$2]\n"
  else
    echo -n ""
  fi
}

function emit_scalar {
  if [ "$2" ]; then
    echo "$1: $2\n"
  else
    echo -n ""
  fi
}

function reprompt {
  # $1 - name of front matter variable
  # $2 - minimum length of user-provided value
  # $3 - maximum length of user-provided value

  if [ -z "$1" ]; then 2> echo "Error: no front matter variable provided"; exit 1; fi
  if [ -z "$2" ]; then 2> echo "Error: no minimum length provided"; exit 1; fi
  if [ -z "$3" ]; then 2> echo "Error: no maximum length provided"; exit 1; fi

  NAME="$1"
  MIN="$2"
  MAX="$3"

  LEADIN="'_%0.s'"
  SPACES="$( eval $(echo printf "$LEADIN" {1..$MIN}) )"
  (( COUNT= (($MAX * 10) - ($MIN * 10) + 5) / 10 ))
  NUMBERS="$( eval $(echo printf '"0123456789%0.s"' {1..$COUNT}) )"
  (( CHARS= $MAX - $MIN ))
  VALUE=""
  #>&2 printf "SPACES='$SPACES'\n"
  #>&2 printf "NUMBERS='$NUMBERS'\n"
  while :
  do
    >&2 printf "Post $1 (30-60 characters):\n$SPACES${NUMBERS:1:$CHARS}\n"
    read -e -i "$VALUE" VALUE
    if $(checkLength "$MIN" "$MAX" "$VALUE"); then break; fi
  done
  >&2 echo
  echo "$VALUE"
}

# Set cwd to project root
GIT_ROOT="$( git rev-parse --show-toplevel )"
cd "${GIT_ROOT}"
if [ ! -f _bin/loadConfigEnvVars ]; then
  echo -e "Error: _bin/loadConfigEnvVars was not found.\ncd \$msp"
  exit 1
fi
source _bin/loadConfigEnvVars

title="$( reprompt Title 30 60 )"

ptitle=${title// /-}                    # convert spaces in title to hyphens
# Convert title to lowercase and remove slashes
plc="$( echo "$ptitle" | tr '[:upper:]' '[:lower:]' | tr -d '[/,]' )"
>&2 printf "Filename slug (without date or filetype): "
read -e -i "$plc" plc

if [ "$1" ]; then
  pdate="$1"                            # TODO verify $1 is YYYY-mm-dd
else
  pdate="$( date +%Y-%m-%d )" 	        # create date as YYYY-mm-dd
fi
read -p 'Publication date: ' -e -i "$pdate" pdate

filename=_drafts/$pdate-$plc.html      # location to create the new file as year-month-day-title.md
mkdir -p _drafts
touch "$filename"                      # create the new blank post

desc="$( reprompt Description 60 150 )"
printf "Post CSS (comma delimited): "; read css
printf "Post Categories (comma delimited): "; read categories
printf "Post Tags (comma delimited): "; read tags
#printf "Post Keywords (comma delimited): "; read keyw
printf "Banner image (.png & .webp): "; read img

printf "Enable code example clipboard icon (Y/n): "; read clipboard
if [[ ! "$clipboard" =~ ^(n|N).* ]]; then
  javascriptEnd="/assets/js/clipboard.min.js"
  javascriptInline="new ClipboardJS('.copyBtn');"
fi

contents="---\n"
contents="$contents$( emit_array  css              "$css" )"
contents="$contents$( emit_array  categories       "$categories" )"
contents="$contents$( emit_scalar description      "$desc" )"
contents="$contents$( emit_scalar image            "$img" )"
contents="$contents$( emit_scalar javascript       "$javascript" )"
contents="$contents$( emit_scalar javascriptEnd    "$javascriptEnd" )"
contents="$contents$( emit_scalar javascriptInline "$javascriptInline" )"
#contents="$contents$( emit_array  keywords         "$keyw" )"
contents="$contents$( emit_scalar last_modified_at "$pdate" )"
contents="$contents$( emit_scalar layout           blog )"
contents="$contents$( emit_array  tags             "$tags" )"
contents="$contents$( emit_scalar title            "$title" )"
contents="$contents---\n"

echo -e "$contents" | sed '/^$/d' > "$filename"             # fill out YAML Front Matter and insert into the new file
echo "Created '$filename'"

#edit

printf "Use mem to append code examples to this post (y/N): "; read clipboard
if [[ "$clipboard" =~ ^(y|Y).* ]]; then
   ps ax | grep '[j]ekyll' | awk -F ' ' '{print $1}' | xargs sudo kill -15
  _bin/mem "$filename" &
  _bin/serve -c
fi