PythonとAutomatorでブログ執筆ツールをつくりました

花村貴史のアイコン画像
Creative

静的サイトジェネレーター(SSG)で作ったサイトの記事はMarkdownファイルで管理します。

WordPressでいうTitle、Date、Description、Category、tagsなどの設定もできて、ファイルの冒頭にFront matterと呼ばれるデータを書きこむことで、SSGがよしなに判断してあれこれしてくれます。

ただ、記事を書くたびに

  • ファイルを作成
  • Front matter(定型文)をコピペ
  • タイトルや日付などを入力

するのは手間がかかるのでmacOSのAutomatorを使ってツールを2つ作りました。

  1. Markdownファイルを作り、任意のエディタを起動するアプリ
  2. gridsome developを起動し、指定のブラウザでlocalhostに接続するショートカットキー

この記事ではPythonやAutomatorをどのように使ったかをまとめます。

1. Markdownファイルを作り、任意のエディタを起動するアプリ

※記事の最後にソースコードを載せました。参考にどうぞ。

PythonでMarkdownファイルを作る

僕のサイトでは以下のように記事ファイルと画像ファイルを配置しています。

/static/posts/blog/
                 |
                 |__001/
                 |    |__img/
                 |    |__001_200322.md
                 |
                 |__002
                 |    |__img/
                 |    |__002_YYMMDD.md
                 |
                (以降、003、004と記事ごとに増えていく)

「ディレクトリ名が1つずつ加算されている」部分やファイル名の「YYMMDD」などがプログラミングの使い所。

ディレクトリやファイル操作をすればいいのでPathlibモジュールを使います。パスをオブジェクトとして操作できるようになります。

参考:pathlib --- オブジェクト指向のファイルシステムパス


次はFront matterです。

これは以下のようなデータ群になってます。「$」ではじまっている変数はスクリプトを実行するたびに設定します。これを毎回コピペするのはすっごく面倒なのでここでもプログラミングの力を使いましょう。

---
title:
date: $date
createdAt: $date
updatedAt: $date
description:
image: $image
slug: $slug
category:
tags:
type: $type
---

「$」は定型文や動的にテキストデータを設定するところなので、Stringモジュールのtemplateを使います。

参考:string --- 一般的な文字列操作


使い方はかんたんで、Front matterの定義ファイルを開いて、substitute()で$変数に値を入れる。それをMarkdownファイルにwrite()でOKです。

# gen_md_file.py(一部抜粋)

import string

md_full_path = f"{dir_path}/{dir_name}_{s_date}.md"

with open("template/front_matter.txt") as fm:
    t = string.Template(fm.read())
    template = t.substitute(date=l_date, image=img_path, slug=slug, type=post_type)

    with open(md_full_path, "w") as new_md:
        new_md.write(template)

Automatorからスクリプトを実行してエディタを起動する

スクリプトは作れたけど、新しく記事を書くたびにpythonコマンドを叩くのは手間です。それにどうせならMarkdownファイルを開いてすぐブログを書きはじめられたら素敵じゃないですか。

そんなときはAutomatorにシェルコマンドを入力してもらいましょう。


Automatorを起動して「アプリケーション」を選択。

AutomatorでPythonを実行するには、シェルから実行してもらうようにしないといけません。以下の順番でつなげていきます。

  1. シェルスクリプトを実行
  2. 指定された Finder 項目を取得
  3. Finder 項目を開く(お好きなアプリを選択)

シェル実行時にMarkdownファイルの絶対パスをprint()で出力しています。それが「指定されたFinder項目を取得」につながって、次の「Finder 項目を開く」へとつながっていきます。


さらに、どうせならAutomatorで作ったアプリのアイコンも変えましょう。

まず、Automatorで作たアプリの情報ウィンドウを表示させます。次に左上のアイコンを選択状態にしてから、あらかじめクリップボードにコピーしておいた画像をcommand+vしてあげればOKです。

2. gridsome developを起動し、指定のブラウザでlocalhostに接続するショートカットキー

記事を書きながらプレビューできたら便利ですよね。今回はショートカットキーで起動するようにします。


Automator を起動して「クイックアクション」を選択。

以下の順でつなげていきます。

  1. 指定されたFinder項目を取得(後述のコマンドファイルを指定)
  2. Finder項目を開く
  3. 一時停止(ビルドにちょっと時間がかかるため一休み)
  4. シェルスクリプトを実行(ブラウザのフルパス + URL)

コマンドファイル

「シェルスクリプトを実行」から直接gridsome developをすると、ctrl+cでGridsomeのプロセスを停止できません。僕から見えない誰かがプロセスを握ってしまっているためだと思います。

なので、以下のコマンドファイルを作り、Terminalを起動してから実行してあげる必要があります。

$ touch _start_gridsome.command
$ vim _start_gridsome.command

cd ~/Develop/MyProject/gridsome_nnamm/
gridsome develop

実行権限をつけるのを忘れずに。

$ chmod a+x _start_gridsome.command

ショートカットキーの設定

クイックアクションはmacOSのサービスとして扱われるため、システム環境設定から任意のショートカットキーを割り当ててあげましょう。

以上で完成です。

おわりに

これで、アプリアイコンをダブルクリックするだけでブログが書けるようになりました。また、必要に応じてショートカットキーでプレビュー画面を起動するということもできました。

大満足!

参考:gen_md_file.py

"""
新規ブログ記事の雛形Markdownファイルを作るスクリプト
所定のディレクトリに、以下のディレクトリを作成する
...blog/
      |__001
           |_img/
           |_001_YYMMDD.md(フロントマター含)
"""

import configparser
import datetime
import pathlib
import string


def create_front_matter_info(work_dir: str) -> dict:
    """ フロントマター用のデータを作成する
    Args:
        work_dir: 所定のディレクトリパス
    Returns:
        dict: フロントマターに設定する情報
    """

    fm_dict = {
        "new_dir_name": "",  # 新記事を格納するディレクトリ名(作業用)
        "new_dir_path": "",  # 上記を含めたフルパス
        "created_date_long": "",  # 記事作成日時ロング版(スクリプト実行日時)
        "created_date_short": "",  # 上記のショート版
        "eye_path": "",  # アイキャッチの画像のパス
        "slug_str": "",  # スラッグ
        "post_type": "blog",  # ポストタイプはblog
    }

    # 新記事のディレクトリ名(ゼロパディング)とフルバス
    p = pathlib.Path(work_dir)
    dir_list = [p.name for p in p.iterdir() if p.is_dir()]
    dir_list.sort()
    latest_dir = int(dir_list[-1])
    fm_dict["new_dir_name"] = str(latest_dir + 1).zfill(3)
    fm_dict["new_dir_path"] = work_dir + fm_dict["new_dir_name"]

    # 新記事の作成日時(long: YYYY-MM-DD HH:MM:SS / short: YYYYMMDD)
    dt = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    fm_dict["created_date_long"] = dt
    fm_dict["created_date_short"] = (dt[2:10]).replace("-", "")

    # アイキャッチの画像パス
    fm_dict["eye_path"] = f'/ec/blog/ec_blog_{fm_dict["new_dir_name"]}.jpg'

    # スラッグ
    fm_dict["slug_str"] = f'{fm_dict["new_dir_name"]}-{fm_dict["created_date_short"]}-'

    return fm_dict


def generate_blog_file(params: dict):
    """ 新記事のディレクトリとファイルを作成する
    Args:
        params: フロントマターに設定する情報
    """

    dir_name = params["new_dir_name"]
    dir_path = params["new_dir_path"]
    l_date = params["created_date_long"]
    s_date = params["created_date_short"]
    img_path = params["eye_path"]
    slug = params["slug_str"]
    post_type = params["post_type"]

    # ディレクトリを作成
    pathlib.Path(dir_path + "/img/").mkdir(parents=True)

    # mdファイルにテンプレートからフロントマターを設定
    md_full_path = f"{dir_path}/{dir_name}_{s_date}.md"
    with open("template/front_matter.txt") as fm:
        t = string.Template(fm.read())
        template = t.substitute(date=l_date, image=img_path, slug=slug, type=post_type)
        with open(md_full_path, "w") as new_md:
            new_md.write(template)

    # macOSのAutomatorと連携するために引数としてファイルパスを出力
    print(md_full_path)


if __name__ == "__main__":
    config = configparser.ConfigParser()
    config.read("config.ini")
    blog_path = config["path"]["blog"]

    generate_blog_file(create_front_matter_info(blog_path))

▶︎ GitHub

※勉強がてらflake8/pylint/blackをPyCharmに導入してみました。pytestもやってみようと思います。

#macOS#Python
花村貴史のアイコン画像

Software Engineer & Photographer

花村貴史

Takashi Q. Hanamura

Web開発をしながら、撮られることに慣れてない方の笑顔やステキな空気感をそっとすくい撮ってます。
横浜育ち38年、脱サラ信州移住2年半を経て、2018年春から関西在住。翌年秋から夫婦生活スタート。関西に来てよかったことは京都が近くなったこと。