tag:crieit.net,2005:https://crieit.net/tags/YAML/feed
「YAML」の記事 - Crieit
Crieitでタグ「YAML」に投稿された最近の記事
2023-05-01T01:32:11+09:00
https://crieit.net/tags/YAML/feed
tag:crieit.net,2005:PublicArticle/18426
2023-05-01T01:32:11+09:00
2023-05-01T01:32:11+09:00
https://crieit.net/posts/YAML
YAML の細かい仕様の話
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">グワーッ!YAML 1.2 って、 yes,no,on,off 系が Boolean リテラルではなくなる破壊的変更あるん!?Docker Compose v2.17.0 で内部パーサが go-yaml/yaml.v3 に更新され、真偽値を yes, no で書いてた compose.yml ファイルが軒並み must be a boolean エラー出まくってて死。<a target="_blank" rel="nofollow noopener" href="https://t.co/PVpdIWFVgA">https://t.co/PVpdIWFVgA</a></p>— ながいの as a サービス (@longer_n) <a target="_blank" rel="nofollow noopener" href="https://twitter.com/longer_n/status/1646154607201075207?ref_src=twsrc%5Etfw">April 12, 2023</a></blockquote>
<p>趣味アカウントのほうでバズった… ってほどでは無いけど、割と反響があったので、 YAML に関する話をいくつか。</p>
<h2 id="YAML 1.2 では Boolean として扱うのは true, false だけ"><a href="#YAML+1.2+%E3%81%A7%E3%81%AF+Boolean+%E3%81%A8%E3%81%97%E3%81%A6%E6%89%B1%E3%81%86%E3%81%AE%E3%81%AF+true%2C+false+%E3%81%A0%E3%81%91">YAML 1.2 では Boolean として扱うのは true, false だけ</a></h2>
<p><a target="_blank" rel="nofollow noopener" href="https://yaml.org/type/bool.html">https://yaml.org/type/bool.html</a></p>
<p>YAML 1.1 では 以下の文字が Boolean として認識されるのが一般的だ。<br />
<code>y|Y|yes|Yes|YES|n|N|no|No|NO|true|True|TRUE|false|False|FALSE|on|On|ON|off|Off|OFF</code></p>
<p>厳密には、 <code>!!bool</code> タグを使って Boolean 型を表現する場合の表記方法だが、基本的には多くのパーサーではタグを書かなくても Boolean 型として扱われる。</p>
<p><a target="_blank" rel="nofollow noopener" href="https://yaml.org/spec/1.2.0/#id2602744">https://yaml.org/spec/1.2.0/#id2602744</a></p>
<p>しかし YAML 1.2 以降ではこれが、 <code>true</code>, <code>false</code> のみが Boolean として認識されるという、だいぶデカい破壊的変更が入っている。</p>
<p>ここで、 1.2.0 の主たる更新点を見てみると…<br />
<a target="_blank" rel="nofollow noopener" href="https://yaml.org/spec/1.2.2/ext/changes/#changes-in-version-12-revision-120-2009-07-21">https://yaml.org/spec/1.2.2/ext/changes/#changes-in-version-12-revision-120-2009-07-21</a></p>
<blockquote>
<h4 id="Changes in version 1.2 (revision 1.2.0) (2009-07-21)"><a href="#Changes+in+version+1.2+%28revision+1.2.0%29+%282009-07-21%29">Changes in version 1.2 (revision 1.2.0) (2009-07-21)</a></h4>
<p>[...]</p>
<ul>
<li>Only true and false strings are parsed as booleans (including True and TRUE); y, yes, on, and their negative counterparts are parsed as strings.</li>
<li>Underlines _ cannot be used within numerical values.</li>
<li>Octal values need a 0o prefix; e.g. 010 is now parsed with the value 10 rather than 8.</li>
<li>The binary and sexagesimal integer formats have been dropped.</li>
<li>The !!pairs, !!omap, !!set, !!timestamp and !!binary types have been dropped.</li>
<li>The merge</li>
</ul>
</blockquote>
<p>参考日本語訳:</p>
<blockquote>
<ul>
<li>true と false の文字列のみがブーリアン(TrueとTRUEを含む)として解析される。 y, yes, on およびそれらの否定語は文字列として解析される。</li>
<li>アンダーライン _ は、数値の中では使用できません。</li>
<li>8進数の値には0oのプレフィックスが必要です。例えば010は8ではなく10という値で解析されるようになりました。</li>
<li>二進法と十進法の整数形式は廃止されました。</li>
<li>ペア、オマップ、セット、タイムスタンプ、バイナリ型は削除されました。</li>
<li>merge</li>
</ul>
</blockquote>
<p>うーん、現代の <a target="_blank" rel="nofollow noopener" href="https://semver.org/lang/ja/">セマンティック バージョニング</a> だったら、絶対にマイナー場ジョンアップじゃダメなヤツ。</p>
<p>こういった仕様のため、 <code>yes</code>, <code>no</code> と書いた際にどう扱われるかは、パーサーの実装による。</p>
<p>例えば、 PyYAML などは Boolean リテラルとして扱われるが、 go-yaml/yaml.v3 では文字列として扱われる。</p>
<p>内部パーサーの更新でこの解釈が変わってしまったために発生したのが、冒頭の問題だ。</p>
<p>Ansible などでは、真偽値を yes, no と書く慣習があるせいで、もうめちゃくちゃあっちこっちで <code>yes</code>, <code>no</code> って書いてるわ…</p>
<p>なお、冒頭の Docker Compose については、 Docker Compose version v2.17.3 で <code>yes</code> 等を書いてもエラーにせずワーニングだけ出すように修正された。<br />
<a target="_blank" rel="nofollow noopener" href="https://github.com/docker/compose/issues/10465">[BUG] Error "must be a boolean" with <code>yes</code> scalar in Docker Compose v2.17.0 · Issue #10465 · docker/compose</a></p>
<hr />
<h2 id="">" は全ての改行をスペースに置き換えるわけではない"><a href="#%26quot%3B%26gt%3B%26quot%3B+%E3%81%AF%E5%85%A8%E3%81%A6%E3%81%AE%E6%94%B9%E8%A1%8C%E3%82%92%E3%82%B9%E3%83%9A%E3%83%BC%E3%82%B9%E3%81%AB%E7%BD%AE%E3%81%8D%E6%8F%9B%E3%81%88%E3%82%8B%E3%82%8F%E3%81%91%E3%81%A7%E3%81%AF%E3%81%AA%E3%81%84">">" は全ての改行をスペースに置き換えるわけではない</a></h2>
<p>ブロックスタイルの文字列表記で、 <code>">"</code> インジゲータを置くと、次の行から途中の改行が半角スペースに置き換えられると説明されがちだ。</p>
<pre><code class="yaml">foo: >
trimmed
line
</code></pre>
<p>しかし、半角スペースに置き換えられる条件はもう少し複雑だ。</p>
<p><a target="_blank" rel="nofollow noopener" href="https://yaml.org/spec/1.2.2/#line-folding">https://yaml.org/spec/1.2.2/#line-folding</a></p>
<p>この Line Folding と言うのがこれに当たるのだが、これは長い行を折りたたんで読みやすくするための機能だ。</p>
<p>折りたたまれた部分(改行)が半角スペース (0x20) に変換されるものだが、</p>
<ul>
<li>複数の改行が続く場合は最初の改行が破棄され、残りは保持される</li>
<li>先頭の空白を含むテキスト行(つまりインデントされた行)は、行の折り返しは適用されない。</li>
</ul>
<p>という仕様がある。</p>
<p>このため、以下のように動作する。</p>
<p>YAML 入力:</p>
<pre><code class="yaml">foo: >
trimmed
line
breaking
but blank line and
nested
line
are are retained.
</code></pre>
<p>JSON 出力:</p>
<pre><code class="json">{ "foo": "trimmed line breaking\nbut blank line and\n nested\n line\nare are retained.\n" }
</code></pre>
<p>つまり、「改行を消す」というよりは、「次の行のインデントの高さがブロックの先頭と同じ場合のみ、行の折りたたみと見なして改行を削除する」という動作となる。</p>
<hr />
<h2 id="マージキー (<<) によるマージは 1回 しか記述できない。"><a href="#%E3%83%9E%E3%83%BC%E3%82%B8%E3%82%AD%E3%83%BC+%28%26lt%3B%26lt%3B%29+%E3%81%AB%E3%82%88%E3%82%8B%E3%83%9E%E3%83%BC%E3%82%B8%E3%81%AF+1%E5%9B%9E+%E3%81%97%E3%81%8B%E8%A8%98%E8%BF%B0%E3%81%A7%E3%81%8D%E3%81%AA%E3%81%84%E3%80%82">マージキー (<<) によるマージは 1回 しか記述できない。</a></h2>
<p>複数のマップをマージしたい場合は、 シーケンス(配列)によって指定する。</p>
<p><a target="_blank" rel="nofollow noopener" href="https://yaml.org/type/merge.html">https://yaml.org/type/merge.html</a></p>
<p>YAML 入力:</p>
<pre><code class="yaml">-
- &ANCHER1 { '0': 0, a: 0, b: 0 }
- &ANCHER2 { b: 99, c: 99 }
- &ANCHER3 { c: 255, d: 255 }
#- # Eror
# '0': 1
# << : *ANCHER1
# << : *ANCHER2
# << : *ANCHER3
# a: 1
- # Single map
b: 1
<< : *ANCHER2
- # Override multiple maps
'0': 1
<< :
- *ANCHER1
- *ANCHER2
- *ANCHER3
a: 1
</code></pre>
<p>JSON 出力:</p>
<pre><code class="json">[
[
{ "a": 0, "b": 0 },
{ "b": 99, "c": 99 },
{ "c": 255, "d": 255 }
],
{ "b": 1, "c": 99 },
{ "0": 1, "a": 1, "b": 0, "c": 99, "d": 255 }
]
</code></pre>
<p>シーケンスで複数のマップを指定した場合で、キーが重複している物は、シーケンス内では順序が前のものが常に優先される。<br />
一方で、マージキーで指定したモノよりは、マージ先のものが優先となる。</p>
<p>ただ、パーサーによっては マージキー (<code><<</code>) を複数書く誤った書き方に対応しているモノがあり、<br />
実際、 Docker Compose v2.17.0 で、内部パーサーが <code>go-yaml/v3</code> に切り替わったときに、誤った書き方が対応から未対応に変わって、以下のようなエラーが出てハマった。</p>
<pre><code>mapping key "<<" already defined at line nnn
</code></pre>
<p>[[BUG]using multiple yaml alias not working. mapping key "</p>
<p>もちろんこれは、マージキーを複数書く方が悪いので、 YAML の方を直す必要がある。</p>
<p>例: https://go.dev/play/p/jos7jc38loK<br />
--></p>
advanceboy