Avdi Grimmの
Learn Advanced Rake in 7 Episodesを、本人の許可を得て翻訳します。以下、
Part 4の翻訳です。
もしあなたがRubyプログラマーなら、
Rake(故
Jim Weirichによって作られたビルドユーティリティー)をほぼ確実に使ったことがあるだろう。しかし、Rakeがいかにパワフルでフレキシブルなツールになりうるかということは理解していないかもしれない。実際、私は、
Quarto(自作のe-book製作ツールチェーン)の土台としてRakeを使うと決めるまで理解していなかった。
この投稿はRakeについてのシリーズの一部で、基本から始めて高度な使用法に進む。2013年の8月から9月に購読者に向け公開された
RubyTapasビデオのシリーズのひとつをもとにしている。各投稿はビデオから始まり、ビデオより文字を好む人のためにスクリプトが続く。
これらのエピソードを無料で公開するにあたり、より多くの人々が、この広く行き渡っているが過小評価されているツールのすべての能力を知り愛するようになることを願う。もしあなたがRakeに感謝するのであれば、Jimへの追悼として
the Weirich Fundへの寄付を検討して欲しい。
今日は、Rakeツールの探求を続けるべく、Rakeのもっともパワフルな機能のひとつである
#pathmap
メソッドをお見せしたいと思います。
これは数エピソード前に考えたFileListです。プロジェクトディレクトリ内にあるHTMLファイルにビルドされるMarkdownファイルのリストを出力します。
require "rake"
Dir.chdir "project"
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
SOURCE_FILES
# => ["ch1.md", "ch3.md", "ch2.md", "subdir/appendix.md", "ch4.markdown"]
Markdown拡張子を
.html
に変換することによって入力ファイルのリストをターゲットファイルのリストに変換するために
#ext
メソッドを使用できることはすでに見てきました。
require './source_files'
SOURCE_FILES.ext('html')
# => ["ch1.html", "ch3.html", "ch2.html", "subdir/appendix.html", "ch4.html"]
ソフトウェアのビルド、ソフトウェアのパッケージング、あるいはシステム管理タスクを扱っている時、ファイルのリストを受け取り、そこから2番目の、修正されたリストを生成したいことはよくあります。あるファイル拡張子のセットを別のセットに変換することは、このような問題の一例すぎません。ファイル拡張子を置換する以外のことを行いたい場合には、Rakeの工具を、ファイル名を改変するための
#pathmap
に取り換える必要があります。
#pathmap
はその引数として、1つの
書式指定文字列をとります。書式指定文字列は、もとのファイル名と異なる部分に対応する文字列を含むコードです。いくつかのコードを試してみましょう。
まず最初に、
%p
はもとのパスをすべて表示します。あまり面白いとは言えませんが。
%f
は、ディレクトリ部分を除いたファイル名を表示します。このコードを使うと、
subdir/appendix.md
が単に
appendix.md
になることに注意してください。
%n
は拡張子とディレクトリ部分のどちらも除いたファイル名を表示します。
%d
はディレクトリを表示し、ファイル名は表示しません。
%x
はファイル拡張子のみを表示します。
そして
%X
は拡張子
以外のすべてを表示します。
require './source_files'
SOURCE_FILES.ext('html')
SOURCE_FILES.pathmap("%p")
# => ["ch1.md", "ch3.md", "ch2.md", "subdir/appendix.md", "ch4.markdown"]
SOURCE_FILES.pathmap("%f")
# => ["ch1.md", "ch3.md", "ch2.md", "appendix.md", "ch4.markdown"]
SOURCE_FILES.pathmap("%n")
# => ["ch1", "ch3", "ch2", "appendix", "ch4"]
SOURCE_FILES.pathmap("%d")
# => [".", ".", ".", "subdir", "."]
SOURCE_FILES.pathmap("%x")
# => [".md", ".md", ".md", ".md", ".markdown"]
SOURCE_FILES.pathmap("%X")
# => ["ch1", "ch3", "ch2", "subdir/appendix", "ch4"]
#pathmap
に渡す文字列には、プレースホルダーのコードだけでなく、任意のテキストも含めることができます。特別に構成したライブライのロードパスでRubyの子プロセスを開始したいとしましょう。ディレクトリのリストがあり、FileListにそのロードパスを含めたいとします。
ディレクトリのリストをコマンドラインの引数(この引数が各ディレクトリをロードパスに追加するようRubyに告げる)に変換するために、各ディレクトリ名の前に
-I
を追加する目的で
#pathmap
を使用できます。(
-I
は、ロードパスとして特別なディレクトリを指定するRubyのコマンドラインフラグです。)
このリストをコマンド文字列に展開すると、リスト内の各ディレクトリに対して-Iが付与された引数を得ます。
require "rake"
load_paths = FileList["mylibs", "yourlibs", "sharedlibs"]
ruby_args = load_paths.pathmap("-I%p")
command = "ruby #{ruby_args} myscript.rb"
# => "ruby -Imylibs -Iyourlibs -Isharedlibs myscript.rb"
これは、
FileList
の別の機能も示しています。すなわち、配列とは異なり、文字列に変換した際に、自身を要素のスペース区切りのリストとしてフォーマットします。
load_paths.to_s # => "mylibs yourlibs sharedlibs"
load_paths.to_a.to_s # => "[\"mylibs\", \"yourlibs\", \"sharedlibs\"]"
#pathmap
にできる他のこととしては、テキスト置換があります。Markdownファイルのリストに戻ってみましょう。ただし、すべてのソースとなるmarkdownを、
sources
と呼ばれるプロジェクトのサブディレクトリに移動したと仮定します。また、生成したHTMLファイルをソースファイルと同じディレクトリに配置するのではなく、ソースディレクトリツリーの構造を反映した
output
ディレクトリに分けて配置したいとします。
ビルドするHTMLファイルのリストを得るために、いつものように
%
記号を持つ
#pathmap
パターンから始めますが、今回は文字のコードの代わりに開きの中括弧を挿入します。その括弧の中で、文字列の最初に
sources/
ディレクトリを探す単純な正規表現を指定します。そしてコンマをつけ、それに続いて置換文字列(
outputs/
ディレクトリの名前)を付け加えます。そして閉じの中括弧を追加します。次に、パスのどの部分に対してこの置換を適用するかを
#pathmap
に告げる必要があります。大文字の
X
を使用しますが、これは前に見たように“ファイル拡張子以外のすべて”を表します。最後に、新しいファイル拡張子として
.html
を追加します。
require "rake"
Dir.chdir "project2"
SOURCE_FILES = Rake::FileList.new("sources/**/*.md", "sources/**/*.markdown")
SOURCE_FILES
# => ["sources/ch1.md", "sources/ch3.md", "sources/ch2.md", "sources/subdir/appendix.md", "sources/ch4.markdown"]
OUTPUT_FILES = SOURCE_FILES.pathmap("%{^sources/,outputs/}X.html")
OUTPUT_FILES
# => ["outputs/ch1.html", "outputs/ch3.html", "outputs/ch2.html", "outputs/subdir/appendix.html", "outputs/ch4.html"]
このコードを評価すると、その結果が、
outputs
サブディレクトリ内に作成されるべきHTMLファイルのリストであることがわかります。ソースファイルツリー内の位置を反映して、
outputs
のサブディレクトリ内に
付録
ファイルがあることに注意して下さい。
実際に、そのファイルを詳しく見ていきましょう。このファイルのビルドを試みると、すでに
outputs
ディレクトリが作成されているにもかかわらず、おそらく
subdir
が作成できないという問題に突き当たることになります。
pandoc
にファイルを生成するよう命令すると、ディレクトリが存在しないので出力ファイルを開けないというエラーになります。
mkdir -p
コマンドでディレクトリを作成できることは知っています。しかし、
mkdir
にはファイル名ではなくディレクトリだけを与える必要があります。このために、ファイル名のディレクトリ部分だけを返すことを命令する書式指定文字列として
%d
をつけて、
#pathmap
を別に呼び出します。
ここでは、
FileList
ではなく文字列に対して
#pathmap
を呼び出していることに注意してください。前回のエピソードで見た
#ext
メソッドと同様、Rakeは
#pathmap
を
String
クラスに追加するので、file listと個々のファイル名文字列を同じように使用できます。
require "rake"
Dir.chdir "project2"
SOURCE_FILES = Rake::FileList.new("sources/**/*.md", "sources/**/*.markdown")
OUTPUT_FILES = SOURCE_FILES.pathmap("%{^sources/,outputs/}X.html")
f = OUTPUT_FILES[3]
f # => "outputs/subdir/appendix.html"
cmd = "mkdir -p #{f.pathmap('%d')}"
cmd # => "mkdir -p outputs/subdir"
信じられないかもしれませんが、このエピソードでは
#pathmap
の全機能のほんの一部を確認したに過ぎません。もし何ができるのかもっと見たいのであれば、Rakeのドキュメントを確認して下さい。複数の置換を一気に行う方法や、テキストを置換するために任意の演算を実行するブロックを使う方法やさらに多くのことを発見するでしょう。
しかし、今日取り上げた内容でも、さまざまなRakeシナリオで
#pathmap
を効果的に使用するのに十分な内容を知ったはずです。ハッピーハッキング!
Rakeについてのエピソード・文章を楽しんでいただけたことを願っている。もし今日、何かしら学んだのであれば、Jimの遺産である教育プログラムの継続を支援するため、
the Weirich Fundに寄付することにより「恩送りすること(訳注:原文は"paying it forward")」を検討して欲しい。 もし、今回のようなビデオをもっと見たければ、
RubyTapas をじっくり見てほしい。シリーズが完結するまで、ほぼ毎日公開していくつもりなので、ほどなくご確認を!
P.S. 特に興味深い方法でRakeを使っているなら、
連絡して欲しい。
関連する投稿:
- Rake Part 2: File Lists
- Rake Part 3: Rules
- Rake Part 1: Files and Rules