Github Pages を Sphinx で

2015/04/19

GitHub Pages というシステムがある。これは、 GitHub<username>.github.io といったプロジェクトを作成すると、そこに配置したファイルがこの URL で公開されるサービスとなっている。

Python には Sphinx (日本語) といったドキュメンテーションツールがあるので、使えないかと思い試した。

結果使えそうなので、以下に流れをまとめる。

細かい点は github のリポジトリ をみてください。

tl;dr

  • ソースと jptomo.github.io のリポジトリを分割した。

  • GitHub Pages では _static などのアンダーバー始まりのディレクトリは 公開できないので、 Sphinx の実行時にパッチを当てるようにした。

  • CSS を追加した。

  • サイドバーのリンクを書き換えた。

  • 日付けのロール my_date を作成した。

ソースコード分割

GitHub Pages ではリポジトリ直下の index.html が先ず表示される。

Sphinxmake html したものがリポジトリ直下に欲しかった。

これを解決するため、記事のソースコードと、 GitHub Pages のコードを分けた。

アンダーバー対策

GitHub Pages では、アンダーバーを接頭辞として持つディレクトリを公開できない。

Sphinx は標準では _static/ などのディレクトリに CSSファイルを書き出すので、対策が必要。

tk0miya さんのロール作成サンプルがあったので 流用させていただきました。ありがとうございます。

以下コード (github):

# -*- coding: utf-8 -*-
#
# Sphinx extension for renaming _static/ directory
#
# Author: Takeshi KOMIYA / License: BSD
#
import re
import os
import shutil


NEW_STATIC_NAME = 'static/'
NEW_SOURCES_NAME = 'sources/'


def on_builder_inited(app):
    if app.builder.name == 'html':
        for old, new in [('_static', NEW_STATIC_NAME),
                         ('_source', NEW_SOURCES_NAME)]:
            replacer = (lambda uri: re.sub(r'^{}/'.format(old), new, uri))
            for i, f in enumerate(app.builder.script_files):
                app.builder.script_files[i] = replacer(f)
            for i, f in enumerate(app.builder.css_files):
                app.builder.css_files[i] = replacer(f)


def on_html_page_context(app, pagename, templatename, context, doctree):
    original_pathto = context['pathto']

    def pathto(otheruri, *args, **kwargs):
        otheruri = re.sub(r'^_static/', NEW_STATIC_NAME, otheruri)
        otheruri = re.sub(r'^_sources/', NEW_SOURCES_NAME, otheruri)
        return original_pathto(otheruri, *args, **kwargs)

    context['pathto'] = pathto


def on_build_finished(app, exception):
    if app.builder.name == 'html' and exception is None:
        for old, new in [('_static', NEW_STATIC_NAME),
                         ('_sources', NEW_SOURCES_NAME)]:
            old_dir = os.path.join(app.outdir, old)
            new_dir = os.path.join(app.outdir, new)
            if os.path.exists(old_dir):
                if os.path.exists(new_dir):
                    shutil.rmtree(new_dir)
                shutil.move(old_dir, new_dir)


def setup(app):
    app.connect('builder-inited', on_builder_inited)
    app.connect('html-page-context', on_html_page_context)
    app.connect('build-finished', on_build_finished)

CSS 追加

Sphinx のテーマは組込の alabaster (github) を利用した。

ただ、サイドバーを右側に寄せたかったので、 CSSを追加し、文章とサイドバーの位置を逆にした。

conf.py の末尾に以下の記載をする。

def setup(app):
    app.add_stylesheet('style.css')

その上で、以下のCSS (github) を _static/ 配下に配置する。

@charset "utf-8";

div.bodywrapper {
  margin: 0;
  width: 730px;
}

div.sphinxsidebar {
  float: right;
  width: 230px;
}

サイドバー修正

標準の前の記事、次の記事、を少し書き換えて、 Top ページへのリンクにしたかった。

先ずは、 conf.py の以下の設定を書き換える。

html_sidebars = {
    '**': [
        'side_index.html',
        'searchbox.html',
        'side_genindex.html',
    ]
}

後は、先程追加した html ファイルを _templates 配下に作成すれば良い。

以下は作成したもの。 なお、 searchbox.html は組込である。

  • side_index.html (github)

    <h3><a href="/index.html">{{ _('Top') }}</a></h3>
    
  • side_genindex.html (github)

    <h3><a href="/genindex.html">{{ _('Index') }}</a></h3>
    

日付のロール作成

ブログタイトル直下の日付を my_date といったロールを作成して、作りたかった。

またまた tk0miya さんのロール作成サンプルがあったので 流用させていただきました。ありがとうございます。

以下コード (github):

# -*- coding: utf-8 -*-
import os
from textwrap import dedent

from docutils.parsers.rst import roles


def on_builder_inited(app):
    roles.register_local_role(
        'my_date',
        roles.CustomRole(
            'my_date',
            roles.generic_custom_role,
            {'class': ['my_date']}, []))


def on_html_collect_pages(app):
    cssdir = os.path.join(app.builder.outdir, '_static')
    cssfile = os.path.join(cssdir, 'roles.css')
    if not os.path.exists(cssdir):
        os.makedirs(cssdir)
    with open(cssfile, 'w') as fp:
        fp.write(dedent('''
        @charset "utf-8";

        span.my_date {
            display: block;
            text-align: right;
            padding-right: 1em;
        }
        ''').strip())
    return ()


def html_page_context(app, pagename, templatename, context, doctree):
    if 'css_files' in context:
        context['css_files'].append('_static/roles.css')


def setup(app):
    app.connect("builder-inited", on_builder_inited)
    app.connect("html-collect-pages", on_html_collect_pages)
    app.connect("html-page-context", html_page_context)

終わりに

Google アナリティクス や各種ソーシャルボタン、コメント機能 (DISQUS) をつけたい。そのうち、そのうちね。