前置き
- 社内勉強会のLTで発表したら好評だったので投下
- 自称YAMLエンジニアのsue445が今まで踏んだ罠をクイズにしました
- Ruby 3.0.0の Psych で動作確認していますが他言語での挙動は調べていません
- Psychがlibyamlベースなので他の言語のパーサでもだいたい同じ挙動をすると思うけど
練習問題
Q: 出力されるものは?
yaml = <<YAML a: 1 YAML YAML.load(yaml) #=> ?
{"a"=>"1"}
{"a"=>1}
- シンタックスエラー
- その他
回答
2
YAMLの数字っぽい文字列はその言語の数字の型(Rubyだと Integer
)として解釈されます。
文字列として解釈させたい場合は "1"
(ダブルクオーテーション)や '1'
(シングルクォーテーション)のように囲んでください
問題1
Q: 出力されるものは?
yaml = <<YAML splash: ふたりはプリキュア Splash Star yes: Yes!プリキュア5 yes_gogo: Yes!プリキュア5GoGo fresh: フレッシュプリキュア! YAML data = YAML.load(yaml) "#{data['splash']},#{data['yes']},#{data['yes_gogo']},#{data['fresh']}" #=> ?
"ふたりはプリキュア Splash Star,Yes!プリキュア5,Yes!プリキュア5GoGo,フレッシュプリキュア!"
"ふたりはプリキュア Splash Star,,Yes!プリキュア5GoGo,フレッシュプリキュア!"
",Yes!プリキュア5,Yes!プリキュア5GoGo,フレッシュプリキュア!"
- シンタックスエラー
回答
2
true
, false
, yes
, no
, on
, off
はダブルクオーテーションなどで囲まない限りYAMLでは全て 真偽値 として扱われます
data = YAML.load(yaml) => {"splash"=>"ふたりはプリキュア Splash Star", true=>"Yes!プリキュア5GoGo!", "yes_gogo"=>"Yes!プリキュア5GoGo", "fresh"=>"フレッシュプリキュア!"}
data = YAML.load("{1: yes, 2: Yes, 3: on, 4: On, 5: true, 6: True}") #=> {1=>true, 2=>true, 3=>true, 4=>true, 5=>true, 6=>true} data = YAML.load("{1: no, 2: No, 3: off, 4: Off, 5: false, 6: False}") #=> {1=>false, 2=>false, 3=>false, 4=>false, 5=>false, 6=>false}
問題2
Q: 出力されるものは?
yaml = <<YAML default: &default slack: webhook_url: "https://example.com/" channel: "random" production: <<: *default slack: channel: "production_notify" YAML data = YAML.load(yaml) data["production"] #=> ?
{"slack"=>{"webhook_url"=>"https://example.com/", "channel"=>"random"}}
{"slack"=>{"webhook_url"=>"https://example.com/", "channel"=>"production_notify"}}
{"slack"=>{"channel"=>"production_notify"}}
- シンタックスエラー
回答
3
&
(アンカー)と *
(エイリアス)で定義済みの値をいい感じに共通化できるのはYAMLのよくあるリファクタリング手法ですが、<<:
(マージ)はdeep merge(要素内に別の要素があった時に再帰的にマージされる)ではなく第1要素だけを上書きするマージ(代入に近い)なので、今回の場合 default
の内容が打ち消されます。
イメージ的にはこんな感じ
data["slack"] = {"webhook_url"=>"https://example.com/", "channel"=>"random"} data["slack"] = {"channel"=>"production_notify"}
余談ですが https://github.com/railsware/global はdeep mergeしてくれるのがかなり便利で、前職のRailsアプリにはだいたい入っていました
問題3
Q: 出力されるものは?
yaml = <<YAML go: - 1.9 - 1.10 - 1.11 - 1.12 YAML data = YAML.load(yaml) data["go"] #=> ?
[1.9, 1.10, 1.11, 1.12]
[1.9, 1.1, 1.11, 1.12]
[1.90, 1.10, 1.11, 1.12]
- シンタックスエラー
回答
2
クオーテーションで囲んでいないので小数として解釈されるため 1.10
は 1.1
として解釈されます
厳密に 1.10
として評価するには "1.10"
のように囲む必要があります。
余談ですがGo 1.10が出た時に .travis.yml
に 1.10
を追加したらGo 1.1でCIが実行されてビルドが失敗したことがあります。
https://github.com/sue445/zatsu_monitor/commit/fc6b8ab806a3c48617cb084437d2d1c018777ee9#comments
問題4
Q: この中でシンタックスエラーになるのはどれか?
a
excludes: - *_test.rb
b
excludes: - test/**
回答
1
a
yaml = <<YAML excludes: - *_test.rb YAML YAML.load(yaml) Traceback (most recent call last): 8: from /Users/sue445/.rbenv/versions/3.0.0/bin/irb:23:in `<main>' 7: from /Users/sue445/.rbenv/versions/3.0.0/bin/irb:23:in `load' 6: from /Users/sue445/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/irb-1.3.0/exe/irb:11:in `<top (required)>' 5: from (irb):62:in `<main>' 4: from /Users/sue445/.rbenv/versions/3.0.0/lib/ruby/3.0.0/psych.rb:280:in `load' 3: from /Users/sue445/.rbenv/versions/3.0.0/lib/ruby/3.0.0/psych.rb:390:in `parse' 2: from /Users/sue445/.rbenv/versions/3.0.0/lib/ruby/3.0.0/psych.rb:456:in `parse_stream' 1: from /Users/sue445/.rbenv/versions/3.0.0/lib/ruby/3.0.0/psych.rb:456:in `parse' Psych::SyntaxError ((<unknown>): did not find expected alphabetic or numeric character while scanning an alias at line 2 column 3)
b
yaml = <<YAML excludes: - test/** YAML YAML.load(yaml) #=> {"excludes"=>["test/**"]}
*_test.rb
の *
が前述のエイリアスとして評価されるのですが、対応する _test.rb
という名前のアンカーが存在しないためシンタックスエラーになります。
シンタックスエラーにしないためには "*_test.rb"
のように囲む必要があります。