RubyのオブジェクトをYAML.dumpしてYAMLの設定ファイルを作るというコードを書いているときに、掲題の挙動に気がついた。
通常、複数行が含まれる文字列をYAML.dumpすると |-
を使った表記になる。
$ ruby -ryaml -e 'puts YAML.dump("foo" => "a\nb")' --- foo: |- a b
しかしながら改行の前にスペースを置くと |-
は使われずダブルクォートで囲われた表記になった。
$ ruby -ryaml -e 'puts YAML.dump("foo" => "a \nb")' --- foo: "a \nb"
どちらの場合もYAMLとしてvalidなのだけど*1、ダブルクォートで囲われている場合ダンプした文字列が長すぎて改行された状態になることがあり*2、その状態のYAMLをうまくパースできない処理系があるっぽい?*3が詳しくは未検証です🙏
Ruby以外の言語ではどうなのか?
というのが気になったのでPython、Node.js、Rust、Goで調べてみた。
Python
Python 3.11.3を使用した。
$ python Python 3.11.3 (main, Apr 7 2023, 11:00:05) [GCC 12.2.0] on linux Type "help", "copyright", "credits" or "license" for more information. >>> import yaml >>> print(yaml.dump({"foo": "a \nb"})) foo: "a \nb" >>> print(yaml.dump({"foo": "a\nb"})) foo: 'a b'
|-
を使っていないが改行がエスケープされているように見える。
Node.js(js-yaml)
Node.js v14.19.0を使用した。標準機能ではYAMLを扱うことができなそうだったので、"Node.js YAML"で検索して最初にできてたjs-yamlを利用した。
$ node Welcome to Node.js v14.19.0. Type ".help" for more information. > const yaml = require('js-yaml') undefined > console.log(yaml.dump({"foo": "a\nb"})) foo: |- a b undefined > console.log(yaml.dump({"foo": "a \nb"})) foo: |- a b undefined
エスケープされず差がなかった。
Rust
Rust playgroundを使い執筆時点のStableバージョンである1.71.0を使った。 Rustも標準機能ではYAMLが使えないのでserde_yamlを利用したが、どのバージョンが使われているかは確認していない。
コード
use std::collections::BTreeMap; fn main() -> Result<(), serde_yaml::Error> { let mut m1 = BTreeMap::new(); m1.insert("foo", "a\nb"); println!("{}", serde_yaml::to_string(&m1)?); let mut m2 = BTreeMap::new(); m2.insert("foo", "a \nb"); println!("{}", serde_yaml::to_string(&m2)?); Ok(()) }
出力
--- foo: "a\nb" --- foo: "a \nb"
playgroundのリンクはこちら↓ https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=cc48f996998c4a0cec9424c1a793d113
出力結果を見るとエスケープされていないように見える。
また、serde_yamlは|-
を使わない実装になっていることがわかる。
Go
The Go Playgroundを使い執筆時点で最新の1.20を使った。
コード
package main import ( "fmt" "gopkg.in/yaml.v3" ) func main() { m := map[string]string{} m["foo"] = "a\nb" d, _ := yaml.Marshal(m) fmt.Printf("%s", string(d)) m = map[string]string{} m["foo"] = "a \nb" d, _ = yaml.Marshal(m) fmt.Printf("%s", string(d)) }
出力
foo: |- a b foo: "a \nb"
playgroundのリンクはこちら↓ https://go.dev/play/p/Ylvd8kP4Bjg
出力結果はRubyと同じ挙動のようにみえる。
各言語での比較まとめ
- エスケープされる: Ruby, Python, Go
- エスケープされない: Node.JS(js-yaml), Rust(serde_yaml)
言語、ライブラリによって差が出るという興味深い結果になった。 この変数(オブジェクト)をYAMLでシリアライズするときの仕様はどこかにあるのだろうか? それとも参考実装があるのだろうか?