Thorってなんなんだろう。

Rails3.0で利用されているThorだけど、まだあまり情報ってないようにおもう。
一番詳しいのは、多分リンク先の記事です。(2010/3/13時点)

Thorってなんなの?yet another rakeなの?

ただ、僕が前の記事であげたgeneratorの仕組みを解読するでは、
railsコマンドをたたいたときにthorが呼ばれてるとことか結構奥深いかんじ。
なので、ちょっとthorを勉強したことをまとめときます。

Thorの開発者のブログを和訳してみる

Thorの開発者はMerbやRails,jQuery等きっとRails開発者が日々お世話になっているであろうYehuda Katzさんです。
彼はThorについてブログで以下のように言及しています。(誤訳とか当然のようにあります)

Title: Thorで作ろう!

この数ヶ月、私はますますRubyのDSLが物足りなくなってきている。
もちろん問題解決についてはできるんだけど、それらは強固なフルスタックなものではない。

そこでthorを使うといいと思う。

thorのアイデアはtextmateのインストールされたバンドルの管理をしてるときに思いついた。
もちろんgetbundleやsubversionから取得できるんだけど、私はそれら全てを一つの方法で取得したいと思った。

バンドルをインストールしている間、私はインストールするときに利用するコマンドをマップしようとした。
class_cliと呼ばれるtextmateのバイナリを含んだ小さいファイルを作成してさらにそれらをマッピングする文法を記述した。
(これは最終的な文法とはちょっと違うけど)

class MyApp
  include CLI

  desc "list [list]", "list some items"
  def list(list = "stuff")
    puts list.split(/,\s*/).join("\n")
  end
end

MyApp.start

バイナリがappを呼ぶことを前提とし、app listとして"one two three"と表示させたい。
私はappにlist, install, installed, uninstallというメソッドを作った。

これはいいなと思い、他のツールからも利用できるように抜き出した。
私は最初github上で"Hermes"というプロジェクトで作り始めたんだけどChris Wanstrathと飲んでいるときに、
Thorっていう名前の方がよりいいと感じたんだ。アルコールの効果なんだけどね。
さらに私は二つの点について納得した。(何回も言うけどちょっと酔っぱらってたんだ)

 ・rakeとsakeはmakeの代わりではなく、スクリプトとして必要とされている 
 ・MyApp.startのような特別な宣言をしなくてはいけないとすると、Thorって誰にも使ってもらえないんじゃないか

つまるところ、Chris Wanstrathはthorが完全なスクリプト言語にすべきだよといい、私は実際にそうすることにしたんだ。

そうして現状のThorは二つのコンポーネントを持つものになった。

 ・CLI/Hermesのような共通基盤部分はThorというクラスをsuperclassにもつことで機能を利用することが出来る(class MyApp < Thorみたいな)
 ・Thor runnerという別サーバやローカルディレクトリ内でThortaskを動作できるものを提供する

Thor runnerは次のように定義できるよ。

# module: random

class Amazing < Thor
  desc "describe NAME", "say that someone is amazing"
  method_options :forcefully => :boolean
  def describe(name, opts)
    ret = "#{name} is amazing"
    puts opts["forcefully"] ? ret.upcase : ret
  end

  desc "hello", "say hello"
  def hello
    puts "Hello"
  end
end

あなたが*.thorファイルを作って、今のディレクトリからおくようにすると次のように利用可能なんだ。
(thorファイルは現在のディレクトリかtasks/以下におくようにすると動くよ)

$ thor -T
Tasks
        • -
amazing:describe NAME [--forcefully] say that someone is amazing amazing:hello say hello $ thor amazing:hello Hello $ thor amazing:describe "This blog reader" This blog reader is amazing $ thor amazing:describe "This blog reader" --forcefully THIS BLOG READER IS AMAZING You can also install local tasks or remote tasks to your system thor cache and make them available anywhere: $ thor install task.thor Your Thorfile contains: # module: random class Amazing < Thor desc "describe NAME", "say that someone is amazing" method_options :forcefully => :boolean def describe(name, opts) ret = "#{name} is amazing" puts opts["forcefully"] ? ret.upcase : ret end desc "hello", "say hello" def hello puts "Hello" end end Do you wish to continue [y/N]? y Storing thor file in your system repository $ thor installed Name Modules random amazing Tasks
        • -
amazing:describe NAME [--forcefully] say that someone is amazing amazing:hello say hello $ thor amazing:hello Hello ... same as above ... thorはファイル名の代わりにsakeのようにURLを利用することも可能なんだ。(open-uriを使ってファイル取りにいってるからね) moduleの更新やアンインストールについては次のように名前規約のようなものが利用されるんだ。 ファイルの先頭に"#module name"のような記述があればそれをデフォルトとして利用する。 それ以外でも、次のように使えるんだ。 thor install task.thor --as my_short_name もしそれらがなければthorは名前をあなたに確認するよ。 それから thor update short_nameで更新しようとするしthor uninstall short_nameでリストから削除する。 もちろん、thor -T(or thor list)で登録されているリストを出力する。だからthortaskを別のツールで管理しなくてもいいし、 ささいなシステムタスクなんかはそこに入れてしまえばいいんだ。 まとめると、thorは自身をホスティングする。 thor runnerはthorクラスをsuperclassとして利用する。その結果、Thorクラスにはいくつかの特徴を追加しているよ。  ・あなたはshort_nameからfull_nameをしることが出来る thor name:map -T これがthor -Tとthor listの違うとこだよ  ・Hash形式でオプションを追加することができるよ(:as => :required)   利用できるオプションは:required,:optional, :boolean。これらのオプションは最終的にあなたのメソッドに渡される。   さらにそれらのusageも出力されるよ。 くわしい情報はgithubを見てね。

本ブログでrakeと違うとこはと聞かれて以下のように答えている。

rakeとの主な違いは記述方式と、コマンドラインオプションです。
rakeを利用すると基本的なコマンドラインオプションを用いて数多くのタスクを定義することが出来ます。

Thorはあなたに構造を与え、かつすばらしいコマンドラインオプションを与えます。(質問の幅が広がります)
これらはRailsやMerbにとっておおきな恩恵を与えるでしょう。

またsakeと一緒でリモートホストから利用できるのも違う点です。

Thorのサンプルを作ってみる

# -*- coding: utf-8 -*-
      
class SampleApp < Thor
  # desc has to have two arguments.
  # First, you have to decide task_name, second argumet is explation of task.
  desc "display", "sample thor task"
  def display
    puts "hello, thor!"
  end 
end

以下のように呼べる。
installするとどこでも呼べるようになる。

bash-3.2$ thor -T
sample_app
                  • -
thor sample_app:display # sample thor task bash-3.2$ cd .. bash-3.2$ thor -T No Thor tasks available bash-3.2$ cd thor/ bash-3.2$ thor -T sample_app
                  • -
thor sample_app:display # sample thor task bash-3.2$ thor install sample.thor Your Thorfile contains: # -*- coding: utf-8 -*- class SampleApp < Thor # desc has to have two arguments. # First, you have to decide task_name, second argumet is explation of task. desc "display", "sample thor task" def display puts "hello, thor!" end end Do you wish to continue [y/N]? y Please specify a name for sample.thor in the system repository [sample.thor]: Storing thor file in your system repository bash-3.2$ cd .. bash-3.2$ thor -T sample_app
                  • -
thor sample_app:display # sample thor task bash-3.2$