Wie verweise ich auf eine YAML-Einstellung von einer anderen Stelle in derselben YAML-Datei?

145

Ich habe die folgende YAML:

paths:
  patha: /path/to/root/a
  pathb: /path/to/root/b
  pathc: /path/to/root/c

Wie kann ich dies "normalisieren", indem /path/to/root/ich es von den drei Pfaden entferne und es als eigene Einstellung habe, so etwas wie:

paths:
  root: /path/to/root/
  patha: *root* + a
  pathb: *root* + b
  pathc: *root* + c

Offensichtlich ist das ungültig, ich habe es gerade erfunden. Was ist die wahre Syntax? Kann es gemacht werden?

Andrew Bullock
quelle
1
Siehe auch: stackoverflow.com/a/41620747/42223
dreftymac

Antworten:

126

Ich denke nicht, dass es möglich ist. Sie können "Knoten" wiederverwenden, aber nicht Teil davon.

bill-to: &id001
    given  : Chris
    family : Dumars
ship-to: *id001

Dies ist eine absolut gültige YAML und Felder givenund familywerden im ship-toBlock wiederverwendet . Sie können einen Skalarknoten auf dieselbe Weise wiederverwenden, aber Sie können nicht ändern, was sich darin befindet, und den letzten Teil eines Pfads aus YAML heraus hinzufügen.

Wenn Sie die Wiederholung so sehr stört, schlage ich vor, Ihre Anwendung auf die rootEigenschaft aufmerksam zu machen und sie jedem Pfad hinzuzufügen, der relativ und nicht absolut aussieht.

vava
quelle
1
Ok, danke, ja, ich muss den rootIn-Code voranstellen . kein Biggie.
Andrew Bullock
2
Die akzeptierte Antwort ist nicht korrekt. Siehe meine Antwort für eine Lösung.
Chris Johnson
Wie geht das, wenn sich Bill-to in einer anderen Datei befindet, die wir importiert haben, wo Ship-to definiert ist?
Prateek Jain
@PrateekJain: Wenn Sie mit mehreren Dateien arbeiten, sollten Sie wahrscheinlich eine eigenständige YAML-Erweiterungsbibliothek wie die hier aufgeführte evaluieren. github.com/dreftymac/dynamic.yaml/blob/master/…
dreftymac
1
Siehe Beispiel 2.9 in yaml.org/spec/1.2/spec.html ; man kann auch auf Skalare verweisen, was fantastisch ist
akostadinov
73

Ja, mit benutzerdefinierten Tags. Beispiel in Python, bei dem das !joinTag Zeichenfolgen in einem Array verbindet:

import yaml

## define custom tag handler
def join(loader, node):
    seq = loader.construct_sequence(node)
    return ''.join([str(i) for i in seq])

## register the tag handler
yaml.add_constructor('!join', join)

## using your sample data
yaml.load("""
paths:
    root: &BASE /path/to/root/
    patha: !join [*BASE, a]
    pathb: !join [*BASE, b]
    pathc: !join [*BASE, c]
""")

Was in ... endet:

{
    'paths': {
        'patha': '/path/to/root/a',
        'pathb': '/path/to/root/b',
        'pathc': '/path/to/root/c',
        'root': '/path/to/root/'
     }
}

Das Array von Argumenten !joinkann eine beliebige Anzahl von Elementen eines beliebigen Datentyps enthalten, sofern sie in Zeichenfolgen konvertiert werden können. Dies !join [*a, "/", *b, "/", *c]entspricht auch den Erwartungen.

Chris Johnson
quelle
2
Ich mag Ihre Lösung, die einfacher zu codieren ist als meine, auf Kosten einer etwas weniger lesbaren YAML.
Anthon
6
Diese Antwort verdient mehr Stimmen. Es ist technisch die genaueste Antwort gemäß der YAML-Spezifikation. Es gibt jedoch eine Einschränkung: Gemäß den tatsächlichen YAML- Implementierungen gibt es nur wenige, die tatsächlich die vollständige YAML-Spezifikation implementieren. Pythons Pyyaml ​​übertrifft viele andere hinsichtlich seiner Einheitlichkeit mit der Spezifikation.
Dreftymac
4
Die Frage scheint darin zu bestehen, einen Wert in einer Yaml-Datei zu referenzieren. Das Hinzufügen einer weiteren Codeebene wäre nicht meine bevorzugte Lösung.
user2020056
1
@ChrisJohnson Vielen Dank für diese Antwort. Ich habe mich gefragt, ob Sie ein Referenzdokument haben, in dem diese Syntax aufgeführt ist. Ich habe gesehen, wie die YAML-Spezifikation an mehreren Stellen im Web erklärt wurde, daher möchte ich nur sicherstellen, dass ich dieselbe Referenz betrachte, die Sie sind. Vielen Dank!
user5359531
3
Diese Lösung hat bei mir nicht funktioniert ( python3?), Aber mit einer einfachen Änderung funktioniert sie wie erwartet. Speziell:yaml.SafeLoader.add_constructor(tag='!join', constructor=join) yaml.load(open(fpth, mode='r'), Loader=yaml.SafeLoader)
benjaminmgross
20

Eine andere Möglichkeit, dies zu betrachten, besteht darin, einfach ein anderes Feld zu verwenden.

paths:
  root_path: &root
     val: /path/to/root/
  patha: &a
    root_path: *root
    rel_path: a
  pathb: &b
    root_path: *root
    rel_path: b
  pathc: &c
    root_path: *root
    rel_path: c
Brian Bruggeman
quelle
5

YML-Definition:

dir:
  default: /home/data/in/
  proj1: ${dir.default}p1
  proj2: ${dir.default}p2
  proj3: ${dir.default}p3 

Irgendwo im Blatt

<p th:utext='${@environment.getProperty("dir.default")}' />
<p th:utext='${@environment.getProperty("dir.proj1")}' /> 

Ausgabe: / home / data / in / / home / data / in / p1

Pavol
quelle
@ AndrewBullock Ich denke, dies sollte die akzeptierte Antwort sein, da es genau Ihr Problem löst.
Honza Zidek
5
Nein, es handelt sich nicht um eine native Verwendung von Variablen in YAML, und es ist in keiner Spezifikationsversion angegeben. Nach einigen Tests funktioniert dies nicht.
Arthur Lacoste
2
Dies funktionierte wahrscheinlich für Pavol mit etwas, das das Yaml vorverarbeitet hat (dh Maven-Resources-Plugin-Filterung)
DeezCashews
1
Nicht Standard Yaml
Dan Niero
3

Ich habe eine auf Packagist verfügbare Bibliothek erstellt, die diese Funktion ausführt: https://packagist.org/packages/grasmash/yaml-expander

Beispiel YAML-Datei:

type: book
book:
  title: Dune
  author: Frank Herbert
  copyright: ${book.author} 1965
  protaganist: ${characters.0.name}
  media:
    - hardcover
characters:
  - name: Paul Atreides
    occupation: Kwisatz Haderach
    aliases:
      - Usul
      - Muad'Dib
      - The Preacher
  - name: Duncan Idaho
    occupation: Swordmaster
summary: ${book.title} by ${book.author}
product-name: ${${type}.title}

Beispiellogik:

// Parse a yaml string directly, expanding internal property references.
$yaml_string = file_get_contents("dune.yml");
$expanded = \Grasmash\YamlExpander\Expander::parse($yaml_string);
print_r($expanded);

Resultierendes Array:

array (
  'type' => 'book',
  'book' => 
  array (
    'title' => 'Dune',
    'author' => 'Frank Herbert',
    'copyright' => 'Frank Herbert 1965',
    'protaganist' => 'Paul Atreides',
    'media' => 
    array (
      0 => 'hardcover',
    ),
  ),
  'characters' => 
  array (
    0 => 
    array (
      'name' => 'Paul Atreides',
      'occupation' => 'Kwisatz Haderach',
      'aliases' => 
      array (
        0 => 'Usul',
        1 => 'Muad\'Dib',
        2 => 'The Preacher',
      ),
    ),
    1 => 
    array (
      'name' => 'Duncan Idaho',
      'occupation' => 'Swordmaster',
    ),
  ),
  'summary' => 'Dune by Frank Herbert',
);
Grasmash
quelle
Ich liebe das Expander-Konzept!
Guillaume Roderick
2

In einigen Sprachen können Sie eine alternative Bibliothek verwenden. Tampax ist beispielsweise eine Implementierung von YAML-Verarbeitungsvariablen:

const tampax = require('tampax');

const yamlString = `
dude:
  name: Arthur
weapon:
  favorite: Excalibur
  useless: knife
sentence: "{{dude.name}} use {{weapon.favorite}}. The goal is {{goal}}."`;

const r = tampax.yamlParseString(yamlString, { goal: 'to kill Mordred' });
console.log(r.sentence);

// output : "Arthur use Excalibur. The goal is to kill Mordred."
Arthur Lacoste
quelle
1

Dass Ihr Beispiel ungültig ist, liegt nur daran, dass Sie ein reserviertes Zeichen ausgewählt haben, mit dem Sie Ihre Skalare beginnen möchten. Wenn Sie das *durch ein anderes nicht reserviertes Zeichen ersetzen (ich verwende dafür eher Nicht-ASCII-Zeichen, da diese selten als Teil einer Spezifikation verwendet werden), erhalten Sie eine vollkommen legale YAML:

paths:
  root: /path/to/root/
  patha: ♦root♦ + a
  pathb: ♦root♦ + b
  pathc: ♦root♦ + c

Dies wird in die Standarddarstellung für Zuordnungen in der von Ihrem Parser verwendeten Sprache geladen und erweitert nichts auf magische Weise.
Verwenden Sie dazu einen lokal standardmäßigen Objekttyp wie im folgenden Python-Programm:

# coding: utf-8

from __future__ import print_function

import ruamel.yaml as yaml

class Paths:
    def __init__(self):
        self.d = {}

    def __repr__(self):
        return repr(self.d).replace('ordereddict', 'Paths')

    @staticmethod
    def __yaml_in__(loader, data):
        result = Paths()
        loader.construct_mapping(data, result.d)
        return result

    @staticmethod
    def __yaml_out__(dumper, self):
        return dumper.represent_mapping('!Paths', self.d)

    def __getitem__(self, key):
        res = self.d[key]
        return self.expand(res)

    def expand(self, res):
        try:
            before, rest = res.split(u'♦', 1)
            kw, rest = rest.split(u'♦ +', 1)
            rest = rest.lstrip() # strip any spaces after "+"
            # the lookup will throw the correct keyerror if kw is not found
            # recursive call expand() on the tail if there are multiple
            # parts to replace
            return before + self.d[kw] + self.expand(rest)
        except ValueError:
            return res

yaml_str = """\
paths: !Paths
  root: /path/to/root/
  patha: ♦root♦ + a
  pathb: ♦root♦ + b
  pathc: ♦root♦ + c
"""

loader = yaml.RoundTripLoader
loader.add_constructor('!Paths', Paths.__yaml_in__)

paths = yaml.load(yaml_str, Loader=yaml.RoundTripLoader)['paths']

for k in ['root', 'pathc']:
    print(u'{} -> {}'.format(k, paths[k]))

welches gedruckt wird:

root -> /path/to/root/
pathc -> /path/to/root/c

Das Erweitern erfolgt im laufenden Betrieb und behandelt verschachtelte Definitionen. Sie müssen jedoch darauf achten, dass keine unendliche Rekursion aufgerufen wird.

Durch Angabe des Dumper können Sie die ursprüngliche YAML aufgrund der On-the-Fly-Erweiterung aus den geladenen Daten sichern:

dumper = yaml.RoundTripDumper
dumper.add_representer(Paths, Paths.__yaml_out__)
print(yaml.dump(paths, Dumper=dumper, allow_unicode=True))

Dadurch wird die Reihenfolge der Zuordnungsschlüssel geändert. Wenn das ein Problem ist, müssen Sie self.deine CommentedMap(importiert von ruamel.yaml.comments.py)

Anthon
quelle
0

Ich habe meine eigene Bibliothek auf Python geschrieben, um Variablen, die aus Verzeichnissen geladen werden, mit einer Hierarchie wie der folgenden zu erweitern:

/root
 |
 +- /proj1
     |
     +- config.yaml
     |
     +- /proj2
         |
         +- config.yaml
         |
         ... and so on ...

Der Hauptunterschied besteht darin, dass die Erweiterung erst angewendet werden muss, nachdem alle config.yamlDateien geladen wurden. Dabei können die Variablen aus der nächsten Datei die Variablen aus der vorherigen überschreiben. Daher sollte der Pseudocode folgendermaßen aussehen:

env = YamlEnv()
env.load('/root/proj1/config.yaml')
env.load('/root/proj1/proj2/config.yaml')
...
env.expand()

Als zusätzliche Option kann das xonshSkript die resultierenden Variablen in Umgebungsvariablen exportieren (siehe yaml_update_global_varsFunktion).

Die Skripte:

https://sourceforge.net/p/contools/contools/HEAD/tree/trunk/Scripts/Tools/cmdoplib.yaml.py https://sourceforge.net/p/contools/contools/HEAD/tree/trunk/Scripts /Tools/cmdoplib.yaml.xsh

Vorteile :

  • einfach, unterstützt keine Rekursion und verschachtelte Variablen
  • kann eine undefinierte Variable durch einen Platzhalter ersetzen ( ${MYUNDEFINEDVAR}-> *$/{MYUNDEFINEDVAR})
  • kann eine Referenz aus der Umgebungsvariablen ( ${env:MYVAR}) erweitern
  • können alle ersetzen , \\um /in einem Pfad Variable ( ${env:MYVAR:path})

Nachteile :

  • unterstützt keine verschachtelten Variablen, kann also keine Werte in verschachtelten Wörterbüchern erweitern (so etwas ${MYSCOPE.MYVAR}ist nicht implementiert)
  • erkennt keine Erweiterungsrekursion, einschließlich Rekursion nach einem Platzhalter
Andry
quelle
0

Mit Yglu können Sie Ihr Beispiel schreiben als:

paths:
  root: /path/to/root/
  patha: !? .paths.root + a
  pathb: !? .paths.root + b
  pathc: !? .paths.root + c

Haftungsausschluss: Ich bin der Autor von Yglu.

lbovet
quelle
Es ist gut, sich einer Bibliothek bewusst zu sein, die diese Funktionalität zusätzlich zu YAML
Dhiraj