(7日目) Sphinx 拡張を作ってみよう (sphinxcontrib.nicovideo)

昨日の記事ではさまざまな Sphinx 拡張を紹介しました。
その中で YouTube の動画を埋め込む Sphinx 拡張を紹介しましたが、
その中には日本の誇るおもしろ動画サービス、ニコニコ動画用の Sphinx 拡張が見当たりませんでした。

ということで、今日は sphinxcontrib.youtube に対抗して sphinxcontrib.nicovideo を作りながら、
Sphinx 拡張の作り方を紹介したいと思います。

使い方

まずは sphinxcontrib.nicovideo の使い方から紹介します。
sphinxcontrib.nicovideo は easy_install でインストールできます。

$ easy_install sphinxcontrib-nicovideo

次に conf.py で sphinxcontrib-nicovideo モジュールを有効にします。

# sphinxcontrib-nicovideo モジュールを読み込む
extensions += ['sphinxcontrib.nicovideio']

sphinxcontrib-nicovideo モジュールを読み込むと nicovideo ロールと
nicovideo ディレクティブが利用できるようになります。

ニコニコ動画用のロールです。 :nicovideo:`sm14912041` と書くとリンクになります。
こうやって :nicovideo:`リンク <sm14912041>` を作ることができます。

次のように書くと動画を埋め込むことができます。

.. nicovideo:: sm14912041

サムネイル表示もできます。

.. nicovideo:: sm14912041
   :thumb:

ロールの作り方

それでは sphinxcontrib-nicovideo の中身を見ていきましょう。
バージョン 0.1.0 のソースコードここから参照することができます

まずは nicovideo ロールの定義部分です。
ロールの定義はハンドラ関数 nicovideo_role() で実現しています。
nicovideo_role() は 7個の引数を取っていますが、それぞれの詳しい定義については
docutils のリファレンスを参考にしてください。


reST でロールを利用するときは :[ロール名]:`[ロール文字列]` という書き方をしますが、
このロール文字列は 2種類の書き方を持っています。

  • `ID`
  • `リンクタイトル `

このロール文字列の処理は split_explicit_title() というユーティリティ関数が提供されているので、
ここでは、パース結果をもとにリンク(nodes.reference ノード)を生成しています。

def nicovideo_role(name, rawtext, text, lineno, inliner, options={}, content=[]):
    """Role for linking to nicovideo pages."""
    text = utils.unescape(text)
    has_explicit, title, movie_id = split_explicit_title(text)

    try:
        movie = NicoVideo(movie_id)
        if has_explicit == False:
            title = movie.title

        ref = nodes.reference(rawtext, title, refuri=movie.url)
        return [ref], []
    except:
        msg = inliner.reporter.error('fail to load nicovideo: %s' % movie_id,
                                     line=lineno)
        prb = inliner.problematic(rawtext, rawtext, msg)
        return [prb], [msg]


最後に作成したハンドラ関数を setup() で nicovideo というロール名で登録します。

def setup(app):
    app.add_role('nicovideo', nicovideo_role)

ここまでが nicovideo ロールの定義です。

ディレクティブの作り方

次に nicovideo ディレクティブを読み解いていきましょう。
ディレクティブはロールと比べると構造が複雑になっています。

Sphinx は docutils というライブラリをベースにしており、

  • reST 文書をノードに変換
  • 出力の際にノードを実形式に変換

という二段階の処理から構成されています。*1

では、まずソースコードのうち「reST 文書をノードに変換」に関わる部分から見ていきます。
まず、nicovideo ノードを定義しています。
この nicovideo ノードは reST 文書の「nicovideo ディレクティブ」を表現するためのノードです。

class nicovideo(nodes.General, nodes.Element):
    pass

次にディレクティブの定義をしています。
NicoVideoDirective クラスは「nicovideo ディレクティブ」を読み込み、
nicovideo ノードに変換するためのクラスです。

クラス変数としてディレクティブを定義(引数を取るか、どのようなオプションを取るか等)しています。
NicoVideoDirective クラスでは

  • 引数を必須にする(required_argument = 1)
  • オプション引数を有効にする(optional_arguments = 1)
  • パラメータを取らないオプション引数 thumb を定義 (option_spec)

という定義をしています。

そして、run() メソッドで reST での定義を nicovideo ノードに変換します。
ここでは引数と thumb オプションの有無をパラメータとして nicovideo ノートを生成しています。

class NicoVideoDirective(Directive):
    """Directive for embedding nico-videos"""

    has_content = False
    required_arguments = 1
    optional_arguments = 1
    final_argument_whitespace = True
    option_spec = {
        'thumb': directives.flag,
    }

    def run(self):
        node = nicovideo(movie_id=self.arguments[0], thumb=('thumb' in self.options))
        return [node]

次に「出力の際にノードを実形式に変換」するためのハンドラを用意します。
visit_nicovideo_node() は指定された動画 ID を元に HTML タグを生成しています。

def visit_nicovideo_node(self, node):
    movie = NicoVideo(node['movie_id'])

    try:
        if node['thumb']:
            # embed movie as thumbnail
            attrs = dict(width=312, height=176, src=movie.thumb_url,
                         scrolling='no', style='border:solid 1px #CCC;',
                         frameborder='0')
            self.body.append(self.starttag(node, 'iframe', **attrs))
            self.body.append(self.starttag(node, 'noscript'))
            self.body.append(self.starttag(node, 'a', href=movie.url))
            self.body.append(movie.title)
            self.body.append('</a></noscript></iframe>')
        else:
            # embed movie using player
            attrs = dict(type='text/javascript', src=movie.thumbjs_url)
            self.body.append(self.starttag(node, 'script', **attrs))
            self.body.append('</script>')
            self.body.append(self.starttag(node, 'noscript'))
            self.body.append(self.starttag(node, 'a', href=movie.url))
            self.body.append(movie.title)
            self.body.append('</a></noscript>')
    except:
        self.builder.warn('fail to load nicovideo: %r' % node['movie_id'])
        raise nodes.SkipNode


def depart_nicovideo_node(self, node):
    pass

最後に setup() 関数でノードとハンドラ、ディレクティブを登録します。

def setup(app):
    app.add_node(nicovideo, html=(visit_nicovideo_node, depart_nicovideo_node))
    app.add_directive('nicovideo', NicoVideoDirective)

ここまでが nicovideo ディレクティブの定義です。

Sphinx 拡張は Sphinx と docutils のルールが把握できればかなりシンプルに作ることができます。
実際、この sphinxcontrib-nicovideo もおおよそ 1時間ぐらいで書き上げています。
sphinx-contrib リポジトリには各種 Sphinx 拡張が登録されているので、
Sphinx 拡張を作るのに興味がある人は一度読んでみることをおすすめします。

*1:正確にはもう少し細かいフェーズがあるようですが、ここでは説明を割愛します。

(6日目) Sphinx 拡張の紹介

Sphinx には Sphinx 拡張(Sphinx extension)と呼ばれるプラグイン機能があります。
Sphinx 拡張を使うとドキュメントを書くのがより便利になったり、
ドキュメントの表現がより豊富になったりします。

今日はその Sphinx 拡張の中から僕がよく使うもの、便利そうで使ってみたいものを
それぞれいくつかピックアップしてご紹介します。

sphinx.ext.todo

Sphinx に同梱されている拡張モジュールで、ドキュメントに ToDo を書き残すことができます。
有効にするには conf.py に以下の記述を追加します。

# sphinx.ext.todo モジュールを読み込む
extensions += ['sphinx.ext.todo']

# ToDo 項目を表示する (デフォルトは表示しない; False)
todo_include_todos = True

sphinx.ext.todo モジュールを読み込むと rst ファイルの中で todo ディレクティブが使えるようになります。

.. todo:: あとで書く

このモジュールを使うと、積み残しのドキュメントタスクを直接埋め込むことができます。

また、todolist ディレクティブを使うとドキュメント内のすべての ToDo をリストにして表示します。

.. todolist::

なお、これらの ToDo は conf.py の todo_include_todos が True のときだけ表示されるため、
他の人に成果物を渡したり、一時的に見せるときに False にすると ToDo を表示しないものを出力することができます。

sphinx.ext.pngmath / sphinx.ext.jsmath

Sphinx に同梱されている拡張モジュールで、LaTeX 形式の数式をサポートします。
sphinx.ext.pngmath は dvipng を介して、sphinx.ext.jsmath は jsMath (JavaScript) を介して数式を表示します。

どちらも依存パッケージを追加でインストールする必要がありますが、
数式を多く含むドキュメントを書く場合は必須の拡張モジュールです。

ここではインストールがかんたんな sphinx.ext.jsmath を使用します。
Sphinx には jsMath で利用するファイル群が含まれていないので、別途インストールします。*1

$ wget 'http://downloads.sourceforge.net/project/jsmath/jsMath/3.6e/jsMath-3.6e.zip'
$ unzip -d _static jsMath-3.6e.zip
$ wget 'http://downloads.sourceforge.net/project/jsmath/jsMath%20Image%20Fonts/1.3/jsMath-fonts-1.3.zip'
$ unzip jsMath-fonts-1.3.zip
$ mv jsMath/fonts _static/jsMath-3.6e
$ rmdir jsMath

次に conf.py で sphinx.ext.jsmath モジュールを有効にします。

# sphinx.ext.jsmath モジュールを読み込む
extensions += ['sphinx.ext.jsmath']

# jsMath のパスを設定する (_static/ からの相対パス)
jsmath_path = 'jsMath-3.6e/easy/load.js'

sphinx.ext.jsmath モジュールを有効にすると math ディレクティブが利用できるようになります。
math ディレクティブに数式を渡すときれいな数式として表示してくれます。

.. math:: (a + b)^2 = a^2 + 2ab + b^2

.. math::
   :label: quite

   (a + b)^2  &=  (a + b)(a + b) \\
              &=  a^2 + 2ab + b^2

.. math:: e^{i\pi} + 1 = 0
   :label: euler

label オプションを付けると数式にラベルと数式番号を付けることができ、
また :eq: ロールを用いて参照することができます(例: :eq:`euler` )。

sphinxcontrib.blockdiag / sphinxcontrib.seqdiag など

いくつか前の記事 で紹介しましたが、blockdiag シリーズの図を埋め込みます。
多くのドキュメントで役に立つ拡張モジュールだと思います。

既に紹介しているのでここでは説明を割愛します。

sphinxcontrib.plantuml

ドキュメントに各種 UML 図を埋め込むための Sphinx 拡張です。
UML 図の生成に PlantUML を利用しています。

利用する際は easy_install でインストールします。

$ sudo easy_install sphinxcontrib-plantuml

また、sphinxcontrib.plantuml は内部で PlantUML を呼び出すため、別途 PlantUML のインストールが必要です。
PlantUML は Java で動作するアプリケーションなので、実行には JRE が必要になります。
PlantUML 自身は Java パッケージとして配布されているので、plantuml.jar をダウンロードするだけです。

$ sudo apt-get install openjdk-6-jre
$ wget 'http://downloads.sourceforge.net/project/plantuml/plantuml.jar'

続いて、conf.py に設定を記述します。

# sphinxcontrib.plantuml モジュールを読み込む
extensions += ['sphinxcontrib.plantuml']

# PlantUML の起動方法を設定する
plantuml = ['java', '-jar', '/path/to/plantuml.jar']

sphinxcontrib.plantuml モジュールを有効にすると uml ディレクティブが利用できるようになります。
uml ディレクティブに定義を渡すと UML 図として表示してくれます。

.. uml::

   Alice -> Bob: Hi!
   Alice <- Bob: How are you?

.. uml::

   class "This is my class" as class1 {
      +myMethods()
      -myMethods2()
      String name
   }
   class class2 as "It works this way too" <<Serializable>> {
      String name
   }

   class2 *-- "foo/dummy" : use

PlantUML はシーケンス、ユースケース、クラス、アクティビティ、コンポーネント、ステート、オブジェクトの各種 UML 図をサポートしているので、
ソフトウェア開発で UML を多用しているケースでは大変役に立つと思います。*2
なお、図ごとに Java のプロセスを立ち上げるので大量に図が入っている場合は少しビルドに時間がかかるようです。

sphinxcontrib.googlechart

拙作の Google Chart を利用してグラフを生成する拡張モジュールです。
以下のグラフに対応しています。

  • 円グラフ (2D/3D)
  • 折れ線グラフ
  • 棒グラフ
  • ベン図
  • 散布図

利用する際は easy_install でインストールします。

$ sudo easy_install sphinxcontrib-googlechart

それぞれのグラフのサンプルはマニュアルページで見ることができます。

sphinxcontrib.googleanalytics

Sphinx で生成した HTML ページに Google Analytics のビーコンを埋め込むための Sphinx 拡張です。
テンプレートの変更などの面倒がないため、Google Analytics ユーザーは必須の拡張モジュールです。

利用する際は easy_install でインストールします。

$ sudo easy_install sphinxcontrib-googleanalytics

インストールしたあとは conf.py に設定を記述します。

# sphinxcontrib.googleanalytics モジュールを読み込む
extensions += ['sphinxcontrib.googleanalytics']

# GoogleAnalytics の ID
googleanalytics_id = 'UA-xxxxxxxx-1'

あとは make html で HTML を生成するだけで完了です。

sphinxcontrib.youtube

Sphinx ドキュメントに Youtube ムービーを埋め込むための Sphinx 拡張です。

現在、sphinxcontrib.youtube はソースのみが公開されている状況なので、
bitbucket から直接ソースコードをダウンロードして利用します。
以下の例では、構成ファイルが 2つだけなので、wget で直接ダウンロードしています。

$ mkdir -p lib/sphinxcontrib
$ wget -O lib/sphinxcontrib/__init__.py https://bitbucket.org/birkenfeld/sphinx-contrib/raw/14db1e6b4f01/youtube/sphinxcontrib/__init__.py
$ wget -O lib/sphinxcontrib/youtube.py https://bitbucket.org/birkenfeld/sphinx-contrib/raw/14db1e6b4f01/youtube/sphinxcontrib/youtube.py

ダウンロード後、conf.py に設定を記述します。

# sphinxcontrib.youtube 用にライブラリパスを追加
sys.path += ["lib"]

# sphinxcontrib.youtube モジュールを読み込む
extensions += ['sphinxcontrib.youtube']

使い方は youtube ディレクティブに動画 ID を指定するだけです。

.. youtube:: IN9YE_HjQQk

sphinxjp.themes.bizstyle

@shkumagai 作の Sphinx のテーマを切り替える sphinxjp.themes.* の一つです。
耳にする範囲ではかなり評判のいいテーマです。

利用する際は easy_install でインストールします。このモジュールは区切りが . なので間違えないよう注意します。

$ sudo easy_install sphinxjp.themes.bizstyle

インストールしたあとは conf.py に設定を記述します。

# sphinxjp.themecore モジュールを読み込む (sphinxjp.themes.bizstyle ではないのに注意)
extensions += ['sphinxjp.themecore']

# テーマ設定
html_theme = 'bizstyle'

あとは make html するだけで、こんな格好いいドキュメントになりますね :-)

その他のモジュール

この記事を作るにあたっておもしろそうなモジュールをピックアップしていたところ、
こんなモジュールも見つかりました。
どれも使いかたによっては、便利な場面がありそうな気がします。

  • docxbuilder
    • @shimizukawa 作の .docx ビルダー。make docx で Word 文書として出力されます。
    • まだバージョンが 0.0.1alpha と低いですが、これからが非常に楽しみです。
  • sphinxfeed
    • Sphinx ドキュメントに RSS フィードを出力させるモジュールです。
  • cheeseshop
    • PyPI ページへのリンクを :pypi:`Sphinx` のように作れるモジュールです。
    • セットアップ手順などで使えそう?
  • issuetracker
    • github, bitbucket などのイシュートラッカーへのリンクを簡単に作れるモジュールだそうです。
    • いい使い方はあまり思いつかなかったのですが、動作に興味があります。
  • sadisplay
    • SQLAlchemy のモデルを可視化するモジュールです。
    • 内部で PlantUML or graphviz を利用しているとのこと。
    • ER 図が簡単に書けるのかもしれません。誰か試してみて!
  • sphinxjp-shibukawa
    • 拙作。簡単なスケジュール表が作れるモジュールです。
    • 名前の通り @shibukawa さんへのプレゼントとして作られたモジュールです。

その他にも便利そうなモジュールがたくさん存在します。
PyPIGoogle で検索するなどして、便利なモジュールを検索してみてください。

*1:Debian では jsmath, jsmath-fonts というパッケージになっているようです。

*2:個人的には文法が複雑でレイアウトしなければならないので、少し苦手なツールですが…

(5日目) omake を使って Sphinx の自動ビルドを行う

Sphinx & blockdiag アドベントカレンダー 5日目です。
今日も Sphinx の tips についてご紹介したいと思います。

Sphinx で文書を作成する際は rst ファイルを書いて、make コマンドを実行する、という繰り返しを行います。
もし rst ファイルの編集中にどのような表示になるのか確認したい場合であっても、
make ファイルを実行して成果物が生成されるまで待つ必要があります。
ビルド処理は実行してから数秒(から数十秒)かかるため、単に表示を確認したい場合でもワンテンポ待つことになります。
これは Sphinx で文書を作る際、少々取っつきづらいところです。

rst ファイルの読み書きになれてくるとどのような表示になるか予測できるようになるため、
表示確認の回数は少なくなりますが、エラーチェックやリファレンスの確認などのために
ビルドを走らせたくなることがあります。

そこで、今日は omake コマンドを使って Sphinx プロジェクトの自動ビルドを行う方法をご紹介します。
omake はファイルの更新を検知してコマンドを実行するビルドシステムです。

Sphinx プロジェクトの作成

omake でビルドする Sphinx プロジェクトに制限などはないのですが、
omake で変更検知しやすいよう、ビルドディレクトリとソースディレクトリを分けておくことをおすすめします。

具体的には sphinx-quickstart で Sphinx プロジェクトを作成する際に、
"Separate source and build directories (y/N)" という質問に Yes と答えます。

この設問に Yes と答えた場合と No と答えた場合で以下のようにディレクトリ構成が変化します。

# No と答えた場合 (デフォルト)
$ find doc -type d
doc
doc/_templates
doc/_build
doc/_static

# Yes と答えた場合
$ find doc -type d
doc
doc/build
doc/source
doc/source/_templates
doc/source/_static

Yes と答えておくと、conf.py や rst ファイルなどソースとなるファイル群が source/ ディレクトリ以下にまとまるため、
omake で監視しやすくなります。*1

omake のインストール

Debian では omake はパッケージ化されているので apt 経由でインストールします。
なお、手元の環境では fam パッケージをインストールしないと omake が正しく動作しなかったので、一緒にインストールします。

$ sudo apt-get install omake fam

CentOS5 でも ocaml-omake というパッケージになっているようなので yum コマンドでインストールできそうです(動作未確認)。

$ sudo yum install ocaml-omake

OMakefile の作成

omake で利用するファイルを生成するため、Sphinx プロジェクトのディレクトリに移動し omake --install を実行します。

$ omake --install
*** omake: creating OMakeroot
*** omake: creating OMakefile
*** omake: project files OMakefile and OMakeroot have been installed
*** omake: you should edit these files before continuing

メッセージが表示されたとおり、OMakeroot, OMakefile というファイルが生成されます。

生成された OMakefile はほぼすべてがコメントという状態になっているので、
すべての内容を消して SphinxMakefile から抽出した内容を保存します。

# OMakefile for Sphinx documentation
#

# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = bin/sphinx-build
PAPER =
BUILDDIR = _build

# Internal variables.
PAPEROPT_a4 = -D latex_paper_size=a4
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_a4) $(SPHINXOPTS) .

SRCS = $(find source/ -type=f)

.DEFAULT: $(SRCS)
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html

赤くマーキングした箇所(.DEFAULT 節)は Sphinx が生成した Makefile の html ターゲットの部分と同じ内容です。
また、オレンジ色にマーキングした箇所(SRCS 定義)が監視対象のファイルです。
ここでは source/ ディレクトリ以下のすべてのファイルを対象にしています。

omake の実行

それでは早速 omake を実行しましょう。

$ omake -P --verbose
*** omake: reading OMakefiles
*** omake: finished reading OMakefiles (0.01 sec)
- build . <.DEFAULT>
+ sphinx-build -b html -d build/doctrees -D latex_paper_size=a4 source build/html
Making output directory...
Running Sphinx v1.1.2
loading translations [ja]... done
loading pickled environment... not yet created
building [html]: targets for 1 source files that are out of date
updating environment: 1 added, 0 changed, 0 removed
reading sources... [100%] index

looking for now-outdated files... none found
pickling environment... done
checking consistency... done
preparing documents... done
writing output... [100%] index

writing additional files... genindex search
copying static files... done
dumping search index... done
dumping object inventory... done
build succeeded.
- exit . <.DEFAULT>, 2.61 sec, code 0
*** omake: done (2.62 sec, 0/0 scans, 1/1 rules, 1/40 digests)
*** omake: polling for filesystem changes

omake を実行すると、最初に一度 Sphinx によるビルドが実行されます。
最後に "polling for filesystem changes" というメッセージを表示したまま、待ち状態になります。


それではこの状態で、他の端末を開いて rst ファイルを編集してみましょう。
ウィンドウを二枚開いて操作しているとわかりやすいのですが、エディタで rst ファイルを保存すると
omake が保存を検知してビルドを実行します。

*** omake: file index.rst changed
*** omake: rebuilding
- build . <.DEFAULT>
+ sphinx-build -b html -d build/doctrees -D latex_paper_size=a4 source build/html
Running Sphinx v1.1.2
loading translations [ja]... done=========================================================================================================            ] 00013 / 00014
loading pickled environment... done
building [html]: targets for 1 source files that are out of date
updating environment: 0 added, 1 changed, 0 removed
reading sources... [100%] index

looking for now-outdated files... none found
pickling environment... done
checking consistency... done
preparing documents... done
writing output... [100%] index

writing additional files... genindex search
copying static files... done
dumping search index... done
dumping object inventory... done
build succeeded.
- exit . <.DEFAULT>, 1.02 sec, code 0
*** omake: done (1.02 sec, 0/0 scans, 2/2 rules, 3/45 digests)
*** omake: polling for filesystem changes

これで自動的にビルドしながら編集を行うことができるようになりました。

注意点

omake を利用する場合の重要な注意点として、"omake は追加されたファイルの保存に反応しない" と言うものがあります。

omake は起動時に監視対象のファイルを確定するため、実行後に追加されたファイルは監視されません。
編集中にファイルを追加する必要がでた場合は、追加後に必ず omake を再起動してください。

文書構成の見直しをしている最中などは omake の恩恵にあずかることが難しいようです。

*1:デフォルトだとビルド結果ソースファイル群のサブディレクトリに配置されるため、監視の設定がややこしくなります。

(4日目) Sphinx の文書を翻訳してみよう (sphinx-gettext-helper)

こんにちは。今日は昨日の続きでもっとかんたんに Sphinx の翻訳する方法を紹介します。。

昨日紹介したとおり Sphinxgettext 機能がサポートしているのは
pot ファイルの生成までで、それ以降の変換処理は手動で行う必要があります。

この変換処理は文章が大きくなって、構成する rst ファイルが増えれば増えるほど手間がかかるので、
何らかの方法で自動化する必要があります。
たとえば @ianlewis さんは Sphinx 1.1 i18n 機能紹介 の中で二つのシェルスクリプトを紹介してくれています。


多くの人がこのようなスクリプトを書いているようなので
車輪の再発明ではあるものの sphinx-gettext-helper というスクリプトを作成しました。
sphinx-gettext-helper を使うと面倒な gettext コマンド群の操作が不要になります。

インストール

sphinx-gettext-helper のインストールは他の python スクリプトと同様に easy_install で行います。

$ sudo easy_install sphinx-gettext-helper

sphinx-gettext-helper は msginit, msgmerge, msgfmt コマンドを利用します。
もしこれらがインストールされていなければ、合わせてインストールしておきます。

Sphinx プロジェクトの設定

gettext を使う場合と同様 conf.py に

locale_dirs = ["locale"]
language = "ja"

という設定を書き足します。
sphinx-gettext-helper は locale_dirs の最初のディレクトリにメッセージカタログを作成します。

rst ファイルから po ファイル、mo ファイルを生成する

sphinx-gettext-helper では一回のコマンド実行で pot ファイルから po ファイル、mo ファイルを生成することができます。*1

$ make gettext
$ sphinx-gettext-helper -p _build/locale --update --build
.. 完了.

sphinx-gettext-helper は Sphinx によって生成された pot ファイルを検索し、すべての pot ファイルを po ファイルに変換します(--update オプション)。
なお、内部では msginit, msgmerge コマンドを利用しているので、初回の変換ではメールアドレスの入力が必要です。

また、--build オプションを指定するとすべての po ファイルを mo ファイルに変換します。
この二つの処理を sphinx-gettext-helper が実行してくれることによって、gettext に関するコマンド操作が不要になります。


おすすめとしては Makefile を以下のように編集し、make gettext で一連の処理をすべて実行することです。

gettext:
        $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
        sphinx-gettext-helper -p $(BUILDDIR)/locale --update --build --statistics
        @echo
        @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."

このように書き換えると make clean gettext html コマンドを実行するだけで HTML 文書ができあがるため、
po ファイルの翻訳を行うことに集中することができます。

また、ここで指定している --statistics オプションは翻訳の状況を教えてくれます。
この表示を確認することで、見落としがちなあいまい(fuzzy)の項目を把握することができます。*2

$ sphinx-gettext-helper -p _build/locale --statistics
index.po: 2 個の翻訳メッセージ, 1 個の翻訳があいまいです, 1 個の未訳のメッセージ.

複数の言語に翻訳を行いたい場合

複数の言語に翻訳を行いたい場合、conf.py に language = 'ja' と書いてしまうと切り替えがしづらくなります。
その場合は以下のようにすることで、複数の言語に対応することができます。

1. conf.py から language の設定を削除する

実行オプションで設定を変更できるよう、language の設定を削除します。

2. Makefile を書き換える

Makefile の先頭付近の定義を以下のように書き換えます。

# You can set these variables from the command line.
LANGUAGE      = ja
SPHINXOPTS    = -D language=$(LANGUAGE)

gettext ターゲットに sphinx-gettext-helper を書き足している場合は、
その部分も合わせて書き換えましょう。

gettext:
        $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
        $(GETTEXTHELPER) -l $(LANGUAGE) -p $(BUILDDIR)/locale --update --build --statistics
        @echo
        @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
3. 実行する

make する際に言語を指定して実行します。

$ make LANGUAGE=en gettext html

こうすることで言語を切り替えながらドキュメントを生成することができます。

まとめ

sphinx-gettext-helper を使うと gettext 機能がすごく使いやすくなるよ!

実際に blockdiag.com のドキュメントは Sphinx + sphinx-gettext-helper を使って翻訳しています。
Jenkins を加えて HTML の自動ビルドも行っているので、
ドキュメント作成や翻訳だけに集中して作業することができるようになっています。

また、sphinx-gettext-helper に追加して欲しい機能があれば、是非 @tk0miya に連絡してください。

*1:オプションを調整することで片方だけにすることもできます。

*2:翻訳元の文章が変わったときは fuzzy フラグが立ちます

(3日目) Sphinx の文書を翻訳してみよう (gettext機能)

こんばんは、Sphinx & blockdiag アドベントカレンダー 3日目です。

今日は Sphinx 1.1 で実装された gettext 機能をご紹介します。
gettext は多言語に対応したアプリケーションを開発するときに利用する API で、
メニューに表示される単語やエラーメッセージなどを利用言語ごとに置き換える仕組みを提供しています。

gettext を利用することで翻訳元のドキュメントファイルを変更することなくドキュメントの翻訳を行い
他の言語(日本語や中国語など)のドキュメントを生成することができます。

概要

Sphinx では .rst ファイルに書かれた文章を言語ごとに置き換えることができます。
最初に英語でドキュメントを書き、gettext を介して翻訳を行うことで複数の言語のドキュメントを作ることができます。
gettext では中間ファイルとして pot ファイル、po ファイル、mo ファイルの 3つのファイルを使用します。

  • pot ファイル
    • rst ファイルから翻訳の対象となる文章を抽出したファイルです。
    • Sphinx では rst ファイルごと、もしくはサブディレクトリごとに 1つの pot ファイルが作成されます。
  • po ファイル
    • pot ファイルから生成されるファイルで、英文と対訳を記述していくファイルです。
    • 翻訳者は po ファイルを書き換えることで翻訳を行います。
    • po ファイルは言語ごとに作成します。日本語は ja という名称で翻訳を行います。
  • mo ファイル (メッセージカタログ)
    • po ファイルをコンパイルし、プログラムから読みやすい形式に変換したものです。
    • Sphinx はこの mo ファイルを参照して翻訳を行います。
    • po ファイル 1つにつき 1個作成されます

Sphinx ではファイルの生成の流れは以下のようになります。

Sphinx での翻訳の流れ

では Sphinx を使って翻訳を行ってみましょう。

まずはサンプルとして index.rst だけで構成されるかんたんなドキュメントの翻訳を行ってみます。

$ ls *.rst
index.rst
$ cat index.rst
===========================
gettext Translation Sample
===========================

This is sample page for translation using gettext.

Sphinx is very good documentation tool,
And blockdiag is simple and powerful diagram generator.
They let you be fun in documentation!

事前設定

まず gettext を利用するための設定を conf.py に書き加えます。

locale_dirs = ["locale"]
language = "ja"

変数 locale_dirs には poファイルや moファイルを配置するディレクトリを指定します(複数指定可能)。
ここでは locale ディレクトリに置くことにしました。
変数 language には翻訳に利用する言語を指定します。*1

rst ファイルから pot ファイルを生成する

次に pot ファイルを生成します。pot ファイルは Sphinx から出力することができます。

$ make gettext
$ ls _build/locale
index.pot

生成された pot ファイルを確認してみましょう。
index.rst から翻訳対象が抜き出されていることが分かります。

$ cat _build/locale/index.pot
 SOME DESCRIPTIVE TITLE.
# Copyright (C) 2011, test
# This file is distributed under the same license as the test package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: test test\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2011-12-03 21:03\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"

#: ../../index.rst:3
# ddfc79cb1d234e3ba5288af09b5ad843
msgid "gettext Translation Sample"
msgstr ""

#: ../../index.rst:5
# be3a44d5720645dca3bbb51170ba6120
msgid "This is sample page for translation using gettext."
msgstr ""

#: ../../index.rst:7
# 4495f0888ea84dc0b5c009de6e5003b8
msgid "Sphinx is very good documentation tool, And blockdiag is simple and powerful diagram generator. They let you be fun in documentation!"
msgstr ""

pot ファイルから po ファイルを生成する

続けて pot ファイルから po ファイルを生成します。
最初に locale ディレクトリを作成します。*2
pot ファイルから po ファイルの生成する際は msginit コマンドを使用します。
msginit を実行するとメールアドレスを聞かれるので、自身のメールアドレスを入力してください。*3

$ mkdir -p locale/ja/LC_MESSAGES
$ msginit --locale=ja --input=_build/locale/index.pot --output=locale/ja/LC_MESSAGES/index.po
ユーザが翻訳に関するフィードバックをあなたに送ることができるように,
新しいメッセージカタログにはあなたの email アドレスを含めてください.
またこれは, 予期せぬ技術的な問題が発生した場合に管理者があなたに連絡が取れる
ようにするという目的もあります.

Is the following your email address?
  tkomiya@...
Please confirm by pressing Return, or enter your email address.
i.tkomiya@gmail.com
http://translationproject.org/team/index.html を検索中... 完了.
A translation team for your language (ja) does not exist yet.
If you want to create a new translation team for ja, please visit
  http://www.iro.umontreal.ca/contrib/po/HTML/teams.html
  http://www.iro.umontreal.ca/contrib/po/HTML/leaders.html
  http://www.iro.umontreal.ca/contrib/po/HTML/index.html

locale/ja/LC_MESSAGES/index.po を生成.

生成された po ファイルは pot ファイルとほぼ同じ形式をしています。
翻訳を行う際はこの po ファイルの msgstr の欄に訳文を入れていきます。

$ cat locale/ja/LC_MESSAGES/index.po
# Japanese translations for test package.
# Copyright (C) 2011, test
# This file is distributed under the same license as the test package.
# Takeshi Komiya <i.tkomiya@gmail.com>, 2011.
#
msgid ""
msgstr ""
"Project-Id-Version: test test\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2011-12-03 21:03\n"
"PO-Revision-Date: 2011-12-03 21:05+0900\n"
"Last-Translator: Takeshi Komiya <i.tkomiya@gmail.com>\n"
"Language-Team: Japanese\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: ja\n"
"Plural-Forms: nplurals=1; plural=0;\n"

# ddfc79cb1d234e3ba5288af09b5ad843
#: ../../index.rst:3
msgid "gettext Translation Sample"
msgstr ""

# be3a44d5720645dca3bbb51170ba6120
#: ../../index.rst:5
msgid "This is sample page for translation using gettext."
msgstr ""

# 4495f0888ea84dc0b5c009de6e5003b8
#: ../../index.rst:7
msgid ""
"Sphinx is very good documentation tool, And blockdiag is simple and powerful "
"diagram generator. They let you be fun in documentation!"
msgstr ""

翻訳する

po ファイルは元の文章と翻訳する分が対になるような形式になっています。
po ファイルをエディタで開いて、それぞれの箇所に訳を埋めていきましょう。

# ddfc79cb1d234e3ba5288af09b5ad843
#: ../../index.rst:3
msgid "gettext Translation Sample"
msgstr "gettext 翻訳サンプルドキュメント"

# be3a44d5720645dca3bbb51170ba6120
#: ../../index.rst:5
msgid "This is sample page for translation using gettext."
msgstr "この文書は gettext を用いた翻訳のサンプルです。"

# 4495f0888ea84dc0b5c009de6e5003b8
#: ../../index.rst:7
msgid ""
"Sphinx is very good documentation tool, And blockdiag is simple and powerful "
"diagram generator. They let you be fun in documentation!"
msgstr ""
"Sphinx はとてもいいドキュメンテーションツールです。"
"そして blockdiag はシンプルかつ強力な図生成ツールです。"
"これらを使って楽しくドキュメンテーションを行いましょう"

po ファイルから mo ファイルに変換する

翻訳が終わったら、Sphinx から利用できるように po ファイルから mo ファイルに変換します。

$ msgfmt locale/ja/LC_MESSAGES/index.po -o locale/ja/LC_MESSAGES/index.mo
翻訳されたドキュメントを生成する

ここまでの道のりを得ると、ドキュメントの生成は簡単です。
いつも通り make コマンドを実行しましょう。

$ make clean html

これで翻訳された HTML ドキュメントが生成されます。

なお、引数に clean ターゲットを含めているのは、
Sphinx ビルダーが rst ファイルのタイムスタンプを見て、
ファイルが更新されていない(ビルド不要)と判断するケースがあるためです。

元のドキュメントを更新するとき

翻訳元のドキュメント(rst) が更新されたときは、上記の手順を繰り返します。

  • make gettext
  • po ファイル毎に以下の手順を実施
    • msgmerge (msginit しないように注意)
    • 翻訳
    • msgfmt
  • make html (など)

一点異なる箇所は、pot ファイルから po ファイルへの変換に msginit コマンドではなく msgmerge コマンドを利用することです。
なお、msginit コマンドは po ファイルを上書きしてしまうため、
更新を行う際は msgmerge コマンドを使用してください。

$ msgmerge -U locale/ja/LC_MESSAGES/index.po _build/locale/index.pot
.. 完了.

なお、rst ファイルが増えたときは pot ファイルも増えます。
増えたファイルはまた新しい po ファイルを作成する必要があるので、
ファイルの増減には気を払う必要があります。

まとめ

Sphinxgettext を使った翻訳は以下の流れで行います。

Sphinxgettext 機能で英語のドキュメントを変更せず、翻訳を行うことができるようになりました。
反面、Sphinx でサポートしている範囲は pot ファイルの生成までとなっているため、
それ以降の変換処理は手動で行う必要があります。

この複雑かつ面倒な変換処理については、sphinx-gettext-helper というスクリプトで改善することができます。
sphinx-gettext-helper については明日のアドベントカレンダーで紹介する予定です。

*1:変数 language は目次やノートの見出しなど、ちょっとしたところに出てくる単語を日本語にしてくれる(Note -> ノート など)ので、翻訳を行わない場合でも ja にしておくと幸せになれると思います。

*2:LC_MESSAGES というディレクトリは gettext のルールに基づいたもので、mo ファイル(メッセージカタログ)はここに設置します。

*3:msginit コマンドがない場合は OS の gettext パッケージをインストールして下さい。Debian であれば sudo apt-get install gettext でインストールできます。

(2日目) blockdiag のインストール〜使い方 (Debian 編)

こんばんは、Sphinx & blockdiag アドベントカレンダー 2日目です。

昨日は Sphinx をインストールして使ってみるという流れを紹介しました。
今度は blockdiag を試してみましょう。

blockdiag とは?

blockdiag は僕こと @tk0miya が開発した画像生成ツールです。
テキストで定義された図を画像ファイルに変換することができます。
blockdiag は目的別にいくつかのパッケージに分かれています。

  • blockdiag
    • ブロック図、フローチャート、画面遷移図などを記述する
  • seqdiag
    • シーケンス図を記述する
  • actidag
    • アクティビティ図を記述する
  • nwdiag
    • ネットワーク系の図(論理ネットワーク図、ラック構成図)を記述する

ドキュメントの図を作る際は MS-Office (Excel, PowerPoint)、Visio、Cacoo、astah* など様々なツールを使いますが、
これらのツールと blockdiag シリーズが大きく異なる点として、
図のレイアウトは blockdiag が自動的に調整し、ユーザーは定義だけ意識すればよいと言うものがあります。

  • メリット
    • 図のメンテナンスが簡単 (定義を変えるだけ)
    • 他のツールと連携して自動生成もできる
    • 手間をかけずに作図できる
    • テキスト形式であるため扱いやすい
  • デメリット
    • レイアウトの微調整はできない
    • ツールが想定していない図を書くことができない
    • 定義方法を覚える必要がある

これらの特徴が生きてくるのは継続的に更新される図や
シンプルなドキュメントを素早く作りたいケースなどと考えています。


blockdiag はもともと図を書く機能を持たない Sphinx の補助機能として作られているの>で、
Sphinx と同じく UNIX 系の考え方に根ざしています。

blockdiag のインストール

blockdiag は 12/2 現在 Debian パッケージとしては提供されていませんが、
Debian JP の @mkouhei さんらの手によってパッケージングの準備が進められています。
おそらく次のリリースである wheezy にはパッケージが収録されると思いますので、楽し>みに待ちましょう:)


ここでは Sphinx と同様に easy_install を使ってインストールを行うのですが、
その前に依存パッケージである PIL (Python Imaging Library) をインストールします。
これはパッケージングされているので apt で入れてしまいましょう。

$ sudo apt-get install python-imaging

つぎに blockdiag をインストールします。
blockdiag は先ほどご紹介したとおり、目的ごとに 4つのパッケージに分かれていますの>で、
ひとつずつインストールします。

$ sudo easy_install blockdiag
$ sudo easy_install seqdiag
$ sudo easy_install actdiag
$ sudo easy_install nwdiag

また、Sphinx 連携用の sphinxcontrib-blockdiag パッケージ群も合わせてインストール>しておきます。

$ sudo easy_install sphinxcontrib-blockdiag
$ sudo easy_install sphinxcontrib-seqdiag
$ sudo easy_install sphinxcontrib-actdiag
$ sudo easy_install sphinxcontrib-nwdiag

さらに日本語を表示するために TrueType フォントをインストールします。
ここでは IPA フォントを利用します。

$ sudo apt-get install ttf-ipafont

ここはお好みに合わせて好きなフォントをインストールしてください。

コマンドラインから blockdiag を利用する

それではさっそく blockdiag を使ってみましょう。
まずはエディタを使って定義ファイルを作ってみましょう。

blockdiag {
  A -> B -> C, D;
}

この定義は

  • A から B へ遷移する
  • B から C と D に遷移する

という要素をもった遷移図です。

この定義ファイル(sample01.diag)を blockdiag コマンドで画像に変換してみます。

$ ls
sample01.diag
$ blockdiag sample01.diag
$ ls
sample01.diag   sample01.png

sample01.png という PNG 画像ファイルが生成されました。*1

定義通り "A から B"、"B から C と D" にそれぞれ遷移しています。

より細かい定義を覚えることでノードを装飾(色、形状、線、文字)したり、
グルーピングしたりすることができます。
これらを組み合わせると以下のような図を記述することができます。



また、ここでは blockdiag を中心に説明しましたが、
seqdiag や actdiag、nwdiag も利用することで幅広い図を作ることができます。

Sphinx にblockdiag の画像を埋め込んでみよう

ここまでは blockdiag 単体での使い方をご紹介しましたが、
ドキュメントは図だけでなく文章が必要となるケースがほとんどです。

blockdiag はコマンドライン用のツールとしてだけではなく、
様々なドキュメントツールと連携して図を文書に埋め込むことができます。

ここでは昨日設定した Sphinx で blockdiag の図を埋め込んでみましょう。
まず、Sphinx の blockdiag 連携機能の設定を行います。
Sphinx プロジェクトの設定ファイル conf.py を以下のように書き換えます。

# extensions 行に 'sphinxcontrib.blockdiag' などを追加する
# 既に他の拡張機能が入っていた場合は末尾に書き足してください。
extensions = ['sphinxcontrib.blockdiag', 'sphinxcontrib.seqdiag', 'sphinxcontrib.actdiag', 'sphinxcontrib.nwdiag']

# TrueType フォントへのパスを書く
blockdiag_fontpath = '/usr/share/fonts/truetype/ipafont/ipagp.ttf'
seqdiag_fontpath = '/usr/share/fonts/truetype/ipafont/ipagp.ttf'
actdiag_fontpath = '/usr/share/fonts/truetype/ipafont/ipagp.ttf'
nwdiag_fontpath = '/usr/share/fonts/truetype/ipafont/ipagp.ttf'

conf.py を書き換えるとドキュメント内で blockdiag ディレクティブが利用できるようになります。
たとえば index.rst に以下の記述を加えてみましょう。

.. blockdiag::

   {
     A -> B -> C, D;
   }

書き加えた後に make html を実行すると、書き加えたブロック図が HTML の中に埋め込まれます。


ここまでが blockdiag の基本的な使い方です。
より詳しい内容が知りたくなった方は blockdiag.com を見てみてください。

また、使い方が分からない場合や困ったこと、知りたいことがある場合は
メーリングリストTwitter の #blockdiag タグで質問するとよいでしょう。

*1:IPA フォント以外のフォン>トを利用している場合は -f オプションで TrueType フォントを指定してください

(0日目) Sphinx & blockdiag アドベントカレンダーはじまります

今日は11月30日、明日から12月がはじまります。
地震やら節電やらいろんなことがあった2011年ももう少しで終わりです。
世間では暗い話題が多かったように思いますが、
みなさんにとっての2011年はいかがだったでしょうか。

さて、明日(12月1日)からは技術屋さんにとっての恒例行事、
アドベントカレンダーを開始しようと思います。
去年は Python アドベントカレンダーに参加して記事を書いていたのですが、
今年は Sphinxblockdiag をテーマに一人アドベントカレンダーをやってみたいと思います。

今年はずっと Sphinx と blockdiag を追いかけ続けていた一年だったので、
その総決算として25個の tips を書いてみたいと思います。

ちなみに僕が Sphinx を使い始めたのは去年の 7月か 8月ぐらいで、
blockdiag を作り始めたのは 9月ぐらいだったと思います。
僕はあまり新しいツールを使いたがらないオールドタイプな人間なのですが、
この二つは今では手になじんだツールになっています。
(一方は僕の代表作になってますしね :-)

なお、これを書いているのはまさに 11月30日当日なのですが、
正真正銘一本も記事のストックをしていません。
(一応箇条書き程度のネタ帳だけは作ってあります)

さぁ、見事完走することができるのか、ぶっつけ本番ノンストップな
この Sphinx & blockdiag アドベントカレンダー、是非ご笑覧ください :-)