Czy istnieje składnia YAML do udostępniania części listy lub mapy?

96

Więc wiem, że mogę zrobić coś takiego:

sitelist: &sites
  - www.foo.com
  - www.bar.com

anotherlist: *sites

I mają sitelisti anotherlistoba zawierają www.foo.comi www.bar.com. Jednak naprawdę chcę, anotherlistaby zawierał równieżwww.baz.com , bez konieczności powtarzania www.foo.comi www.baz.com.

W ten sposób otrzymuję błąd składni w parserze YAML:

sitelist: &sites
  - www.foo.com
  - www.bar.com

anotherlist: *sites
  - www.baz.com

Samo użycie kotwic i aliasów wydaje się niemożliwe, aby zrobić to, co chcę, bez dodania kolejnego poziomu podstruktury, na przykład:

sitelist: &sites
  - www.foo.com
  - www.bar.com

anotherlist:
  - *sites
  - www.baz.com

Co oznacza, że ​​konsument tego pliku YAML musi być tego świadomy.

Czy istnieje czysty sposób YAML na zrobienie czegoś takiego? A może będę musiał użyć przetwarzania post-YAML, takiego jak zaimplementowanie zastępowania zmiennych lub automatyczne podnoszenie niektórych rodzajów podstruktury? Robię już tego rodzaju przetwarzanie końcowe, aby obsłużyć kilka innych przypadków użycia, więc nie jestem do tego całkowicie przeciwny. Ale moje pliki YAML będą pisane przez ludzi, a nie generowane maszynowo, więc poza standardową składnią YAML chciałbym zminimalizować liczbę reguł, które muszą być zapamiętane przez moich użytkowników.

Chciałbym również móc zrobić analogiczną rzecz z mapami:

namedsites: &sites
  Foo: www.foo.com
  Bar: www.bar.com

moresites: *sites
  Baz: www.baz.com

Przeszukałem specyfikację YAML i nie mogłem niczego znaleźć, więc podejrzewam, że odpowiedź brzmi „nie, nie możesz tego zrobić”. Ale jeśli ktoś ma jakieś pomysły, byłoby świetnie.


EDYCJA: Ponieważ nie było odpowiedzi, przypuszczam, że nikt nie zauważył niczego, czego nie mam w specyfikacji YAML i że nie można tego zrobić w warstwie YAML. Otwieram więc pytanie od pomysłu na przetwarzanie końcowe YAML, aby pomóc w tym, na wypadek, gdyby ktoś znalazł to pytanie w przyszłości.

Ben
źródło
Uwaga: ten problem można również rozwiązać za pomocą standardowego użycia kotwic i aliasów w YAML. Zobacz też: Jak scalać tablice YAML?
dreftymac

Odpowiedzi:

53

Typ klucza scalania jest prawdopodobnie tym, czego potrzebujesz. Używa specjalnego <<klucza mapowania do wskazywania połączeń, umożliwiając użycie aliasu do mapowania (lub sekwencji takich aliasów) jako inicjatora do scalenia w jedno mapowanie. Ponadto nadal możesz jawnie zastąpić wartości lub dodać więcej, których nie było na liście scalania.

Należy zauważyć, że jako pierwszy przykład działa to z odwzorowaniami, a nie sekwencjami. Ma to sens, kiedy się nad tym zastanowić, a Twój przykład wygląda na to, że prawdopodobnie i tak nie musi być sekwencyjny. Po prostu zmiana wartości sekwencji na klucze mapujące powinna załatwić sprawę, jak w następującym (nieprzetestowanym) przykładzie:

sitelist: &sites
  ? www.foo.com  # "www.foo.com" is the key, the value is null
  ? www.bar.com

anotherlist:
  << : *sites    # merge *sites into this mapping
  ? www.baz.com  # add extra stuff

Kilka rzeczy do zauważenia. Po pierwsze, ponieważ <<jest to klucz, można go określić tylko raz na węzeł. Po drugie, gdy jako wartość używamy sekwencji, kolejność jest znacząca. W tym przykładzie nie ma to znaczenia, ponieważ nie ma powiązanych wartości, ale warto o tym pamiętać.

kittemon
źródło
Oh dziękuję Ci! To bardzo pomocne. Szkoda jednak, że nie działa w przypadku sekwencji. Masz rację, że w tym przykładzie kolejność nie jest ważna; to, co mam, jest koncepcyjnie zbiorem, ale to odwzorowuje znacznie bardziej sekwencję niż odwzorowanie. I struktura tego, co z tego mam, ma znaczenie (dlatego nie chciałem po prostu dodawać kolejnej warstwy zagnieżdżenia, aby scalić moje struktury), więc posiadanie mapowania, dla którego muszę zignorować (wszystkie puste) wartości, nie naprawdę działa.
Ben
3
Nie widzę nic na ten temat w aktualnej oficjalnej specyfikacji YAML: yaml.org/spec/1.2/spec.html . Ta strona nie zawiera słowa „scalanie”, tekstu „<<” ani wyrażenia „typ klucza”. Jednak składnia << działa w pakiecie Python yaml. Czy wiesz, gdzie mogę dowiedzieć się więcej o tego rodzaju dodatkowych funkcjach?
Ben
1
Nie ma tego bezpośrednio w specyfikacji, jest opisane w repozytorium tagów. Inne schematy mają ogólny opis i łącze. Oprócz kluczy scalających istnieją również zestawy i zestawy uporządkowane; jednakże YAML traktuje zbiory jako rodzaj odwzorowania (np. powyższy przykład mógłby być zaimplementowany jako zbiór). Czy Twój język umożliwia zamianę kluczy z wartościami w wynikowym mapowaniu? Myślę, że nawet gdybyś musiał to wdrożyć samodzielnie, byłoby to czystsze; co najmniej wszystkie dane byłyby już zgrupowane razem, a Twój YAML byłby standardowy.
kittemon
Jednak zestawy nie są mapowaniami; mapowanie to zestaw skojarzeń klucz-wartość. Kiedy pracuję yaml.load(...)w Pythonie, otrzymuję słownik jako reprezentację mapowania YAML. Tak, łatwo jest przetworzyć to w zestawie, ale muszę wiedzieć, że tak się stało (a semantyczna złożoność podczas odczytu / zapisu plików konfiguracyjnych jest znacznie większa, jeśli reguła brzmi: „zestawy są zapisywane jako mapy z wartościami null” ). Biorąc pod uwagę, że potrzebuję przetwarzania końcowego między yaml.load(...)danymi wynikowymi i korzystania z nich, niezależnie od tego, czy używam, <<czy MERGEprawdopodobnie będę się trzymać MERGE(które już zaimplementowałem).
Ben
2
Tak, stwierdziłem, że to !!setdziała. Zbyt wiele niejasnych schematów. Pliki te są przystosowane do odczytu / zapisu przez ludzi, którzy niekoniecznie są ekspertami od YAML. Ludzie będą zapisywać swoje listy witryn jako listy YAML, a następnie będą chcieli je scalić i przekonwertować całość na zestaw ORAZ pamiętaj, aby wyraźnie oznaczyć go jako zestaw ... Mam kilka innych ustandaryzowanych post- przetwarzanie rzeczy razem z MERGEtak czy inaczej. Ale dzięki za pomoc!
Ben
17

Jak wskazywały poprzednie odpowiedzi, nie ma wbudowanej obsługi rozszerzania list w YAML. Oferuję jeszcze jeden sposób na samodzielne wdrożenie. Rozważ to:

defaults: &defaults
  sites:
    - www.foo.com
    - www.bar.com

setup1:
  <<: *defaults
  sites+:
    - www.baz.com

Zostanie to przetworzone na:

defaults:
  sites:
    - www.foo.com
    - www.bar.com

setup1:
  sites:
    - www.foo.com
    - www.bar.com
    - www.baz.com

Chodzi o to, aby połączyć zawartość klucza zakończonego znakiem „+” z odpowiadającym mu kluczem bez „+”. Zaimplementowałem to w Pythonie i opublikowałem tutaj .

Cieszyć się!

Aleksandra Ryżowa
źródło
2
Uwaga: ten problem można również rozwiązać za pomocą standardowego użycia kotwic i aliasów w YAML. Zobacz też: Jak scalać tablice YAML?
dreftymac
12
Czy to oznacza, że ​​to podejście działa tylko z oddzielnym narzędziem, które łączy sitesi sites+. Mam na myśli narzędzie, które musi zaimplementować użytkownik, bo nie jest to yamlzachowanie domyślne ?
stan0
7

(Odpowiadając na moje własne pytanie na wypadek, gdyby rozwiązanie, którego używam, było przydatne dla każdego, kto będzie szukał tego w przyszłości)

Ponieważ nie można tego zrobić w czystym YAML, zaimplementuję to jako „transformację składni” znajdującą się pomiędzy parserem YAML a kodem, który faktycznie używa pliku konfiguracyjnego. Tak więc moja podstawowa aplikacja nie musi w ogóle martwić się o żadne przyjazne dla człowieka środki zapobiegające redundancji i może po prostu działać bezpośrednio na wynikowych strukturach.

Struktura, której zamierzam użyć, wygląda następująco:

foo:
  MERGE:
    - - a
      - b
      - c
    - - 1
      - 2
      - 3

Który zostałby przekształcony w odpowiednik:

foo:
  - a
  - b
  - c
  - 1
  - 2
  - 3

Lub z mapami:

foo:
  MERGE:
    - fork: a
      spoon: b
      knife: c
    - cup: 1
      mug: 2
      glass: 3

Zostanie przekształcony w:

foo:
  fork: a
  spoon: b
  knife: c
  cup: 1
  mug: 2
  glass: 3

Bardziej formalnie, po wywołaniu parsera YAML w celu pobrania obiektów natywnych z pliku konfiguracyjnego, ale przed przekazaniem obiektów do reszty aplikacji, moja aplikacja przejdzie po grafie obiektów w poszukiwaniu mapowań zawierających pojedynczy klucz MERGE. Wartość skojarzona z MERGEmusi być listą list lub listą map; każda inna podkonstrukcja jest błędem.

W przypadku list-of-list cała mapa zawierająca MERGEzostanie zastąpiona listami podrzędnymi połączonymi razem w kolejności, w jakiej się pojawiły.

W przypadku listy map cała mapa zawierająca MERGEzostanie zastąpiona przez jedną mapę zawierającą wszystkie pary klucz / wartość w mapach potomnych. Tam, gdzie klucze nakładają się, MERGEzostanie użyta wartość z mapy podrzędnej występująca jako ostatnia na liście.

Podane powyżej przykłady nie są zbyt użyteczne, ponieważ można było napisać bezpośrednio żądaną strukturę. Bardziej prawdopodobne jest, że pojawi się jako:

foo:
  MERGE:
    - *salt
    - *pepper

Umożliwiając tworzenie listy lub mapy zawierającej wszystko w węzłach salti pepperużywanej w innym miejscu.

(Nadal podaję tę foo:zewnętrzną mapę, aby pokazać, że MERGEmusi to być jedyny klucz w jej mapowaniu, co oznacza, że MERGEnie może pojawić się jako nazwa najwyższego poziomu, chyba że nie ma innych nazw najwyższego poziomu)

Ben
źródło
6

Aby wyjaśnić coś z dwóch odpowiedzi tutaj, nie jest to obsługiwane bezpośrednio w YAML dla list (ale jest obsługiwane w przypadku słowników, zobacz odpowiedź Kittemona).

asmeurer
źródło
Uwaga: ten problem można również rozwiązać za pomocą standardowego użycia kotwic i aliasów w YAML. Zobacz też: Jak scalać tablice YAML?
dreftymac
5

Aby odeprzeć odpowiedź Kittemona, zauważ, że możesz tworzyć mapowania z wartościami null przy użyciu alternatywnej składni

foo:
    << : myanchor
    bar:
    baz:

zamiast sugerowanej składni

foo:
    << : myanchor
    ? bar
    ? baz

Podobnie jak w przypadku sugestii Kittemona, pozwoli to na użycie odniesień do kotwic w mapowaniu i uniknięcie problemu z sekwencją. Musiałem to zrobić po odkryciu, że komponent Symfony Yaml v2.4.4 nie rozpoznaje ? barskładni.

wołowina_boolean
źródło
jak myanchorwygląda
ssc