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="&quot;&gt;&quot; は全ての改行をスペースに置き換えるわけではない"><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="マージキー (&lt;&lt;) によるマージは 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 # &lt;&lt; : *ANCHER1 # &lt;&lt; : *ANCHER2 # &lt;&lt; : *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