ClassX::Commandableで簡単CLIアプリ

東京Ruby会議の懇親会で一番反響が良かったのはClassX::Commandableだったということでブログでもあたらめて紹介しておく。

MooseにはMooseX::GetoptとかMooseX::App::Cmdという便利なモジュールがあったのでMooseX::GetoptをパクってClassX::Commandableというのを作ったという話。

require 'classx'

$ClassXCommandableMappingOf[Symbol] = String

class YourApp 
  include ClassX
  extend ClassX::Commandable

  has :arg1, 
    :kind_of => Symbol, 
    :desc => 'please specify arg1',
    :coerce => { String => proc {|val| val.to_sym } }

  has :arg2,
    :kind_of => Integer,
    :desc => "this is arg2",
    :optional => true

  def run
    # do something!!
    p attribute_of
  end
end

if $0 == __FILE__
  YourApp.from_argv.run
end

↑のように書いて実行すると、

yoshimi% ruby example/commandable.rb                                                                                                                    [ ~/Sources/ruby/classx ] 
example/commandable.rb [options]
    -a, --arg1 String                please specify arg1
        --arg2 [Integer]             this is arg2
    -h, --help                       show this document

のようにヘルプが出てくる。

ヘルプに書いてあるようにこのプログラムを実行するためには--arg1は必須。

yoshimi% ruby example/commandable.rb -a hoge                                                                                                            [ ~/Sources/ruby/classx ] 
{"arg1"=><#ClassX::Attribute {:kind_of=>Symbol, :coerce=>{String=>#<Proc:0x00363300@example/commandable.rb:13>}, :desc=>"please specify arg1"}:1662200 @data=:hoge >, "arg2"=><#ClassX::Attribute {:kind_of=>Integer, :desc=>"this is arg2", :optional=>true}:1662120  >}

のように指定すると実行される。

yoshimi% ruby example/commandable.rb -a hoge --arg2 fuga                                                                                                [ ~/Sources/ruby/classx ] 
example/commandable.rb [options]
    -a, --arg1 String                please specify arg1
        --arg2 [Integer]             this is arg2
    -h, --help                       show this document

マニュアルによると --arg2はIntegerだが、文字列を指定しているのでヘルプが出る。

yoshimi% ruby example/commandable.rb -a hoge --arg2 10                                                                                                  [ ~/Sources/ruby/classx ] 
{"arg1"=><#ClassX::Attribute {:kind_of=>Symbol, :coerce=>{String=>#<Proc:0x00363684@example/commandable.rb:13>}, :desc=>"please specify arg1"}:1662280 @data=:hoge >, "arg2"=><#ClassX::Attribute {:kind_of=>Integer, :desc=>"this is arg2", :optional=>true}:1662200 @data=10 >}

こんどはちゃんと数字を指定しているのでおk

ところでarg1の定義のところで

  has :arg1, 
    :kind_of => Symbol, 
    :desc => 'please specify arg1',
    :coerce => { String => proc {|val| val.to_sym } }

Symbolと指定しているのにどうやって変換しているかというと、

まずこのattributeにcoerceオプションを渡してStringで引数を渡した場合にSymbolに変換できるようにしておく。

そのあと、

$ClassXCommandableMappingOf[Symbol] = String

のように$ClassXCommandableMappingOfというグローバル変数にattributeの型名がSymbolの場合はコマンドラインでは文字列を取るように指定してあげる。

これで文字列で受けとる → Symbolで変換 というのを実現している。

グローバル変数というのがどうなのかなと思いつつもっといい仕様があれば教えていただけると嬉しいです。

ちなみにショートオプションは会議後に角谷さんから「ショートオプションサポートしてほしい」といわれてつけたのでHEADにしか入ってないです。

さきほど0.0.3をリリースしたのでそれに入っています。