DSL by JRuby at JavaOne2012 JVM language BoF #jt12_b101
-
Upload
takeshi-akima -
Category
Technology
-
view
898 -
download
2
description
Transcript of DSL by JRuby at JavaOne2012 JVM language BoF #jt12_b101
階層化JavaプロパティファイルDSL作成 JRuby編2012/04/04 akimatter
12年4月5日木曜日
自己紹介秋間武志
@akimatter
Java10年、Ruby6年
Ruby Business Commons運営委員
株式会社グルーヴノーツテクニカルプロデューサー/プログラマ
12年4月5日木曜日
要件compiler.error.message.varNotFound=...compiler.error.message.incompatibleType=...compiler.error.maxReport=compiler.files.input.encoding=compiler.files.output.encoding=
compiler { error { message { varNotFound= incompatibleType= } } maxReport= files { input.encoding= output.encoding= }}
のように擬似的に階層的に書けると、より読みやすくなることが考えられる。このような、プロパティファイルのキーを階層構造で記述できるDSLを作成する事とする。
12年4月5日木曜日
cascading_properties
名前重要
ライブラリ名 cascading_properties
モジュール名 CascadingProperties
12年4月5日木曜日
ノードの名前はメソッド
アンダースコアがついているPropertiesのキーの一部となる部分は、Rubyではメソッドとして書ける。
compiler { error { message { varNotFound "variable not found" incompatibleType "incompatible type" } } maxReport 10 files { input.encoding "SJIS" output.encoding "UTF-8" }}
12年4月5日木曜日
specはこんな感じ valid_source1 = <<-EOS compiler { error { message { varNotFound "variable not found" incompatibleType "incompatible type" } } maxReport 10 files { input.encoding "SJIS" output.encoding "UTF-8" } } EOS
subject{ CascadingProperties.load(valid_source1) }
it "should export to java.util.Properties" do props = subject.to_properties props.should be_a(java.util.Properties) props.size().should == 5 props.getProperty("compiler.error.message.varNotFound" ).should == "variable not found" props.getProperty("compiler.error.message.incompatibleType").should == "incompatible type" props.getProperty("compiler.maxReport").should == '10' props.getProperty("compiler.files.input.encoding").should == 'SJIS' props.getProperty("compiler.files.output.encoding").should == 'UTF-8'end
12年4月5日木曜日
設計
12年4月5日木曜日
オブジェクトの関連
compiler { error { message { varNotFound "variable not found" incompatibleType "incompatible type" } } maxReport 10 files { input.encoding "SJIS" output.encoding "UTF-8" }}
ツリー構造にマッピングできそう
12年4月5日木曜日
Object#method_missingメソッド
オブジェクトに定義されていないメソッドが呼び出された際に呼び出されるフックメソッド。
呼び出されたメソッド名とそれに渡された引数とブロックが、method_missingの引数とブロックとして渡される。
12年4月5日木曜日
メソッドのコンテキストcompiler {
}
error {
} maxReport 10 files {
}
message {
}
input.encoding "SJIS"output.encoding "UTF-8"
varNotFound "variable not found"incompatibleType "incompatible type"
メソッドはその外側のノードをselfとするコンテキストで呼び出される
12年4月5日木曜日
BlankSlateObjectクラスには予めたくさんのメソッドが定義されているので、多くの語彙をmethod_missingで扱えるようにするためには、既存のメソッドができるだけ定義されていないクラスを使う。このようなクラスをBlankSlateと呼ぶ。
class CascadingProperties::BlankSlate used = %w[instance_eval send class to_s hash inspect respond_to? should] regexp = Regexp.new("/^__|^=|^\%|", used.map{|m| "^#{m}$"}.join('|') + '/') instance_methods.each { |m| undef_method m unless m =~ regexp }end
定義時にused以外のメソッドをundef_methodして無かったことにしている。
12年4月5日木曜日
クラスの構成
12年4月5日木曜日
もっとも重要な部分
引数の数やブロックの有無で振る舞いが異なる
class CascadingProperties::Node < CascadingProperties::BlankSlate def method_missing(name, *args, &block) name = name.to_s case args.length when 0 then @_hash[name] ||= CascadingProperties::Node.new(&block) when 1 then @_hash[name] = args.first else @_hash[name] = args end endend
12年4月5日木曜日
java.util.Propetiesへの出力
class CascadingProperties::Node < CascadingProperties::BlankSlate def to_properties(dest = nil, parent = nil) dest ||= java.util.Properties.new @_hash.each do |k, v| key = parent ? "#{parent}.#{k}" : k if v.respond_to?(:to_properties) v.to_properties(dest, key) else dest.setProperty(key, v.to_s) end end dest endend
12年4月5日木曜日
java.util.Propetiesへの出力
def to_properties(dest = nil, parent = nil) dest ||= java.util.Properties.new @_hash.each do |k, v| key = parent ? "#{parent}.#{k}" : k if v.respond_to?(:to_properties) v.to_properties(dest, key) else dest.setProperty(key, v.to_s) end end dest end
引数destはデフォルトではnil。jruby上で動く場合ならdestはjava.util.Propertiesを
生成する
12年4月5日木曜日
java.util.Propetiesへの出力
def to_properties(dest = nil, parent = nil) dest ||= java.util.Properties.new @_hash.each do |k, v| key = parent ? "#{parent}.#{k}" : k if v.respond_to?(:to_properties) v.to_properties(dest, key) else dest.setProperty(key, v.to_s) end end dest end
値がto_properties可能ならそれを呼び出した結果を、そうでないのなら値を文字列として 、destに
setPropertyする
12年4月5日木曜日
java.util.Propetiesも拡張できるrequire 'java'require 'cascading_properties'
java.util.Properties.instance_eval do def load_dsl(dsl_text) node = CascadingProperties.load(dsl_text) node.to_properties endend
12年4月5日木曜日
おまけ
12年4月5日木曜日
設定ファイルでERBを
ERBを使うことで、動的な値を設定ファイルに記述すること
も可能。
compiler { error { message { varNotFound "variable not found" incompatibleType "incompatible type" } } maxReport <%= ENV['MAX_REPORT'] %> files { input.encoding "SJIS" output.encoding "UTF-8" }}
12年4月5日木曜日
簡単にERBも使えるように def load_file(filepath) load(File.read(filepath)) end
def load_file(filepath) erb = ERB.new(IO.read(filepath)) erb.filename = filepath text = erb.result load(text) end
12年4月5日木曜日
ソースコード
https://github.com/akm/cascading_properties
rspecでテストも書いてあるよ
12年4月5日木曜日