nanoc導入メモ 3/5 「カスタマイズ」編

nanocはデフォルトでは極めてシンプルな静的Webサイトを生成するだけですが、 付属のヘルパを利用することで新着記事表示やタグ分類機能などが使用できるようになります。

また、hamlやsassといったDSLを利用することでサイトの構築・メンテナンスを効率化したり、 Markdownおよび独自のフィルタによって記事作成時の手間を軽減することができます。

今回は、このサイトを構築するにあたり実施したカスタマイズの一部を紹介します。

Markdown, Haml, Sassのフィルタを適用する

まずはサイト構築にあたり面倒なHTMLとCSSのコーディングを避けるためにMarkdown, Haml, Sassを使えるようにします。

Markdownパーサのインストール

Markdownパーサの実装はたくさんあるので迷うところですが、私は多機能かつ処理の早そうなRedcarpetを選びました。Markdownパーサの種類については以下のページが参考になります。

RedcarpetはC実装を含むためインストールにはDevKitが必要です。RubyInstaller - Downloadsからダウンロードして以下のコマンドでインストールします。

> ruby dk.rb init
config.ymlが生成されるので末尾にRubyのインストールディレクトリを追加
    - C:/path/to/ruby-install-dir
> ruby dk.rb install
> gem install redcarpet

Haml, Sassのインストール

HamlとSassはインデントでタグの入れ子構造を表すテンプレート言語です。SassにはSCSSというCSSの文法に近づけた派生版がありますが、Sassの方が記述量が少なくて済むのでおすすめです。

> gem install haml
> gem isstall sass

フィルタの適用

「Rules」ファイルを以下のように編集することで、コンテンツの拡張子を基に各フィルタを適用することができます。

compile '*' do
  if item.binary?
    # don't filter binary items
  else
    case item[:extension]
      when 'md'
        filter :redcarpet, :options => {:fenced_code_blocks => true}
        layout 'default'
      when 'haml'
        filter :haml
        layout 'default'
      when 'sass'
        filter :sass
    end
  end
end

route '*' do
  if item.binary?
    item.identifier.chop + '.' + item[:extension]
  else
    case item[:extension]
      when 'sass'
        item.identifier.chop + '.css'
      else
        item.identifier + 'index.html'
    end
  end
end

layout '*', :haml, :ugly => true

最後のlayoutルールでhamlのフィルタに設定している「:ugly => true」オプションは、出力するHTMLのインデントを取り除くもので、preタグに余計な空白が入ることを防ぐために必要になります。(参考:Markdown and code block indentation - nanoc | Google グループ)

CSSフレームワークのCompassとsusyを利用する

グリッドレイアウトのCSSフレームワークを使用することでクロスブラウザ対応のnカラムレイアウトを簡単に実現することができます。

グリッドレイアウトのフレームワークとしてはBlueprintが有名ですが、今回はよりシンプル?なsusyを利用してみました。(なおBlueprintが固定幅なのに対し、susyは可変幅という特徴があるようです)

Compass, susyのインストール

susyはCompassのプラグインとして実装されているため、先にCompassをインストールしておきます。 Compassではsassで利用できる便利なMix-inが多数定義されています。

> gem install compass
> gem install compass-susy-plugin

以下のコマンドでsusyを使ったCompassプロジェクトのひな形を生成しておきます。

> compass create myproject -r susy -u susy -x sass

Compass, susyの適用

nanocのサイトフォルダに「.compass」フォルダを作成し、上記の「compass create」で生成された「config.rb」をコピーし、設定をnanocに合わせ変更します。

# Require any additional compass plugins here.
require 'susy'

# Set this to the root of your project when deployed:
http_path = "/"
project_path = File.expand_path(File.join(File.dirname(__FILE__), '..'))
css_dir = "output/stylesheets"
sass_dir = "content/stylesheets"
images_dir = "content/images"

# You can select your preferred output style here (can be overridden via the command line):
output_style = :compressed

# To enable relative paths to assets via compass helper functions. Uncomment:
# relative_assets = true

# To disable debugging comments that display the original location of your selectors. Uncomment:
line_comments = false

preferred_syntax = :sass

また、「sass」フォルダにある「screen.sass」と「_base.sass」を「content/stylesheets」配下にコピーします。以降、スタイルは「screen.sass」に記述します。

Rulesに以下を追加することでCompassとsusyが利用可能になります。

require 'compass'

Compass.add_configuration "#{File.dirname(__FILE__)}/.compass/config.rb"
sass_options = Compass.sass_engine_options

compile '*' do
  if item.binary?
    # don't filter binary items
  else
    case item[:extension]
      # ~~~中略~~~
      when 'sass'
        filter :sass, sass_options.merge(:syntax => item[:extension].to_sym)
    end
  end
end

susyのグリッドレイアウトは各要素に「full」「columns」「alpha(左寄せ)」「omega(右寄せ)」といったMix-inを加えることで定義します。

参考までに、当サイトのレイアウトは以下のようになっています。

#page
  +container
  #header
    +full
  #nav
    +full
  #section
    +columns(8)
    +alpha
  #sidebar
    +columns(4)
    +omega
    #side_left_nav
      +columns(2,4)
      +alpha(4)
    #side_right_ad
      +columns(2,4)
      +omega(4)
  #footer
    +full

Bloggingヘルパ、Taggingヘルパを利用する

ブログに必要な「トップページ・サイドバーへの新着記事表示」「RSS配信」「アーカイブページ」「タグ機能」などを実装していきます。

「lib/default.rb」で以下のヘルパをincludeしておきます。なお、下記の一行目はRuby1.9のマジックコメント、末尾はraise_encoding_error対策です。

# coding: utf-8

include Nanoc::Helpers::Blogging
include Nanoc::Helpers::LinkTo
include Nanoc::Helpers::Tagging

Encoding.default_external = 'UTF-8'

Nanoc::Helpers::Blogging

Bloggingヘルパでは記事となるコンテンツに「kind: article」と「created_at: <タイムスタンプ>」の属性を加えることになっています。

各記事で毎回これらの属性を指定するのは面倒なので、「Rules」に以下を追加して「articles」フォルダ配下のコンテツに自動的に属性を設定するようにしました。(ファイル名の先頭を「YYYY-MM-DD」とする前提です)

preprocess do
  articles = items.select {|item| item.identifier =~ %r|^/articles/.*/|}
  articles.each do |item|
    item.attributes[:kind] ||= "article"
    item.attributes[:created_at] ||= item.identifier.match(/\d\d\d\d-\d\d-\d\d/).to_s
  end
end
新着記事表示

トップページを表示する「content/index.haml」は以下のように記述します。当サイトでは記事本文のh3以降を「続きを読む」で表示するようにしています。

- sorted_articles[0, 6].each do |article|
  %h2 #{link_to(article[:title], article.path)}
  - if article_desc_match_data = article.compiled_content.match(/^.*?(?=<h3>)/m)
    = article_desc_match_data.to_s
    %p.readmore
      = link_to("記事の続きを読む &raquo;", article.path)
  - else
    = article.compiled_content

サイドバーに新着記事のリストを表示するには「layouts/default.haml」で以下のように記述します。

%h2 Recent posts
%ul.recent_posts
  - sorted_articles[0, 6].each do |article|
    %li #{link_to(article[:title], article.path)}
RSS配信

RSS配信(Atom feedの生成)を行うためには追加でbuilderのgemが必要です。

> gem install builder

まず、「config.yaml」でAtom feedの生成に使われる属性を設定します。

base_url: http://n.blueblack.net
title: ナレッジエース
author_name: nase
author_uri: http://n.blueblack.net

次に「content/atom_feed.erb」を以下の内容で作成します。

<%= atom_feed :limit => 6 %>

また、「Rules」に以下のルールを追加します。

compile '/atom_feed' do
  filter :erb
end

route '/atom_feed' do
  '/atom_feed.xml'
end

最後に「layouts/default.haml」のhead内に以下を追加して完了です。

%link{:rel => "alternate", :type => "application/atom+xml", :title => "Atom feed", :href => "/atom_feed.xml"}/
アーカイブページ

過去記事を月毎の一覧で出力する方法を紹介します。(参考:Using nanoc to create a list of blog articles, sorted by month and year)

「lib/default.rb」に以下のメソッドを追加します

def grouped_articles
  sorted_articles.group_by do |a|
    [ Time.parse(a[:created_at]).year, Time.parse(a[:created_at]).month ]
  end.sort.reverse
end

以下の内容で「content/archives.haml」を作成すれば月毎に記事一覧が表示されます。

%h2 記事一覧
- grouped_articles.each do |yearmonth, articles_this_month|
  %h3 #{yearmonth.first}年#{yearmonth.last}月
  %ul
    - articles_this_month.each do |article|
      %li
        %span #{Time.parse(article[:created_at]).strftime("%d日")}
        = link_to(article[:title], article.path)

Nanoc::Helpers::Tagging

Taggingヘルパの基本的な利用方法は「Getting Started」編の「Writing some Custom Code」で見たとおりです。

当サイトではNanoc のカスタマイズ - タグを管理するで公開されているタグ毎の記事数をカウントするメソッドを使わせていただいています。(「lib/default.rb」に以下を追記)

module TagUtil
  def count_by_tag(items = nil)
    items = @items if items.nil?
    count_by_tag = Hash.new(0)
    items.each do |item|
      if item[:tags]
        item[:tags].each do |tag|
          count_by_tag[tag] += 1
        end
      end
    end
    count_by_tag
  end
end
include TagUtil

「layouts/default.haml」は以下のようになります。

#article
  - if @item[:kind] == 'article'
    %h2 #{link_to(@item[:title], @item.path)}
    %p.article_meta Created at: #{@item[:created_at]} Tags: #{tags_for(@item, {:base_url => "/tags/index.html#"})}
  = yield
-#~~~中略~~~
#sidebar
  -#~~~中略~~~
  %h2 Tags
  - tags = count_by_tag(@items)
  - if tags
    %ul
      - tags.sort_by{|e| e[0]}.each do |k, v|
        %li #{link_for_tag(k, "/tags/index.html#")} x #{v}
  - else
    %p (none)

生成するページ数を削減するためタグ毎の記事一覧はまとめて一つのページに出力し、ページ内のアンカーにリンクするようにしています。タグ毎の記事一覧となる「content/tags.haml」の内容は以下のとおりです。

%h2 タグ一覧
- tags = count_by_tag(@items)
- if tags
  - tags.sort_by{|e| e[0]}.each do |k, v|
    %h3{'id' => "#{k}"} #{link_for_tag(k, "/tags/index.html#")} x #{v}
    %ul
      - items_with_tag(k).sort_by{|e| e[:created_at]}.reverse.each do |item|
        %li= link_to(item[:title], item.path)
- else
  %p (none)

おわりに

予想以上に長くなってきたので「カスタマイズ」編は以上とします。書ききれなかった部分について、「Markdown独自拡張」編と「運用効率化バッチ」編として別途掲載します。

連載一覧

blog comments powered by Disqus