optparse は便利なんだけど、ぱっと使うには少し機能が重たく感じるときがあった。
で、機能削って使うときに簡単なやつを書いてみた。(実装は optparse に依存しまくり。)
使い方はこんなかんじ。
# 1. フラグ扱い(指定されていれば true)したければ :act_as_flag 指定 # 2. ショートネームを独自に指定したければ :short_names 指定 # 3. 数字は整数型扱い # 4. 少数は浮動小数点型扱い # 5. ただし 0 から始まる数値は文字列扱い # 6. 他は文字列扱い # 7. 省略されたオプションをトラックしたいときはブロック渡してその中で適当に処理 # 8. すべてのオプションはデフォルトで省略可 require 'lib/optparselet' # 一番単純な使い方 parser = OptionParserlet.new([:ability, :bicycle, :cycle]) argv = %w{ -a foo -b bar -c baz } parser.parse(argv) # => {:ability => "foo", :bicycle => "bar", :cycle => "baz"} # 1. フラグ扱い(指定されていれば true)したければ :act_as_flag 指定 parser = OptionParserlet.new([:ability, :bicycle, :cycle], :act_as_flag => [:cycle]) argv = %w{ -a foo -b bar -c } parser.parse(argv) # => {:ability => "foo", :bicycle => "bar", :cycle => true} # 2. ショートネームを独自に指定したければ :short_names 指定 parser = OptionParserlet.new([:ability, :bicycle, :cycle], :short_names => [:r, :e, :m]) argv = %w{ -a foo -b bar -c baz } # Long Name の短縮も使える parser.parse(argv) # => {:ability => "foo", :bicycle => "bar", :cycle => "baz"} argv = %w{ -r foo -e bar -m baz } # 当然、Short Name も使える parser.parse(argv) # => {:ability => "foo", :bicycle => "bar", :cycle => "baz"} # 3. 数字は整数型扱い # 4. 少数は浮動小数点型扱い # 5. ただし 0 から始まる数値は文字列扱い parser = OptionParserlet.new([:ability, :bicycle, :cycle]) argv = %w{ -a 23 -b 43.150 -c 0123 } parser.parse(argv) # => {:ability => 23, :bicycle => 43.15, :cycle => "0123"} # 7. 省略されたオプションをトラックしたいときはブロック渡してその中で適当に処理 parser = OptionParserlet.new([:ability, :bicycle, :cycle]) argv = %w{ -a foo -b bar } # -c を省略して呼出し options = parser.parse(argv) do |options| options[:cycle] = "default value" end options # => {:ability => "foo", :bicycle => "bar", :cycle => "default value"} # 8. すべてのオプションはデフォルトで省略可 parser.parse(argv) # => {:ability => "foo", :bicycle => "bar"} (エラーにならない)
オプションの取り回しが野暮ったくて拡張性がつぶされてるとか、実装で修正したいところはあるけど、とりあえず公開。
てことで、コードはこちら。
require 'optparse' class OptionParserlet def self.parse(argv, names, options = {}, &block) new(names, options).parse(argv, &block) end def parse(argv, &block) argv = ['--help'] if argv.size.zero? options = @options names = @names params = {} @parser = OptionParser.new do |opts| opts.banner = options[:help] || options[:banner] || "Usage: #{File.basename($0)} [options]" if options[:short_names] define_option_with_short_name( name_pair(names, options[:short_names]), opts, options, params ) else define_option(names, opts, options, params) end opts.parse(argv) end block.call(params) if block params end def initialize(names, options = {}) @names = names @options = options end private def define_option(names, optparse, options, params) names.each do |id| if flag_option?(id, options) optparse.on("--#{id.to_s}") {|f| params[id] = f } else optparse.on("--#{id.to_s} [val]") {|v| params[id] = parse_val(v) } end end end def define_option_with_short_name(names, optparse, options, params) names.each_pair do |id, short_name| if flag_option?(id, options) optparse.on("-#{short_name.to_s}", "--#{id.to_s}") {|f| params[id] = f } else optparse.on("-#{short_name.to_s}", "--#{id.to_s} [val]") {|v| params[id] = parse_val(v) } end end end def flag_option?(id, options) options[:act_as_flag] && options[:act_as_flag].include?(id) end def name_pair(long_names, short_names) alist = [long_names, short_names].transpose Hash[*alist.flatten] end def parse_val(str) case str when /\A0(?:\d+\.\d+|\d+)\z/ str.to_s when /\A\d+\z/ str.to_i when /\A\d+\.\d+\z/ str.to_f else str.to_s end end end
ショートカットでクラスメソッドを定義したから、実際はそっちだけ使ってれば十分かと。