qwikWeb の仕組み 【第 2 回】 qwikWeb のプラグインを作ってみよう

qwikWeb のプラグインを作ってみよう

本稿では、qwikWeb のプラグインの作り方について解説する。実際にいくつかのプラグインを書きながら、どのようにすれば qwikWeb を拡張できるのかを実践してみる。

目標

qwikWeb は元々、グループのメンバーだけが使う非公開 Wiki サイトの実現を目標としており、プライベートな利用において必要な機能の実現を優先している。しかし、qwikWeb も徐々に公開 Wiki サイトのために利用されることが増えてきた。元々プライベートな利用を優先してきたという経緯から、公開 Wiki サイトとして必要な機能はまだまだ十分に実装されているとは言えない状況である。そのような機能を開発していくには、私一人では到底力が足りない。そこで、「 qwikWeb 開発に参加してくれる人を増やそう!」というのがこの連載の狙いである。

qwikWeb は、それぞれの部品ごとに機能を追加していけるように構成している。 その中でも、プラグインはもっとも基本的な拡張方法であり、このプラグインの作り方から解説を始める。

セットアップ方法

qwikWeb 開発版のインストール方法は前回解説したが、前回からはリポジトリを CVS から Subversion へと変更しているため、ソースコードのチェックアウト方法が変わっている。最初からもう一度解説することとする。

「 ~/qwik 」以下に qwikWeb をインストールすると仮定する。 まず、リポジトリより最新版を取得する。

% cd
% svn checkout svn://rubyforge.org//var/svn/qwik/qwik

このようにして最新版を取得する。次に、qwikWeb を起動する。

% cd qwik
% ruby bin/qwikweb-server -d -c etc/config.txt
[2006-01-26 08:53:26] INFO  WEBrick 1.3.1
[2006-01-26 08:53:26] INFO  ruby 1.8.4 (2005-10-29) [i486-linux]
[2006-01-26 08:53:36] INFO  Qwik::Server#start: pid=2345 port=9190

このようにして、取得したディレクトリからデバッグモードで起動する。 make コマンドが使える場合は、

% cd qwik
% make

として、単に make をうつだけでよい。

qwikWeb が起動すると、WEBrick の起動メッセージが表示される。

にアクセスしてみると、qwikWeb の入口となるページが表示される。 qwikWeb サーバは通常はポート 9190 で立ち上がるが、 ポート指定を含まない URL からアクセスできるようにしたい場合は、

を参照して、リバースプロキシを設定することになる。

また、システムにインストールしたい場合のために、setup.rb を使ってインストールできるように準備中である。現在はまだすぐにうまくいく状態になってないが、概略を説明しておく。

% sudo ruby setup.rb

とすると、ファイルはそれぞれ目標となるディレクトリにインストールされる。 標準では下記のようなディレクトリ構成となる。

  • /usr/bin/qwikweb-server
  • /usr/bin/quickml-server
  • /usr/bin/qwik-service
  • /usr/local/lib/site_ruby/1.8/qwik/*
  • /usr/local/lib/site_ruby/1.8/i486-linux/xmlformatter.so
  • /usr/share/qwik/*
  • /etc/qwik/config.txt

このようにしてインストールした後に、設定ファイル「 /etc/qwik/config.txt 」を編集して、適切なパスを設定する必要がある。 標準的なディレクトリであれば、設定ファイル後半のコメントを外すだけでよいはずである。

また、えとー氏によって Debian 用のパッケージが作成されている。

この下に .deb ファイルがあるので、sources.list などを適切に設定すれば、 apt-get でインストールできるようになる。 いつも Debian パッケージを作成してくれているえとー氏に感謝いたします。

qwikWeb プラグインの作り方

前回は、qwikWeb の基本的な要素として、わびさび方式クラス構造テスト方法について述べた。 本稿では、その基本的な要素をふまえた上で、実際にプラグインを実装し、拡張する方法について詳述する。

HTML を出力するプラグイン

まず最も簡単なプラグインとして、ごく単純に HTML を出力するプラグインを作ってみよう。

まず新しいプラグインファイルを作成する。

% cd ~/qwik/lib/qwik
% vi act-test.rb

ライブラリのあるディレクトリに移動し、 「 act-test.rb 」というファイルを新規作成する。 (もちろんエディタは vi じゃなくてもかまわない。)

qwikWeb では、このディレクトリ中にある「 act- 」からはじまるファイルを自動的に読み込むようになっている。そのため、「 act-test.rb 」というファイル名のファイルを作成すれば、それだけで自動的に読み込まれる。 そのため、「 act-test.rb 」というファイル名でなくても「 act- 」から始まっていれば、別のファイル名でもかまわない。

module Qwik
  class Action
    def plg_strong(text)
      return [:strong, text]
    end
  end
end

このように記述し、qwikWeb サーバを再起動する。

% cd ../..
% ruby bin/qwikweb-server -d -c etc/config.txt &

FrontPage にアクセスする。

先程作成したプラグインを実際に使ってみよう。

{{strong('hello, world!')}}

編集画面に移動し、このような一文を挿入すると、画面には「 hello, world! 」という 文字列が strong で表示されたことと思う。

次に、このプラグインをすこしだけ修正してみよう。 「 act-test.rb 」の中身をこのように変更し、保存する。

   def plg_strong(text)
     return [:i, text]
   end

つまり、「 :strong 」だったところを「 :i 」に変更するわけである。

次に、先程表示したページを再読み込みする。

Internet Explorer では、Ctrl を押しながら F5 を押すと、強制リロードとなる。 文字列がイタリックで表示されるようになっただろうか。

ここで、qwikWeb サーバを再起動していないことに注目してほしい。デバッグモードで立ち上げた場合、qwikWebサーバは、ファイルの書き換えを自動的に検知し、書き換えられたファイルを再読み込みする。そのため、qwikWeb サーバをデバッグ中にファイルを書き換えた場合は、再起動しなくても自動的にプログラムは更新される。ファイル作成直後に一度再起動したのは、ファイルを require するのはサーバ起動直後のみであり、再起動しなければ新しく作成し たファイルは読み込まれないからである。一旦 require で読み込まれれば、その後は自動再読み込みされるようになる。ここで述べるプラグインの開発のようにプログラムの一部分を頻繁に更新しながら開発を進める場合、ファイルを更新するたびにそのプログラムの本体を再起動させなければならないとすると、開発効率が大変落ちる。 qwikWeb ではファイルを更新したらリロードするだけでよいので、プログラムの変化を逐次確認できるようになる。

この処理は、qwikWeb から分離し、単体で使うこともできる。 この autoreload 処理は「autoreload.rb」というファイルで行っている。 下記のようにして使う。

require 'autoreload'
AutoReload.start(1, true)

require したファイルは全て自動的に監視対象となるため、どのファイルを変更しても自動的に再読み込みしてくれる。一つ目の引数では、ファイル更新確認の間隔を指定する。この例では、一秒置きに require された全ファイルの更新をチェックする。二つ目の引数は、verbose mode の設定である。verbose mode では、ファイルが再読み込みされたときは、下記のようなメッセージが表示される。

reload: "/home/eto/qwik/lib/qwik/act-test.rb"

このようにして、任意のプログラムにおいて、動的にファイルの自動更新をさせることができる。

さて、プラグインの作り方に話を戻す。先程「 :i 」に変更したところをもう一度「 :strong 」に戻しておこう。次に、テストコードを追加してみる。プログラムができてからテストコードを書いたのではテストファーストではないが、qwikWeb のプラグインの開発ではブラウザでどのように表示されるかを確認しながら開発を進めることが多いため、テストケースは後で作ることが多い。先程のプログラムに、下記コードを追加する。

if $0 == __FILE__
  $LOAD_PATH << '..' unless $LOAD_PATH.include? '..'
  require 'qwik/test-common'
  $test = true
end

if defined?($test) && $test
  class TestActTest < Test::Unit::TestCase
    include TestSession

    def test_all
      ok_wi([:strong, "t"], '{{strong(t)}}')
    end
  end
end

この中で実質的に意味を持つのは一行だけである。 それ以外は、テストケース記述に要する定型句と考えてほしい。

     ok_wi([:strong, "t"], '{{strong(t)}}')

この一行は、Wiki ページ中に

{{strong(t)}}

と記述すると、

[:strong, "t"]

に変換されるということを示している。 このテストケースの左側はわびさび方式で書かれた HTML である。 HTMLで表現するとすれば、

<strong>t</strong>

となる。このように、テストの結果記述においても、わびさび方式を使っている。

テストを実行してみる。

% ruby act-test.rb 
Loaded suite act-test
Started
.
Finished in 0.065539 seconds.

1 tests, 1 assertions, 0 failures, 0 errors

assertion の実行が無事確認された。

このように、ブラウザから表示させなくてもコマンドラインから実行を確認できるのが、このテスト方法の大きな利点である。よくあるパターンだと、コードを書き換えたら、いちいちブラウザからページをリロードしてちゃんと表示されるかを確認しなくてはならない。毎回 ブラウザを操作しなければならないため非常に面倒である。また、エラー表示は「 error.log 」を確認しなければならないなど、エラー表示の確認が面倒だったりすることもある。qwikWeb のテスト方式は単なる普通のコマンドラインからのプログラム実行と同じなので、容易にデバッグを行える。

ここでのポイントは、プログラムが想定通りに実行されるかどうかと、ブラウザ上に想定通りに表示されるかどうかを、別の問題として切り分けているということである。プログラムがエラーがなく実行されるかどうか、想定した通りの値が計算されるかどうかは、ユニットテストで確認できる。しかし、ブラウザ上に正しく表示されるかどうかは、実際にブラウザに表示させてみ ないとわからない。このようにテストを二分割することによって、まずエラーなく実行できることを確認し、その後にブラウザ上で表示させながら結果表示を改善していく形で開発を進めていくことができる。

このようにして、プラグインのコードを書き、ブラウザで表示させ、そのテストケースを書き、テストを実行してみるというプラグイン開発における一連のプロセスをたどってみることができた。

ここで述べた方法を使って、もう少し実践的なプラグインを作ってみる。このような単純に HTML を出力するだけのプラグインではあまり面白い効果を発揮させられないと思われるかもしれないが、工夫次第では様々な処理をさせることができる。

滝川クリステルプラグイン

最近話題の「滝川クリステルジェネレータ」を題材にして、滝川クリステル女史を Wiki ページ中に表示するプラグインを作ってみる。 名付けて「滝川クリステルプラグイン」である。 「滝川クリステルジェネレータ」の作者に感謝いたします。

作る過程は省略し、結果としてできたコードを引用しつつ解説する。

  • qwik/lib/qwik/act-christel.rb
module Qwik
  class Action
    def plg_christel(width = 320)
      width = width.to_i.to_s
      content = yield
      message, image_url = content.to_a
      message.chomp!
      image_url.chomp! if image_url
      query_str = {
        :m => message.set_page_charset.to_utf8,
        :u => image_url
      }.to_query_string
      url = "http://gedo-style.com/crstl/crstl.php?#{query_str}"
      return [:img, {:src=>url, :width=>width}]
    end
  end
end

最初から順番に解説していく。

module Qwik
  class Action
    def plg_christel(width = 320)
      width = width.to_i.to_s

まずプラグインは、Action というクラスの「 plg_ 」から始まるメソッドとして定義される。プラグインの引数は、同じくメソッドへの引数として渡される。引数を省略可能にすることもできる。ここでは引数には横幅の数値だけをとることとしている。

プラグインは、引数以外にも情報を受け取ることができる。プラグインの中に複数行の中身を記述することができる。これを複数行プラグインと呼んでいる。 そのような複数行プラグインの場合は、プラグインに囲まれた内容は yield で取得する。

例えば、

{{christel
This is a test.
This is a test, too.
}}

このように記述した場合、

     content = yield

とすると、contentにはプラグインに囲まれた二行の文字列がはいる。

このプラグインの場合には、その中身は二行しか使わないので、

     message, image_url = content.to_a

として、一行目のメッセージ、二行目の画像 URL を受け取る。

次に、行末の改行を処理してから、

     message.chomp!
     image_url.chomp! if image_url

その取得した情報を元にクエリ文字列を生成している。

     query_str = {
       :m => message.set_page_charset.to_utf8,
       :u => image_url
     }.to_query_string

ここではまず、与えられたメッセージを文字コード変換している。

message.set_page_charset.to_utf8

これは一風変った方法で、まず最初に現時点での文字コードは何かという情報を一旦文字列自身に持たせ、次に目標となる文字コードに変換している。これは、将来的にはページに使う文字コードを変更しようと思っているため、後で文字コードが切り替わってもプログラムを修正しなくても済むように、まわりくどい書き方をしている。

次に、Hash#to_query_string を呼び出して query_string に変換している。

     url = "http://gedo-style.com/crstl/crstl.php?#{query_str}"
     return [:img, {:src=>url, :width=>width}]

最後に、url を組み立てて、img 要素として出力している。要するに、最終的には単に一つの img タグを出力しているだけである。たったこれだけであるが、プラグインから情報を入力し、必要な画像を出力するところまでを実現することができた。

テストケースをどのように書いているのか見てみよう。

     ok_wi([:img, {:width=>'320',
	:src=>"http://gedo-style.com/crstl/crstl.php?m=a"}],
    "{{christel\na\n}}")

このテストケースはどういう意味かというと、Wiki ページ中に

{{christel
a
}}

というプラグインを記述すると、このような HTML に変換されるということである。

開発の手順としては、最初からこのようなテストケースが用意しているわけではなく、まずは適当にプラグインを書いてみて、ちゃんと期待通りに表示されるように開発し、その後にテストケースに書いている。その意味ではテストファーストではないが、一旦テストケースを作れば、後でそれを変更・拡張することが容易になる。

実際の出力例は、下記ページから見ることができる。

動的にコンテンツを生成するプラグイン

実際には、プラグインにはもう少し複雑な動作をさせたいと思うことがあるだろう。例えば、Wiki ページ中に動的に生成したコンテンツを埋込みたいと思うことがあるだろう。 ここではそのような構造を持つプラグインとして、「高橋メソッドプラグイン」を作ってみる。 これは、Wiki ページ中に高橋メソッドによるプレゼンテーションを埋込むというプラグインである。

高橋メソッドプラグイン

このプラグインは、WEBLAB@AJIBIT にて公開されている「高橋メソッドマシーン」という Flash を使用している。 「高橋メソッドマシーン」の作者に感謝します。

実際のプラグインのコードを引用しつつ解説する。

  • qwik/lib/qwik/act-takahashi.rb
    def plg_takahashi
      files = @site.files(@req.base)
 
      # Copy T_method_module.swf from theme directory.
      fname = 'T_method_module.swf'
      if ! files.exist?(fname)
         swf_path = @config.theme_dir.path+'swf'+fname
         return nil if ! swf_path.exist?
         swf = swf_path.read
         files.overwrite(fname, swf)
      end
 
      # Generate a text file.
      content = yield
      content.chomp!
      text = content.set_page_charset.to_utf8
      files.overwrite('textData.txt', text)
 
      # Generate a html file.
      (中略)
      html_str = html.format_xml
      files.overwrite('takahashi.html', html_str)

      # Embed the html file as iframe.
      h = "#{@req.base}.files/takahashi.html"
      return [:div, {:class=>'takahashi'},
         [:iframe, {:src=>h, :style=>'width:700px;height:400px;border:0;'}, ''],
         [:br],
         [:div, {:style=>'margin: 0 0 1em 0;'},
           [:a, {:href=>h, :style=>'font-size:x-small;'},
             _('Show in fullscreen.')]]]
    end

最初から順に解説する。

   def plg_takahashi

ここで、高橋メソッドプラグインを定義している。

     files = @site.files(@req.base)

ここでは、「 @req.base 」というページの添付ファイルにアクセスしようとしている。「 @req.base 」というのは、現在のアクセスしているページのページキーとなる。「 FrontPage.html 」にアクセスしている場合は「 FrontPage 」となる。省略された場合には「 FrontPage 」となる。 「 @site.files(@req.base) 」という処理で、「 @req.base 」というページキーに対応する添付ファイルインスタンスを取得するという意味になる。これは「 page-files.rb 」にて定義されており、詳細な使い方はそのファイルで確認することができる。

     # Copy T_method_module.swf from theme directory.
     fname = 'T_method_module.swf'
     if ! files.exist?(fname)
       swf_path = @config.theme_dir.path+'swf'+fname
       return nil if ! swf_path.exist?
       swf = swf_path.read
       files.overwrite(fname, swf)
     end

ここではまず、高橋メソッドを実現するためのFlashファイル「 T_method_module.swf 」を添付ファイル内にコピーしている。

     # Generate a text file.
     content = yield
     content.chomp!
     text = content.set_page_charset.to_utf8
     files.overwrite('textData.txt', text)

次に、その Flash ファイルにパラメータとして与えるテキストファイルを生成している。 このようにして、その場で動的に添付ファイルを作り出している。

     # Generate a html file.

次に、ページ中に IFRAME で埋込むための html ファイルを生成している。

     # Embed the html file as iframe.

最後に、その動的に生成した html ファイルを埋込むための IFRAME 要素を生成している。

このようにして、このプラグインにおいて、表示するのに必要な Flash ファイルそれ自身をコピーし、パラメータとして与えるテキストファイルを生成し、埋込み用の html ファイルを生成し、それを IFRAME としてページ中に埋め込んでいる。

このようにしてパラメータファイルが必要な Flash コンテンツを動的に埋込むことができた。テキストを基本とした Wiki ページに動的なコンテンツを埋め込んでいくことによって、Wiki ページを複合的なコンテンツを表示させるための基盤として扱うことができるようになる。

実際の出力例は、下記ページから見ることができる。

コード表示プラグイン

HTML を生成し、かつ同時に動的にコンテンツを生成するプラグイン例として、コード表示プラグインを紹介する。

実行結果はこのようになる。

ここでは、単にコードを行番号付きで表示しているだけに見えるかもしれないが、実は表示を工夫している。普通の行番号付きの表示では、行の先頭に行番号をつけて出力しているため、その表示をコピー&ペーストすると、行番号も一緒にコピーされてしまう。 しかしここでの出力例は、普通にコードの内容をコピー&ペーストできる。それはなぜかというと、行番号はテキストではなく、画像として背景に表示しているからだ。行番号は、動的に PNG ファイルを生成し、それをスタイルシートで背景に表示している。このようにして、行番号の表示と、簡単にコピー & ペーストできるという特徴を同時に実現している。

  • qwik/lib/qwik/act-code.rb

実際のコードはこのファイルである。

   def plg_code(filename=nil)
     content = ''
     content = yield if block_given?
     pre = [:pre]
     content.each_with_index {|line, index|
        line.chomp!
        linenum = index + 1
        klass = 'line '
        klass += if linenum % 2 == 0 then 'even' else 'odd' end
        style = "background-image: url(.num/#{linenum}.png);"
        pre << [:span, {:class=>klass, :style=>style}, line]
        pre << "\n"
     }
     return [:div, {:class=>'code'}, pre]
   end

このようなコードによって、yield で入力された文字列を、一行づつ別々の span タグにいれ、それぞれの背景に画像を埋め込み、pre タグに入れていく。

スタイルシートは、現在は一箇所にまとめて管理している。

  • qwik/share/theme/css/base.css

この中で、code クラスのスタイルを定義している。

背景画像として指定されるのは、どのような URL か。

url(.num/#{linenum}.png);

このような URL である。つまり一行目であれば「 .num/1.png 」という画像が埋込まれることになる。この URL は一見静的なファイルを参照しているように見えるが、実はそうではなく、実際には動的に数字の画像を生成している。この「 .num 」というのはアクションプラグインという。実際のコードを見てみよう。

   def act_num
     return c_nerror(_('Error')) if ! $have_gd
     args = @req.path_args
     return c_nerror(_('Error')) if args.nil? || args.empty?
     filename = args.first
     return c_nerror(_('Error')) unless /\A([0-9]+)\.png\z/ =~ filename
     str = $1
     return c_nerror(_('Error')) if 10 < str.length
     files = @site.files('FrontPage')
     if ! files.exist?(filename)
        png = Action.generate_png(str)
        files.put(filename, png)
     end
     path = files.path(filename)
     return c_simple_send(path.to_s, 'image/png')
   end

以下、順に見てみる。

     return c_nerror(_('Error')) if ! $have_gd

GD という外部ライブラリを使って、動的に画像を生成しているため、 ライブラリが無かったらエラーになる。

     args = @req.path_args
     return c_nerror(_('Error')) if args.nil? || args.empty?

「 @req.path_args 」という記述で、アクションプラグインに続いて与 えられたパスを配列として取得できる。パスが指定されてなければエラーにする。

     filename = args.first
     return c_nerror(_('Error')) unless /\A([0-9]+)\.png\z/ =~ filename

一つ目の値を取り出し、ファイル名として適切かどうかを判定する。

     str = $1
     return c_nerror(_('Error')) if 10 < str.length

10 文字以内であることを確認する。

     files = @site.files('FrontPage')

FrontPage の添付ファイルを扱うインスタンスを取得する。

     if ! files.exist?(filename)

もしファイル名に相当するファイルが存在していなかったら、

png = Action.generate_png(str)
files.put(filename, png)

対応する画像を生成し、その画像を添付ファイルとして保存する。 「 generate_png 」の中身は、与えられた文字列を元に PNG 画像を生成し、その中身を返している。つまりここでは、一旦生成した画像を、添付ファイルに保存する形でキャッシュしているわけだ。

     path = files.path(filename)

このようにして添付ファイルのパスを取得する。

     return c_simple_send(path, 'image/png')

そのパスを「 c_simple_send 」に渡して、Content-Type には image/png を指定しつつ、ファイルそのものをリクエストの結果として返す。

このようにして、リクエストに応じて動的に生成するコンテンツを作り出すこともできる。ページに埋込むプラグインと、URL から直接呼び出すアクションプラグインとを組み合わせることで、柔軟な処理を可能としている。

まとめ

本稿では、動的にコンテンツを生成するプラグインとして、「滝川クリステルプラグイン」「高橋メソッドプラグイン」「コード表示プラグイン」について、実際の実装方法を見ながら解説した。

みなさまもぜひ、qwikWeb のプラグイン制作にトライしてみてください。 面白いプラグインができたら、ぜひメーリングリストで教えてください。 メーリングリストの加入方法は、次のページに記述しています。

著者について

えと こういちろう (Rails 使ったら負けかなと思ってる)。 *1

ついに私もブログ始めました。

ブログといいつつ tDiary なんだけどな。

更新日時:2006/03/01 17:06:58
キーワード:
参照:[Rubyist Magazine 0013 号] [0013 号 巻頭言] [各号目次] [prep-0013]

*1 もちろん冗談ですけどね。;-)