Practice of Programming

プログラム とか Linuxとかの話題

Nanocで記事ごとにUUIDを付けてリンク作ってPermalinkとする

静的ファイルジェネレータの弱点(と僕が勝手に思っている)は、URL=ファイルのパスというところです。 何が困るかと言うと、移動した場合にURLが変わってしまう。

これを解消するために、記事ごとにUUIDをつけて、実際のパスへのシンボリックリンクを作り、それをPermalinkとして使えば良いということです。

メタデータを利用する

Nanocのドキュメントの上部には、メタデータを入れることができます。

---
KEY: VALUE
---

のような感じです。ここに、UUID用に

---
NANOC_UUID: "....."
---

というのを、挿入します。こうすると、コンパイル時に attributeとして使えるようになります。layoutをhamlで書いていたとすると、

@item[:NANOC_UUID]

で、参照できます。filter内であれば、下記のように参照できます。

@item.attributes[:NANOC_UUID]

こうしておけば、permalinkようのURLとして、上記のUUIDを使ったURLを作るとか、 index tree を作るような処理のなかで、通常のパスの代わりに、UUIDのリンクの方を使うようにすればよいかと思います。

コード

最初 filterで作っていたのですが、こういうのはRules内のpreprocessでやるべきでした。filter内だと、attributeの変更はできませんが、preprocessであれば、attributeの変更ができます。

preprocess do
  @items.each do |item|
    path = item.raw_filename.sub(/^.+\/content\//, "content/")
    # markdownのみ対象
    next if path != nil && !path.match(/\.md$/);

    content = ''

    File.open(path, 'r') do |f|
      content += f.read
    end

    if content.match(/^NANOC_UUID: (.+)$/)
      # すでにある場合それを作る(とり方がちょっと雑ですが)
      uuid = $1
    else
      # 新しく作成して先頭に書き込む
      uuid = SecureRandom.uuid
      if (content.match(/\A---/))
        content = content.sub(/\A---/, "---\nNANOC_UUID: " + uuid + "\n")
      else
        content = "---\nNANOC_UUID: " + uuid + "\n---\n" + content
      end

      File.open(path, "w") do |f|
        f.puts(content)
      end
      # attribute に追加する
      item.attributes[:NANOC_UUID] = uuid
    end

    if (uuid)
      link = 'output/' + uuid + '.html'
      if !File.exist?(link)
        # output のルートに、実際のパスへのリンクを作る
        File.symlink(path.sub(/^content\/(.+)\.md$/, "\\1.html"), link)
      end
    end
  end
end

結果

こんな感じのリンクが出来上がるので、permlink として、b247771c-a07a-4cb6-835d-ee8b2d461e49.html の方を使えばいい感じです。

lrwxrwxrwx 1 ktat ktat    10 Apr 29 16:01 b247771c-a07a-4cb6-835d-ee8b2d461e49.html -> index.html
-rw-r--r-- 1 ktat ktat 12169 Apr 29 16:02 index.html

元のファイルにメタデータを追加しているので、nanoc コマンドのラッパーのshell scriptかなんかをかいて、git commit するようにしとくのが良いのではないかなと思います。

元のパスをURL欄からコピーされるのを防ぎたければ、パスにUUIDが含まれていない場合は、JavaScriptで強制的に移動させれば良いんじゃないかなと思います。

注意

filter :relativize_path を使っていると、相対パスになってしまうので、元ファイルの場所がルートに存在しない場合、シンボリックリンクをルートに置いている関係上、相性が悪いです。 cssやjsは、絶対パスで指定するようにする必要があります。