Nikola を始める話
2018-10-27

目次

前回の引っ越し から 約半年ほどしか経っていませんが 先日ブログを Miyadaiku から Nikola に変えました。

ついでに少しレイアウトも変えたけど多少は見やすくなりましたか?

Nikola は Miyadaiku と同じく Python製の 静的サイトジェネレータです。

実は前から少し気になってました(意味深

経緯 (Circumstances)

このブログの場合は技術記事をメインで書いています。

特殊な例ですが、記事ごとに環境を分けられれば状況の再現や検証が容易なので 設定ファイルなどを記事と同じディレクトリに置いて管理をしたいと思っていました。

そのとき 関係ないファイルがビルドされるとエラーが発生したりビルドが長引いたりするため、 ビルド対象のファイルから除外する必要がありました。

Nikola は除外設定ではありませんが、ビルド対象のファイルを自分で選択できるようになっているので 要件を満たすことができ、採用となりました。

始め方 (How to get started)

詳しくは Getting Started | Nikola を見ればいいんですが少しだけ解説。

インストール (Installation)

必要であれば仮想環境を作ってから

$ python3 -m venv venv
$ source venv/bin/activate

nikolaをインストールします。

(venv) $ pip install nikola

簡単ですね。

初期化 (Initialization)

init コマンドを使って サイトの初期化ができます。

(venv) $ nikola init --demo sitename

Creating Nikola Site
====================

This is Nikola v8.0.0.  We will now ask you a few easy questions about your new site.
If you do not want to answer and want to go with the defaults instead, simply restart with the `-q` parameter.
--- Questions about the site ---
Site title [My Nikola Site]:
Site author [Nikola Tesla]:
Site author's e-mail [n.tesla@example.com]:
Site description [This is a demo site for Nikola.]:
Site URL [https://example.com/]:
Enable pretty URLs (/page/ instead of /page.html) that don't need web server configuration? [Y/n]
--- Questions about languages and locales ---
We will now ask you to provide the list of languages you want to use.
Please list all the desired languages, comma-separated, using ISO 639-1 codes.  The first language will be used as the default.
Type '?' (a question mark, sans quotes) to list available languages.
Language(s) to use [en]:

Please choose the correct time zone for your blog. Nikola uses the tz database.
You can find your time zone here:
https://en.wikipedia.org/wiki/List_of_tz_database_time_zones

Time zone [Asia/Tokyo]:
    Current time in Asia/Tokyo:
Use this time zone? [Y/n]
--- Questions about comments ---
You can configure comments now.  Type '?' (a question mark, sans quotes) to list available comment systems.  If you do not want any comments, just leave the field blank.
Comment system:

That's it, Nikola is now configured.  Make sure to edit conf.py to your liking.
If you are looking for themes and addons, check out https://themes.getnikola.com/ and https://plugins.getnikola.com/.
Have fun!

ディレクトリに移動するとこんな感じのファイル構成になってます。

(venv) sitename $ ls
README.txt conf.py    files      galleries  images     listings   pages      posts      templates

用途としては、次のような感じだと思う。

conf.py:

Nikolaの設定ファイル。

files/:

記事以外の静的ファイル(ビルド対象でないファイル)を入れるディレクトリ。そのままの構成で output に出力される。

galleries/:

記事として公開したい画像などのメディアファイルを入れるディレクトリ(使ってないので詳細は不明)

images/:

画像ファイルを入れるディレクトリ。サムネイルも勝手に作ってくれるっぽい?(使ってないので詳細は不明)

listings/:

ファイルの一覧を見せるためのディレクトリ。ソースファイルとかを入れるだけで一覧ページを作ってくれる(使ってないので詳細は不明)

pages/:

固定ページを見せるためのディレクトリ(使ってないので詳細は不明)

posts/:

記事を入れるディレクトリ。ブログ等の時系列な記事はこの中に入れる。

templates/:
  • 記事のテンプレートを入れるディレクトリ。
  • テーマのテンプレートではない
  • 今の所は使ってないため詳細はわからないが、便利だったら使うかも

ブログ記事を書く場合、 new_post コマンドを使うと posts/ ディレクトリに空の記事ファイルを作成してくれます。

(venv) sitename $ nikola new_post
Creating New Post
-----------------

Title: test
Scanning posts........done!

ファイルの中身はこんなかんじ

.. title: test
.. slug: test
.. date: 2018-10-09
.. tags:
.. category:
.. link:
.. description:
.. type: text

Write your post here.

備考

posts/ 配下にフラットに記事ファイルを置いておくのが Nikola で想定される一般的な使い方だと思います。 個人的な意見を言わせてもらうと記事が多くなるにつれて、運用するのが辛くなってくるので、 頻繁に見返したり書き換えたりする場合はディレクトリを区切ったほうがよいと思います。 区切った場合であっても その中を辿ってビルドしてくれます。

Miyadaiku の場合は このディレクトリ構成を全く同じように保ってファイルを出力するのですが、 Nikola の場合は 記事を配置したディレクトリパス(posts/ からの相対)を起点として slug に指定した文字列をつなげたのが最終的な公開パスとなります。

この記事は posts/2018/start-nikola/ というディレクトリの index.rst というファイルに 書いているんですが、slug は常に index としています。 これにより記事を置いているディレクトリパスがそのまま公開パスとなります

前述した new_post コマンドは ファイルしか作成できないため、 自分の場合は 記事の雛形となるディレクトリを毎回コピーして作るようにしています。 (これはMiyadaikuの頃から同じ)

参考
sitemap.xml includes not published posts

ビルド (Build)

記事のビルドは build を使います。

(venv) sitename $ nikola build
Scanning posts........done!
.  scale_images:output/images/frontispiece.jpg
.  scale_images:output/images/illus_001.jpg
.  render_posts:timeline_changes
.  render_posts:cache/pages/dr-nikolas-vendetta.html
.  render_posts:cache/pages/path_handlers.html
.  render_posts:cache/pages/creating-a-theme.html
Trying "doc" as canonical role name.
.  render_posts:cache/pages/charts.html
.  render_posts:cache/pages/social_buttons.html
.  render_posts:cache/pages/listings-demo.html
.  render_posts:cache/pages/1.html
.  render_posts:cache/pages/bootstrap-demo.html
.  render_posts:cache/pages/extending.html
.  render_posts:cache/pages/internals.html
.  render_posts:cache/pages/manual.html
.  render_posts:cache/pages/quickref.html
.  render_posts:cache/pages/quickstart.html
.  render_posts:cache/posts/1.html
.  render_posts:cache/pages/theming.html
.  render_sources:output/pages/dr-nikolas-vendetta/index.rst
.  render_sources:output/pages/path-handlers/index.rst
.  render_sources:output/pages/creating-a-theme/index.rst
.  render_sources:output/pages/charts/index.rst
.  render_sources:output/pages/social_buttons/index.rst
.  render_sources:output/pages/listings-demo/index.rst
.  render_sources:output/pages/about-nikola/index.rst
.  render_sources:output/pages/bootstrap-demo/index.rst
.  render_sources:output/pages/extending/index.rst
.  render_sources:output/pages/internals/index.rst
.  render_sources:output/pages/handbook/index.rst
.  render_sources:output/pages/quickref/index.rst
.  render_sources:output/pages/quickstart/index.rst
.  render_sources:output/posts/welcome-to-nikola/index.rst
.  render_sources:output/pages/theming/index.rst
.  render_galleries:output/galleries
.  render_galleries:output/galleries/demo
.  render_galleries:output/galleries/index.html
.  render_galleries:output/galleries/rss.xml
.  render_galleries:output/galleries/demo/tesla4_lg.thumbnail.jpg
.  render_galleries:output/galleries/demo/tesla4_lg.jpg
.  render_galleries:output/galleries/demo/tesla_tower1_lg.thumbnail.jpg
.  render_galleries:output/galleries/demo/tesla_tower1_lg.jpg
.  render_galleries:output/galleries/demo/tesla_conducts_lg.thumbnail.jpg
.  render_galleries:output/galleries/demo/tesla_conducts_lg.jpg
.  render_galleries:output/galleries/demo/tesla_lightning2_lg.thumbnail.jpg
.  render_galleries:output/galleries/demo/tesla_lightning2_lg.jpg
.  render_galleries:output/galleries/demo/tesla_lightning1_lg.thumbnail.jpg
.  render_galleries:output/galleries/demo/tesla_lightning1_lg.jpg
.  render_galleries:cache/galleries/demo/index.html
.  render_galleries:output/galleries/demo/index.html
.  render_galleries:output/galleries/demo/rss.xml
.  copy_files:output/favicon.ico
.  copy_files:output/images/nikola.png
.  render_listings:output/listings/index.html
.  render_listings:output/listings/hello.py.html
.  render_listings:output/listings/hello.py
.  render_listings:output/listings/__pycache__/index.html
.  copy_assets:output/assets/css/bootblog.css
.  copy_assets:output/assets/css/bootstrap.min.css
.  copy_assets:output/assets/css/theme.css
.  copy_assets:output/assets/js/jquery.min.js
.  copy_assets:output/assets/js/bootstrap.min.js
.  copy_assets:output/assets/js/popper.min.js
.  copy_assets:output/assets/css/nikola_rst.css
.  copy_assets:output/assets/css/nikola_ipython.css
.  copy_assets:output/assets/css/html4css1.css
.  copy_assets:output/assets/css/rst.css
.  copy_assets:output/assets/css/ipython.min.css
.  copy_assets:output/assets/css/rst_base.css
.  copy_assets:output/assets/css/baguetteBox.min.css
.  copy_assets:output/assets/js/html5.js
.  copy_assets:output/assets/js/fancydates.js
.  copy_assets:output/assets/js/gallery.min.js
.  copy_assets:output/assets/js/fancydates.min.js
.  copy_assets:output/assets/js/gallery.js
.  copy_assets:output/assets/js/baguetteBox.min.js
.  copy_assets:output/assets/js/html5shiv-printshiv.min.js
.  copy_assets:output/assets/js/justified-layout.min.js
.  copy_assets:output/assets/js/moment-with-locales.min.js
.  copy_assets:output/assets/xml/atom.xsl
.  copy_assets:output/assets/xml/rss.xsl
.  copy_assets:output/assets/css/code.css
.  render_taxonomies:output/index.html
.  render_taxonomies:output/categories/cat_nikola/index.html
.  render_taxonomies:output/categories/blog/index.html
.  render_taxonomies:output/categories/demo/index.html
.  render_taxonomies:output/categories/nikola/index.html
.  render_taxonomies:output/categories/python/index.html
.  render_taxonomies:output/2012/index.html
.  render_taxonomies:output/archive.html
.  render_taxonomies:output/categories/index.html
.  render_pages:output/pages/handbook/index.html
.  render_pages:output/pages/quickstart/index.html
.  render_taxonomies:output/categories/python.xml
.  render_pages:output/pages/creating-a-theme/index.html
.  render_taxonomies:output/rss.xml
.  render_pages:output/posts/welcome-to-nikola/index.html
.  render_pages:output/pages/theming/index.html
.  render_pages:output/pages/internals/index.html
.  render_pages:output/pages/path-handlers/index.html
.  render_pages:output/pages/bootstrap-demo/index.html
.  render_taxonomies:output/categories/nikola.xml
.  render_pages:output/pages/charts/index.html
.  render_taxonomies:output/categories/cat_nikola.xml
.  render_pages:output/pages/social_buttons/index.html
.  render_pages:output/pages/listings-demo/index.html
.  render_pages:output/pages/dr-nikolas-vendetta/index.html
.  render_taxonomies:output/categories/blog.xml
.  render_pages:output/pages/about-nikola/index.html
.  render_pages:output/pages/quickref/index.html
.  render_pages:output/pages/extending/index.html
.  render_taxonomies:output/categories/demo.xml
.  create_bundles:output/assets/css/all-nocdn.css
.  create_bundles:output/assets/css/all.css
.  create_bundles:output/assets/js/all-nocdn.js
.  create_bundles:output/assets/js/all.js
.  sitemap:output/sitemap.xml
.  sitemap:output/sitemapindex.xml
.  robots_file:output/robots.txt

serve コマンドでサーバを起動できます。

(venv) sitename $ nikola serve
INFO: serve: Serving HTTP on 0.0.0.0 port 8000...

アクセスしてみると

nikola-top.png

見れましたね。やったぜ。

備考

(venv) sitename $ nikola auto とすることで サーバ起動ファイルの変更監視 を並行して行えます。

conf.py の修正

自分の場合は自分でテーマを作っているためいくつか関係のないところもありますが、 基本的には 次の箇所を変更し、ほかはいじってません。

BLOG_AUTHOR = "Righ"  # (translatable)
BLOG_TITLE = "くろのて"  # (translatable)
SITE_URL = "http://note.crohaco.net/"
BLOG_EMAIL = "なにか@gmail.com"
BLOG_DESCRIPTION = "書いたことは覚えたことにしよう"  # (translatable)

DEFAULT_LANG = "ja"

THEME = "cronote"

POSTS = (
    ("posts/*.rst", "", "post.tmpl"),
#    ("posts/*.md", "", "post.tmpl"),
#    ("posts/*.txt", "", "post.tmpl"),
#    ("posts/*.html", "", "post.tmpl"),
)
TIMEZONE = "Asia/Tokyo"

COPY_SOURCES = False
EXTRA_THEMES_DIRS = ['themes']

特筆するところは以下の3箇所です。

  • THEME = "cronote" はテーマの指定です。後述します。
  • COPY_SOURCES = False にしたのは 記事のHTMLの元になった rst が同じ名前(拡張子を除く)で勝手に公開されるのを防止するためです
  • POSTS の 第2インデックスを "" (空文字) にしているのは 記事が出力されるパスのプリフィックスを消すためです。
    • これにより元の記事のパスを変えずに保持できました。

テーマ (Theme)

設定ファイルの THEME 変数ではテーマ名を指定します。

私の場合は上記で cronote というテーマを使っているのですが、 これは自作のテーマです。

自作が面倒な場合は Themes for Nikola で 自分の好きなテーマを選択しましょう。(2018/10時点で)35種類あります。

自作する場合、このあたりを参考にして書くと良いでしょう。

jinja2mako というテンプレートエンジンが使えます。

罠 (Trap)

HTML の改変 (Modification HTML)

実は以下のツイートは Nikola へのリプレース直後に発生した問題でした。

Metaタグでの文字コード指定が抜けていたことが原因で発生していたので、 <meta charset="utf-8" /> に指定したら解消しました。

しかし、テンプレートには <meta http-equiv="content-type" content="text/html; charset=UTF-8" /> (確か) とちゃんと記述されていました。なんで削られたんだろう。謎です。

また、特殊なHTMLタグを描画したい場合も、期待通りに出力されないことがあります。

例えば私のブログでは Google のカスタム検索 プラグインを 埋め込んでるんですが、 以下のようなHTMLをテンプレートに記述したところ

<div>
<script>
  (function() {
    var cx = '004084638320604569659:o5gzqwuvr4g';
    var gcse = document.createElement('script');
    gcse.type = 'text/javascript';
    gcse.async = true;
    gcse.src = 'https://cse.google.com/cse.js?cx=' + cx;
    var s = document.getElementsByTagName('script')[0];
    s.parentNode.insertBefore(gcse, s);
  })();
</script>
<gcse:search></gcse:search>
</div>

<gcse:search></gcse:search> の部分が <search></search> に変換されてスクリプトが動かないということがありました。

たぶんビルド後のHTMLを一度パースしたときに 対応してない一部の情報が欠落してるんじゃないかなと思います。

パースの対象にならないように script タグの中で出力するとうまくいきます。

<script>
  document.write('<gcse:search></gcse:search>');
</script>

Tag のパーマリンクが小文字に変換される (Translation into lowercase)

簡単に言うと Python というタグは python というパーマリンクでアクセスできます。

このブログの場合は Python のタグは http://note.crohaco.net/tags/python/ です。

なので テンプレートを自作する場合は

<a href="/tags/{{ tag.lower() }}/">{{ tag }}</a>

のようにします。

v7 用のプラグインが v8 で動作しない (not working in v8)

実際に version 7 は使ったことないので正確にはわからないんですが、 一部の公式プラグインは version 8 で動作しませんでした。

少し直せば動くので v8 のプラグインを見ながら修正してみてください。 意外と簡単です。

作ったプラグインについては別記事で公開します。

docutils の writer が html4css1 で動作している (docutils writer mode)

Nikola は 内部的に docutils を使って描画しているんですが、 writer は html4css1 を使っているようです。 そのため、一部で意図しない出力となる可能性があります。

自分の場合は 前にMiyadaiku を使っていたときは、 field-list の出力が dl (definition list) タグで出力されていましたが、 Nikola に変えて table で出力されるようになりました。

たとえば

:URL: http://note.crohaco.net/
:名前: くろのて

みたいなやつです。

html4css1 モードで dl タグで出力したい場合は definition list 記法を使って以下のように書きます。

URL
  http://note.crohaco.net/
名前
  くろのて

警告

definition list ではヘッダと内容の間に空行を入れてはいけません。 (内容がただの引用文になります)

最初は原因が全くわからなかったんですが、 ぼくらの @shimizukawa さんが 10分 で解決してくれました
shimizukawa [10:08]
https://repo.or.cz/docutils.git?a=blame_incremental;hb=28086129e5087833cb8da00b920f92f3f533104f;f=docutils%2Fdocutils%2Fwriters%2F_html_base.py#l720
https://repo.or.cz/docutils.git?a=blame_incremental;hb=705901dedf01dcbe499f3eb1591bbaa255f59571;f=docutils%2Fdocutils%2Fwriters%2Fhtml4css1.py#l353
ふむ
使ってるdocutilsのwriterが違うんだね
`docutils.writers.html4css1` の場合、field-listはtableで出力される
`docutils.writers.html5_polyglot` の場合、field-listはdl/dtで出力される
Sphinxでも、html5 writerを使うオプションを有効にしたら同じ結果になりそう
http://www.sphinx-doc.org/ja/master/usage/configuration.html#confval-html_experimental_html5_writer

なんだただの神か

ちなみに dl タグを horizontal な テーブルのように表示するためには CSS を当てる必要があります。 今どきなら flexbox ですね。

参考
dl要素をtableのような段組レイアウトにする | 25egg:Webデザイナー備忘録

終わりに (Conclusion)

変える前は、Miyadaiku でできたことが全部 Nikola でできるのか?という不安があったんですが、 一応全部できたと思っています。

Nikola は プラガブルな設計になっているので 機能が足りない場合は プラグインを使うことで概ね補完できそうです。

社内だと matsupods さんが仲間です。 (The Pym Particle)

Docsはわかりやすくて、記事を書くまで特に苦労することはなかった感じです。 Travis CIの説明もわかりやすく、ファーストトライでできました。(Pelicanはかなり苦労していて、一晩潰れた記憶あります)

(from BP slack)

割と機能が豊富なので、みなさんもどうでしょう(巻き込んでいくスタイル)

参考