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?
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.
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.
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
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
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."
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',
);
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 :
${MYUNDEFINEDVAR}
-> *$/{MYUNDEFINEDVAR}
)${env:MYVAR}
)\\
à /
dans une variable de chemin (${env:MYVAR:path}
)Inconvénients :
${MYSCOPE.MYVAR}
n'est pas implémenté)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
)