Date: 2012-04-24
Tags: python, docutils, textile, rst2textile

rst2textile-0.1.0 リリースと実装の裏話

先日 Sphinx + 翻訳 Hack-a-thon 2012.04 にて rst2textile-0.1.0 をリリースしました。

rst2textileの紹介

rst2textileは reStructuredText 形式のテキストを textile 形式に変換するためのツールです。例えば以下のように変換することが出来ます。

元になるsample.rstファイル:

==========
Heading1
==========

:Date: Today
:Author: SpamEgg
:Location: Here

Heading2
==========

Heading3
----------

Heading4
^^^^^^^^^^

*emphasized* (e.g., italics)

**strongly emphasized** (e.g., boldface)

- An item in a bulleted (unordered) list

- Another item in a bulleted list

  - Second Level

  * Second Level Items

    * Third level

#. An item in an enumerated (ordered) list xxxxxxx

#. Another item in an enumerated list yyyyyy

   #. Another level in an enumerated list vvvvvvvv


Blockquotes

   This text will be enclosed in an HTML blockquote element.

   Second Paragraph.

Links

   `link text <link_address>`_

Images

    .. image:: image.png

以下のコマンドで変換実行:

$ rst2textile.py sample.rst sample.txt

出力されたsample.txtファイル:


h1. Heading1

Date:
   Today

Author:
   SpamEgg

Location:
   Here

h2. Heading2


h3. Heading3


h4. Heading4

_emphasized_ (e.g., italics)
*strongly emphasized* (e.g., boldface)
* An item in a bulleted (unordered) list
* Another item in a bulleted list
** Second Level
** Second Level Items
*** Third level
# An item in an enumerated (ordered) list xxxxxxx
# Another item in an enumerated list yyyyyy
## Another level in an enumerated list vvvvvvvv
Blockquotes

bq. This text will be enclosed in an HTML blockquote element.
bq. Second Paragraph.
Links

bq. "link text":link_address
Images

!image.png!

このrst2textileパッケージは内部にdocutils_textileというdocutilsのwriter実装を持っています。rstファイルをパースして中間形式のノードツリーにするところまではdocutilsがやってくれるので、docutils_textileが実装するべきことはそれほど難しくありません。 Docutilsのnode-treeを疑似XMLで出力する で紹介した方法でノードツリーの構造を確認しながら実装を進めていきます。ただこれがけっこう時間がかかります。textileのフォーマットに一つずつ対応していくので、ひたすら手がかかります。

そこで楽しく実装できるように、余計なこと...工夫をしてみました。

Distutils2でパッケージング

パッケージを作成するためにDistutils2のpysetupコマンドを使ってsetup.cfgを作成し、後方互換性のためにsetup.pyも生成しました。

pysetupは Distutils2/packagingのpysetup createが長い で紹介したように、以下のように実行しています。

$ pysetup create
(対話形式で入力、 setup.cfgが生成される)

$ pysetup generate-setup
(setup.pyが生成される)

その後、いくつかのDistutils2のバグに悩みながらsetup.pyをsetuptools対応に書き換えたり、一部のUnicode文字列をstrにencodeするようコードを書き換えたりしました。こういった課題がクリアされればパッケージングはかなり楽になると思いますが、今の Distutils2-1.0a4 ではまだ厳しいですね。みんなでバグレポート出して改善しましょう。

一応、rst2textileは setup.py / setup.cfg とも調整してあるので、以下のどちらでもインストール可能です。

pip:

$ pip install rst2textile

pysetup:

$ pysetup install rst2textile

README.rstにテスト可能な変換サンプルを用意

README.rst にrst2textileの変換例として、以下のように記載しています。

List items
-----------
.. container:: test, rst, textile

   rst::

      - An item in a bulleted (unordered) list

      - Another item in a bulleted list

        - Second Level

        * Second Level Items

          * Third level

   textile::

      * An item in a bulleted (unordered) list
      * Another item in a bulleted list
      ** Second Level
      ** Second Level Items
      *** Third level

この記述はテスト出来るように、docutils標準の containerディレクティブでマーキングしてあって、test.pyの中でこのcontainerノードを見つけてテストするようにしてあります。もし実装に間違いがある状態で実行すると、実行結果は以下のようになります。

$ python test.py
......F......
======================================================================
FAIL: runTest (__main__.ReSTTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "test.py", line 42, in runTest
    self.assertEqual(self.expect, self.actual, message)
AssertionError: Convertion Mismatch (rst -> docutils_textile)
##source:
- An item in a bulleted (unordered) list

- Another item in a bulleted list

  - Second Level

  * Second Level Items

    * Third level

##expect:
* An item in a bulleted (unordered) list
* Another item in a bulleted list
** Second Level
** Second Level Items
*** Third level

##actual:
* An item in a bulleted (unordered) list
* Another item in a bulleted list
 * Second Level
 * Second Level Items
  * Third level

##node-tree:
<document source="<string>">
    <bullet_list bullet="-">
        <list_item>
            <paragraph>
                An item in a bulleted (unordered) list
        <list_item>
            <paragraph>
                Another item in a bulleted list
            <bullet_list bullet="-">
                <list_item>
                    <paragraph>
                        Second Level
            <bullet_list bullet="*">
                <list_item>
                    <paragraph>
                        Second Level Items
                    <bullet_list bullet="*">
                        <list_item>
                            <paragraph>
                                Third level


----------------------------------------------------------------------
Ran 13 tests in 0.004s

FAILED (failures=1)
$

上記のように「source(入力)」「expect(期待)」「actual(実際)」「node-tree(疑似XML)」といった実装修正の手がかりを全て表示してくれます。この仕組みを用意するためにdocutilsのコードをけっこう読む羽目になりましたが、それ以降は実装がかなり楽になりました。

test.pyがどのように実装されているかについては https://bitbucket.org/shimizukawa/rst2textile/src/tip/test.py を参照して下さい。

テストとして見ると無駄に努力していますが、以下のようなことをやりたくてこの仕組みを用意しました。

  • README.rst で変換サンプルを提示したい

  • 変換サンプルはテキストのままでも読みたい(自動生成したくない)

  • 実装時にnode-treeのpseudoxml出力が欲しい

  • docutils標準外のdirective等は使用したくない(PyPI公開のため)

  • 以下のフローで実装を進めたい

    1. フォーマット対応の対象とする rst, textile の対を README.rst に書く

    2. python test.py を実行してactualとnode-treeを確認する

    3. docutils_textileのwriter実装を行う

    4. テストが通るまで2,3を繰り返す

    5. リリースではサンプルとしてREADME.rstをそのまま利用する

上記フローならtextile仕様把握後にnode-treeを確認しながら実装できて楽だし、サンプルと実装とテストが乖離しないのでメンテも楽かなと思います。あくまで壊れてないサンプルを提示するのが主体なので、この方式上でフォーマットの組み合わせを細かくとかは考えない方向で。

まとめ

rst2textileって、自分は使う予定があんまりないので、今対応してないフォーマット部分は要望次第で実装ということにしたいなあ。