Sphinx 10周年に寄せて #sphinx10th

本日、Sphinx-1.7.2 をリリースしました。いくつかのバグが修正されているので、試してみてください。
pypi.python.org

さて、リリースするまですっかり忘れていましたが、本日 3月21日は Sphinx の誕生日です。しかも、なんと 10周年です。

Release 0.1.61611 (Mar 21, 2008)
================================

* First public release.

github.com

Sphinx と僕の (だいたい) 10年間

僕が Python を使いはじめた頃から、既に Sphinx はよく使われていたので、かなり古くからあるツールだと思っていたのですが、実は 僕の Python 暦(2010年ごろ)とそんなに変わらないのですね。実際、Python を使いはじめた頃に 0.6.6 を使ってみたり、1.0 のリリースパーティに参加したりした覚えがあって、自分の記憶と CHANGES が一致しています。
sphinx-users.jp

その後、僕は blockdiag シリーズを作り、Sphinx 拡張職人になり、そして Sphinx のメンテナになり…とドキュメンテーションツールに関わり続けてきました。
Python をはじめてから、ずっと Sphinx とその周辺ツールにかかわっていますし、Sphinx を作るために Python を覚えたと言っても過言じゃないかもしれません。
すくなくとも、今では Sphinx が僕のホームであることは間違いありません。
なんとなく Python を使いはじめただけだというのに、なんとも不思議なことです。

この10年、Sphinx は僕の人生を巻き込んで前に進んできました。
毎日英語の読み書きをさせられたり、書籍を書くことになったり、雑誌に寄稿したりと、Sphinx に関わってから僕の生活は一変しました。
Sphinx のおかげで知り合った友人もたくさんいます。Google さんにも表彰されました。
まさか僕が関わっているソフトウェアがあの Debian に収録されていて、さらにカーネルPython、その他諸々のドキュメントを支えているというのは想像もつかなかったことです。

決して派手ではないですが、Sphinx は僕を大きく変えたのです。

10years

10年前はまだ SphinxPython に出会う前で、何者かになりたいというワナビーだった頃です。
10年後のいまは実用的なソフトウェアのメンテナとして活動しています。

もしかすると、夢を叶えてなりたかったものになれたのかもしれません。
もしかすると、適当なところに居着いてしまっただけかもしれません。

かつては自分のやりたいこと、好きなことが選べる可能性を秘めていました。
いまは新しいものに触る時間は取れないし、アイディアがあっても完成させることもなかなかできません。

前に進んだのか、先細ったのか。
そのあたりは僕にもわかりませんが、きっとこの先 10年も Sphinx はそこにあるでしょう。
ソフトウェアがあるかぎり、ドキュメントは必要とされ続けるでしょうから。

あれから10年も。これから10年も。
10年前の自分が今の自分を想像できなかったように、10年後の自分が Sphinx とどう関わっているのかは想像がつきません。
でも、願わくば、なんらかの形で Sphinx と付き合っていけると良いな、と今は思っています。
これからも、きっと Sphinx は前に進んでいることでしょう。来年には Sphinx 2.0 がリリースされる予定です。

次の10年も、Sphinx が多くの開発者の手助けできることを祈って。
おめでとう、Sphinx。ありがとう、Sphinx

#ポエム *1

*1:渡辺美里の 10years を思い出しながら書いていたら、なんだかセンチメンタルでポエミーな記事になってました。あとで恥ずかしくなって転げそうだけど、ぼちぼち日も変わってしまうし、そのまま公開しちゃいます。

Re: Markdownにmetaタグを入れる

つい最近、こんな記事が出ていました。
kamekokamekame.net

いいですね、コミュニティ。さっと拡張を書いてくれる人に優しさを感じます (自画自賛)。

markdown の拡張子は?

さて、この記事では source-read イベントをフックして md_prolog を実現しました。
でも、このコード、よく読むとイベントハンドラで filename.endswith('.md') とか決め打ちしていますね。
これでよいのでしょうか?

適当にぐぐってみると、こんなやりとりが見つかります。
superuser.com
.markdown とか .mkd とか、いろいろ使ってる人がいるんですね。
先ほどのコードは完璧ではありません。

Sphinx における markdown の拡張子

Sphinx では source_parsers の設定で、Markdown パーサを有効にすることが多いですよね*1
source_parsers は conf.py にこんな感じで定義します。

from recommonmark.parser import CommonMarkParser

source_parsers = {
    '.md': CommonMarkParser,
}

source_suffix = ['.rst', '.md']

source_parsers 変数に「拡張子」と「パーサクラス」のペアを列挙します。
そして、source_suffix に対象となる拡張子のリストを並べます。


ここの設定を工夫することで、好みの拡張子を割り当てることができます。

任意の拡張子を考慮した md_prolog

さて、このように Sphinx では任意の拡張子を Markdown 文書として扱うことができます。
これに対して、冒頭の記事で紹介されているコードは対応できていません。
拡張子がハードコードされているため、設定によっては正しく動きません。

これに対応するには次のようなコードを書きます。

from recommonmark.parser import CommonMarkParser

def on_source_read(app, docname, source):
    extensions = tuple([ext in (ext, parser) in app.config.source_parsers.items()
                        if parser is CommonMarkParser])

    filename = app.env.doc2path(docname)
    if filename.endswith(extensions):
        source[0] = app.config.md_prolog + "\n\n" + source[0]

このコードでは source_parsers の値を元に CommonMarkParser が処理する拡張子のファイルかどうかを判定しています。
ちょっとわかりづらいコードになっていますが、これは無事に動くはずです。

いまやほとんどの人は .md を使っているような気もするのですが、つい .markdown を使いたいひとや .mkd 派の貴方はこのコードを使ってみると良いでしょう。

*1:API を使って足すこともできるのですが、recommonmark は使っていません

オライリーへの入稿用の Sphinx拡張、sphinxcontrib-getstart-sphinx をリリースしました。

いつぞやの記事で「Sphinx をはじめよう 第2版」を書いた話をしたのを覚えてらっしゃいますか?ちなみに、オライリーさんのページはこちらです。以前に紹介したときは、紙媒体はイベントなどのオフラインでの購入に限定されていたのですが、いまでは通販でも手に入るようです。
電子書籍も好評発売中ですので、興味がある方はぜひとも見ていただければ、と思います。
www.oreilly.co.jp

さて、宣伝から話を戻しましょうか。
オライリーマニアのみなさんに於かれましては、オライリー・ジャパンさんの電子書籍の制作に Re:VIEW が深く関わっているのは既にご存知かと思います(参考:Community Blog - オライリー・ジャパンのePUBフォーマットを支える制作システム)。

この本の出版の際も、Sphinx で書かれた原稿を Re:VIEW に変更し、レビューや校正の段階では Re:VIEW 原稿を修正するというステップを踏んでいました。Sphinx をメンテしていて、reStructuredText に慣れ親しんだ身としては、ちょっと残念な気分でした。

Re:VIEW 記法に慣れていないというのもありますし、来るべき(?)第3版のために、reSTの元原稿も同時に変更していたというのもあり、Re:VIEW で原稿をいじるというのがちょっと負担だったのは確かです。reviewbuilder などによって、こうしたインピーダンスミスマッチは減ってきているとはいえ、いまだに乗り越えなくてはならない壁が残っていたのが、この書籍の制作過程でした。

さて、ここで黙っていたら男がすたるってもんですよね。Sphinx 拡張メイカー (自称)の名にかけて、こういう問題は拡張のちからでねじ伏せなくてはなりません。
というわけで、いつものごとく Sphinx 拡張を作りました*1。それが sphinxcontrib-getstart-sphinx です。

この拡張は、README に

sphinxcontrib-getstart-sphinx is a collection of Sphinx extensions to build Sphinxをはじめよう 第2版 (Getting Started with Sphinx 2nd Edition).

とあるように、Sphinx をはじめよう 第2版 のビルド用に作られた拡張群です。当初は sphinxcontrib-oreillyjapan にしようかと思ったのですが、流石にネームスペースが強すぎたので自重しました。

このパッケージには以下の拡張が含まれています。

  • better_docref
    • :doc: による参照を書籍向けのいい感じにする
    • 具体的には『第1章「Sphinxとは」では…』のように章番号を付けた参照に置き換える
  • column
    • コラムを書くための column ディレクティブを追加する
    • もちろん Re:VIEW への変換にも対応している
  • footnote_relocator
    • 脚注を各パートの末尾に移動する
    • 標準の Sphinx では章や節の途中に脚注を置くので、EPUB など向けに出力を調整する拡張
  • glossary_decorator
    • glossary の用語部分を太字にする。これでキミのドキュメントもオライリー風だ!
  • oreilly_review_table
    • オライリーさんの内部ツールに合わせて Re:VIEW 出力形式をごにょごにょする

getstart_sphinx 拡張はアラカルト形式ですので、この中の任意のものを有効にすることもできます (もちろん全部有効にしてもオッケー)。

というわけで


というのが一歩進んだわけです。

Sphinx をはじめよう 第2版 ともどもよろしくお願いします。

*1:書き上げたまでは良かったものの、最近まで放置していたので、なんというか、ごめんなさい

Sphinx のバグを 1ヶ月で 80件以上クローズした件

1月の頭に合宿をやった勢いで、1月はひたすらメンテをやってました。
Sphinx-1.7 のリリースが近いのでひたすらチケットをちぎっては投げ、ちぎっては投げした結果、
1ヶ月で 83件ものチケットをクローズすることができました。ちなみに PR のマージは 100本超えました。
f:id:tk0miya:20180203175356p:plain

もちろん自分一人の力ではなくて、PR を投げてくれた人、レポートをしてくれた人を含め、色んな人に助けられての結果…なんですが、右下のコミットグラフにあるようにかなりの作業を僕がやっつけたので、Sphinx はオレが育てたと言っても過言ではありません。

おかげさまで 1.7.0 もどうにかリリースできる目処が付いてきましたし、とても嬉しいことにイシューの数が減少傾向にあります。2/3 18:00 現在、久しぶりに合算 700件の大台を割りました。
f:id:tk0miya:20180203175849p:plain

2.0 への道筋も見えてきた ということもあり、この後山場(?)を迎える Sphinx ですが、引き続きメンテを続けていこうと思っています。
とはいえ、このペースを維持すると流石にきついので、どこかでペースを落としたりサボったりしながら、ですけどね。

ちなみに、Sphinx プロジェクトは OSS に関わってみたい人、メンテナンスしてみたい人、などなど随時募集しています。
事実上のプロジェクトリーダーは日本人ですし*1、slack で日本語で会話できるコミッターが数人いますし、手伝いやすいかもしれませんよ!
Linux カーネルPython に書籍の執筆など、多くの利用者がいるので、使われてる感もばっちりです。
興味がある人は僕あてにメンション投げたり、PR 投げたりしてみてください。


P.S.
ちなみにさっきのグラフだとイシュー減ってる感がありますが、実際には誤差です*2
がんばっていきまっしょい
f:id:tk0miya:20180203180553p:plain

*1:厳密な定義とか役職はないので、あくまで事実上のもの。

*2:さっきのグラフは 2016/1 からの差分です

Sphinx 開発合宿をしました。

合宿してきました。

当日や流れや施設の様子など、詳しいところは他の参加者の記事に譲ります。
takuan-osho.hatenablog.com
www.freia.jp

そういや今回全然写真撮ってないや。

感想

  • 普段はひとりでやっているので、他の人を巻き込んでやれるのは新鮮
  • SR+ なメンテナと会話することで、いくつかのチケットの方針が決まった
  • 手がまわっていない、古いチケットのトリアージやクローズが進んだ
  • Sphinx-1.7 での対応内容が整理された (ほぼ先送りした)
  • Sphinx-2.0 のゴールが見えてきた
  • いくつか新しい PR が飛んで来て助かった

普段はイシューの物量に押し流されて、どんどんノイジーになっているイシューリストがいくらか整理されましたし、
コアに関する議論の時間が取れたことで、止まっていたチケットが先に進むきっかけになりました。
ひとりで進めていくと、こうしたタスクが積もりに積もってしまうので、非常に助かりました。
(短時間で片付く軽めのタスク(質問とか、軽微なバグとか)ばかり片付くので、徐々に濃いやつばかり残っていく)

目で見てわかりやすいデータとして、Sphinx のイシューの増加がひさしぶりに減少傾向に転じました(一番右端のところ)*1

f:id:tk0miya:20180112151845p:plain

施設の話

また、今回使った国立女性教育会館ですが、合宿向けの非常によい施設でした。

  • 都内から近い (東上線沿線民だとさらに近い)
  • 観光施設がないので気が散らない
  • めっちゃ安い。一泊二日でフル活動しても 5,000円ちょい
    • 5人部屋 + 夕食 + 朝食 + 会議室 2日弱
    • アメニティに浴衣は含まれていないので要注意

ただ、このあたりの要素を合宿に求めてる人には向いてなさそうです。

  • 温泉 (大浴場は夜しか使えず。深夜は不可)
  • 近接のコンビニ (徒歩10分のところにあります。ドラッグストアもその近くに)
  • 美味しいご飯 (施設内の食堂がメインです)
  • インスタ映え

次回向けのメモ

  • ネットワークの準備をしていこう
    • 今回はネットワークまわりのことをほとんど考えずに行ったので、みんなテザリングだった
      • 会議室は問題なかったが、居室は電波弱めだったみたい
    • 有線からのブリッジがあると幸せそう
    • ただ、IP がもらえないみたいな声も上がっていたので、多少の格闘が発生するかも
      • 厳密には今回、ブリッジ? がひとつあったのだけどうまく設定できずに早々に諦めた
  • ちゃんと寝よう
    • 睡眠不足で死んでいるうさたーんが散見されたので (僕も眠かった)、粘らずほどよく寝るのが吉

まとめ

いくらか気になるところがないわけではないですが、開発合宿として使うにはよいところでした。
予約してくれた @shimizukawa ありがとう。

おかげで Sphinx の開発がいろいろ進みました。参加してくれたみんなありがとう。

他のひとたちの都合次第ではあるけど、年に 1-2回ぐらい行きたいところですね。

*1:当日夜計測の速報データです。今月の後半の進み具合によっては、グラフの向きが変わります。がんばりましょう。

2017年を振り返る / 2018年の抱負

あけましておめでとうございます。今年も毎年恒例の抱負エントリからはじめましょう。
年末はひとりアドベントカレンダー的なことをやろうとしていたのですが、とっちらかって終わってしまっていますね。書き溜めてある記事はないのですが、まだネタはありそうなので、どこかで愚痴を形にできるとよいのですが…。

それはさておき、今回もさくさくと去年を振り返ってみましょう。

2017年の抱負 として挙げたのは

  • ひきつづき Sphinx のメンテナ活動をする
  • Sphinx 以外のネタにも手を出す
  • なにか使えるツールをリリースする (少なくとも一本以上)
  • 技術書を読み続ける。時間を取る。
  • 家の片付け
  • 健康生活

の6個でした。

まずはそれぞれについて振り返っていきます。

ひきつづき Sphinx のメンテナ活動をする:△

今年は仕事と執筆、体調不良のおかげでいつものペースでコードが書けずにもやっとしていました。

コミットの量を見てみても、7-9月と12月前半がスカスカです (ref: Contributors to sphinx-doc/sphinx · GitHub) 。
f:id:tk0miya:20180102101342p:plain

そんな状況だったので、相も変わらずイシューが大変なことになっています。1月2日現在だと 658本のイシュー + PR があります。年末に作ったイシュー増加傾向のグラフもいい感じに右肩上がりになっていて萌えますね。
f:id:tk0miya:20180102102731p:plain

毎年のことですがジリ貧ですね。

Sphinx 以外のネタにも手を出す:✕

Sphinx のことで精一杯 (というか時間が足りないぐらい) だったので、他に手を出す余裕はありませんでした。

なにか使えるツールをリリースする (少なくとも一本以上):△

念願の CommonMark パーサを一本書きました。完成度 70-80% ぐらいのところで、遊んでる場合ではないと我に返って Sphinx 業に戻ってしまったので、残念ながらリリースには至っていないのですが、やはりパーサを書くのは楽しいですね。
github.com

ただ、作るところまではよかったものの、リリースできなかったので△ってところで。

他には sphinxcontrib-reviewbuilder 0.0.8 : Python Package Index のバージョンアップを手伝ったりしました。これで書籍の入稿フォーマットとして Sphinx が使いやすくなったはずです。Sphinx 本でも利用しています。

技術書を読み続ける。時間を取る。:△

2017年も 新宿 Book-a-thon に助けられました。

実践DDD本を読み進めるのにかなり苦労した挙句に、いまは Real World HTTP に浮気しているという感じですね。
さらに、途中で Sphinx 本の校正をはさんだりしたので、今年はいつもに増して本を読めていないなーという感じです。

初心に戻って、今年は本を読む時間を確保したいですね。

家の片付け:✕

ごめんなさい、ごめんなさい、ごめんなさい。

健康生活:✕

11月末あたりからハードな風邪を引いてしまって、まだ 1ヶ月かかってようやく落ち着いてきたところです。
体重はほぼ安定しているのが唯一の救いですかね。

その他の振り返り

Google の Open Source Peer Bonus Program を受賞?した

詳細はこちら Announcing more Open Source Peer Bonus winners | Google Open Source Blog
日頃の Sphinx 業を評価していただいたので非常にありがたいですね。
その後、いろんな人にも祝っていただきました。

表彰には感謝しつつも、気負わずにマイペースでやっていくことにします。
年も変わったので、もうこれで自慢するのは難しいですしね。

Sphinx 本を改定した

今年の時間を吸い取っていったもののひとつです。微調整しよう、だったはずが大手術になってしまった案件です。
www.oreilly.co.jp

ただ、その甲斐もあってかなり読みやすくなったはずです。興味がある方は是非見ていただきたいですね。

まとめ / 2017年の抱負

去年の抱負の成果は ○ 0つ、△ 3つ、× 3つでした。散々な感じですね。
体感的にもそんな感じなので、今年は精進していきたいところです。

というわけで、さっくり今年の抱負。

  • Sphinx のメンテナ活動をがんばる
    • 去年の分を取り戻すべく、なるべく時間を割いていきます
    • 今度こそ 2.0 への道筋を決めるべく、機能を形にしていきたいですね
  • なにか使えるツールをリリースする (少なくとも一本以上)
    • いつもの。Sphinx を優先するので努力目標ぐらい
    • 少なくとも pycmark をリリースするというのはやりたい
  • 技術書を読み続ける。時間を取る。
    • 引き続き
  • 家の片付け
    • 来年の振り返りでは土下座せずに済むようにしたい
  • 健康生活
    • ここ二年ぐらい調子が安定しないので気をつけていきましょう。

Markdown/CommonMark 探訪(3):段落の割り込み

今日は Sphinx-users.jp の総会に行ってきました。
今年はとくになにも特別な活動をしない会長さんだったので、やや後ろめたい部分があったのですが、それもこれでおさらばです。
あたらしい会長の @usaturn 氏に頑張ってもらえれば、と思います。

さて、そんな感じで書き始めるのは第三夜でございます。
今日は小ネタで段落の割り込みについて紹介します。

CommonMark では記法ごとに段落に割り込むことができるかどうかが決まっています。
たとえば、themantic breaks は段落に割り込めます。

Foo
***
bar

これは foo, <hr />, bar と解釈されるわけですね。

一方でインデントされたコードブロック(Indented Code Blocks)は段落に割り込むことができません。

Foo
    bar

これは Foo bar というひとつの段落として扱われます。

このあたりは直感的のような、そうでないような、ちょっとむずかしいところですね。

それでは、この段落の割り込みまわりで、おや? とおもう例を紹介していきます。

HTML タグ

昨日も触れましたが、HTML はタグによって動作が変わります。
ブロック要素系のタグは段落に割り込むことができますが、インライン系のタグは割り込めません。

つまり

Foo
<div>
bar
</div>

はふたつのブロック (Foo という段落と <div> 以降の HTML ブロック) と解釈されますが、

Foo
<a href="bar">
baz

はひとつの段落にインライン HTML 要素があると解釈されます。

ここの解釈によって、強調やリンクなどのインライン要素の展開結果が異なるのですが、タグによって動作が違うというのは覚えづらいですね。Markdown マスターを目指す方は要注意です。

番号付きリスト

もうひとつは CommonMark のおかしなルールです。

The number of windows in my house is
14.  The number of doors is 6.

はひとつの段落として解釈されるのですが、なんと

The number of windows in my house is
1.  The number of doors is 6.

は 1行目は段落、2行目は番号付きリストとして解釈されます。

1番からはじまる番号付きリストは、段落に割り込むことができる要素なのです。
これが 2番から始まる場合は段落の割り込みが発生せず、段落が続いているものと解釈されます。

行の折り返しのあとに 1. や 1) からはじまる文章がある場合は注意が必要です。

themantic breaks の例外

Themantic breaks が段落の割り込みを発生させることは先ほど紹介しましたが、じつは特定の記号を使うと解釈結果が変化します。

Foo
---
bar

上記の例のように区切り文字に === や --- を使った場合、段落の割り込みは発生しないのですが、解釈結果が変化します。
前半のパートは段落ではなく、見出し (setext headings) として扱われます。
HTML で書くと <h2>Foo</h2><p>bar</p> となり、見出し(Foo)につづく本文(bar)とみなされます。

割り込みとは少しちがうのですが、 themantic breaks を間に入れようとしたときは、この点に注意が必要です。

訂正(12/10):=== は themantic breaks ではありません。--- だけでした。

おまけ:箇条書き

段落ではないですが、箇条書きでも割り込み的な例外処理があります。
それが themantic breaks の解釈です。

* Foo
* * *
* Bar

という記述があると、CommonMark パーサは箇条書き(Foo)、Themantic breaks (区切り線)、箇条書き(Bar) の 3つのパーツとみなします。

これが、別の箇条書き記号を使っていると解釈が変わります。

- Foo
- * *
- Bar

この場合、3項目のリストがあって、2番目の項目は空のリストを持っていると解釈します。

説明されると理解できるのですが、このリストの割り込みは微妙な挙動でした。
pycmark でも実装を進める中で、最後の方にこの問題が見つかって、がっかりしながら修正することになりました orz