【Ruby】YAMLにローカル変数を埋め込む

こんにちは、しきゆらです。
前回から2か月ほどたっていて驚いている今日この頃、いかがお過ごしでしょうか。

今回は、ちょっと前に書いた「【Ruby】YAMLに環境変数を埋め込む」で任意の文字列を環境変数に置換する方法をメモしましたが、そのちょっと発展した話です。
タイトル通り、YAMLにローカル変数を埋め込むにはどうすればよいかなぁ、と思って調べてみたら何とかなったのでちょっとメモしておきます。


前回の方法ふりかえり

前回の「【Ruby】YAMLに環境変数を埋め込む」で行った方法は、以下の通り。

  • 環境変数を置換するためのマーカーを決める
    • ここでは「${variable}」としています
  • マーカーを拾うことができる正規表現を作る
  • マーカーをもとにマッチした部分を環境変数に置き換える
  • マーカーを環境変数に置き換えたYAML文字列をパースしてオブジェクトへ変換する

いかに過去のコードをそのまま載せておきます。

# YAMLファイルを文字列として読み込み
yaml_str = File.read("path/to/yaml.yml")
# ${ENV_VARIABLE}形式で埋め込んだ環境変数を置換する
yaml_str.gsub!(/\${\w*}/) do |matched|
  ENV[matched.gsub(/\W*/, "")]
end
 
# YAML文字列をオブジェクトに変換
YAML.safe_load(yaml_str, symbolize_names: true)

このコードの中で、マーカーを正規表現でマッチさせる部分はそのままに
環境変数に置き換える部分をローカル変数に差し替えるにはどうすればいいかな、というのが今回の話です。

ローカル変数を取得するには

Rubyは調べてみるとなんでもできますね。
今回の、コードの中のローカル変数を取得する方法はないかなぁと思って調べたら、組み込みライブラリにありました。

class Binding
ローカル変数のテーブルと self、モジュールのネストなどの情報を保持するオブジェクトのクラスです。

https://docs.ruby-lang.org/ja/latest/class/Binding.html

まさにほしい機能ですね。
こいつを使って実現していきましょう。

ローカル変数を取得するコード

Bindingクラスのメソッドにある「local_variable_get」というメソッドを使って、
そのタイミングで定義されているローカル変数を取得することができます。
ちょうど、「instance_variable_get」のローカル変数版なイメージです。

こいつと、先ほどの環境変数を置換するコードをマージすると以下のような感じ。

def test(yaml_str, binding)
  yaml_str.gsub!(/\${\w+}/) do |match|
    # 環境変数を埋め込むところを、ローカル変数に置き換えただけ
    binding.local_variable_get(match.gsub(/\W*/, "").to_sym)
  end
  
  YAML.safe_load(yaml_str, symbolize_names: true)
end

はい、まんま環境変数置換のコードをローカル変数置換に置き換えただけです。
第2引数のbindingは、Bindingクラスのインスタンスです。

実際に動かすとこんな感じ。

irb(main):001:0> require "yaml"
=> true
irb(main):002:1* def test(yaml_str, binding)
irb(main):003:2*    yaml_str.gsub!(/\${\w+}/) do |match|
irb(main):004:2*      # 環境変数を埋め込むところを、ローカル変数に置き換えただけ
irb(main):005:2*      binding.local_variable_get(match.gsub(/\W*/, "").to_sym)
irb(main):006:1*    end
irb(main):007:1*
irb(main):008:1*    YAML.safe_load(yaml_str, symbolize_names: true)
irb(main):009:0> end
=> :test
irb(main):010:0> yaml_str = "token: ${token_string}"
=> "token: ${token_string}"
irb(main):011:0> token_string = "laksjlkasjlkajsdlkjasdkljalkdjaslad"
=> "laksjlkasjlkajsdlkjasdkljalkdjaslad"
irb(main):012:0> token_string
=> "laksjlkasjlkajsdlkjasdkljalkdjaslad"
irb(main):013:0> bd = binding
=> #<Binding:0x00007f91399b6780>
irb(main):014:0> test(yaml_str, bd)
=> {:token=>"laksjlkasjlkajsdlkjasdkljalkdjaslad"}

呼び出す前にBindingクラスのインスタンスを生成して食わせないといけないので、面倒といえば面倒ですが
これでひとまずローカル変数をYAMLに埋め込むことができそうです。
YAML文字列をファイルから読み込み、マーカーを正規表現で見つけつつローカル変数に置換する、という形です。

正規表現がある程度書ければ、マーカー部分は任意に置き換え可能なので
自分なりに書きやすい形でYAMLに埋め込むことができます。
頑張れば実装できる、を地で行っただけのコードですね。はい。

まとめ

今回は、以前書いた「【Ruby】YAMLに環境変数を埋め込む」をもとに、ローカル変数を埋め込むにはどうすればいいかを調べつつ、ちょっと直して実現したお話でした。

今回は、ここまで。
おわり。

Ruby

Posted by shikiyura