2011年8月12日

docutils でブログ記事作成

ブログの記事は reST (reStructuredText) で書いて HTML に変換していますが、 パソコンを買い替えたので環境を移行しなければなりません。 ということで、メモを書いておきます。

docutils のインストール

まずは Docutils モジュールをインストールします。 Sphinx をインストールすれば依存関係でインストールされていますが、 そうでない場合は個別にインストールします。

$ site=blog-site
$ virtualenv --distribute $site
$ cd $site
$ . bin/activate
$ pip install docutils

docutils には rst から始まる Python スクリプトがいくつか付属しています。

$ ls bin/rst*.py
bin/rst2html.py
bin/rst2latex.py
bin/rst2man.py
bin/rst2odt.py
bin/rst2odt_prepstyles.py
bin/rst2pseudoxml.py
bin/rst2s5.py
bin/rst2xetex.py
bin/rst2xml.py
bin/rstpep2html.py

rst2html.py を使うと、 reST で記述されたファイルを HTML に変換できます。 たとえば、 article.rst というファイルを HTML に変換する場合には次のように実行します。

$ rst2html.py article.rst >article.html

ブログ記事に使う

プレビュー用と投稿用の HTML を生成できるようにします。 ブログ投稿用のアプリケーションを使えばそんなものは必要ないかもしれませんが、 HTML をベタ書きできた方が何かと都合が良いこともありそうなので、 この方法にしています。

プレビュー用の HTML は先ほどのコマンドで生成できます。 しかし、投稿用には HTML の <head> タグなどは不要ですので、 これを取り除くことにします。

2種類のアプローチが考えられますが、ここでは後者を採用します。

  1. 生成された HTML をパースして、 <body> の中を抽出する。
  2. そもそも HTML の <body> だけを生成させる。

rst2html.py のオプション

rst2html.py--help オプションをつけて実行すると、 たくさんのオプションが利用できることが分かります。 なんとなくメモしておくものだけを抜粋して日本語にしておきます。

--strip-comments
        コメント要素を除去します。
--leave-comments
 コメント要素を残しておきます。 (default)
--input-encoding=<name[:handler]>, -i <name[:handler]>
 入力テキストのエンコーディングと、オプションで エラーハンドラーを設定します。 Default: <locale-dependent>:strict.
--input-encoding-error-handler=INPUT_ENCODING_ERROR_HANDLER
 デコードできない文字に対するエラーハンドラーを設定します。 Choices: "strict" (default), "ignore", and "replace".
--output-encoding=<name[:handler]>, -o <name[:handler]>
 出力テキストのエンコーディングと、オプションで エラーハンドラーを設定します。 Default: UTF-8:strict.
--output-encoding-error-handler=OUTPUT_ENCODING_ERROR_HANDLER
 デコードできない文字に対するエラーハンドラーを設定します。 "strict" (default), "ignore", "replace", "xmlcharrefreplace", "backslashreplace".
--language=<name>, -l <name>
 言語を設定します。 (BCP 47 言語表記). Default: en.
--no-file-insertion
 外部ファイルのコンテンツを挿入するディレクティブを無効にします。 ディレクティブは "include" と "raw" です。
--file-insertion-enabled
 外部ファイルのコンテンツを挿入するディレクティブを有効にします。 ディレクティブは "include" と "raw" です。 Enabled by default.
--no-raw
  "raw" ディレクティブを無効にします。
--raw-enabled
 "raw" ディレクティブを有効にします。 Enabled by default.
--template=<file>
 テンプレートファイルを指定します。 テンプレートファイルは UTF-8 でエンコードされていなければなりません。 Default is "lib/python2.7/site-packages/docutils/writers/html4css1/template.txt".
--stylesheet=<URL>
 スタイルシートの URL をカンマ区切りで指定します。 --stylesheet と --stylesheet-path の設定を上書きます。
--stylesheet-path=<file>
 スタイルシートのパスをカンマ区切りで指定します。 --link-stylesheet があると、出力する HTML の相対パスとしてパスは上書きされます。 Default: "lib/python2.7/site-packages/docutils/writers/html4css1/html4css1.css"
--embed-stylesheet
 出力する HTML にスタイルシートを埋め込みます。 スタイルシートファイルは、処理中にアクセスできなければなりません。 This is the default.
--link-stylesheet
 出力する HTML にスタイルシートをリンクさせます。 Default: embed stylesheets.

この中に、 --template オプションがあります。 ヘルプメッセージに表示されるデフォルトのテンプレートは次のようになっています。

%(head_prefix)s
%(head)s
%(stylesheet)s
%(body_prefix)s
%(body_pre_docinfo)s
%(docinfo)s
%(body)s
%(body_suffix)s

見た目のままですが、 $(body)s だけにすると、文章部分だけを出力できます。 つまり、次のように実行できます。

$ rst2html.py --template=blog-template.txt article.rst >article-body.html

これでも目的は達成できますが、出力ファイル名をいちいち入力するのは面倒ですから、 Python スクリプトにまとめることにします。

Python スクリプトで使う

rst2html.py は単なるラッパースクリプトで、内部的には docutils.core.publish_cmdline() を呼び出しています。 つまり、 subprocess.call()rst2html.py を呼び出すこともできますし、 docutils.core.publish_cmdline() を使うこともできます。

docutils のモジュールを使う場合は、次のようなスクリプトにまとめられます。

スクリプト

__doc__ = """
python %prog {manuscript}

Parse a reST manuscript and write out it in two way HTML formats.
A file whose suffix is ".html" is for preview, and a file whose suffix
is ".txt" is for blogger's article.
"""

import optparse
import os
import tempfile

from docutils.core import publish_cmdline

# see: lib/python2.7/site-packages/docutils/writers/html4css1/template.txt
RST_TEMPLATE_SOURCE = '''
%(body)s
'''

def parse_args():
    parser = optparse.OptionParser(__doc__)

    opts, args = parser.parse_args()

    if not args:
        parser.error("no arguments found.")

    return args


def publish_restructured_text(manuscript):
    fname = tempfile.mktemp()
    with open(fname, 'w') as temp:
        temp.write(RST_TEMPLATE_SOURCE)
    basic_option = ['--strip-comments']
    basename, _ = os.path.splitext(os.path.basename(manuscript))
    source = '%s.txt' % (basename,)
    preview = '%s.html' % (basename,)
    argv = ['--template=%s' % (fname,), manuscript, source]
    publish_cmdline(writer_name='html', argv=basic_option+argv)
    argv = [manuscript, preview]
    publish_cmdline(writer_name='html', argv=basic_option+argv)
    os.unlink(fname)
    print "Write as single HTML page and only body element."
    print "  single page : %s (%dbytes)" % (preview, os.path.getsize(preview))
    print "  only body   : %s (%dbytes)" % (source, os.path.getsize(source))


def main():
    manuscripts = parse_args()

    for manuscript in manuscripts:
        if os.path.exists(manuscript):
            publish_restructured_text(manuscript)
        else:
            print "%s is not found." % (manuscript,)

終わりに

Mac を買い替えたので環境を移行する必要がありました。 整理がてら記事にしましたが、必ずしも docutils で頑張る必要もなく、 Sphinx でラップした方が良いかもしれません。 ともあれ、まずは愚直に移行することにしました。

その他、テキストファイルで原稿を書いてブログで公開するソフトウェアはいくつかあります。

Web サービスを使えない状況や、単一のドキュメントを整形する場合には、 このような方法も便利かもしれませんね。

コメントを投稿