Ruby2.4のoptparseでHashにパース結果をマッピングするintoという便利オプションを調べた
表題の通りなんですが
上記のページの「Parse CLI options into a Hash」の項目を見ると、以下のように parse
メソッドに :into
でハッシュオブジェクトを渡すと自動でマッピングしてくれるという地味に便利な機能が追加された。
上記のURLから抜粋
require 'optparse' require 'optparse/date' require 'optparse/uri' cli = OptionParser.new do |options| options.define '--from=DATE', Date options.define '--url=ENDPOINT', URI options.define '--names=LIST', Array end config = {} args = %w[ --from 2016-02-03 --url https://blog.blockscore.com/ --names John,Daniel,Delmer ] cli.parse(args, into: config) config.keys # => [:from, :url, :names] config[:from] # => #<Date: 2016-02-03 ((2457422j,0s,0n),+0s,2299161j)> config[:url] # => #<URI::HTTPS https://blog.blockscore.com/> config[:names] # => ["John", "Daniel", "Delmer"]
:into
で便利になるのはわかるものの、このサンプルを見ると define
って何だろう、とか短いオプション名(-u
とか)も指定した場合どうなるの?ってところが色々と疑問だったので調べてみた。
define is 何
情弱なのでリファレンスマニュアルに載ってないAPIだから define
を使ったオプションの定義したことがなかった(実は載っているのかと思ってドキドキしたけどやっぱり載ってない)。
というわけでRubyのソースを確認して見るとこんな感じ
https://github.com/ruby/ruby/blob/v2_4_2/lib/optparse.rb#L1465-L1505
抜粋すると以下のように、いつも使っている on
メソッドが内部で呼び出しているメソッドなんですね。
def define(*opts, &block) top.append(*(sw = make_switch(opts, block))) sw[0] end def on(*opts, &block) define(*opts, &block) self end
ソース内の雰囲気からすると、 on
は公開用で define
は内部で使う用っぽい気がするけど*1、普通に呼び出せるしどっちでも良いのかもしれない。まあ、今まで通り on
系を使えば良いかなって気がしますね……。
オプション名 is 何
割り当てるオプション名ってどう決まるのか。
options.on '-a, '--argument=VAL'
みたいに定義した時にどっちが使われるのかなという話でもありますし、オプション名に -
が含まれていた場合どうなるのか(ネタバレすると:"foo-bar"
みたいになる)など疑問が湧きますよね。
これを知るにはざっくり :into
オプションの流れを追う必要があるのですが、結論を先に書いておくと
- 短いオプション名と長いオプション名があった場合長いオプション名が使用される
- オプション名の先頭ハイフンを取り除いた文字列を
.to_sym
した値になる
という動作のようです。
プログラム的には
- ここで
:into
があった場合の動作が定義されていて、オプション名を.to_sym
している - ハッシュの名前としては
switch_name
の値が使われて switch_name
は長い名前を優先して使用する
という流れのようですね。
まとめ
OptionParser.new do |options| options.on '-r', '--ruby=VAL' do |val| val.downcase end options.on '-p', '--perl=VAL' do |val| val.upcase end options.on '-g', '--go-lang' do 'go' end options.on '-d' do true end options.on_tail '-h', '--help', 'hi' end
こんな定義をした場合、 into
オプションでHashを渡した場合は
{ ruby: 'ruby', perl: 'PERL', 'go-lang': 'go', d: true }
こんな値が得られます。便利ですね。
サンプルソースコードとテストケースは以下にあります(主に test/unit
を使ってみたかっただけw)。
https://github.com/sugamasao/optparser_ruby24_into_sample
*1:実際のところはどうなんでしょうか?