このブログは元々Pelican製だったものを、4月の最初にNikola製に移行しました。まだ一週間経っていませんが、Pelicanに戻しました。

PelicanもNikolaもPython製の静的ブログジェネレータです。

Nikolaのnew_postサブコマンドは魅力

この記事はNikoaから離れる話ですが、離れる前にNikolaの良かった点としてnew_postサブコマンドをあげます。

このサブコマンドは、新しい記事の雛形を生成するコマンドです。これが便利でした。雛形程度ならファイルをコピーするだけでも済みますが、それでも日付が入ったりと、自分で雛形ファイルを用意してコピーする以上に便利でした。

Pelican向けに作成

@task
def new(c, filename):
    now = datetime.datetime.now(
        datetime.timezone(datetime.timedelta(hours=+9), 'JST'))
    created_at = now.isoformat(timespec='seconds')
    underline = '#' * len(filename)
    with open(f'content/{now.year}/{filename}.rst', 'w') as f:
        f.write(f"""{filename}
{underline}

:slug: {filename}
:date: {created_at}
:modified: {created_at}
:tags: Python, Pelican
:category: N/A

.. SUMMARY_END

""")

便利だったのでPelican向けに自作しました。

invoke経由でinvoke new 新しい記事のファイル名のように使います。新しいファイルはcontent/{年}/{ファイル名}.rstに作られます。年が入るのは私の好みです。

Pelicanはpelican-quickstartというコマンドでブログの雛形を作るとMekafileやinvokeのtasks.pyを生成してくれます。このnewコマンドはtasks.pyに追記しました。

なぜNikolaからPelicanに戻したのか

きっかけは「?」

私は記事のタイトルが極力そのまま記事のURLに含まれるようにしたいと考えています。記事のタイトルを後から修正した場合は例外です。

今日投稿したばかりの「posts/2020/SqlHueyに?を含んだパスワードをDSNを文字列の一部として与えると、期待通りにパースされずにデータベースに接続できない」は記事のタイトルに?を含んでいます。?はURLの中で?から先をパラメータにするという特別な意味を持ちます。この場合posts/2020/SqlHueyにがパス、を含んだパスワードをDSNを文字列の一部として与えると、期待通りにパースされずにデータベースに接続できないがパラメータです。この記事のようにURLに?を含めたいが?を特別な意味に取ってほしくないとき、?をURLエンコードした%3Fを?の代わりにURLに入れます。

?を使うから問題になると分かっているのだから、?を消すなり-などの別の文字を代わりにつかうなりしたら良いとも思うのですが、今回の渡しは?を扱えるようにしたい気持ちが勝りました。

件の記事はタイトルが長いので、ここからは投稿の?URLを例にします。

Nikolaはビルドが止まる

Nikolaは記事中にメタデータとしてslugを書くことで記事のパスを指定できます。このslugに投稿の?URLを与えてビルドすると、sitemap.xmlの生成中に投稿のが見つからないという報告をし、異常終了します。sitemap.xmlの生成後に記事が実際にあるかのチェックをしているように見えました。この時点でslugに含まれた?を期待通りに扱えていないことが分かります。

この動作を修正できないか、できればパッチを送ろうかと考えてNikolaのソースコードを読み始めたのですが、私には難解過ぎるものでした。このsitemap.xmlを解決してもその先で同じような問題が出てくるかもしれません。

Pelicanはビルドはできるが、リンクが切れる

PelicanもNikolaと同じ様にメタデータにslugがあります。同じく投稿の?URLを与えビルドすると、ビルドが正常に完了しました。しかし記事の一覧からその記事へのリンクが投稿の?URL.htmlと?をそのまま含んだものになっているため、リンク切れを起こしていました。

Pelicanの問題は簡単に解決

修正のためにPelicanのソースコードも読みました。こちらはNikolaのものと違い、私に分かりやすいものでした。本体に修正をしてパッチを送るのも良いですが、本体に修正を施さずとも、テンプレートでも解決できるし、プラグインでも解決できることが分かりました。ここで言うテンプレートはNikolaの良いところで言っていた雛形とは別で、HTMLのレイアウトを定義したものです。

このブログではテンプレートで解決しています。

Pelicanのテンプレート内で記事へのリンクを張るには通常article.urlで記事のURLを得ます。例えば<a href="/{{ article.url }}">{{ article.title }}</a>のように使います。しかしarticle.url投稿の?URL.htmlのように?をそのまま出力してしまうので、?を%3Fに置換します。この置換はJinja2のフィルタを定義すれば簡単にできます。

pelicanconf.pyに以下を定義します。

JINJA_FILTERS = {
    'permalink': lambda article: article.url.replace('?', '%3F')
}

これでテンプレート内でarticle|permalinkが使えるようになりました。先の例を書き直すと<a href="/{{ article|permalink }}">{{ article.title }}</a>です。このブログのテンプレートは自作なので、この記事へのリンクを書き直すのは容易でした。

プラグインで修正するのなら、articleのurlを置換すると良いでしょうが、こちらは確認していません。

戻った理由を一言で言うと、Pelicanのソースコードの方が好み

ソースコードを修正する機会はそれほどないと思いますが、それでも修正したくなった時に簡単に修正できることは魅力です。