scriptライブラリでRubyスクリプトそのものをオブジェクトにする!
scriptというライブラリがある。これは、Rubyスクリプトファイルをオブジェクトとして扱うものだ。
gem化されてないのでインストールは手作業で。
wget http://redshift.sourceforge.net/script/script-0.3.tgz cd script-0.3 ruby install.rb config ruby install.rb setup sudo ruby install.rb install
ふつうにloadやrequireでRubyスクリプトを読み込んだらグローバルな名前空間を汚染してしまうので、時として困ることがある。そこでscriptを使えば、特定のモジュール(に毛が生えたやつ)でスクリプトを実行してくれる。おまけに実行後の状態にアクセスすることもできるぞ。
require 'script' EXTERN = "/tmp/hoge.rb" open(EXTERN, "w"){|f| f.write <<END_OF_EXTERN_SCRIPT } FOO = 7 def hoge_meth :hoge end @foo = :iv lv = :local END_OF_EXTERN_SCRIPT # スクリプトファイルをオブジェクトとして扱う。 s = Script.load EXTERN # => #<Script:/tmp/hoge.rb> # ファイル名 s.__main_file # => "/tmp/hoge.rb" # 定数参照 s::FOO # => 7 # メソッド呼び出し s.hoge_meth # => :hoge # インスタンス変数参照 s.instance_variable_get :@foo # => :iv # ローカル変数のリスト s.__local_variables # => ["__file__", "lv"] # ローカル変数を得る s.__local_variable_get :lv # => :local # Moduleを継承している Script.ancestors # => [Script, Module, Object, Kernel]
Rubyスクリプトを設定ファイルとして使うときには便利だろうね。と思ったのでやってみた。
Script.to_structを定義すれば外部スクリプトのローカル変数の集合を構造体として得ることができる。
require 'script' def Script.to_struct(file) s = Script.load file lvs = s.__local_variables.map {|e| e.to_sym } Struct.new(*lvs).new.tap do |struct| lvs.each {|lv| struct[lv] = s.__local_variable_get(lv) } end end EXTERN = "/tmp/vars.rb" open(EXTERN, "w"){|f| f.write <<END_OF_EXTERN_SCRIPT } name = "rubikitch" site = "http://www.rubyist.net/~rubikitch/" blog = "http://d.hatena.ne.jp/rubikitch/" END_OF_EXTERN_SCRIPT # スクリプト中のローカル変数の集合を構造体として得る s = Script.to_struct EXTERN # => #<struct # __file__="/tmp/vars.rb", # name="rubikitch", # site="http://www.rubyist.net/~rubikitch/", # blog="http://d.hatena.ne.jp/rubikitch/"> # 構造体はアクセサとしてもハッシュ形式でもアクセスできるから萌え s.name # => "rubikitch" s[:name] # => "rubikitch" s["name"] # => "rubikitch" s.site # => "http://www.rubyist.net/~rubikitch/" s.blog # => "http://d.hatena.ne.jp/rubikitch/"
スクリプト中のインスタンス変数を[]でアクセスできるようにしてみた。
ただ、これだと未定義の場合にnilが返るのでタイプミスしたときに検出されないな。
構造体にした方が安全だろう。
require 'script' class Script def [](var) instance_variable_get("@#{var}") end end EXTERN = "/tmp/vars.rb" open(EXTERN, "w"){|f| f.write <<END_OF_EXTERN_SCRIPT } @name = "rubikitch" @site = "http://www.rubyist.net/~rubikitch/" @blog = "http://d.hatena.ne.jp/rubikitch/" END_OF_EXTERN_SCRIPT s = Script.load(EXTERN) # => #<Script:/tmp/vars.rb> s[:name] # => "rubikitch" s["name"] # => "rubikitch" s[:site] # => "http://www.rubyist.net/~rubikitch/" s[:blog] # => "http://d.hatena.ne.jp/rubikitch/" s[:undefined] # => nil
追記
名前空間、、なんだかさわやかな、 たとえばしゃぼんだまに名前が書いてあるみたいな、
う〜ん…どうなんだろ。
名前空間は名前とそれが指すものの対応を表すもの…と考えておくとよい。
身近な例では、人の名前だ。こんなケースを考えてみよう。
- 1組に一人「山田」さんがいる。(山田1)
- 2組にも一人「山田」さんがいる。(山田2)
1組で「山田さん」と言ったら山田1のことを指すよね。「1組のメンバー」という名前空間で「山田」という名前で表される人は山田1しかいないので特定できる。で、1組で山田2を表したいときは「『2組の』山田さん」と言わなきゃならない。
変数だとこんなケースになる。「a」と名前がついている変数はそれぞれ別物になっている。それぞれ別の名前空間にある変数だから。メソッド内では個別のローカル変数の名前空間が用意される。
def meth1 a = 2 end def meth2 a = 3 end
クラス名(定数名)でも。*1で「HTTP」と書くとNet::HTTPのことだし、*2で書くとURI::HTTPになる。*3だとフルネームでNet::HTTPやURI::HTTPと書かないといけない。*3でHTTPと書くと「HTTPなんて定数はねえぞ」と怒られる。
module Net class HTTP; end # *1 end module URI class HTTP; end # *2 end # *3