YAMLで配列をマージし、ruby経由でロードしたい-
some_stuff: &some_stuff
- a
- b
- c
combined_stuff:
<<: *some_stuff
- d
- e
- f
私は結合された配列を次のようにしたいのですが [a,b,c,d,e,f]
エラーを受け取ります:ブロックマッピングの解析中に予期したキーが見つかりませんでした
YAMLで配列をマージするにはどうすればよいですか?
YAMLで配列をマージし、ruby経由でロードしたい-
some_stuff: &some_stuff
- a
- b
- c
combined_stuff:
<<: *some_stuff
- d
- e
- f
私は結合された配列を次のようにしたいのですが [a,b,c,d,e,f]
エラーを受け取ります:ブロックマッピングの解析中に予期したキーが見つかりませんでした
YAMLで配列をマージするにはどうすればよいですか?
回答:
シェルコマンドのシーケンスを実行することが目的である場合は、次のようにしてこれを実現できる場合があります。
# note: no dash before commands
some_stuff: &some_stuff |-
a
b
c
combined_stuff:
- *some_stuff
- d
- e
- f
これは次と同等です。
some_stuff: "a\nb\nc"
combined_stuff:
- "a\nb\nc"
- d
- e
- f
私はこれを使っています gitlab-ci.yml
(質問に対する@ rink.attendant.6コメントに回答するため)。
requirements.txt
gitlabからのプライベートリポジトリのサポートに使用する作業例:
.pip_git: &pip_git
- git config --global url."https://gitlab-ci-token:${CI_JOB_TOKEN}@gitlab.com".insteadOf "ssh://git@gitlab.com"
- mkdir -p ~/.ssh
- chmod 700 ~/.ssh
- echo "$SSH_KNOWN_HOSTS" > ~/.ssh/known_hosts
- chmod 644 ~/.ssh/known_hosts
test:
image: python:3.7.3
stage: test
script:
- *pip_git
- pip install -q -r requirements_test.txt
- python -m unittest discover tests
use the same `*pip_git` on e.g. build image...
どこrequirements_test.txt
に
-e git+ssh://git@gitlab.com/example/example.git@v0.2.2#egg=example
この投稿は、次のコンテキストを前提としています。
lfender6445は、YAMLファイル内の2つ以上のリストをマージし、それらのマージされたリストを解析時に1つの特異なリストとして表示させたいと考えています。
これは、YAMLアンカーをマッピングに割り当てるだけで取得できます。この場合、必要なリストがマッピングの子要素として表示されます。ただし、これには注意が必要です(下記の「落とし穴」を参照)。
以下の例では、3つのマッピング(list_one, list_two, list_three
)と、これらのマッピングを適切に参照する3つのアンカーとエイリアスがあります。
YAMLファイルがプログラムに読み込まれると、必要なリストが取得されますが、読み込み後に少し変更が必要になる場合があります(以下の落とし穴を参照)。
list_one:&id001 -a -b -c list_two:&id002 -e -f -g list_three:&id003 -時間 - 私 -j list_combined: -* id001 -* id002 -* id003
## list_combined [ [ "a"、 "b"、 「c」 ]、 [ "e"、 "f"、 「g」 ]、 [ "h"、 "私"、 「j」 ] ]
このアプローチでは、YAMLのエイリアスとアンカー機能を使用して、マージされたリストを作成できます。
出力結果はリストのネストされたリストですが、これはflatten
メソッドを使用して簡単に変換できます。
flatten
方法の例flatten
;; 配列の配列をマージ/フラット化するflatten
;; http://ruby-doc.org/core-2.2.2/Array.html#method-i-flattenflatten
;; https://softwareengineering.stackexchange.com/a/254676/23884これはうまくいきません:
マージはYAML仕様でのみサポートされており、シーケンスではサポートされていません
あなたは<<
マージキー:
とそれに続くキー/値のセパレータと参照である値を持ち、同じインデントレベルでリストを続けることで完全に混合しています
これは正しいYAMLではありません。
combine_stuff:
x: 1
- a
- b
したがって、あなたの構文例はYAML拡張の提案としては意味がありません。
複数の配列をマージするようなことをしたい場合は、次のような構文を検討する必要があります。
combined_stuff:
- <<: *s1, *s2
- <<: *s3
- d
- e
- f
どこにs1
、s2
、s3
あなたが新しい順に併合してから持ちたいというシーケンス上のアンカー(図示せず)されd
、e
そしてf
それに追加。しかし、YAMLはこれらの種類の構造の深さを最初に解決するため、マージキーの処理中に利用できる実際のコンテキストはありません。処理された値(アンカーされたシーケンス)をアタッチできる配列/リストはありません。
@dreftymacで提案されているアプローチを取ることができますが、これには、どのネストされたシーケンスをフラット化するかを知る必要があるという大きな欠点があります(つまり、ロードされたデータ構造のルートから親シーケンスへの「パス」を知ることによって)。または、ネストされた配列/リストを検索してロードされたデータ構造を再帰的にウォークし、それらすべてを無差別にフラット化します。
IMOのより良い解決策は、タグを使用してデータ構造をロードし、フラット化を行うことです。これにより、フラット化が必要なものとそうでないものを明確に示すことができ、このフラット化がロード中に行われるか、アクセス中に行われるかを完全に制御できます。どちらを選択するかは、実装の容易さと時間とストレージスペースの効率の問題です。これは、同じトレードオフのニーズが実現するためになされるべきことであるマージキーの機能を、常に最善である単一の解決策がありません。
たとえば、私のruamel.yaml
ライブラリは、セーフローダーを使用している場合、ロード時にブルートフォースのマージディクテーションを使用します。これにより、通常のPythonディクトである辞書がマージされます。このマージは事前に行う必要があり、データを複製しますが(スペース効率が悪い)、値の検索は高速です。往復ローダーを使用する場合、マージをマージせずにダンプできるようにしたいので、それらを別々に保つ必要があります。往復読み込みの結果として読み込まれるデータ構造のようなdictは、スペース効率は良いですが、マージでdict自体にないキーを検索する必要があるため、アクセスが遅くなります(これはキャッシュされないため、毎回行う必要があります)。もちろん、このような考慮事項は、比較的小さな構成ファイルではあまり重要ではありません。
以下flatten
は、リストとタグ付けされた項目にオンザフライで再帰するタグ付きオブジェクトを使用して、Pythonのリストのマージのようなスキームを実装しますtoflatten
。これらの2つのタグを使用して、YAMLファイルを作成できます。
l1: &x1 !toflatten
- 1
- 2
l2: &x2
- 3
- 4
m1: !flatten
- *x1
- *x2
- [5, 6]
- !toflatten [7, 8]
(フローvsブロックスタイルシーケンスの使用は完全に任意であり、ロードされた結果には影響しません)。
keyの値であるアイテムを反復する場合、これはでm1
タグ付けされたシーケンスに「再帰」しtoflatten
ますが、他のリスト(エイリアスの有無にかかわらず)を単一のアイテムとして表示します。
これを実現するためのPythonコードの可能な方法の1つは次のとおりです。
import sys
from pathlib import Path
import ruamel.yaml
yaml = ruamel.yaml.YAML()
@yaml.register_class
class Flatten(list):
yaml_tag = u'!flatten'
def __init__(self, *args):
self.items = args
@classmethod
def from_yaml(cls, constructor, node):
x = cls(*constructor.construct_sequence(node, deep=True))
return x
def __iter__(self):
for item in self.items:
if isinstance(item, ToFlatten):
for nested_item in item:
yield nested_item
else:
yield item
@yaml.register_class
class ToFlatten(list):
yaml_tag = u'!toflatten'
@classmethod
def from_yaml(cls, constructor, node):
x = cls(constructor.construct_sequence(node, deep=True))
return x
data = yaml.load(Path('input.yaml'))
for item in data['m1']:
print(item)
出力:
1
2
[3, 4]
[5, 6]
7
8
ご覧のとおり、フラット化が必要なシーケンスでは、タグ付きシーケンスのエイリアスを使用するか、タグ付きシーケンスを使用できます。YAMLでは次のことはできません。
- !flatten *x2
つまり、アンカーされたシーケンスにタグを付けます。これにより、本質的に異なるデータ構造になります。
明示的なタグを使用することは、YAMLマージキーのように魔法をかけるよりもIMOの方が優れています<<
。他に何もない場合、マージキーの<<
ように動作させたくないキーを持つマッピングを持つYAMLファイルがある場合は、フープを通過する必要
があります。たとえば、C演算子をその説明にマッピングする場合英語(または他の自然言語)。
次の条件下で、マッピングをマージしてから、それらのキーをリストに変換できます。
some_stuff: &some_stuff
a:
b:
c:
combined_stuff:
<<: *some_stuff
d:
e:
f:
{{ combined_stuff | list }}