予習 Rails3 (2) Thor

さて、2回目の今回は Thor。北欧神話で言うと最強らしい。
http://ja.wikipedia.org/wiki/%E3%83%88%E3%83%BC%E3%83%AB

でも Rails3 で使うのはこっち。
http://github.com/wycats/thor

Ruby の Thor は平たく言えばコマンドライン操作に対して、より簡便なオプション解釈の手段を提供してくれるDSLだ。個人的には Sake に似ているなーという印象。オプション解釈と聞いて GNU の getopt とか getopt_long といったライブラリが思い浮かぶ人もいるかもしれないが、Thor ができるのはもうちょっと広範囲に及ぶ。

Thor はgemで簡単にインストールできる。

$ sudo gem install thor

渡せるオプションは以下の形式。

:boolean - is parsed as --option or --option=true
:string - is parsed as --option=VALUE
:numeric - is parsed as --option=N
:array - is parsed as --option=one two three
:hash - is parsed as --option=name:string age:integer

では実際に例を。マニュアルから引用。使い方としては Thor クラスを継承することに始まる。

class App < Thor                                                 # [1]
  map "-L" => :list                                              # [2]

  desc "install APP_NAME", "install one of the available apps"   # [3]
  method_options :force => :boolean, :alias => :string           # [4]
  def install(name)
    user_alias = options[:alias]
    if options.force?
      # do something
    end
    # other code
  end

  desc "list [SEARCH]", "list all of the available apps, limited by SEARCH"
  def list(search="")
    # list everything
  end
end

このスクリプトを app.thor として保存し、同一ディレクトリで

$ thor list

もしくは

$ thor -T

とすると

$ thor -T
app
---
/usr/local/ruby_1_9_2/bin/thor app:install APP_NAME  # install one of the available apps
/usr/local/ruby_1_9_2/bin/thor app:list [SEARCH]     # list all of the available apps, limited by SEARCH

このようにタスクと、そのデスクリプションが表示される。なお上記のソースでは install、list、force、aliasの4つのオプションを定義している。そのため、

$ thor app:install test --force --alias=useralias

もしくは

$ thor app:list search_string

といった指定が可能になる。なお、システム全体でタスクを共有したい場合

# thor install app.thor

とすることで app.thor をどのディレクトリにいても利用できるようになる。なお、タスクのインストールはリモートURLからも可能だ。一度インストールした後にタスクに変更を加えた場合は、

# thor update app.thor

などとしてシステムにインストールされたタスクのアップデートを行う。
ところで、Thor には Thor::Group というクラスがあり、定義されたメソッドすべてを一度に実行可能だ。

class Counter < Thor::Group
  desc "Prints 1, 2, 3"

  def one
    puts 1
  end

  def two
    puts 2
  end

  def three
    puts 3
  end
end

これを counter.thor として保存してある場合、

$ thor counter

としてクラス名のみをthorコマンドに渡すことで結果「1 2 3」を得られる。
あと、Thor のマニュアルの最後には簡単なジェネレーターのサンプルがあるんだけど、Rails3 のジェネレータを理解するのに役立つだろう。

class Newgem < Thor::Group
  include Thor::Actions

  # Define arguments and options
  argument :name
  class_option :test_framework, :default => :test_unit

  def self.source_root
    File.dirname(__FILE__)
  end

  def create_lib_file
    template('templates/newgem.tt', "#{name}/lib/#{name}.rb")
  end

  def create_test_file
    test = options[:test_framework] == "rspec" ? :spec : :test
    create_file "#{name}/#{test}/#{name}_#{test}.rb"
  end

  def copy_licence
    if yes?("Use MIT license?")
      # Make a copy of the MITLICENSE file at the source root
      copy_file "MITLICENSE", "#{name}/MITLICENSE"
    else
      say "Shame on you…", :red
    end
  end
end

このようにかなりよくできているモジュールなので、Rails を使う以外に簡単なコマンドラインアプリケーションやバッチ処理などでも Thor を使うことでその開発がぐっと楽になるハズ。なお、ここで紹介した以外にももっとたくさんの機能があるのでドキュメントを見てほしい。