ラベル Translation の投稿を表示しています。 すべての投稿を表示
ラベル Translation の投稿を表示しています。 すべての投稿を表示

2014年7月14日月曜日

Readcarpet README 翻訳

RedcarpetのREADMEをライセンスにもとづき翻訳します。

原文は、RedcarpetのGitHubリポジトリコミットd69951e110を使用しています。

なお、ライセンス部分については、原文のままです。以下、翻訳です。



Redcarpetは、砂糖、スパイス、そしてイケてるすべてもので書かれています


Redcarpetは、butterfliesやpopcorn風味のMarkdown処理系Rubyのライブラリです。

このライブラリは人によって書かれています


RedcarpetはVicent Martíによって書かれました。Robin DupretMatt Rogersによってメンテナンスされています。

Redcarpetは、Sundownライブラリとその作者(Natacha Porté, Vicent Martí, および多くの素晴らしい貢献者)の存在なしには、ありえなかったでしょう。

Gemですべてをインストールできます


Redcarpetは、Ruby gemとして簡単に利用可能です。いくつかのネイティブ拡張をビルドしますが、パーサは単独で動作し、ライブラリのインストールは必要ありません。Redcarpet 3.0からは、Rubyのバージョン1.9.2(または、1.9モードのRubinius)以降が必要です。

$ [sudo] gem install redcarpet

Ruby 1.8.7で使用するなら、バージョン2.3.0を使う必要があります。

$ [sudo] gem install redcarpet -v 2.3.0

RedcarpetのソースはGitHubにあります。

$ git clone git://github.com/vmg/redcarpet.git

そして本当に簡単に使えるようです


Redcarpetライブラリのコアは、Redcarpet::Markdownクラスです。このクラスの各インスタンスは1つのRendererにアタッチされます。Markdownクラスは、ドキュメントのパースを行い、出力を生成するためにアタッチされたレンダラを使います。

Redcarpet::Markdownオブジェクトは必要とされる設定にもとづき一度だけインスタンス化されれることが推奨されており、パースのたびに再利用されます。

# Markdownパーサーを初期化
markdown = Redcarpet::Markdown.new(renderer, extensions = {})

ここでは、変数renderer はレンダラオブジェクトを参照しており、Redcarpet::Render::Baseを継承しています。与えられたオブジェクトがインスタンス化されていない場合、ライブラリがデフォルト引数でインスタンス化します。

Markdownオブジェクトを使ったレンダリングは、Markdown#renderで行われます。RedCloth APIとは異なり、レンダリングするテキストは引数として渡されます。再利用を促すためMarkdownインスタンス内には保存されません。
例:

markdown.render("This is *bongos*, indeed.")
# => "<p>This is <em>bongos</em>, indeed.</p>"

パーサが使用するMarkdown拡張を含んだハッシュを指定することもできます。以下の拡張が使用できます。

  • :no_intra_emphasis: 単語内の強調をパースしません。foo_bar_bazのような文字列は、<em>タグを生成しません。

  • :tables: テーブル(PHP-Markdownスタイル)をパースします。

  • :fenced_code_blocks: 囲まれた (fenced) コードブロック(PHP-Markdownスタイル)をパースします。3つ以上の~またはバッククォートで区切られたブロックをコードとして扱います。コードのインデントは必要ありません。オプションで、言語の名前をコードブロックの開きの囲い (fence) の末尾に追加しても構いません。

  • :autolink: <>で囲われていない場合にもリンクをパースします。http, httpsおよびftpプロトコルの自動リンクが、自動的に検知されます。Emailアドレスや、プロトコルがないwwwから始まるリンクも処理します。

  • :disable_indented_code_blocks: 通常のmarkdownコードブロックをパースしません。Markdownは4つのスペースで始まる行をコードブロックに変換しますが、このオプションはその挙動を抑止します。fenced_code_blocks: trueとともに使用することを推奨します。

  • :strikethrough: 取り消し線(PHP-Markdownスタイル)をパースします。2つの~が取り消し線の開始を表します。(例 this is ~~good~~ bad)。

  • :lax_spacing: Markdown標準のように、HTMLブロックを空行で囲む必要がありません。

  • :space_after_headers: ヘッダの先頭にあるハッシュ記号(訳注:#)とそれに続く文字の間にスペースが常に必要になります(例:#this is my headerは無効なヘッダになります)。

  • :superscript: ^の後を上付き文字としてパースします。隣接する上付き文字は一緒にネストされ、連続する値は括弧で囲むことができます。(例:this is the 2^(nd) time)。

  • :underline: アンダースコアによる強調を下線としてパースします。これは_下線_ですが、これは*斜体*のままです

  • :highlight: 強調をパースします。これは==強調==です<mark>highlighted</mark>と表示されます。

  • :quote: 引用をパースします。これは"引用"です<q>quote</q>と表示されます。

  • :footnotes: 脚注(PHP-Markdownスタイル)をパースします。脚注は参照スタイルのリンクと同じように動作します。テキストに隣接するマーカー(例:This is a sentence.[^1])と、独立した行に書かれた脚注の定義(例:[^1]: This is a footnote.)で構成されます。脚注の定義はドキュメント内ならどこにでも書けます。

例:

markdown = Redcarpet::Markdown.new(Redcarpet::Render::HTML, autolink: true, tables: true)

あなた、ランチにレンダラーを何個か詰めておいたわよ


Redcarpetには、Redcarpet::Render::HTMLRedcarpet::Render::XHTMLの2つのレンダラが内蔵されており、それぞれ、HTMLとXHTMLを出力します。これらのレンダラは実際にはCで実装されているため、優秀なパフォーマンスを提供します — 他のRubyで実装されたMarkdown処理系より桁違いに速いです。

以前はHTML出力にだけ適用されていたレンダリングフラグはすべて、Redcarpet::Render::HTMLクラスに移動されました。以下のようにレンダラをインスタンス化する際に有効になります。

Redcarpet::Render::HTML.new(render_options = {})

上記は、HTMLレンダラを初期化します。以下のフラグが使用可能です。

  • :filter_html: ユーザが入力したHTMLの出力を許可しません。

  • :no_images: <img>タグを生成しません。

  • :no_links: <a>タグを生成しません。

  • :no_styles: <style>タグを生成しません。

  • :escape_html: HTMLタグをエスケープします。このオプションは、:no_styles, :no_links, :no_images および :filter_htmlより優先されます。つまり、存在するタグはすべて取り除かれずにエスケープされます。

  • :safe_links_only: 安全と考えられるプロトコルのリンクのみを生成します。

  • :with_toc_data: HTMLアンカーを、出力されるHTMLの各ヘッダに追加して、各セクションへのリンクを可能にします。

  • :hard_wrap: 段落中で、もとのMarkdownに改行がある場合、HTMLの<br>タグを挿入します(デフォルトでは、Markdownはこれらの改行を無視します)。

  • :xhtml: XHTML準拠のタグを出力します。このオプションは、Render::XHTMLレンダラでは常に有効になります。

  • :prettify: google-code-prettify用に、prettyprintクラスを<code>タグに追加します。.

  • :link_attributes: リンクを追加するための、特別な属性のハッシュです。

例:

renderer = Redcarpet::Render::HTML.new(no_links: true, hard_wrap: true)

HTMLレンダラには別バージョンとしてRedcarpet::Render::HTML_TOCがあります。これは、Markdown文書のヘッダにもとづいてHTMLに目次を出力します。

このレンダラオブジェクトをインスタンス化する際には、任意でnesting_levelオプションを渡すことができます。このオプションは整数で、指定したレベルまでのヘッダのみをレンダリングすることができます。

さらに、ピュアなRubyカスタムレンダラ、あるいは既存レンダラの拡張を書くために、抽象ベースクラスRedcarpet::Render::Baseを使用できます。

自分で料理することもできます


カスタムレンダラは、既存のレンダラを継承して作成します。ビルトインされたレンダラであるHTML, XHTMLは次のように拡張できます。

# コードブロックをハイライトするカスタムレンダラを作成します
class HTMLwithPygments < Redcarpet::Render::HTML
  def block_code(code, language)
    Pygments.highlight(code, lexer: language)
  end
end

markdown = Redcarpet::Markdown.new(HTMLwithPygments, fenced_code_blocks: true)

しかし、新規レンダラをゼロから作成することもできます(Manpageレンダラの実装例として、lib/redcarpet/render_man.rbを見てみます)。

class ManPage < Redcarpet::Render::Base
  # ドリルを手に取り -- ここから始めます
end

以下のインスタンスメソッドがレンダラにより実装されます。

ブロックレベルの呼び出し

メソッドの戻り値がnilの場合、そのブロックはスキップされます。ドキュメント要素に対するメソッドが実装されていない場合、そのブロックはスキップされます。

例:

class RenderWithoutCode < Redcarpet::Render::HTML
  def block_code(code, language)
    nil
  end
end

  • block_code(code, language)
  • block_quote(quote)
  • block_html(raw_html)
  • footnotes(content)
  • footnote_def(content, number)
  • header(text, header_level)
  • hrule()
  • list(contents, list_type)
  • list_item(text, list_type)
  • paragraph(text)
  • table(header, body)
  • table_row(content)
  • table_cell(content, alignment)

Spanレベルの呼び出し

戻り値nilはデータを何も出力しません。ドキュメント要素に対するメソッドが実装されていない場合、spanの内容はそのままコピーされます。

  • autolink(link, link_type)
  • codespan(code)
  • double_emphasis(text)
  • emphasis(text)
  • image(link, title, alt_text)
  • linebreak()
  • link(link, title, content)
  • raw_html(raw_html)
  • triple_emphasis(text)
  • strikethrough(text)
  • superscript(text)
  • underline(text)
  • highlight(text)
  • quote(text)
  • footnote_ref(number)

注意:レンダラメソッドをオーバーライドする時は、必ずそのメソッドのレベルに一致するレベルのHTML要素を返してください(例:ブロックレベル呼び出しをオーバーライドする時はブロック要素を返します)。そうしなければ、意図しない出力になる可能性があります。

低レベルのレンダリング

  • entity(text)
  • normal_text(text)

ドキュメントのヘッダ

他の要素の前にレンダリングされます。

  • doc_header()

ドキュメントのフッタ

他の要素の後にレンダリングされます。

  • doc_footer()

前処理/後処理

特別なコールバックです。レンダリング処理の開始前後に全ドキュメントを前処理または後処理します。

  • preprocess(full_document)
  • postprocess(full_document)

より詳しい説明は、"How to extend the Redcarpet 2 Markdown library?"で確認できます。

ズボンがさらにかっこよくなります


Redcarpet 2には単独で動作するSmartyPants実装があり、オリジナルの実装に完全に準拠しています。存在するパーサとしては、桁違いに最速のSmartyPantsパーサです。

このSmartyPantsパーサは、Redcarpet::Render::SmartyPantsにあります。モジュールとして実装されているので、単独でもミックスインとしても使用可能です。

任意のRendererクラスにミックスインすると、レンダリングの完了後にSmartyPantsによる置換を実行するために、postprocessメソッドをオーバーライドします。

# Mixin
class HTMLWithPants < Redcarpet::Render::HTML
  include Redcarpet::Render::SmartyPants
end

# Standalone
Redcarpet::Render::SmartyPants.render("<p>Oh SmartyPants, you're so crazy...</p>")

SmartyPantsは、すでにレンダリングされたHTMLに対して動作します。HTMLタグの中身や、<code><pre>のような特定のHTMLブロックの内容を置換しません。

退屈な法律のこと


Copyright (c) 2011-2013, Vicent Martí

Permission to use, copy, modify, and/or distribute this software for anypurpose with or without fee is hereby granted, provided that the abovecopyright notice and this permission notice appear in all copies.

THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIESWITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OFMERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FORANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGESWHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN ANACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OFOR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

2014年7月2日水曜日

Semantic Versioning 2.0.0 翻訳

Semantic Versioning 2.0.0を、ライセンス(Creative Commons - CC BY 3.0)にもとづき翻訳し、以下に公開しました。



原文は、GitHubにあるSemantic Versioning Specificationv2.0.0のタグがついているコミットを使用しています。この日本語訳に関するフィードバックは、semver-ja.github.ioのissueからお願いします。

なお、翻訳にあたっては下記を参考にさせて頂きました。

2014年6月27日金曜日

Learn advanced Rake in 7 episodes 翻訳

Avdi GrimmLearn Advanced Rake in 7 Episodesに含まれる7つのエピソードをすべて翻訳しました。そのまとめとして、Learn Advanced Rake in 7 Episodesを、本人の許可を得て翻訳しました。各パートへのリンクは翻訳したものを指しています(原文へのリンクも併記しています)。以下、翻訳です。



RakeはRuby界で普及しているが、そのパワーはたびたび低く見積もられ十分に活用されていない。この2週間にわたり、Rakeで行ったことをRubyTapasのビデオシリーズとして、連続して投稿してきた。そこでは、共通のタスクを優雅に自動化するのを助けるRakeの高度な機能を詳しく説明している。便利なよう、以下にすべてのエピソードのリストをまとめておく。もとの投稿(気軽に順を追って理解したい人のためのスクリプトとソースコードを含む)へのリンクも含まれている。

Part 1: Files and Rules

(原文:Part 1: Files and Rules)

Part 2: File Lists

(原文:Part 2: File Lists)


Part 3: Rules

(原文:Part 3: Rules)


Part 4: Pathmap

(原文:Part 4: Pathmap)


Part 5: File Operations

(原文:Part 5: File Operations)


Part 6: Clean and Clobber

(原文:Part 6: Clean and Clobber)


Part 7: MultiTask

(原文:Part 7: MultiTask)


これは、Rakeの全機能の完全なガイドではないが、より効率的に作業する助けになる何かを、ここで見つけてくれればと思う。


もし、すばらいいツールであるRakeに感謝しているのなら、Weirich Fundへの寄付を考えてほしい。また、このようなスタイルのビデオがお好みなら、RubyTapas.comをさらにチェックして欲しい。


おっと、特におもしろい何かのためにRakeをつかっているのなら、教えてほしい


ハッピーハッキング!

関連する投稿:
  1. Rake Part 1: Files and Rules
  2. Rake Part 7: MultiTask
  3. Rake Part 6: Clean and Clobber

Rake Part 7: MultiTask 翻訳

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



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

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

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






このRakeの連続シリーズは終わりに向かって進んでいます。前回までのいくつかのビデオを通して、私がそうであったように、Rakeのパワーのすごさが分かってくるようになってきたと思います。しかしおそらく、素のRubyやシェルスクリプトを利用することに対するRakeを使用することの利点については、まだ懐疑的でしょう。もしそうであれば、今日がその印象を変える日だと思います。Rakeが、ただ同然で与えてくれる驚くほどパワフルな能力をお見せしたいと思います。


電子書籍を作るとします。pygmentizeユーティリティを使ってシンタックスハイライトされたHTMLに変換するために準備した、テキストを取り除いた数百のコード目録が含まれたディレクトリがあります。


これは、このタスクを行う小さなRakefileです。目録のリスト、“highlights”のリスト(これは、HTMLの最終成果物です)、そしてhighlightと呼ばれるすべてを生成するタスクを定義しています。最後に、pygmentizeを実行して目録ファイルから.htmlファイルを生成するルールを定義しています。また、highlightタスクに依存するデフォルトタスクも定義しています。

 
require "rake/clean"

task :default => :highlight

LISTINGS   = FileList["listings/*"]
HIGHLIGHTS = LISTINGS.ext(".html")
CLEAN.include(HIGHLIGHTS)

task :highlight => HIGHLIGHTS

rule ".html" => ->(f){ FileList[f.ext(".*")].first } do |t|
  sh "pygmentize -o #{t.name} #{t.source}"
end

pygmentizeタスクを使ったソースコードのハイライトは時間が掛かります。たくさんのソースファイルがある場合、多くの時間が掛かります。もしrakeをtimeコマンドの下で実行すると、そのプロセスに48秒かかることがわかります。

 
$ time rake
...
pygmentize -o listings/fd673484d50a66ea67fcd20e0c55f038a729e4d7.html listings/fd673484d50a66ea67fcd20e0c55f038a729e4d7.rb
pygmentize -o listings/ff6e24090e794c4db847b10ca993c872ca804101.html listings/ff6e24090e794c4db847b10ca993c872ca804101.rb

real    0m47.961s
user    0m41.912s
sys     0m4.852s

今のところ、これらのハイライトされたファイルは1つずつビルドされています。しかし、今は2013年で、2つの物理コアが載ったコンピュータを私は持っていて、ハイパースレッディングにより4つの仮想コアがあることになります。どうして2つ以上のファイルを同時にビルドできないのでしょうか?


それは実際に可能です。我々がすべきことは、rakefileの1行をtaskからmultitaskに変えるだけです。

 
multitask :highlight => HIGHLIGHTS

これは、:highlightタスクの事前条件を並行に処理できることをRakeに告げます。並列化したいタスクそのものではなく、並列化したいタスクに依存するタスクにこの変更を行っている点に注意してください。


Rakeをもう一度実行します。Rakeが数百のRakeサブプロセスを同時に並列で起動し、それらが同じSTDOUTに出力を行うため、かなりみだれた出力になります。


25秒を少し超えてたところで、ビルドが完了します。この1つの変更により、処理時間をおよそ半分にカットしました!

 
$ time rake
...

real    0m25.701s
user    1m13.492s
sys     0m8.272s

何個のタスクを並列で実行するかを微調整したい場合は、同時に実行できるプロセスの最大数をRakeに告げるために-jオプションを使用できます。各仮想コアにたいして1つ、すなわち4を指定してみます。


興味深いことに、今度はほんの少し時間がかかります。どうしてかはよく分かりません。

 
$ time rake -j 4

real    0m26.752s
user    1m10.300s
sys     0m7.208s

先程述べたように、並列に実行するにはコードに1行変更を加えればよいといいましたが、それは少し嘘です。本当は、コードにまったく変更を加えずにタスクを並列に実行するよう、Rakeに命令することができます。multitasktaskに戻して、-mオプションつきでrakeを実行します。これは、すべてのタスクをmultitaskとして扱うよう、Rakeに告げます。


再び、歪んだ出力が確認できます。そして一段落すると、合計時間が25秒超であることが再び確認できます。

 
$ time rake -m

real    0m25.606s
user    1m13.060s
sys     0m7.856s

Rakeの並列化は賢くもあります。もし他のタスクが:highlightタスクに依存していれば、次のフェーズに進む前に、すべてのpygmentizeプロセスが完了するまで待機します。


では、Rakeを使ったビルドの自動化から得たものは何でしょう?タスクを達成するための複雑な依存関係やルールを宣言するための簡単な方法だけではありません。ファイル操作のための便利なメソッドのセットだけではありません。扱いやすいコマンドラインのフロントエンドだけではありません。それらすべてに加え、繰り返し行われるタスクの並列化がただで手に入ります。そして、それが私の言うハッピーハッキングです!




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

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

関連する投稿:
  1. Rake Part 1: Files and Rules
  2. Rake Part 6: Clean and Clobber
  3. Rake Part 3: Rules

2014年6月26日木曜日

Rake Part 6: Clean and Clobber 翻訳

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



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

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

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




前回のエピソードでは、ビルドスクリプトの成果物をクリーンアップするRakeタスクを定義しました。再帰的に“outputs”ディレクトリを削除する単純な方法でこれを実現しました。

 
task :clean do
  rm_rf "outputs"
end

時にはクリーンアップはこれほど単純ではありません。今日は、これまで開発してきたRakefileをいくらか修正したバージョンから始めましょう。


以前と同じように、MarkdownファイルをHTMLに変換しています。直前のエピソードと異なるのは、HTMLファイルがソースファイルと同じディレクトリに生成されることです。つまり、ソースディレクトリと出力ディレクトリは分かれていません。


MarkdownファイルをHTMLにビルドするルールに加え、いくつかの新しいルールも追加しました。すべてのHTMLフラグメントファイルを単一のbook.htmlに結合するルールがあります。そして、Calibre電子書籍パッケージのebook-convertコマンドを使ってbook.htmlをEPUBフォーマットの電子書籍に変換するルールがあります。最後に、Amazonのkindlegenを使って、受け取ったEPUBファイルをKindle互換の.mobiファイルに変換するルールがあります。


最後の調整として、:defaultルールを.epubターゲットと.mobiターゲットに依存するよう更新しています。

 
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 => ["book.epub", "book.mobi"]
task :html => SOURCE_FILES.ext(".html")

rule ".html" => ->(f){source_for_html(f)} do |t|
  sh "pandoc -o #{t.name} #{t.source}"
end

file "book.html" => SOURCE_FILES.ext(".html") do |t|
  chapters   = FileList["**/ch*.html"]
  backmatter = FileList["backmatter/*.html"]
  sh "cat #{chapters} #{backmatter} > #{t.name}"
end

file "book.epub" => "book.html" do |t|
  sh "ebook-convert book.html #{t.name}"
end

file "book.mobi" => "book.epub" do |t|
  sh "kindlegen book.epub -o #{t.name}"
end

def source_for_html(html_file)
  SOURCE_FILES.detect{|f| f.ext('') == html_file.ext('')}
end

このビルドスクリプトは、2つの異なるカテゴリのファイルを生成します。

  • 中間ファイル。すべてのHTMLファイルはこのカテゴリに含まれます。これらのファイルは、全ビルドプロセスが終了すると必要がなくなるので、一時ファイルとも呼ばれます。
  • 成果物である電子書籍ファイル。これらのファイルは、全プロセスの最終目的です。

プロジェクトの自動クリーンアップについて言うなら、これらの2つの異なるカテゴリのファイルを別々に取り扱いたいでしょう。電子書籍の成果物はそのまま残し、中間ファイルだけをクリーンアップしたいこともあれば、すべての生成されたファイルを一掃し、白紙の状態から始めたいこともあります。


これらの2つのタイプのクリーンアップを扱うタスクを書くこともできます。あるいは、Rakeのオプションライブラリrake/cleanを使うこともできます。


rake/cleanを使うには、まずrequireします。そうすると、新たなグローバル定数CLEANを使用できるようになります。この定数はFileListであり、最初は空です。

 
require 'rake/clean'

CLEAN                           # => []
CLEAN.class                     # => Rake::FileList

どのファイルが中間ファイルであるかをRakeに告げるためにCLEANリストを使用できます。まず、Markdownファイルから生成されたHTMLファイルのリストを追加しましょう。

CLEAN.include(SOURCE_FILES.ext(".html"))
そして結合されたbook.htmlをリストに追加します。

 
file "book.html" => SOURCE_FILES.ext(".html") do |t|
  chapters   = FileList["**/ch*.html"]
  backmatter = FileList["backmatter/*.html"]
  sh "cat #{chapters} #{backmatter} > #{t.name}"
end
CLEAN.include("book.html")

次に、ファイルをCLOBBERと呼ばれる他のリストに追加します。このリストはどのファイルが最終成果物であるかをRakeに告げます。CLOBBERリストに.epubおよび.mobiの電子書籍ファイルを追加します。

 
file "book.epub" => "book.html" do |t|
  sh "ebook-convert book.html #{t.name}"
end
CLOBBER << "book.epub"

file "book.mobi" => "book.epub" do |t|
  sh "kindlegen book.epub -o #{t.name}"
end
CLOBBER << "book.mobi"

Rakefileの1か所で、これらのファイルをCLEANCLOBBERのリストに追加することもできたかもしれません。しかし、ファイルを生成する各ルールのすぐ隣にそれぞれを追加することにより、これらのルールを完全に削除したり修正したりする時に、CLEANリストまたはCLOBBERリストの関連するエントリーを変更することを覚えている可能性が高くなります。


コマンドラインで、rake -Tコマンド使って使用可能なタスクの一覧を表示するようRakeに告げると、定義していないcleanclobberという2つのタスクが使用できることが確認できます。


電子書籍ファイルを生成するために、まずrakeを引数なしで実行します。プロジェクト内のファイルを一覧表示すると、様々な.html中間ファイルと最終成果物である.epub.mobiファイルが確認できます。

 
$ rake
$ tree
.
├── backmatter
│   ├── appendix.html
│   └── appendix.md
├── book.epub
├── book.html
├── book.mobi
├── ch1.html
├── ~ch1.md
├── ch1.md
├── ch2.html
├── ch2.md
├── ch3.html
├── ch3.md
├── ch4.html
├── ch4.markdown
├── Rakefile
├── scratch
│   └── test.md
└── temp.md

rake cleanを実行しても、何も出力されません。しかし、プロジェクトの中身を再び一覧表示すると、すべての.htmlファイルが消去されていることが確認できます。

 
$ rake clean
avdi@hazel:~/Dropbox/rubytapas/134-rake-clean/project$ tree
.
├── backmatter
│   └── appendix.md
├── book.epub
├── book.mobi
├── ~ch1.md
├── ch1.md
├── ch2.md
├── ch3.md
├── ch4.markdown
├── Rakefile
├── scratch
│   └── test.md
└── temp.md

そしてrake clobberを実行すると、見つからなかったファイルについて、たくさんの警告が表示されます。これは、clobberはまずcleanタスクを実行しますが、そのタスクはすでに実行されているからです。このタスクはすでに存在しないたくさんのファイルを削除しようとしますが、心配ありません。これらの警告は無害であり、安心して無視できます。


clobberの実行後にプロジェクトの中身を見てみると、中間ファイルと共に電子書籍ファイルがなくなっていることが確認できます。


rake/cleanについては以上ですが、ビルドされたファイルを削除するためにproject独自のクリーンアップタスクを書く必要はありませんでした。適切なファイルあるいはファイルパターンをCLEANCLOBBERのリストに追加しただけで、あとはRakeにしてもらいました。ハッピーハッキング!




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

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

関連する投稿:
  1. Rake Part 5: File Operations
  2. Rake Part 2: File Lists
  3. Rake Part 4: Pathmap

2014年6月25日水曜日

Rake Part 5: File Operations 翻訳

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



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

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

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





これが前回までのいくつかのエピソードで使用したRakefileです。プロジェクトの“sources”サブディレクトリの中にMarkdownソースファイルがあり、“outputs”サブディレクトリの中に、並列の階層構造をなすHTMLファイルを生成します。

 
SOURCE_FILES = Rake::FileList.new("sources/**/*.md", "sources/**/*.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.pathmap("%{^sources/,outputs/}X.html")

rule ".html" => ->(f){source_for_html(f)} do |t|
  sh "pandoc -o #{t.name} #{t.source}"
end

def source_for_html(html_file)
  SOURCE_FILES.detect{|f| f.ext('') == html_file.ext('')}
end

“outputs”ディレクトリの中に入力ファイルの階層構造を再生成しているので、HTMLファイルを生成する前に、目的のディレクトリが存在することを保証する必要があります。Rakeでこれを実現する簡単な方法は、directoryタスクを使用することです。これはディレクトリを対象としていることを除けばfileタスクと似ていますが、fileタスクとは異なり、存在しない場合にディレクトリを作成する方法をコードとして与える必要がありません。単にタスクを定義することにより、必要ならばディレクトリを作成するようRakeに暗黙的に指示します。


“.html”ルールに対して、このディレクトリを依存関係のリストに追加します。

 
rule ".html" => [->(f){source_for_html(f)}, "outputs"] do |t|
  sh "pandoc -o #{t.name} #{t.source}"
end

rakeを実行すると、HTMLファイルを生成する前にディレクトリが作成されることが確認できます。しかし、残念ながら、appendix.htmlファイルをビルドしようとする時に問題に遭遇します。このファイルはsourcesディレクトリのサブディレクトリの中にあるので、HTMLの出力ファイルは、outputsディレクトリの対応するサブディレクトリの中に出力して欲しいのです。しかし、このサブディレクトリはまだ存在しません。

 
t$ rake
mkdir -p outputs
pandoc -o outputs/backmatter/appendix.html sources/backmatter/appendix.md
pandoc: outputs/backmatter/appendix.html: openFile: does not exist (No such file or directory)
rake aborted!
Command failed with status (1): [pandoc -o outputs/backmatter/appendix.html...]
/home/avdi/Dropbox/rubytapas/133-rake-file-operations/project/Rakefile:16:in `block in <top (required)>'
Tasks: TOP => default => html => outputs/backmatter/appendix.html
(See full trace by running task with --trace)

(訳注:1行目の先頭のtは原文のタイポと思われる。)

HTMLファイルの出力前に、このディレクトリや他の中間ディレクトリが存在することを保証するには、対象ファイルのディレクトリ部だけを渡すために#pathmap使って、mkdir -pシェルコマンドを実行することも可能です。

 
sh "mkdir -p #{t.name.pathmap('%d')}"

しかし、Rakeにはこのためのショートカットがあります。シェルコマンドを実行する代わりに、mkdir_pメソッドをタスクの中で使用することができます。

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

task :default => :html
task :html => SOURCE_FILES.pathmap("%{^sources/,outputs/}X.html")

directory "outputs"

rule ".html" => [->(f){source_for_html(f)}, "outputs"] do |t|
  mkdir_p t.name.pathmap("%d")
  sh "pandoc -o #{t.name} #{t.source}"
end

def source_for_html(html_file)
  SOURCE_FILES.detect{|f| 
    f.ext('') == html_file.pathmap("%{^outputs/,sources/}X")
  }
end

ここでrakeを実行すると、markdownからHTMLへの変換されるたびに、前もって対象ディレクトリが存在することが保証されます。

 
$ rake
mkdir -p outputs/backmatter
pandoc -o outputs/backmatter/appendix.html sources/backmatter/appendix.md
mkdir -p outputs
pandoc -o outputs/ch1.html sources/ch1.md
mkdir -p outputs
pandoc -o outputs/ch2.html sources/ch2.md
mkdir -p outputs
pandoc -o outputs/ch3.html sources/ch3.md
mkdir -p outputs
pandoc -o outputs/ch4.html sources/ch4.markdown

ビルドスクリプトを書いているときに、素早くすべての生成されたファイルを消す簡単な方法があれば便利なことはよくあります。これに対処するタスクを追加してみましょう。ここでも、シェルコマンドを実行する代わりに、Rakeのヘルパーメソッドを使用します。これはシェルのrm -rfコマンドに対応し、いかなる警告や確認もせずに再帰的にファイルとディレクトリを削除します。

 
task :clean do
  rm_rf "outputs"
end

Rakeにはこのようなフィル操作を行うヘルパーメソッドがたくさんあり、そのすべてがUNIXシェルの同等なコマンドにちなんで名づけられています。これは、いくつかの点で便利です。一例をあげると、それらはRuby自身のメソッドなので、文字列展開などをすることなく、ファイルやfile listを直接渡せます。


また、Rakeの“quiet”フラグも感知します。Rakeコマンドは-qフラグつきで実行すると、必要な動作はすべて行いますが、STDOUTへの出力は行いません。

 
$ rake -q
$

これらのヘルパーほとんどすべては、Rubyの標準ライブライであるFileUtilsから直接受け継がれています。ですから、使用可能なヘルパーすべてのリストを確認したければ、単にthe FileUtils documentationを確認してください。


今日は以上です。ハッピーハッキング!




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

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

関連する投稿:

  1. Rake Part 4: Pathmap
  2. Rake Part 2: File Lists
  3. Rake Part 1: Files and Rules
  4.  


     

2014年6月24日火曜日

Rake Part 4: Pathmap 翻訳

Avdi GrimmLearn 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は#pathmapStringクラスに追加するので、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を使っているなら、連絡して欲しい

関連する投稿:
  1. Rake Part 2: File Lists
  2. Rake Part 3: Rules
  3. Rake Part 1: Files and Rules

2014年6月23日月曜日

Rake Part 3: Rules 翻訳

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



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

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

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





前回までの2回のエピソードでは、MarkdownファイルをHTMLにビルドするためにRakefileを作成しました。今のところRakefileは動作していますが、いくつか重複があります。 HTMLファイルをビルドするためのほとんど同じルールが2つあります。1つは.md拡張子を持つソースファイルを探し、 もう1つは.markdown拡張子をもつファイルを探します。 それらを単一の、より一般的なルールにまとめられるといいですね。
 
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

2番目のルールを取り除くことから始めましょう。この状態でrakeを実行すると失敗します。

 
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
$ rake
rake aborted!
Don't know how to build task 'ch4.html'

Tasks: TOP => default => html
(See full trace by running task with --trace)

先に進む前に、このエラーメッセージについて少し話しましょう。 このメッセージは、“タスク‘ch4.html’をビルドする方法がわかりません”と言っています。 これは多くのことを教えてはくれません。ch4.htmlと呼ばれるtaskが話題となっているので、少し紛らわしくもあります。ch4.htmlはビルドしたいfileであり、 タスクではありませんよね?


ここから次のことがわかります。Rakeは、ビルドするよう依頼されているすべてのものをタスクとして考えています。単純なタスクとファイルタスクの唯一の違いは、 ファイルタスクの場合、タスク名に一致するファイルがあるか、そして事前条件となるファイルよりそのファイルのほうが新しい(タスクを実行する必要はない)かを Rakeが知っているということです。


このケースでは、なぜRakeがこのファイルをビルドできないのか、私たちは知っています。 どうやってビルドするかを告げるルールを単に削除したからです。 しかし、もしそれを知らなかっとしたらどうでしょう? このメッセージは、どう対処したらよいかについては多くを教えてはくれません。


Rakeが何をしようとしているかをより理解するために、Rakeに–traceフラグを渡すことができます。今度は、Rakeはたどるべきパンくずリストを残し、何をしようとしたかを教えてくれます。最初に、デフォルトタスクを呼び出しています。 デフォルトタスクは“html”タスクに依存しているので、次にhtmlタスクが呼ばれます。

その次のステップは、ch4.htmlをビルドする方法がわからないためrakeが中断したことをそっけなく通知し、その後にRubyのスタックトレースが続きます。

 
 $ rake --trace
  ** Invoke default (first_time)
  ** Invoke html (first_time)
  rake aborted!
  Don't know how to build task 'ch4.html'
  /home/avdi/.rvm/gems/ruby-1.9.3-p327/gems/rake-10.1.0/lib/rake/task_manager.rb:49:in `[]'
  /home/avdi/.rvm/gems/ruby-1.9.3-p327/gems/rake-10.1.0/lib/rake/task.rb:53:in `lookup_prerequisite'
  /home/avdi/.rvm/gems/ruby-1.9.3-p327/gems/rake-10.1.0/lib/rake/task.rb:49:in `block in prerequisite_tasks'
  /home/avdi/.rvm/gems/ruby-1.9.3-p327/gems/rake-10.1.0/lib/rake/task.rb:49:in `map'
  /home/avdi/.rvm/gems/ruby-1.9.3-p327/gems/rake-10.1.0/lib/rake/task.rb:49:in `prerequisite_tasks'
  /home/avdi/.rvm/gems/ruby-1.9.3-p327/gems/rake-10.1.0/lib/rake/task.rb:195:in `invoke_prerequisites'
  /home/avdi/.rvm/gems/ruby-1.9.3-p327/gems/rake-10.1.0/lib/rake/task.rb:174:in `block in invoke_with_call_chain'
  /usr/lib/ruby/1.9.1/monitor.rb:211:in `mon_synchronize'
  /home/avdi/.rvm/gems/ruby-1.9.3-p327/gems/rake-10.1.0/lib/rake/task.rb:168:in `invoke_with_call_chain'
  /home/avdi/.rvm/gems/ruby-1.9.3-p327/gems/rake-10.1.0/lib/rake/task.rb:197:in `block in invoke_prerequisites'
  /home/avdi/.rvm/gems/ruby-1.9.3-p327/gems/rake-10.1.0/lib/rake/task.rb:195:in `each'
  /home/avdi/.rvm/gems/ruby-1.9.3-p327/gems/rake-10.1.0/lib/rake/task.rb:195:in `invoke_prerequisites'
  /home/avdi/.rvm/gems/ruby-1.9.3-p327/gems/rake-10.1.0/lib/rake/task.rb:174:in `block in invoke_with_call_chain'
  /usr/lib/ruby/1.9.1/monitor.rb:211:in `mon_synchronize'
  /home/avdi/.rvm/gems/ruby-1.9.3-p327/gems/rake-10.1.0/lib/rake/task.rb:168:in `invoke_with_call_chain'
  /home/avdi/.rvm/gems/ruby-1.9.3-p327/gems/rake-10.1.0/lib/rake/task.rb:161:in `invoke'
  /home/avdi/.rvm/gems/ruby-1.9.3-p327/gems/rake-10.1.0/lib/rake/application.rb:149:in `invoke_task'
  /home/avdi/.rvm/gems/ruby-1.9.3-p327/gems/rake-10.1.0/lib/rake/application.rb:106:in `block (2 levels) in top_level'
  /home/avdi/.rvm/gems/ruby-1.9.3-p327/gems/rake-10.1.0/lib/rake/application.rb:106:in `each'
  /home/avdi/.rvm/gems/ruby-1.9.3-p327/gems/rake-10.1.0/lib/rake/application.rb:106:in `block in top_level'
  /home/avdi/.rvm/gems/ruby-1.9.3-p327/gems/rake-10.1.0/lib/rake/application.rb:115:in `run_with_threads'
  /home/avdi/.rvm/gems/ruby-1.9.3-p327/gems/rake-10.1.0/lib/rake/application.rb:100:in `top_level'
  /home/avdi/.rvm/gems/ruby-1.9.3-p327/gems/rake-10.1.0/lib/rake/application.rb:78:in `block in run'
  /home/avdi/.rvm/gems/ruby-1.9.3-p327/gems/rake-10.1.0/lib/rake/application.rb:165:in `standard_exception_handling'
  /home/avdi/.rvm/gems/ruby-1.9.3-p327/gems/rake-10.1.0/lib/rake/application.rb:75:in `run'
  /home/avdi/.rvm/gems/ruby-1.9.3-p327/gems/rake-10.1.0/bin/rake:33:in `<top (required)>'
  /home/avdi/.rvm/gems/ruby-1.9.3-p327/bin/rake:23:in `load'
  /home/avdi/.rvm/gems/ruby-1.9.3-p327/bin/rake:23:in `<main>'
  Tasks: TOP => default => html
#+END_HTML

Let's ask Rake why it was trying to build =ch4.html= in the first
place. We can do this by running Rake with the =-P= flag, which tells
it to dump a list of prerequisites.

#+BEGIN_EXAMPLE
$ rake -P
rake default
    html
rake html
    ch1.html
    ch2.html
    ch3.html
    subdir/appendix.html
    ch4.html

この出力により、htmlタスクはch4.htmlを含むファイルのリストに依存していることが明確になります。


このRakefileの何が問題かをまだ知らないふりをしていることを忘れないでください。 Rakeの考え方について多くの知見を集めましたが、今のところ HTMLファイルとMarkdownファイルの関連(この問題を修正するために理解する必要があるなにか)については依然分かっていません。


Rakeの思考プロセスをより深く見るために、Rakefileファイル内で、Rake.application.options.trace_rulesオプションにtrueを設定します。 このオプションを有効にすると、その名前が示す通り、Rakefileに定義されたルールについてトレース情報を表示するようRakeに命じます。


注:このビデオの作成後、Jim Weirichから、このオプションは`–rules`コマンドラインオプションとしても利用可能であるとの指摘があった。

 
Rake.application.options.trace_rules = true

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

再びrake -traceを実行すると、今度はタスク呼び出しのパンくずリストに加えて、新たな情報を確認できます。それぞれのファイルのビルドに対して、Rakeが使おうとしたルールと、 そのルールの中でどの.htmlファイルがどの対応する.mdファイルに依存しているかを教えてくれます。 ch4.htmlにたどり着くと、rakeは失敗します。事前条件ファイルch4.mdが見つからなかったと明示されてはいませんが、我々の目の前にあるこの情報により、何が問題かを合理的に推定することができます。

 
$ rake --trace
** Invoke default (first_time)
** Invoke html (first_time)
Attempting Rule ch1.html => ch1.md
(ch1.html => ch1.md ... EXIST)
Attempting Rule ch2.html => ch2.md
(ch2.html => ch2.md ... EXIST)
Attempting Rule ch3.html => ch3.md
(ch3.html => ch3.md ... EXIST)
Attempting Rule subdir/appendix.html => subdir/appendix.md
(subdir/appendix.html => subdir/appendix.md ... EXIST)
Attempting Rule ch4.html => ch4.md
(ch4.html => ch4.md ... FAIL)
rake aborted!
Don't know how to build task 'ch4.html'
/home/avdi/.rvm/gems/ruby-1.9.3-p327/gems/rake-10.1.0/lib/rake/task_manager.rb:49:in `[]'
/home/avdi/.rvm/gems/ruby-1.9.3-p327/gems/rake-10.1.0/lib/rake/task.rb:53:in `lookup_prerequisite'
/home/avdi/.rvm/gems/ruby-1.9.3-p327/gems/rake-10.1.0/lib/rake/task.rb:49:in `block in prerequisite_tasks'
/home/avdi/.rvm/gems/ruby-1.9.3-p327/gems/rake-10.1.0/lib/rake/task.rb:49:in `map'
/home/avdi/.rvm/gems/ruby-1.9.3-p327/gems/rake-10.1.0/lib/rake/task.rb:49:in `prerequisite_tasks'
/home/avdi/.rvm/gems/ruby-1.9.3-p327/gems/rake-10.1.0/lib/rake/task.rb:195:in `invoke_prerequisites'
/home/avdi/.rvm/gems/ruby-1.9.3-p327/gems/rake-10.1.0/lib/rake/task.rb:174:in `block in invoke_with_call_chain'
/usr/lib/ruby/1.9.1/monitor.rb:211:in `mon_synchronize'
/home/avdi/.rvm/gems/ruby-1.9.3-p327/gems/rake-10.1.0/lib/rake/task.rb:168:in `invoke_with_call_chain'
!/home/avdi/.rvm/gems/ruby-1.9.3-p327/gems/rake-10.1.0/lib/rake/task.rb:197:in `block in invoke_prerequisites'
/home/avdi/.rvm/gems/ruby-1.9.3-p327/gems/rake-10.1.0/lib/rake/task.rb:195:in `each'
/home/avdi/.rvm/gems/ruby-1.9.3-p327/gems/rake-10.1.0/lib/rake/task.rb:195:in `invoke_prerequisites'
/home/avdi/.rvm/gems/ruby-1.9.3-p327/gems/rake-10.1.0/lib/rake/task.rb:174:in `block in invoke_with_call_chain'
/usr/lib/ruby/1.9.1/monitor.rb:211:in `mon_synchronize'
/home/avdi/.rvm/gems/ruby-1.9.3-p327/gems/rake-10.1.0/lib/rake/task.rb:168:in `invoke_with_call_chain'
/home/avdi/.rvm/gems/ruby-1.9.3-p327/gems/rake-10.1.0/lib/rake/task.rb:161:in `invoke'
/home/avdi/.rvm/gems/ruby-1.9.3-p327/gems/rake-10.1.0/lib/rake/application.rb:149:in `invoke_task'
/home/avdi/.rvm/gems/ruby-1.9.3-p327/gems/rake-10.1.0/lib/rake/application.rb:106:in `block (2 levels) in top_level'
/home/avdi/.rvm/gems/ruby-1.9.3-p327/gems/rake-10.1.0/lib/rake/application.rb:106:in `each'
/home/avdi/.rvm/gems/ruby-1.9.3-p327/gems/rake-10.1.0/lib/rake/application.rb:106:in `block in top_level'
/home/avdi/.rvm/gems/ruby-1.9.3-p327/gems/rake-10.1.0/lib/rake/application.rb:115:in `run_with_threads'
/home/avdi/.rvm/gems/ruby-1.9.3-p327/gems/rake-10.1.0/lib/rake/application.rb:100:in `top_level'
/home/avdi/.rvm/gems/ruby-1.9.3-p327/gems/rake-10.1.0/lib/rake/application.rb:78:in `block in run'
/home/avdi/.rvm/gems/ruby-1.9.3-p327/gems/rake-10.1.0/lib/rake/application.rb:165:in `standard_exception_handling'
/home/avdi/.rvm/gems/ruby-1.9.3-p327/gems/rake-10.1.0/lib/rake/application.rb:75:in `run'
/home/avdi/.rvm/gems/ruby-1.9.3-p327/gems/rake-10.1.0/bin/rake:33:in `<top (required)>'
/home/avdi/.rvm/gems/ruby-1.9.3-p327/bin/rake:23:in `load'
/home/avdi/.rvm/gems/ruby-1.9.3-p327/bin/rake:23:in `<main>'
Tasks: TOP => default => html

さて、ルールを再び動くようする時です。メソッドsource_for_htmlを定義することから始めます。 このメソッドは、HTMLファイルの名前を引数にとり、対応するMarkdownファイルの名前を返します。そのように動作させるには、ソースファイルのリストへのアクセスが必要です。 今のところ、リストはローカル変数で、このメソッド内からはアクセスできないため、定数に変更します。


そして、 拡張子を除いた名前が、与えられたHTMLファイル名に一致する最初のソースファイルをソースファイルのリストから探します。 拡張子を除いた名前だけで比較するために、#extメソッドを再び使用しています。 HTML出力ファイル名のリストを得るためにこのメソッドをソースファイルのリストに使用したことを、あなたは覚えているかもしれません。今回は、ファイル拡張子を完全に取り除くために、空の文字列を#extに渡しています。

 
def source_for_html(html_file)
  SOURCE_FILES.detect{|f| f.ext('') == html_file.ext('')}
end

あなたが、“ちょっと待てよ!”というのが聞こえます。“前は、#ext メッセージをFileList に送ったけど、ここではそれぞれのファイル名文字列に送っているじゃないか!どうしてそれが動くんだ?”


ここから次のことがわかります。Rakeは、FileListがサポートしているのと同じメソッドのいくつかをサポートするようRubyのStringクラスを変更しているので、FileListと個々のファイル名が交換可能なものとして同じ操作ができます。


HTMLファイル名を与えると、それを生成するのに必要なソースのMarkdownファイルを探すことができるメソッドがあるので、このメソッドを使う.htmlルールを作る必要があります。

ルールの中の.md依存関係をラムダに置き換えることにより、これを実現します。ラムダの中で、1つの引数をとり#source_for_htmlメソッドに渡します。 Rakeは、.htmlファイルのビルドを試みる時、ターゲットファイルの名前を、事前条件として与えたラムダに引き渡します。 そして、このラムダの戻り値が、存在するファイルに一致するかどうか確認します。もし一致すれば、ルールが一致したと考え、関連するコードの実行に進みます。

 
rule ".html" => ->(f){source_for_html(f)} do |t|
  sh "pandoc -o #{t.name} #{t.source}"
end

まだルールのトレースを有効にしているので、 変更されたルールを使ってRakeがどのように判断しているか、ウインドウに出力されます。 ch4.htmlターゲットにたどり着くと、依存関係がch4.mdではなく ch4.markdownであると、正しく決定します。そのファイルを見つけて、 ch4.htmlファイルをビルドします。

 
$ rake
Attempting Rule ch1.html => ch1.md
(ch1.html => ch1.md ... EXIST)
Attempting Rule ch2.html => ch2.md
(ch2.html => ch2.md ... EXIST)
Attempting Rule ch3.html => ch3.md
(ch3.html => ch3.md ... EXIST)
Attempting Rule subdir/appendix.html => subdir/appendix.md
(subdir/appendix.html => subdir/appendix.md ... EXIST)
Attempting Rule ch4.html => ch4.markdown
(ch4.html => ch4.markdown ... EXIST)
pandoc -o ch4.html ch4.markdown

これで、長い拡張子・短い拡張子のいずれのMarkdownファイルからもHTMLファイルをビルドする一般的なルールができました。しかし、もっと大切なことは、 何をどのようにビルドするかを決定するルールが、Rakeでどのように動作するかについて、より多くの知見を得たことです。 ハッピーハッキング!


ちなみに、これが最終的なRakefileになります。

 
Rake.application.options.trace_rules = true

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" => ->(f){source_for_html(f)} do |t|
  sh "pandoc -o #{t.name} #{t.source}"
end

def source_for_html(html_file)
  SOURCE_FILES.detect{|f| f.ext('') == html_file.ext('')}
end



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

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

関連する投稿:
  1. Rake Part 1: Files and Rules
  2. Rake Part 2: File Lists
  3. Double-Load Guards in Ruby