トップ «前の日記(2017年12月17日) 最新 次の日記(2017年12月19日)» 編集

KeN's GNU/Linux Diary


2017年12月18日

_ [reviewml] Re:VIEW Hacks (1) 〜review-ext.rbの基本構成

Re:VIEWはRubyで書かれていますが、作業フォルダにreview-ext.rbという名前のファイルを置いておくと、コンパイル実行前に読み込んで内部挙動を置き換えることができます。

ある程度まとまったら電子書籍にでもしようと思っていますが、このreview-ext.rbや各種フォルダを使ったRe:VIEWのカスタマイズやハックについて、気の向いたときにパラパラとメモがてら日記にまとめていこうと考えています。

Rubyは既存のクラスの動的な変更や拡張、すなわちモンキーパッチを許容しており、先に定義したクラスやそのメソッドを後から別の定義によって上書きできます(定数など一部の例外はあります)。作業フォルダに置いたreview-ext.rbは、起動後すぐにlib/review/book/base.rb#self.update_rubyenv内のKernel.loadで読み込まれ、Re:VIEWの実装挙動を上書きします。

なお、第三者向け提供のシステムに備えて環境変数REVIEW_SAFE_MODEの数値で2ビット目が立っているときにはロードしないという仕組みは一応用意しているのですが、ほかにもいろいろ悪さをできるところはあるはずで、あまり期待はしないでください(PullRequest歓迎です)。

ではreview-ext.rbのシンプルな例を示しましょう。

 module ReVIEW
   module HTMLBuilderOverride
     def builder_init_file
       super
       STDERR.puts "Initialize!"
     end
   end

   class HTMLBuilder
     prepend HTMLBuilderOverride
   end
 end

ReVIEW::HTMLBuilderはHTML変換ビルダのクラスです。review-compile --target=htmlやreview-epubmakerで使われます。このビルダの挙動を変えるために、HTMLBuilderOverride(名前は何でもよいのですが)というモジュールを用意し、HTMLBuilderではprependメソッドで取り込むように設定します。

メソッドを上書きするのに私は以前はHTMLBuilderの下に実装を記述していたのですが、@takahashimさんに「prepend」を教えていただきました。prepend導入の最大のメリットは、上書きモジュールのメソッド内で「super」を使ってクラスの元々のメソッドを呼び出せることです。「初期化に少し加えたい」「結果の文字列を少しいじりたい」といった場面のために元々のメソッドの内容を全部コピーして修正するのは、馬鹿馬鹿しい上にRe:VIEW本体の実装が変わったときに厄介な問題を孕んでしまうので、これがすっきりするのはとても便利です。

上記の例では、ビルダの初期化を担うbuilder_init_fileメソッドをオーバライドし、元々のメソッドをsuperで実行した後に「Initialize!」とエラー出力に表示するようにしてみました。引数付きメソッドなら「super(引数, ...)」を使えばよいし、値返しのメソッドなら「s = super(引数, ...)」のように変数に格納させてから加工すればよいでしょう。

review-compile --target htmlでreファイルを変換してみると、最初に「Initialize!」と表示されるはずです。

Re:VIEWは今も改良を続けているプロダクトであり、外形上の変化は互換性をできるだけ保って影響が少ないように努めてはいるものの、内部のコードは頻繁に書き変わっています。つまり、Re:VIEW側の実装バージョンアップ次第でモンキーパッチが動かなくなる可能性は常にある、というリスクは念頭に置いておきましょう。良い機能であるという確信があれば、モンキーパッチで対処するよりもGitHubにissueなりPRを出していただいたほうがよいでしょう。