サイト作った

Posted on 2014-01-26 tags: haskell, website

作ってみた

もう何番煎じかわからないレベルだけど HakyllMighttpd で作ってみた。

Hakyll は Ruby 製の Jekyll の Haskell クローンで、これらは 静的サイトジェネレータ と呼ばれる類のソフトウェアである。 Hakyll はバックグラウンドに Pandoc を使っており Pandoc のサポートする書式ならなんでも使える (たぶん) 。 Ruby も好きなので Jekyll を使うのもアリだったが、遠い昔よくわからずに使ってみてなんだかよくわからんなあという印象が先行してしまったため今回は Hakyll にした。

また Mighttpd (マイティー) は Haskell 製の HTTP サーバで、 nginx よりパフォーマンスが出るらしい。ソースコードは1500行程度で非常にシンプル。今回は使うだけで中身を全然触れていないのだけどそのうちこれも読む。

作成の際には Hakyll 公式ページ はもちろん、このネタの日本での第一人者と思われる @tanakhさんのページ も大いに参考にした。

経緯など

これまでに何度か自分のブログを作ったことはあった。最初は はてなダイアリー 、次は GitHub Pages を使ったもので、最後に はてなブログ である。しかしそのどれもが半年〜1年ほどしか続かなかった。気分を変えるために異なるサービスをいくつか使ってみたが、結局長続きするものはなかった。

なぜ長続きしなかったのか。生活環境の変化により文章を書くのが厳しくなったとか、デザインがみんな同じで気に入らなかったとか、とか、そういう話はある。しかし重要なのは本気度が足りないことだと思った。エンジニアは常に学ばなければ死んでしまう生き物である。学ぶ際にはインプットだけではあまり意味がなく、アウトプットこそが重要であることはよく知られている。これから自分のドメインを使ってきちんとアウトプットしていくという決意も込めて新しく作ることにした。自分のお金でやるならきちんと書くだろう。たぶん。この記事はその第一歩である。

最近大学の後輩たちの間で日記コンテンツを作るのが流行しており、本サイトもその影響を受けたことは否めない。

技術的な話

Hakyll

ルーティングの書き方など最初は見よう見まねであったが、しばらくいじると感覚がつかめてくる。そこで少し複雑な設定も行ったのでそれについて書くことにする。例えば Hakyll では記事のメタデータを以下のようなフォーマットで記事の先頭に記述する。

1
2
3
4
5
----
title: タイトル
description: 概要
published: 2014-01-05
----

ここで横着な私は published を書くのが面倒くさいと思った。そこで見てみると、世の Hakyll を使っているサイトには URL が YYYY-mm-dd の形式になっているものが多い。 Hakyll のソースを見てみると確かに Hakyll.Web.Template.Context.getItemUTCpublisheddate フィールドがない場合はファイル名から読むようになっている。これを使えばいい。しかし気に入らないのはその形式で、 YYYY-mm-dd-* 固定となっているのだ。すべてのファイルを YYYY-mm-dd-* のように配置するのか。これは嫌だ。例えば年ごとにディレクトリを変えたい、しかし YYYY/YYYY-mm-dd-* は冗長だ。

そこでソースを読みつつ YYYY/mmdd* の形式にできるようにした。まず Hakyll.Web.Template.Context.field 関数の型は

1
field :: String -> (Item a -> Compiler String) -> Context a

となっており、

を指定する。 toFilePath $ itemIdentifier item でファイルパスが取得できるので、

1
2
3
4
5
6
7
dateField :: String -> Context String
dateField key = field key $ item -> do
    let name = toFilePath $ itemIdentifier item
        dirname = takeDirectory name
        basename = takeBaseName name
        dateString = takeFileName dirname ++ take 4 basename
    ...

とすれば、 */YYYY/mmdd* というファイルパスから YYYYmmdd 形式の文字列が得られる。これをよしなにフォーマットして return すればいい。一応フォーマット文字列も引数に取るようにして、最終的には次のようになった。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import Data.Time.Format (formatTime, parseTime)
import System.FilePath (takeBaseName, takeDirectory, takeFileName)
import System.Locale (defaultTimeLocale)

dateField :: String -> String -> Context String
dateField key format = field key $ \item -> do
    let name = toFilePath $ itemIdentifier item
        dirname = takeDirectory name
        basename = takeBaseName name
        dateString = takeFileName dirname ++ take 4 basename
        time :: UTCTime
        time = fromMaybe
            (error $ "file `" ++ name ++ "` doesn't match to format `%Y/%m%d*`")
            (parseTime defaultTimeLocale "%Y%m%d" dateString)
    return $ formatTime defaultTimeLocale format time

これを使うには次のように Context を指定すれば良い。

1
2
3
4
import Data.Monoid (mconcat)

ctx :: Context String
ctx = mconcat [ dateField "published" "%Y-%m-%d", defaultContext ]

また、記事の一覧などのページで Hakyll.Web.Template.List.recentFirst という関数を使いたかったのだがこれも getItemUTC を使っているため動かない。そこで単純にその記事のファイルパスを降順にソートする関数を定義してそれを代わりに使うことにした。

1
2
3
4
5
6
7
8
9
10
11
import Control.Arrow ((***))
import Control.Monad (join)
import Data.List (sortBy)

recentFirst :: (MonadMetadata m, Functor m) => [Item a] -> m [Item a]
recentFirst = return . sortBy (flip cmp)
  where
    cmp :: Item a -> Item a -> Ordering
    cmp = curry $ uncurry compare . map' (toFilePath . itemIdentifier)
    map' :: (a -> b) -> (a, a) -> (b, b)
    map' = join (***)

本当は description を書くのも面倒なので、自動でその記事のタイトルと最初のパラグラフあたりをとってくるようにできないかと思っている。これは後で試す。

デザイン

デザインは悩みの種だったが、適当に CSS テンプレートを探して気に入ったものを使うことにした。

商用サイト向けと書いてあるが個人でも問題ないようなのでありがたくいただいた。横幅や文字サイズなど少しだけカスタマイズし、またソースコードハイライト用の CSS も書いて使っている。

右上に表示しているアイコンはそれぞれ次の 32px のものを使った。 Feed Icons は適当に編集してある。

サーバ

個人用途の VPS では さくらの VPS が一番安定なのだけど、どうせ静的ファイルを置くだけなので安さをとって ServersMan@VPS にした。安いだけあってなかなか厳しいスペックだけどなんとかなるだろう。ちなみに Mighttpd で立てた HTTP サーバに ab でベンチとったら 700 QPS くらいだった。まあこんなにアクセスが来ることはたぶんない。

ディストリビューションは CentOS に嫌気がさしていたので Ubuntu で。ドメインはずっとお金を払いつつもほとんど使っていなかった daimatz.net を使う。

流行の言葉に Infrastructure as Code というものがある。サーバ設定を完全にスクリプトに落としこんでしまい、手作業をなくすという考え方だ。これまでの経験から自分個人が運用しているサーバは頻繁にリプレースしたくなるものと思っていたが、これは「もっとよい構築方法があるはず」とか「設定を整理しておこう」という意識が働いているからだと思った。流行に乗ってサーバ設定を完全にコードに落としこんでしまうことにすれば新しいソフトウェアを使うときが来ても安心である。

サーバ設定ツールとして Chef, Ansible, Fabric を試してきたが、 Fabric が一番手に馴染んだのでそれを使っている。冪等性とか言われているけど実際完全に冪等性を担保するのは難しいと思っていて、いっそ冪等性を完全に捨てても使いやすいものを使う方針である。やっぱ冪等性ほしいわ〜となったときに cuisine は使うかもしれない。

まとめのような何か

Hakyll というか Haskell の良いところで型を見れば大体の処理がわかるというのがあって、今回も大きな困難なく作ることができた。 Scala を最近書いているが Scala にはあまりその文化がないように感じていて、どうしても実装を見なければいけないことが多いと思う。というか Scaladoc を見るよりコードを見るほうが早いことが多い。そのぶんコードを読む習慣がついたのはよかったのだけど、やはり Haskell の文化が恋しいなあと思うわけです。あと ghc-mod がとても使いやすい。 Vim から使える のもすばらしい。

環境作ったのできちんとアウトプットしていこうと思った。

comments powered by Disqus
ページのトップへ戻る