ハンドラクラスの作成
ファイル名をRubyコードにルーティングする簡単なフレームワークができたので、HTMLファイルのハンドラクラスを作成します。このクラスは、ターゲットのファイル名に相当する少なくとも1つの引数と、追加パラメータのオプション配列を受け取るrunメソッドを実装する必要があります。クラス名は当然ながらHtmlであり、このクラスを含んでいるファイルは「html.rb」という名前で「handlers」サブディレクトリに置かれます。
class Html DEFAULT_BROWSER = 'firefox' def run file, args if args.empty? system( "#{DEFAULT_BROWSER} #{file}" ) else dispatch_on_parameters file, args end end def dispatch_on_parameters file, args cmd = args.shift send( "do_#{cmd}", file, args ) end def do_opera file, args=nil system( "opera #{file} #{args}" ) end def do_konq file, args=nil system( "konqueror #{file} #{args}" ) end end
このコードでは、まずデフォルトブラウザの定数を定義しています。追加の引数が省略されると、ターゲットファイルはFirefoxで起動されます。なお、場合によってはこれを変更して実行コマンドを定義する必要があることに注意してください。私のUbuntuマシンでは、明示的なパスなしでFirefoxを実行し、ブラウザを立ち上げることができます。しかし、例えばWindowsの場合は、実行ファイルへのフルパスが必要な可能性があります。
追加のパラメータがある場合、runはdispatch_on_parametersを呼び出します。dispatch_on_parametersはargs配列の先頭のアイテムを取り出し、それを使ってメッセージ文字列を動的に作成します。すべてのRubyオブジェクトにはsendメソッドが組み込まれています。このメソッドを使用すると、オブジェクトに明示的にメッセージを送信できます。ここで行っているようにメソッドを単独で使用すると、現在のオブジェクトを受け取るものとみなされます。つまり、コードは自身に対してメッセージを送信します。
実際の引数の前にdo_を追加しているのは、メソッド名の重複を防ぐためです。例えば、先頭の引数がexitだったとしても、おそらく、Rubyのexitメソッドを呼び出すつもりではないでしょう。do_exitを呼び出すことで、正しい動作を決定できます。
このハンドラコードは、パラメータ処理の可能性を示すちょっとした例にすぎません。現状でも、ターゲットのHTMLファイルをデフォルトブラウザで起動したり、特定のブラウザで起動したりできます。
$ ./go index.html opera $ ./go index.html konq
時間外の作業でさらなる改善
教育的で実用的なサンプルができ上がりましたが、これを少しだけ掘り下げてみましょう。この作業を行うと10分の目安は過ぎてしまいますが、それだけの価値はあります。
標準のRubyディストリビューションには、あらゆる種類のタスク用の豊富なライブラリが含まれています。その中で最も興味深いものの1つが、Sean Russellが開発したREXMLです。REXMLは純粋なRubyで作成されているXMLパーサーです。REXMLを使用すると、通常のW3C DOM APIではなく、RubyスタイルのAPIを使ってXMLを操作できます。Seanの功績はすぐにRubyの標準ライブラリの一部になりました。
話を簡単にするために、今回のサンプルのHTMLファイルではXHTMLを使用する必要があります。REXMLが処理するのはXMLのみだからです(Hpricotのように、ほぼ任意のHTMLを処理できる非常に優れたRubyツールもありますが、別のライブラリをインストールする必要があり、その説明はこの記事の範囲を超えています)。整形式のXHTMLソースになっている場合には、サンプルのHTMLハンドラでファイル分析を実行できます。Htmlクラスの最後に次のコードを追加すると、XHTMLに関する簡単なレポートを生成できます。
def do_report( file, args=nil ) require 'rexml/document' begin dom = REXML::Document.new( IO.read( file ) ) if args.empty? puts basic_xhtml_report( dom ) else puts report_on( dom, args.first ) end rescue Exception warn "There was a problem reading '#{file}':\n#{$!}" end end def report_on dom, element els = dom.root.elements.to_a( "//#{element}" ) "The document has #{els.size} '#{element}' elements" end def basic_xhtml_report( dom ) report = [] css = dom.root.elements.to_a( '//link[@rel="stylesheet"]' ) unless css.empty? report << "The file references #{css.size} stylesheets" css.each do |el| file_name = el.attributes['href'] file_name.gsub!( /^\//, '') unless File.exist?(file_name) report << "*** Cannot find stylesheet file '#{file_name}'" end end end js = dom.root.elements.to_a( '//script' ) unless js.empty? report << "The file references #{js.size} JavaScript files" js.each do |el| file_name = el.attributes['src'] file_name.gsub!( /^\//, '') unless File.exist?(file_name) report << "*** Cannot find JavaScript file '#{file_name}'" end end end report.join( "\n" ) end
ここでは多くの処理が行われていますが、重要なのはdo_reportメソッドです。このコードではREXMLのDocumentオブジェクトを作成し、それをdomに割り当てています。余分な引数がない場合は、基本のレポートが得られます。それ以外の場合は、特定の要素について簡単な調査が行われます。
report_onメソッドはドキュメント引数と要素名を受け取り、REXMLのXPath機能を使ってその要素の使用頻度を見つけます。これはあくまでも初歩にすぎませんが、デモンストレーションと、プログラミングの出発点としての役割は十分に果たしています。
basic_xhtml_reportメソッドも同様ですが、このメソッドは特定の要素のセットに焦点を当てています。REXMLを使ってすべてのCSSおよびJavaScript参照を見つけ、Fileクラスを使って参照されているファイルの有無を確認します。これも複雑なものではありませんが、プロジェクトの機能拡張に役立っています。
最小限の枠組みで簡潔かつ表現豊かなコードを作成
これまでの説明で、以下に示すようなRubyの特筆すべきいくつかの機能について理解できたことでしょう。
- Rubyは基本的にオブジェクト指向の言語であり、「メッセージに応答するオブジェクト」を主要な概念としています。
- Rubyは強力な動的型を採用しています。「型」の概念は、特定のクラス名や継承階層よりもむしろ、オブジェクトが実行できる動作に基づきます。オブジェクトの動作は、メッセージとメソッドのリテラルマッピングに限定されません。動作が実行時に動的に構成されることもあります。
- Rubyクラスはオープンです。コアクラスの動作を、アプリケーションにとって適切だと思われる動作に自由に変更できます。
オープンなクラスと動的な動作の組み合わせによって、最小限の枠組みを利用して、簡潔かつ表現豊かなコードを作成できます。Rubyは陰からあなたのコーディングを支えてくれます。
