2014年6月17日火曜日

Rake Part 1: Files and Rules 翻訳

Avdi GrimmLearn Advanced Rake in 7 Episodesを、本人の許可を得て翻訳します。以下、Part 1の翻訳です。



もしあなたがRubyプログラマーなら、Rake (故Jim Weirichによって作られたビルドユーティリティー) をほぼ確実に使ったことがあるだろう。しかし、Rakeがいかにパワフルでフレキシブルなツールになりうるかということは理解していないかもしれない。 実際、私は、Quarto (自作のe-book製作ツールチェーン)の土台としてRakeを使うと決めるまで理解していなかった。

この投稿はRakeについてのシリーズの一部で、基本から始めて高度な使用法に進む。 2013年の8月から9月に購読者に向け公開されたRubyTapasビデオのシリーズのひとつをもとにしている。 各投稿はビデオから始まり、ビデオより文字を好む人の ためにスクリプトが続く。

これらのエピソードを無料で公開するにあたり、より多くの人々が、 この広く行き渡っているが過小評価されているツールのすべての能力を知り愛するようになることを願う。もしあなたがRakeに感謝するのであれば、Jimへの追悼として the Weirich Fundへの寄付を検討して欲しい。





これから数回のエピソードにわたりRakeについて見ていきます。 気に入ってもらえるといいのですが。

おそらく、あなたはどこかでRakeを使ったことがあるでしょう。少なくとも、Railsプロジェクトに関連した様々なRakeタスクを実行したことがあるでしょう。自分自身でRakefileを書いたことが あるかもしれません。

しかしおそらく、あなたはRakeの持つ可能性のほんの一面に触れたにすぎません。 それは、数週間前まで私にも当てはまることでした。自分のRakefileやtaskファイルを書いたことは確かにありましたが、Rakeができることすべてについて本当に詳しく調べることは決してありませんでした。Rakeを本当に学習することに時間を費やした今、驚くべきパワーをもつツールだと実感しています。 私が学んだことをみなさんと共有していきます。

まずはRakeの基本を復習することから始めます。

Pandocを使ってHTMLに変換したいMarkdownファイルが含まれるディレクトリがあるとします。 複数のファイルをひとつずつ繰り返し変換する単純なスクリプトを書けるかもしれません。

%W[ch1.md ch2.md ch3.md].each do |md_file|
  html_file = File.basename(md_file, ".md") + ".html"
  system("pandoc -o #{html_file} #{md_file}")
end
 
しかし、このスクリプトを実行すると、ソースファイルに変更がなかった場合でも毎回すべてのHTMLファイルを再生成します。markdownファイルがとても大きい場合、 長時間待つことになってしまいます。

代わりに、Rakefileを作って、HTMLを生成するRakeタスクを書いてみましょう。 スクリプトと同じように、入力ファイルのリストに対して繰り返し処理を行い、 対応するHTMLファイルを決定することから始めます。しかし、ここから違いが出てきます。html_fileがmarkdownファイルに依存関係を持つことを宣言するために、 Rakeのfileメソッドを使用します。そしてブロックの中で、シェルコマンドを使用して markdownファイルからHTMLファイルをどのように取得するかをRakeに伝えます。

ここで書いたのはルール(正確には3つのルール)で、それぞれのルールがMarkdownのソースファイルから特定のHTMLファイルをビルドする方法をRakeに伝えています。

%W[ch1.md ch2.md ch3.md].each do |md_file|
  html_file = File.basename(md_file, ".md") + ".html"
  file html_file => md_file do
    sh "pandoc -o #{html_file} #{md_file}"
  end
end

これ自体、すでにRakefileとして使用できます。コマンドライン上で、HTMLファイルの1つをビルドするようrakeに伝えることができ、rakeはそれを受け入れます。 すでに、スクリプトに対する利点、すなわち実行中のコマンドをRakeが表示しているのを確認できます。

$ rake ch1.html
pandoc -o ch1.html ch1.md

Rakeに同じファイルをもう一度ビルドするよう伝えた場合には、何も起こりません。 これは、HTMLファイルが作成された後にMarkdownファイルが変更されたかを確認するために、Rakeがファイルの変更時刻をチェックしているからです。 Markdownファイルは変更されていないのでHTMLファイルを再ビルドする必要がないということを、Rakeは理解しています。
 
$ rake ch1.html
$

Markdownファイルを修正しRakeをもう一度実行すると、再びHTMLファイルをビルドします。
 
$ rake ch1.html
$

ファイルが再ビルドされる必要があるかをRakeが追跡しているのは素晴らしいことです。 しかし、どのファイルをビルドしたいかを指定する必要があるのは面倒です。 Rakeに古くなったHTMLファイルを単に再ビルドさせるほうが望ましいです。

これを実現させるには、Rakefileにtaskを追加します。“html”という名前をつけ、 3つのHTMLファイルに依存するようにします。
 
task :html => %W[ch1.html ch2.html ch3.html]

%W[ch1.md ch2.md ch3.md].each do |md_file|
  html_file = File.basename(md_file, ".md") + ".html"
  file html_file => md_file do
    sh "pandoc -o #{html_file} #{md_file}"
  end
end

このタスク自身はコードを持ちません。しかし“html”タスクをビルドするとRakeに告げると、 RakeはHTMLファイルへの依存関係に従います。すでに書いたルールにより、 Rakeはこれらのファイルをビルドする方法を知っていので、ビルドを進めます。
 
$ rake html
pandoc -o ch1.html ch1.md
pandoc -o ch2.html ch2.md
pandoc -o ch3.html ch3.md

Markdownファイルのひとつを修正してRakeタスクをもう一度実行すると、 Rakeが更新されたファイルのみを再ビルドすることが確認できます。
 
$ rake html
pandoc -o ch2.html ch2.md

このコマンドを何度も実行するつもりなら、htmlタスクに依存関係を持つ :defaultタスクを宣言することで、より便利にすることができます。
 
task :default => :html
task :html => %W[ch1.html ch2.html ch3.html]

%W[ch1.md ch2.md ch3.md].each do |md_file|
  html_file = File.basename(md_file, ".md") + ".html"
  file html_file => md_file do
    sh "pandoc -o #{html_file} #{md_file}"
  end
end

これにより、rakeを単に引数なしで実行してファイルを再ビルドできます。
 
$ rm *.html
$ rake
pandoc -o ch1.html ch1.md
pandoc -o ch2.html ch2.md
pandoc -o ch3.html ch3.md

ここまでは、ファイルにルールとタスクを宣言する方法を見てきました。

3つのファイルルールにはすべて、“.md”ファイルを“.html”ファイルに変換する、という共通のパターンがあります。実際、このパターンは繰り返し出てくるので、eachループを使ってルールの生成を自動化しました。明示的にループを書く代わりに、Rakeに “.md”ファイルを“.html”ファイルに変換する方法を伝え、あとはRake自身に解決してもらう ようにしてみましょう。

名前がファイル拡張子.htmlruleを宣言することによって、これを実現します。 このルールは、ファイル拡張子 .mdに依存しています。そしてブロックをオープンします。 このブロックは、tという名前のブロック引数を受け入れます。tという名前にしたのは、 RakeのTaskオブジェクトにバインドしているからです。

ブロックの内側では、シェルコマンドを実行するためにshコマンドを使用しています。以前と同様、pandocコマンドで始まっていますが、出力ファイル名としてタスクのname属性を展開しています。そして入力ファイルとして タスクのsource属性を使用しています。
 
task :default => :html
task :html => %W[ch1.html ch2.html ch3.html]

rule ".html" => ".md" do |t|
  sh "pandoc -o #{t.name} #{t.source}"
end

これで終わりです。 HTMLファイルを削除しRakeをもう一度実行すると、 前と同じようにファイルが生成されることが確認できます。
 
$ rm *.html
$ rake
pandoc -o ch1.html ch1.md
pandoc -o ch2.html ch2.md
pandoc -o ch3.html ch3.md

ところで、ここでは何が起こっているのでしょう?引数を指定していないので、Rakeは :defaultタスクを実行します。:defaultタスクは:htmlタスクに依存し、:htmlタスクは3つの.htmlファイルに依存しています。Rakeは1つ目のch1.htmlから処理を始め、そのファイルが存在するかを確認します。 ファイルは存在しないので、Rakeはそのファイルをビルドする方法を見つけようとします。

まず、明示的にch1.htmlと名付けられたルールを探しますが、それらは すべて削除されています。

Rakeが本当に見つけたのは新しいルールです。対応する.mdファイルから .htmlファイルを生成するというルールを使用します。 そのルールをch1.htmlに適用して、対応するファイル、すなわちch1.mdが 存在することを発見します。 これは、ルールが一致したのでそれに従って実行するということを意味します。 そして、残りの存在しない.htmlファイルに対して、すべてのプロセスを繰り返します。

Rakeについてお見せしたいことはもっとたくさんありますが、RubyTapasでは 1回につき1つのアイデアとしているので、将来のエピソードのために 残しておきます。お見逃しなく、そして、ハッピーハッキング!



Rakeについてのエピソード・文章を楽しんでいただけたことを願っている。もし今日、何かしら学んだのであれば、Jimの遺産である教育プログラムの継続を支援するため、the Weirich Fundに寄付することにより「恩送りすること(訳注:原文は"paying it forward")」を検討して欲しい。 もし、今回のようなビデオをもっと見たければ、RubyTapas をじっくり見てほしい。シリーズが完結するまで、ほぼ毎日公開していくつもりなので、ほどなくご確認を!

P.S. 特に興味深い方法でRakeを使っているなら、連絡して欲しい

関連する投稿:
  1. Keep local modifications in Git-tracked files
  2. RVM Proxies for Common Commands? (Solved)
  3. Creating Cowsays.com Part 2: Unit Tests and Cow Files

0 件のコメント:

コメントを投稿