2014年6月20日金曜日

Rake Part 2: File Lists 翻訳

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



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

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

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





前回のエピソードでは、このRakefileを書きました。これは3つのMarkdownファイルから HTMLファイルへのビルドを自動化します。

 
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

本当は、処理するファイルを追加するたびに、このファイルを修正したくありません。 その代りに、自動的にビルドするファイルをRakefileに見つけさせたいのです。

いくつかの実験をするために、サンプルのプロジェクトディレクトリを作りました。 そこには4章分のMarkdownファイルと、サブディレクトリに1つの付録用のファイルが含まれていて、それらすべてがHTMLファイルにビルドされるべきものです。 また、それ以外のビルドしたくないファイルがいくつかあります。 ~ch1.mdファイルはエディタによって残された一時ファイルのようです。 また、scratchディレクトリには、無視すべきファイルが含まれています。

 
$ tree
.
├── ~ch1.md
├── ch1.md
├── ch2.md
├── ch3.md
├── ch4.markdown
├── scratch
│   └── test.md
├── subdir
│   └── appendix.md
└── temp.md

このプロジェクトはGitのバージョン管理下にあります。もしGitに、管理しているファイルの一覧を表示するよう告げれば、先程見たファイルのサブセットを確認できます。 特に、temp.mdと呼ばれるファイルがないことに注意してください。このファイルは Gitに現在登録されおらず、今後も登録されることはないでしょう。 また、ビルドするファイルのリストからも除外されるべきものです。

 
$ git ls-files
ch1.md
ch2.md
ch3.md
ch4.markdown
scratch/test.md
subdir/appendix.md
~ch1.md

ビルドすべきファイルだけを自動的に発見するために、Rakeのfile listを使います。 file listがどんなもので何ができるのかを調べてみましょう。

file listを作るために、Rake::FileList クラスの添字演算子を使用し、 ファイルを表す文字列のリストを引き渡します。

 
require 'rake'
files = Rake::FileList["ch1.md", "ch2.md", "ch3.md"]
files # => ["ch1.md", "ch2.md", "ch3.md"]

ここまでは、あまりエキサイティングではありません。しかし、まだ始まったばかりです。 ファイルをひとつずつ列挙する代わりに、FileListを使ってshell globパターンを渡すことができます。パターン*.mdを与えてみましょう。

 
require 'rake'
Dir.chdir "project"
files = Rake::FileList["*.md"]
files # => ["ch1.md", "temp.md", "ch3.md", "ch2.md", "~ch1.md"]

ここでFileListのパワーが分かり始めます。しかし、これは我々が望むファイルのリスト通りではりません。無視したいファイルがいくつか含まれ、必要なファイルがいくつか足りません。


足りないファイルから対処していきます。長い拡張子を使っているファイルを見つけるために*.markdownパターンを追加します。

 
require 'rake'
Dir.chdir "project"
files = Rake::FileList["*.md", "*.markdown"]
files # => ["ch1.md", "temp.md", "ch3.md", "ch2.md", "~ch1.md", "ch4.markdown"]

しかしまだ付録のファイルがありません。これを修正するために、globパターンをプロジェクトのディレクトリーツリー内のあらゆるレベルに一致するよう変更します。

 
require 'rake'
Dir.chdir "project"
files = Rake::FileList["**/*.md", "**/*.markdown"]
puts files 

# >> ch1.md
# >> temp.md
# >> ch3.md
# >> ch2.md
# >> scratch/test.md
# >> ~ch1.md
# >> subdir/appendix.md
# >> ch4.markdown

これで、4章分と付録のすべてが見つかりましたが、同時に不要なものもたくさん 集めてしてしまいました。 ファイルの一覧を選別していきましょう。 このために、exclusionパターンを使用します。


文字~で始まるファイルを無視するところから始めます。

 
require 'rake'
Dir.chdir "project"
files = Rake::FileList["**/*.md", "**/*.markdown"]
files.exclude("~*")
puts files 

# >> ch1.md
# >> temp.md
# >> ch3.md
# >> ch2.md
# >> scratch/test.md
# >> subdir/appendix.md
# >> ch4.markdown

次にscratchディレクトリ内のファイルを無視します。それが可能なことを示すために、shell globの代わりに正規表現を使って除外します。

 
require 'rake'
Dir.chdir "project"
files = Rake::FileList["**/*.md", "**/*.markdown"]
files.exclude("~*")
files.exclude(/^scratch\//)
puts files 

# >> ch1.md
# >> temp.md
# >> ch3.md
# >> ch2.md
# >> subdir/appendix.md
# >> ch4.markdown

まだtemp.mdが残っています。先程見たように、このファイルはGitで管理されていません。 Gitで管理されていないファイルを無視するexclusionルールを作成したいと思います。 これを実現するために、.excludeにブロックを渡します。その中に Gitがファイルを感知しているかどうかを判断する呪文を書きます。

 
require 'rake'
Dir.chdir "project"
files = Rake::FileList["**/*.md", "**/*.markdown"]
files.exclude("~*")
files.exclude(/^scratch\//)
files.exclude do |f|
  `git ls-files #{f}`.empty?
end
puts files 

# >> ch1.md
# >> ch3.md
# >> ch2.md
# >> subdir/appendix.md
# >> ch4.markdown

これにより一時ファイルが除外され、ついに取り扱いたいファイルの一覧だけが残りました。


次に、もう少しFileListの定義の意図がわかるよう、コードを修正します。 添字の省略記法をFileList.newに変更し、ブロックを引き渡します。 FileListはこのブロックに自身をyieldします。これは、ブロックの中にすべての exclusionが設定できることを意味します。

 
require 'rake'
Dir.chdir "project"
files = Rake::FileList.new("**/*.md", "**/*.markdown") do |fl|
  fl.exclude("~*")
  fl.exclude(/^scratch\//)
  fl.exclude do |f|
    `git ls-files #{f}`.empty?
  end
end
puts files 

# >> ch1.md
# >> ch3.md
# >> ch2.md
# >> subdir/appendix.md
# >> ch4.markdown

Rakefileに戻る前に、ファイルの一覧に対して、もう1箇所変更する必要があります。 Rakefileでは、ビルドに対応するソースファイルではなく、ビルドされるファイルの一覧が必要でした。 入力ファイルの一覧を出力ファイルの一覧に変換んするために、#extメソッドを使用します。 そのメソッドに.htmlファイルの拡張子を渡すと、もとのMarkdown拡張子がすべて.htmlに 置き換えられた、新たなファイルの一覧を返します。

 
require 'rake'
Dir.chdir "project"
files = Rake::FileList.new("**/*.md", "**/*.markdown") do |fl|
  fl.exclude("~*")
  fl.exclude(/^scratch\//)
  fl.exclude do |f|
    `git ls-files #{f}`.empty?
  end
end
puts files.ext(".html")

# >> ch1.html
# >> ch3.html
# >> ch2.html
# >> subdir/appendix.html
# >> ch4.html

Rakefileに戻る準備ができました。ハードコードされた対象ファイルのリストを先程作ったFileListに置き換えます。


.mdまたは.markdownのいずれの拡張子をもつMarkdownファイルもサポートしているので、 どちらのファイルからもHTMLファイルをビルドできることをRakeに告げるよう、あと1箇所変更する必要があります。今のところ、単にルールを重複することによってこれを実現します。 将来的に、この重複を避ける方法を見ていきます。

 
source_files = Rake::FileList.new("**/*.md", "**/*.markdown") do |fl|
  fl.exclude("~*")
  fl.exclude(/^scratch\//)
  fl.exclude do |f|
    `git ls-files #{f}`.empty?
  end
end

task :default => :html
task :html => source_files.ext(".html")

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

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

rakeを実行すると、しかるべきHTMLファイルがすべてビルドされることを確認できます。

 
$ rake
pandoc -o ch1.html ch1.md
pandoc -o ch2.html ch2.md
pandoc -o ch3.html ch3.md
pandoc -o subdir/appendix.html subdir/appendix.md
pandoc -o ch4.html ch4.markdown

Rakeについて、今日はこれで十分でしょう。ハッピーハッキング!




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

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

関連する投稿:
  1. Rake Part 1: Files and Rules
  2. Keep local modifications in Git-tracked files
  3. Loading plugins with Rubygems

0 件のコメント:

コメントを投稿