Rubyでプログラムを作成している際に、プログラムのオプションを以下の2つのソースから取得したい場合があります。
- JSONファイル
- コマンドラインオプション
こういう場合に、よく使う実装の方法を共有します。
基本的なコンセプト
- オプションは一つのオブジェクトに集める。
例:class Options
- オブジェクト生成時に、JSONファイルからオプションを読み込む。
例:opt = Options.new('some.json')
とするとJSONファイルからオプションを読み込む。 - オプションにアクセスするときはオブジェクトの accessor を使う。
例:opt.option_1
のような感じでアクセスできる。 - コマンドライン引数は OptionParser を使ってハッシュに保存しする。
- オプションのオブジェクトは、ハッシュから値を取り込むメソッドを持つ。
これによりコマンドライン引数から生成した設定値のハッシュ取り込める。
例:opt.import_hash(options)
のような感じで使える。
実装例
#!/usr/bin/env ruby
require "json"
class Options
# list of supported keys.
TEMPLATE_SAMPLE = { 'option_1' => 'Bool', 'option_2' => 'String' }
def initialize(filename = "config.json", template = TEMPLATE_SAMPLE)
@template = template
File.exist?(filename) and File.open(filename) do |j|
import_hash(JSON.load(j))
end
end
def import_hash(hash)
return unless hash.is_a?(Hash)
@template.keys.each do |key|
add_variable(key, hash[key]) if hash.has_key?(key)
end
end
private def add_variable(name, value)
if (@template[name].downcase == 'bool')
# 'bool' type needs special handling in ruby.
raise "value is not bool" if (!!value != value)
else
raise "value is not #{@template[name]}" unless (value.is_a?(eval(@template[name])))
end
# crate instance variable.
instance_variable_set("@#{name}", value)
# add getter method.
self.class.send(:attr_reader, name) unless respond_to?(name)
end
end
if $0 == __FILE__
require 'optparse'
opt = Options.new('config.json')
option = {}
arg = OptionParser.new
arg.on('-1 OPTION', '--option_1=OPTION', 'Set option 1') { |v| option['option_1'] = v.eql?("true") }
arg.on('-2 OPTION', '--option_2=OPTION', 'Set option 2') { |v| option['option_2'] = v }
arg.parse!(ARGV)
opt.import_hash(option)
p opt.option_1
p opt.option_2
end
JSONファイルはこんな感じです。
{
"option_1": true,
"option_2": "test"
}
補足
- 定数
TEMPLATE_SAMPLE
が、オプションのテンプレートになっていて、名前と想定する型の組を定義しています。 - モジュールとして再利用できるようにテンプレートは初期化の引数として指定することもできるようにしてあります。
- Ruby では Bool 型はありませんが、ないと不便なので
Bool
キーワードが型として含まれる場合の処理は個別に書いています。