あなたの知らない toctree の世界

numfig を実装してやらぁ!と息巻いてコードを書き始めたところ、
シンプルだと思っていた toctree の裏に思わぬ魔境が広がっていて
思わず部屋の隅っこで震えてしまった @tk0miya です。

Sphinx のリファレンスを舐めるように読み尽くした toctree マニアには常識かもしれませんが、
さっと読み飛ばした人々には toctree は謎機能の山に見えてくるはずです。

URL が指定できる

リファレンスの一節にそっとこんなことが書かれています。

また、ドキュメント名の代わりに、HTTPのURLを指定することで外部へのリンクを追加することもできます。

toctree って他の reST ファイルをリンクするためにだけあるんじゃなかったんですね。
早速使ってみましょう。

.. toctree::

   http://www.google.co.jp/

そしてこれをビルドしてみるとこんなふうになります。
f:id:tk0miya:20140819175541p:plain

どうやら URL を指定するとリンクテキストが None になってしまうようです。
この動作についてリファレンスには説明がありませんが、
toctree にはタイトルを付ける記法があるのでそれで回避するとよいでしょう。

.. toctree::

   Google <http://www.google.co.jp/>

いまいち使い所がつかめていないのですが、
目次に URL を入れたくなったときに使ってみてください。

ちなみに LaTeX の目次には効果がありません。

:numbered: オプションが引数を取るようになった

Sphinx 1.1 から numbered オプションが引数を取るようになっています。

特定の深さまでのナンバリングだけを行うこともできます。
numbered 引数に対して、数値で深さを指定してください。

というわけで、このオプションを指定してみます。
まず深さを指定しない場合の例。
f:id:tk0miya:20140819181106p:plain
続いて :numbered: 2 を指定した場合。
f:id:tk0miya:20140819181127p:plain
そして :numbered: 1 を指定した場合。
f:id:tk0miya:20140819181139p:plain

番号を振る階層が変化していますね。

なお、このオプションは LaTeX 経由で PDF を出力した場合は効力がありません。
そもそも LaTeX 経由ででは :numbered: 指定なしでも章番号が割り振られますし、
他のフォーマットでは :numbered: の指定は効果がないので、
HTML 専用オプションとして割り切って考えるのが良さそうです。

謎の self 指定

リファレンスを読み進めていくと、こんな記述もあります。

self は特別なエントリー名として扱われます。toctreeディレクティブを含むドキュメント自身を表します。これは、toctreeを使用して、”サイトマップ”を作成したい場合に便利です。

どうやら self という文字列を toctree に指定することができるようです。
試してみましょう。先ほどのドキュメントに加筆してみます。

.. toctree::
   :numbered:

   sub
   self

するとこんな出力になります。
f:id:tk0miya:20140819185229p:plain

何に使えるのか、いまいちよくわかりませんね。


ちなみに、self という名前は予約されているので self.rst というファイルを作っても
toctree で指定することはできません。ご注意ください。
subdir/self は問題ないので、self.rst を作れと遺言を受け取った方はディレクトリを掘るとよいでしょう。

toctree で循環参照をすると warning で注意される

toctree は名前の通りツリー構造を期待しているので、循環参照が起きることは想定していません。そのため、循環参照が起きるような指定をすると warning が出てきます。

例えば、自分自身のファイル名を指定すると…

.. toctree::

   index

こんなふうに注意されます。

/Users/tkomiya/work/tmp/foo/index.rst:: WARNING: circular toctree references detected, ignoring: index <- index

複数のファイルをまたがって参照しても注意されます。
次の例は index -> sub -> subsub1 -> index という循環がある場合の warning です。

/Users/tkomiya/work/tmp/foo/sub.rst:: WARNING: circular toctree references detected, ignoring: sub <- index <- subsub1 <- sub
/Users/tkomiya/work/tmp/foo/subsub1.rst:: WARNING: circular toctree references detected, ignoring: subsub1 <- sub <- index <- subsub1
/Users/tkomiya/work/tmp/foo/index.rst:: WARNING: circular toctree references detected, ignoring: index <- subsub1 <- sub <- index

なお、循環参照と :numbered: オプションを組み合わせると Sphinx ごと死ぬというバグを見つけたので、
循環参照マニアの人はこのバグが直るのを待つと良さそうです。


また、toctree はツリーにせよ、というコンセプトを無視して、
複数のファイルに特定のファイルをつなげて、ひし形に toctree を組むこともできます。
そうすると、navigation link が壊れたり*1、:numbered: による採番が狂ったりします。

まとめ

toctree は徐々に機能が追加されてきているものの、何に使うのかよくわからないものもあり、
アンバランスかつバギーな状態のようです。

せっかくですのでバグハンターの方々は、この辺りで楽しまれると良いのではないでしょうか。


あ、循環参照してる文書で make latex したら死んだ。
終わる。

*1:次へ、前への繰り返しで文書を辿れなくなる

Cacoo の画像を Sphinx に埋め込む拡張を作ってみた: sphinxcontrib_cacooimage

今日、こんなツイートをみました。

Cacoo で更新した図を Sphinx に貼り付けるにはローカルに保存する必要があります。
せっかく便利なサービスを使っているというのにこれはちょっと不便ですね。

というわけで、Cacoo 上の図を Sphinx に取り込むための拡張である sphinxcontrib_cacooimage を作ってみました。
gist18192f92226a9bfc2e6c

この拡張をインストールすると cacoo-image, cacoo-figure という 2つのディレクティブが利用できるようになります。
それぞれ image, figure ディレクティブの cacoo 版で、引数には画像 ID を取ります。

最後に conf.py に Cacoo の API キーをセットすれば準備は ok です。
あとは make html をすれば Sphinx に画像を取り込むことができます。

Cacoo を使ってる人は使ってみてはいかがでしょうか。

Sphinx ではどのようにラベルとキャプションを結びつけているのか

Sphinx ではラベル記法と :ref: 記法を使って、
ドキュメントの様々な位置にラベルを張り、それを参照することができます。

.. _target:

section
---------

セクションへの :ref:`参照 <target>` を作ります。

さて、このとき、ラベルを貼っているのがセクションタイトルなどの場合は
:ref:`target` という書き方でキャプションをリンクタイトルにすることができます。

ラベルとキャプションを認識する

Sphinx は次の順序でラベルを認識します。

  1. reST ファイルのパース処理において、ラベル記法をパターンマッチで見つける (docutils.parsers.rst.states:Body#explicit_construct())
  2. target ノードを生成する (docutils.parsers.rst.states:Body#make_target())
  3. ドキュメントに target ノードの情報を登録する (docutils.nodes:document#note_explicit_target())
  4. std ドメインにてリンクターゲットを処理する (sphinx.domains.std:StandardDomain#process_doc())
    • ドキュメントに登録された target ノードの情報を、ラベルデータとして再登録
    • ラベルを貼ったノードが section, figure, image, table のいずれかのノードであり、キャプションを持っている場合、キャプションをラベルデータとともに記録

Sphinx は docutils と処理が入り交じっていて何が行われているのか把握しづらいのですが、
複数のファイル間でクロスリファレンスを行うために、
docutils がパースしたリンク情報を std ドメインに蓄積、再構築して、
ファイルをまたいでもラベルやキャプションが参照できるようにするという処理になっています。

ここで作成したラベルとキャプションはファイルを超えてリンクを貼る (domain#resolve_xref() のすゝめ) で紹介した
domain#resolve_xref() などで取り出すことができます。
:ref: で参照するときにも同様にドメインを介して、ラベルとキャプションを取り出して、
リンクを組み立てています。

独自のノードを作るときの問題

Sphinx がラベルとキャプションを扱うときの動作を説明してきましたが、
もしキャプションを扱うような Sphinx 拡張を作る場合は上記のことを頭に入れておく必要があります。

ポイントとなるのは ラベルを貼ったノードが section, figure, image, table のいずれかのノードであり という部分で、
独自に作成したノードの場合は :ref: で参照する際にキャプションを取り出すことができません。

そのため、どのような Sphinx 拡張を作るかによってアプローチが変わってきます。

画像(やテーブル)を扱う Sphinx 拡張を作る場合は…

graphviz や PlantUML, blockdiag などのように、画像を生成する系列の拡張の場合は、
通常のアプローチでは次のような実装をすることが多いと思います。

  • Directive では独自ノードを作り
  • visitor 関数で独自ノードを画像に変換する

しかし、キャプションをうまく扱うためには次のようなやり方をするとよいでしょう。

  • Directive では figure ノード、caption ノードと画像を表す独自ノードを作る
  • visitor 関数で独自ノードを画像に変換する

こうすることで、Sphinxドメインにキャプションを認識させつつ、
独自ノードの部分は自分の visitor 関数で処理することができます。
このやり方は sphinx.ext.graphviz の実装が参考になります(figure_wrapper() のあたり)。

ここでは画像について触れましたが、テーブル系に関しては
table ノード、title ノードを使ってテーブルの外郭部分を構築するとよいでしょう。

それ以外のノードの場合は…

それ以外のノードに関しては、現時点ではラベルとキャプションを紐付けることはできません。
:ref: 記法を使ってキャプションを取り出すことは諦めることになります。
回避方法としては、:ref: に変わる別のロールを設けるやり方があります。
new_numfig.py の実装(:ref:num: ロールを作った)が参考になるかもしれません。

なお、コミッターの @shimizukawa さんもその部分は認識しているようなので、
リクエストを上げ続けるといずれ改善されるかもしれません。(C'mon PR!!)


話は変わって LaTeX の話

コードを読み進めていくと、LaTeX においてもラベルは少し特殊な扱いになっています。

sphinx.writers.latex:LaTeXTranslator#visit_target() を見ると

  • 次のノードが section, figure, table の場合は各ノードの visitor 関数でラベル作成を行う
  • それ以外のノードの場合は \phantomsection を使って、その場にダミーのリンクターゲットを作って、そこにラベルを作成する

という手順を踏んでいます。

生成された PDF を見ると、リンク先の位置がちょっと変わることになります。
ここも画像やテーブル系の拡張であれば、先ほど紹介したやり方で回避するとよいでしょう。

まとめ

Sphinx が内部でどのようにクロスリファレンスを実現しているのかを把握すると、
独自の Sphinx 拡張を作るときに役に立ちます。
フローが複雑でコードが追いづらい箇所ではありますが、
キャプションを扱う拡張を書く場合は目を通しておくと使い勝手が上がると思います。

ちなみに、このまとめは sphinx.ext.graphviz のキャプションが :ref: で拾えなかったのを直す際に調べました。

Re: numfig の闇と向き合う

numfig の闇を垣間見るのにも飽きてきたので、これまで見た numfig 系拡張の問題をカバーすべく
自分でも numfig 系拡張を書いてみた。
new_numfig.py

ざっくり動かしたところ、うまく動いているようにみえる。

既存の fignum 系拡張との違いはこんな感じ。

  • 表の自動採番にも対応してみた
  • :num: ロールを :ref:num: ロールにリネームした(:ref: の拡張っぽく見えるでしょ?)。
  • :ref:num: ロールは HTML 形式でもちゃんと動く
  • 複数の HTML 間でも番号は重複せず、ユニークな番号が割り振られる
  • toctree に :numbered: オプションを付けると 1-1, 1-2 のような枝番を割り振っている
  • 拡張性を持っているので、あとで対応モジュールを増やせるはず

実装してみてわかったこと

ファイルをまたがった採番とその参照

ファイルをまたがって番号が重複しないようにするのは非常に難しい。
Sphinx の処理が次の順序で行われているので、きれいなやり方で実装することができない。

  1. reST の解釈
  2. doctree-read イベント
  3. セクション番号の割り振り (sphinx.environment.BuildEnvironment#assign_section_numbers())
  4. doctree の pickle 化
  5. ここからは rst ファイルごとの処理 (HTML ファイル書き出し)
    1. doctree の復元 (unpickle 化)
    2. doctree-resolved イベント
    3. ライター処理 (visitor メソッド呼び出し)

自動採番で枝番を使おうとすると採番のタイミングはセクション番号の割り振りより後になるのだが、
それより後ろのフックポイントは doctree-resolved イベントと visitor メソッドの二箇所しかない。
だが、どちらの処理も rst ファイル単位で処理が行われ、
他の rst ファイルの doctree はメモリ上に存在しないため、
:ref:num: ロールで図表番号を取得する際に何番が割り振られているのか知るすべがないのだ。

本来であれば doctree-read イベントで自動採番をして、図表番号を含んだ状態で pickle 化するのが望ましいのだが、
doctree-read イベントは

Emitted when a doctree has been parsed and read by the environment, and is
about to be pickled. The *doctree* can be modified in-place.

という説明にも関わらず、pickle 化のもっと前のところで呼び出されてしまう。


new_numfig.py では、他の rst ファイルを読みだして、自動採番処理を再実行することで、
番号が割り振られた doctree を再現して :ref:num: の処理を行っている。
効率はかなり悪いので、あまり好ましいやり方ではない。

ドキュメントの更新への対応

いまの new_numfig.py は部分的にドキュメントが更新された場合に、
番号がずれる可能性を持っている。

先頭に図を追加すると、以降の図表番号はひとつずつずれることになるのだが、
その依存関係を構築していないため、他のファイルは make clean を実行するまで更新が行われない。

また、末尾のファイルを更新した場合でも、それまでに採番した番号を記憶していないので
新たに 1から割り振られてしまう。


前者は BuildEnvironment#check_dependents() のように番号変更の影響を伝えることで、
後者は採番した情報を pickle 化して保存することで対応できると思われる。

今回は実験用の実装なので、make clean してしのいで欲しい。

LaTeX と HTML で Sphinx の動きが違う

new_numfig.py ではオリジナルの numfig.py と同様に、
LaTeX への出力では自動採番は LaTeX にまかせているのですが、
LaTeX と HTML で出力が違うところで異なる番号が割り振られてしまった。

いくつか例を挙げると

  • graphviz のキャプションが LaTeX に出力されない (ので番号も割り振られない) 訂正 (7/31 22:30): キャプションが何故か図の上に出ていました。
  • :name: オプションが LaTeX でラベルにならないことがある

のような違いがいくつか見受けられた。

この辺りはこつこつ Sphinx を直していくしかない模様。

まとめ

  • new_numfig.py を実装して、既存の numfig 系実装の問題を改善してみた
  • Sphinx の拡張インターフェースでは numfig の実現に届かないことが分かった
  • また Sphinx にバグを見つけた

実装に必要な情報が揃ってきたと思われるので、numfig を真に必要としている人は頑張っていただきたい。

ファイルを超えてリンクを貼る (domain#resolve_xref() のすゝめ)

昨日の記事で、どの拡張も HTML に変換した時に他のページにリンクできないという体たらくなので、
Sphinx においけるリンクの解決方法をまとめておく。

解決方法: domain#resolve_xref() を使う

以上。


見出しで言い切ったのでこれ以上ネタも無いのだが、コードを使って補足しておく。

Sphinx のベースになっている docutils では、単体のファイルしか扱わないため
ラベルを意味する reftarget ノードとリンク(参照)を表す reference ノードのふたつで用が足りていて、
リンクの解決はドキュメントの先頭から travrese() をして、該当のラベルを見つけるだけでよかった。

しかし、Sphinx では複数のファイルを組み合わせて文書を構築するため、
ファイル A からファイル B へリンクすることもある。
だが、ファイル A にあるリンクを解決しようとしても、
そのときにファイル B を処理し終わっているとは限らない。
まだ参照しているラベルが登場していない可能性も十分にある。

そのため、Sphinx ではリンクには pending_xref というノードを用い、
すべてのファイルの読み込みがひと通り終わった後にリンクの解決を行っていく*1
その時に利用されるのが domain 機構とそのメソッドである domain#resolve_xref() である。

domain#resolve_xref() を使う

サンプルとして、カスタムロールである :myref: を定義する。
:myref: は :ref: と同じ動きとし、ラベルを受け取ってリンクを作るものとする。

:myref:`a_subsection`

Sphinx には XRefRole というクロスリファレンス用のロールジェネレータが用意されているので
これを使うと簡単に :myref: ロールを定義することができる。

from docutils import nodes
from sphinx.roles import XRefRole


class myref(nodes.reference):
    pass


def setup(app):
    app.add_node(myref)
    app.add_role('myref', XRefRole(nodeclass=myref))

myref ノード用のクラスを定義して、XRefRole を使いながら Sphinx に定義を登録するだけである。

もちろんこのままでは未知のノードとして扱われるため、どの形式にも変換することはできない。
参照を解決しつつリンクを生成する処理を追加する必要がある。

ここでは Sphinx のリンク解決フェーズである doctree-resolved イベントで処理をする。

def on_doctree_resolved(app, doctree, docname):
    domain = app.builder.env.domains['std']
    for node in doctree.traverse(myref):
        xref = domain.resolve_xref(app.builder.env, docname, app.builder,
                                   'ref', node['reftarget'], node, node)
        if xref:
            node.replace_self(xref)
        else:
            app.builder.warn('unknown label: %s' % node['reftarget'])
            node.parent.remove(node)


def setup(app):
    app.connect('doctree-resolved', on_doctree_resolved)

ラベルの参照を解決する場合は app.builder.env.domains['std'] にある resolve_xref() を呼び出すとよい。
言語ドメインの参照(関数やモジュールなど)を解決する場合は domains['cpp'] や domains['python'] などを使うことになる。
domain#resolve_xref() は参照の解決ができた場合は reference ノードを、
参照が解決できなかった(ラベルが見つからなかったなど)場合は None を返す。

ここでは node.replace_self() で reference ノードに置き換えてリンクを生成しているが、
開発しているツールの都合に合わせてうまく変換して使うと良いだろう。
HTML 用途であれば、リンク先の URI 情報が reference ノードに収められている。
同一ファイル内へのリンクであれば xref['refid'] が、別ファイルへのリンクであれば xref['refuri'] を参照すると良い。

blockdiag でもこの仕組みを使って clickable map を実装している。

参照先のノードを知りたい

domain#resolve_xref() では参照先の URI とキャプション情報を得ることができるが、
ツールによってはもっと突っ込んでノードそのものを得たいこともあるだろう。

その場合は Sphinx の内部データ構造に手を突っ込むことになる。
ここに記載する情報は Sphinx 1.2.2 で確認したものなので、将来的に構造が変わる可能性もあるのでそのつもりで読んで欲しい。


さすがに domain 内部にも参照先のノードそのものの情報は入っていないので、
記録さている docname, labelid から該当のノードを復元する必要がある。

def on_doctree_resolved(app, doctree, docname):
    domain = app.builder.env.domains['std']
    for node in doctree.traverse(myref):
        target_docname, target_labelid, _ = domain.data['labels'].get(node['reftarget'])
        target_doctree = app.builder.env.get_doctree(target_docname)
        for target in target_doctree.traverse(nodes.Element):
            if target_labelid in target['names']:
                // here!

かいつまんで説明すると、

  • domain.data['labels'] から docname, labelid の情報を得る*2
  • app.builder.env.get_doctree() を使って該当ファイルの doctree を復元する
  • node.traverse() を使って該当のラベルが付いている(= names 属性に含まれている)ノードを見つけ出す

あとはここで得た情報を元にうまくやると良いだろう。
注意点として、リンク先のノードは一時的に復元したものであって、
これを書き換えても生成されたドキュメントには反映されないことが挙げられる*3

まとめ

お前らリンクを作るときはちゃんと domain#resolve_xref() を使え。

ちなみにこういう情報はリファレンスに載ってないのでひたすらコードを読むと良い。
訂正 (7/30 23:30): id:shimizukawa の指摘によると domain#resolve_xref() は最新のドキュメントに記載があるとのこと。1.2 系のリリース以降、開発者向けドキュメントも強化しているらしい。

*1:ノードの名前どおり、未解決のクロスリファレンス(ファイルをまたがるリファレンス)を表しているのだと思う。

*2:読み飛ばしている最後の情報にはキャプションが入っている。

*3:そういったことがやりたい場合は Transform を使うとよさそうだが、ファイルをまたがる場合はちょっと複雑になると予想される

numfig の闇と向き合う

前の記事で

あとは numfig の応用で自動採番をすることもできるようになるでしょう。

なんてことを書いてみたのだけど、よく考えてみると一度も numfig を使ったことがなかったので
これを機に挑戦してみんとてするなり。

numfig をインストールする

numfig はパッケージングされておらず、ファイルひとつだけぽつんとリポジトリに置いてあります。

pip などは使えませんが、curl で落としてきて、load path を書き換えてあげれば
Sphinx 拡張として読み込むことができます。

$ cd /path/to/doc
$ curl -LO https://bitbucket.org/arjones6/sphinx-numfig/raw/b2345f7d9fabefbd103c31cc0c4c26c68b29ac6a/numfig.py
$ vi conf.py
import os
import sys
sys.path.insert(0, os.path.dirname(__file__))
extensions += ['numfig']

numfig は拡張をインストールしておけば、figure ディレクティブを書くだけで自動的に番号を振ってくれます。

.. figure:: logo.jpg

   this is caption

また、ラベルを付けておくと :num: や :page: などのロールを使って、
図の番号を取得したり、該当ページへとリンクを張ったりすることができます。

numfig の闇

さて、ここまでは順調ですが、使ってみると気になるところがいくつも出てきます。

  • HTML 形式のサポートが適当
    • :page: はスキップされてしまいリンクが作成されない
    • そもそも :num: を使うと HTML に変換できない
    • 自動採番の範囲がページ単位 (別の rst ファイルに移動すると 1番から)
  • figure だけしか扱うことができない
    • sphinx.ext.graphviz で図を作ってもスルー (1.2 から caption が指定できるのに…)
    • table なんてもってのほか
  • ラベルにアンダースコアを使うとリンク切れになる
  • :num: も :page: も別の章の figure が対象だとリンクできない
    • 同じ章の中なら問題なし
    • tex のソースを読むとラベル名に \label{chapter2:hello-world} と chapter 番号がついてるので… orz

ちょっと使っただけでこんな感じで文句が出てくるわけですが、
調べていくとどうやらこの numfig 、作りっぱなしで放置されているもののようです。

おそらく作者は TeX で論文を書いたところで満足してしまったんじゃないでしょうか。

その他の numfig 実装

fork 先を覗いていくと、いくつか numfig のバグを直したものが見つかります。
しかし、調べていくとかすかな希望が絶望に変わります。

  • https://bitbucket.org/keik/sphinx-numref
    • table も扱えるようにしたもの。
    • HTML で :num: が使えない問題は治っているものの、rst ファイルをまたぐと落ちる
  • https://bitbucket.org/takaakiaoki/sphinx-numfig
    • こちらも table に対応したもの
    • HTML で :num: は使えないまま
  • sphinxtr
    • Sphinx で論文を書くための拡張群的な位置付け
    • table や subfigure (独自形式) に対する自動採番に対応
    • 大きく書き換えられているが、やはり HTML ではコケた orz
    • 別の章だとリンクできないのは相変わらず

autonumber を試す。

numfig が気になってるとつぶやいたところ、@shimizukawa から autonumber.py を紹介されました。

autonumber.py は sphinx-dev group で名乗りを上げた numfig 界の新星という位置付けっぽい。

インストールは numfig と同様です。

$ curl -LO https://raw.githubusercontent.com/SimTk/openmm/master/docs/sphinx/autonumber.py
$ vi conf.py
import os
import sys
sys.path.insert(0, os.path.dirname(__file__))
extensions += ['autonumber']

使い方は numfig と少々異なり、caption の一部として :autonumber: ロールを使うようです。

.. figure:: logo.jpg

   :autonumber:`Figure, hello` caption
autonumber
ロールの前半(カンマの前まで)が図番号の prefix になります。

(この例だと Figure 1-1 と番号がつく)

また、図番号を取得する際は :numref: を使います。

:numref:`Figure, hello`

参照する際は :autonumber: ロールと同じものを使います。

autonumber.py の印象 (というかダメ出し)

  • ロールで実装されているので、場所を選ばない
    • table でも他のものでも地の文でも使える
  • README がないので使い方がわかりづらい
  • toctree に :numbered: オプションを渡さないと落ちる
  • rst ファイルをまたがると参照できない
  • Sphinx/docutils のリンクの仕組み(ラベル)をまったく使っていない
  • 図表番号はとれるが、リンクを作ることはできない
  • 書きづらい上に、いちいち :autonumber: を書かないと採番されない

つらい。

まとめ

今日現在、まともな numfig の実装なんてなかったんや… orz

コード片(code-block)にキャプションをつける

rstのファイルでリストに通し番号を入れる(久々にシェル芸) を見て、"code-block にキャプション付けられなかったっけ?" とおもって調べてみたところ、
Sphinx の code-block ディレクティブも docutils の code ディレクティブもどちらもキャプションをサポートしていませんでした。

僕らが普段目にする雑誌では、コード片にはそれぞれ「リスト 1. 〜〜〜」のようなキャプションがついています。
それと同様の記述は Sphinx ではできないのでしょうか。

よろしい、ならば拡張だ

標準のディレクティブを書き換えるのは気が進まない*1のですが、目的のためならば手段は選んでいられません。
早速 code-block ディレクティブを書き換えることにしましょう。

ということで、書き換え終わったものがこちらです。

code-block ディレクティブに :caption: オプションを加えたものです。

HTML では次のように出力されます。
f:id:tk0miya:20140726020048p:plain

また、PDF では次のように出力されます。
f:id:tk0miya:20140726020108p:plain

まとめ

adhoc なやりかたですが、code-block ディレクティブに :caption: オプションを付けることができました。
あとは numfig の応用で自動採番をすることもできるようになるでしょう。

問題はこの機能は標準ではないということなので、興味がある人は本家にプッシュしてみてください。

*1:標準の reST のように見えて、標準ではない動きをするものというのは非常に気味の悪いものです。