web-dev-qa-db-fra.com

comment référencer un "paramètre" YAML ailleurs dans le même fichier YAML?

J'ai le YAML suivant:

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

Comment puis-je "normaliser" ceci en supprimant /path/to/root/ sur les trois chemins et l’avoir comme paramètre, quelque chose comme:

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

Évidemment, c'est invalide, je viens de l'inventer. Quelle est la vraie syntaxe? Cela peut-il être fait?

110
Andrew Bullock

Je ne pense pas que c'est possible. Vous pouvez réutiliser "noeud" mais n'en faites pas partie.

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

YAML est parfaitement valide et les champs given et family sont réutilisés dans ship-to bloc. Vous pouvez réutiliser un nœud scalaire de la même manière, mais il n’est pas possible de modifier son contenu et d’ajouter la dernière partie d’un chemin à partir de YAML.

Si les répétitions vous dérangent beaucoup, je suggère de mettre votre application au courant de la propriété root et de l'ajouter à chaque chemin qui semble relatif et non absolu.

101
vava

Oui, en utilisant des balises personnalisées. Exemple en Python, permettant à la balise !join De joindre des chaînes dans un tableau:

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]
""")

Ce qui résulte en:

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

Le tableau d'arguments de !join Peut contenir un nombre quelconque d'éléments de n'importe quel type de données, à condition qu'ils puissent être convertis en chaîne. !join [*a, "/", *b, "/", *c] Répond donc à vos attentes.

63
Chris Johnson

Une autre façon de voir cela consiste simplement à utiliser un autre champ.

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
15
Brian Bruggeman

Définition YML:

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

quelque part dans thymeleaf

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

Sortie:/home/data/in// home/data/in/p1

4
Pavol

Dans certaines langues, vous pouvez utiliser une bibliothèque alternative. Par exemple, tampax est une implémentation des variables de traitement YAML:

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."
2
Arthur Lacoste

J'ai créé une bibliothèque, disponible sur Packagist, qui remplit cette fonction: https://packagist.org/packages/grasmash/yaml-expander

Exemple de fichier YAML:

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}

Exemple de logique:

// 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);

Tableau résultant:

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',
);
1
grasmash

J'ai écrit ma propre bibliothèque sur Python pour développer les variables chargées à partir de répertoires avec une hiérarchie telle que:

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

La principale différence ici est que l’extension ne doit être appliquée qu’après tous les config.yaml fichiers est chargé, où les variables du fichier suivant peuvent remplacer les variables du précédent, le pseudocode devrait donc ressembler à ceci:

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

En option supplémentaire, le script xonsh peut exporter les variables résultantes en variables d’environnement (voir la section yaml_update_global_vars une fonction).

Les scripts:

https://sourceforge.net/p/contools/contools/HEAD/tree/trunk/Scripts/Tools/cmdoplib.yaml.pyhttps://sourceforge.net/p/ contools/contools/HEAD/arbre/tronc/Scripts/Outils/cmdoplib.yaml.xsh

Avantages :

  • simple, ne supporte pas la récursion et les variables imbriquées
  • peut remplacer une variable non définie par un espace réservé (${MYUNDEFINEDVAR} -> *$/{MYUNDEFINEDVAR})
  • peut développer une référence à partir d'une variable d'environnement (${env:MYVAR})
  • peut remplacer tout \\ à / dans une variable de chemin (${env:MYVAR:path})

Inconvénients :

  • ne supporte pas les variables imbriquées, donc ne peut pas développer les valeurs dans les dictionnaires imbriqués (quelque chose comme ${MYSCOPE.MYVAR} n'est pas implémenté)
  • ne détecte pas la récursion d'expansion, y compris la récursion après la mise d'un espace réservé
0
Andry

Le fait que votre exemple soit invalide est niquement car vous avez choisi un caractère réservé pour démarrer vos scalaires. Si vous remplacez le * avec un autre caractère non réservé (j’ai tendance à utiliser des caractères non-ASCII pour cela car ils sont rarement utilisés dans le cadre d’une spécification), vous vous retrouvez avec un YAML parfaitement légal:

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

Cela se chargera dans la représentation standard des mappages dans la langue utilisée par votre analyseur et ne développera rien par magie.
Pour ce faire, utilisez un type d’objet par défaut localement comme ci-après Python:

# 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]))

qui va imprimer:

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

L'extension se fait à la volée et gère les définitions imbriquées, mais vous devez faire attention à ne pas appeler la récursion infinie.

En spécifiant le dumper, vous pouvez vider le fichier YAML d'origine des données chargées, en raison de l'extension à la volée:

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

cela modifiera l'ordre des clés de mappage. Si c'est un problème, vous devez faire self.d a CommentedMap (importé de ruamel.yaml.comments.py)

0
Anthon