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

Markdown/CommonMark 探訪(2):HTML

Markdown/CommonMark の不思議な仕様を紹介していくこのシリーズ、第二回は HTML Blocks です*1

Markdown には 2種類の HTML 要素があります。ひとつはブロック要素、もうひとつはインライン要素です。
HTML ブロックは、HTML のブロックレベルの要素をそのまま Markdown で扱うためのものです。マークアップされたものはその名の通りブロック要素として扱われます。

<table><tr><td>
this is table!
</td></tr></table>

もうひとつのインライン要素は、段落やリスト、表の中など、任意の箇所で HTML を使えます。

This is a <em>markdown</em> world!

これらの例を見ると HTML 記法は比較的シンプルなものに見えます。では、これはどう解釈されるのでしょうか。ブロック要素?インライン要素?

<h1>hello markdown!</h1>

それでは、これは?

<pre>
A example a PRE tag which is not closed...

最後にもうひとつ。これは?

<em>
A example an EM tag which is not closed...

じつは 3つともブロック要素として解釈されます。
ルールを追っていくと、それぞれ以下の理由でブロック要素として判定されます。

  • <h1> タグから始まる行はブロック要素として扱われる
  • <pre> タグから始まる行もブロック要素として扱われる
  • <em> タグは本来ブロック要素ではないが、開始タグ単体で登場した場合はブロック要素

一方で、以下のようなケースはインライン要素として扱われます。

hello <h1>markdown!</h1>!

# ブロックレベルのタグであっても、行頭でなければインライン扱い
<em> A example an EM tag which is not closed...

# 開始タグと同じ行に記述があるので、インライン扱い

CommonMark のルールに従うと、ちょっとしたマークアップの違いで HTML タグはブロック要素として扱われたり、インライン要素として扱われたりと、解釈がかんたんに変わります。

最後におだいをひとつ残して、今日は終わりにしようと思います。

<del>*em*</del>
<del>
*em*
</del>
<del>

*em*

</del>

それぞれ、どんな風にレンダリングされるのかちょっと考えてみてください。
ちなみに答えはここ(
commonmark.js demo )から確認できるようにしておきました。

*1:思いつきで書き始めたので、ネタが尽きるまで書いていこうと思います

Markdown/CommonMark 探訪(1):laziness

Markdown パーサを書くにあたってちょっと苦労したのが Laziness という考え方です。

Markdown では、引用ブロック (blockquotes) を書く際、引用符を付けるのは最初の行だけでよいと定められています。

> This is a blockquote with two paragraphs. Lorem ipsum dolor sit amet,
consectetuer adipiscing elit. Aliquam hendrerit mi posuere lectus.
Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus.

> Donec sit amet nisl. Aliquam semper ipsum sit amet velit. Suspendisse
id sem consectetuer libero luctus adipiscing.

https://daringfireball.net/projects/markdown/syntax#blockquote

引用符がある行から段落が終わるまでは、すべて引用ブロックの中の段落として扱われます。
たしかにこれは書きやすいだろうし、読めなくはないので、記法として理解できます。

しかし、さまざまな実装の共通項を探る CommonMark では、こんな風に整理されています。

>>> foo
> bar
>>baz

http://spec.commonmark.org/0.28/#example-214

このテキストを処理系に渡すと、3重にネストされた引用ブロックの中に「foo, bar, baz という 3行の段落」がある、と解釈されます。
えっ。
テキストとしての見た目と、あまりに違う解釈結果ですね。

laziness はリストでも有効

この laziness の考え方はリストでも適用されています。

*   Lorem ipsum dolor sit amet, consectetuer adipiscing elit.
Aliquam hendrerit mi posuere lectus. Vestibulum enim wisi,
viverra nec, fringilla in, laoreet vitae, risus.
*   Donec sit amet nisl. Aliquam semper ipsum sit amet velit.
Suspendisse id sem consectetuer libero luctus adipiscing.

印刷物とかでもこういうレイアウトが使われていることもあり、理解はできます。

では、こちらはどうでしょう。

> 1. > Blockquote
> continued here.

http://spec.commonmark.org/0.28/#example-256

2行目の「continued here」は内側の引用ブロックの内部の段落の一部です。
えっ。
またですか。

laziness の範囲

徐々に Markdown の世界観がつかめてきましたか? それではこれはどうでしょう。

> - foo
- bar

引用ブロックの中に箇条書きが記述されていますね。
さて、これはどう解釈されるのでしょうか。

実はこれは

  • 「foo」は引用ブロックの内側のリストとして扱われる
  • 「bar」は引用ブロックの外側の (別の) リストとして扱われる

と解釈されます。

というのは、laziness が適用される対象は「段落」だけです。その他の記法には laziness は適用されません。
楽しいですね。

面倒くさい例としては

> lorem ipsum
> ---

は引用ブロックの中に <h2> の見出しが記述されると判定されるのに対して

> lorem ipsum
---

は引用ブロックの後ろに区切り線 (themantic break) がある、と解釈されるものがあります。

pycmark ではどうしているのか

段落用のプロセッサーで、次のような処理をしています。
1. 同じインデントレベル (引用符やリストアイテムなどのレベル) の中で読み進める
2. その状態で空行や区切り線などの割り込み要素がきた場合は、段落の処理は終了
3. インデントレベルの最後まで段落が続いた場合は、LazyLineReader という laziness に対応した読み込みモジュールを使って読み進める
4. 割り込み要素が来るまで、段落の一部として解釈を続ける


ということで、pycmark でも無事に laziness な段落は正しく扱えます。
多用すると読みづらくなりそうですが、つかってみてください。

Re: SphinxCon JP 2017

38度の熱が出たので、楽しみにしていた SphinxCon JP 2017 を休むことになってしまいました。
おかげで、Sphinx の魔改造の話HTML テンプレートへの提案など、興味深い発表を聞きそびれてしまったのは非常に残念です。
資料は公開されているので、それぞれ噛み締めながら何度か読み返そうと思っています。

さて、今回の SphinxCon では僕はトーク枠を持っていなかったのですが、実はイベントに合わせて作っていたものがあります。
LT でお披露目しようかと思っていたのですが、間に合わなかった上に風邪でダウンしてしまったので、代わりにここで紹介しようと思います。
作ったのは pycmark というパッケージです。
github.com

まだ README すら置いていないので、なにものかがわかりづらいのですが、docutils 向けの CommonMark パーサです*1
現状では、ひととおりの記法をサポートしているので、触ってもらえるレベルにはなったかと思います。
TAB やコントロール文字類、特殊文字類の対応が済んでいないので、もう少し開発を続けるつもりです。
もう少し進めて一段落したら、一度リリースするつもりです。

使ってみる

未リリースなので、リポジトリから直接入れます。

$ pip install git+https://github.com/tk0miya/pycmark

ちなみに Python3.x 専用です。いまのところ 3.6 でしか動作確認していません。
いまのところ遡ってサポートする気はないので、使いたい人は Python のバージョンを上げてください。

Sphinx 拡張としての作り込みはしていない*2ので、conf.py に setup 関数を書き足し、直接 pycmark を有効にします。

html_experimental_html5_writer = True


def setup(app):
    from pycmark import CommonMarkParser
    app.add_source_parser('.md', CommonMarkParser)

ついでに、HTML5 writer を有効にしておきます。標準で利用される HTML4 writer では正しく動作しません。

設定はこれで完了です。あとは .md ファイルを置いて、ビルドするだけです。

pycmark のゴール

さて、Sphinx では recommonmark が CommonMark パーサとして知られています。
それなのに、敢えて新しい CommonMark パーサを開発したのはわけがあります。

recommonmark がベースにしている CommonMark-py はコア部分がモノリシックなモジュールとして実装されています。これはシンプルで正しい設計なのですが、残念ながら拡張性がありません。
CommonMark の生まれた経緯などを考えると、パーサは拡張可能である必要はないのです。しかし、実際には gfm 然り、kibe.la 然り、新し目の Markdown の派生言語は CommonMark をベースとしています。つまり、これらのパーサを用意するには CommonMark パーサをさらに拡張する必要があります。こうしたときに CommonMark-py では都合が悪いのです。

pycmark はプラガブルな構造になっており、個々の記法を個別に無効化したり、新たな記法を追加できます。
現時点では、CommonMark の範囲の実装しか提供していませんが、いずれ gfm などのよく使われている記法のプラグインを用意する予定です。

まとめ

pycmark という CommonMark パーサを作っています。
現在の pycmark CommonMark パーサとしてはおおよそ完成しているものの、未熟なところが多くまだ使用に耐えません。
しかし、いずれは gfm などの記法も解釈できる CommonMark サブセットパーサにまで実装を進める予定です。

そこまでたどり着くと、これまでの markdown 資産が活かせ、Sphinx が使える範囲が更に広まるのではないかと考えています。

はてさて、これが夢物語で終わるのか、ちゃんと実装が完了するのかはお楽しみにお待ちください。
なお、pycmark はいつでもバグレポートと PR をお待ちしております :-)

ひとまずファーストリリースを近いうちに (年内に?) やりたいところですね。

*1:Python の CommonMark パーサだから pycmark です。いい名前が思いつかなければ、このままリリースされます。

*2:現時点では、あくまで docutils の parser モジュールとしてだけ動きます