come fare riferimento a una "impostazione" YAML da altrove nello stesso file YAML?


146

Ho il seguente YAML:

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

Come posso "normalizzare" questo, rimuovendo /path/to/root/dai tre percorsi e averlo come sua impostazione, qualcosa del tipo:

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

Ovviamente non è valido, l'ho appena inventato. Qual è la vera sintassi? Si può fare?


Risposte:


127

Non penso sia possibile. Puoi riutilizzare "nodo" ma non parte di esso.

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

Questo è YAML e campi perfettamente validi givene familyvengono riutilizzati in ship-toblocco. Puoi riutilizzare un nodo scalare allo stesso modo, ma non c'è modo di cambiare ciò che è dentro e aggiungere l'ultima parte di un percorso dall'interno di YAML.

Se la ripetizione ti disturba così tanto ti suggerisco di rendere la tua applicazione consapevole della rootproprietà e aggiungerla ad ogni percorso che sembra relativo non assoluto.


1
Ok grazie, sì, devo anteporre il rootcodice in. nessun problema.
Andrew Bullock,

2
La risposta accettata non è precisa. Vedi la mia risposta per una soluzione.
Chris Johnson,

come fare, se la fatturazione si trova in un altro file, che è stato importato dove è stata definita la spedizione ?
Prateek Jain,

@PrateekJain: se hai a che fare con più file, probabilmente farai del tuo meglio per valutare una libreria autonoma di miglioramento YAML, come quella elencata qui. github.com/dreftymac/dynamic.yaml/blob/master/…
dreftymac

1
Vedi esempio 2.9 in yaml.org/spec/1.2/spec.html ; si può anche fare riferimento a scalari che è fantastico
Akostadinov

72

Sì, utilizzando tag personalizzati. Esempio in Python, facendo in modo che il !jointag unisca le stringhe in un array:

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

Che si traduce in:

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

L'array di argomenti !joinpuò avere un numero qualsiasi di elementi di qualsiasi tipo di dati, purché possano essere convertiti in stringa, quindi !join [*a, "/", *b, "/", *c]fa quello che ti aspetteresti.


2
Mi piace la tua soluzione, più semplice nella codifica, quindi la mia a costo di YAML leggermente meno leggibile.
Anthon,

7
Questa risposta merita più voti positivi. È tecnicamente la risposta più accurata in base alle specifiche YAML. C'è un avvertimento, tuttavia, in base alle attuali implementazioni YAML , ci sono pochi che implementano effettivamente le specifiche YAML complete. Il piyaml di Python è al di sopra e al di là di molti altri in termini di uniformità con le specifiche.
dreftymac,

5
La domanda sembra riguardare il riferimento a un valore IN un file yaml. Aggiungere un altro livello di codice non sarebbe la mia soluzione preferita.
user2020056

1
@ChrisJohnson Grazie per questa risposta, mi chiedevo se tu avessi un documento di riferimento che elencasse questa sintassi. Ho visto le specifiche YAML spiegate in più punti sul Web, quindi voglio solo assicurarmi di guardare lo stesso riferimento che sei. Grazie!
user5359531

3
Questa soluzione non ha funzionato per me ( python3?) Tuttavia con una semplice modifica a quanto sopra funziona come previsto. In particolare:yaml.SafeLoader.add_constructor(tag='!join', constructor=join) yaml.load(open(fpth, mode='r'), Loader=yaml.SafeLoader)
benjaminmgross,

20

Un altro modo di vedere questo è semplicemente usare un altro campo.

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

5

Definizione YML:

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

Da qualche parte in thymeleaf

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

Output: / home / data / in / / home / data / in / p1


@AndrewBullock Penso che questa dovrebbe essere la risposta accettata, poiché risolve esattamente il tuo problema.
Honza Zidek,

5
No, non è un uso nativo della variabile in YAML e non è specificato in nessuna versione delle specifiche. Dopo alcuni test, questo non funziona.
Arthur Lacoste,

2
Questo probabilmente ha funzionato per Pavol usando qualcosa che ha pre-elaborato lo yaml (ovvero il filtro dei plugin delle risorse
maven

1
Non standard Yaml
Dan Niero

3

Ho creato una libreria, disponibile su Packagist, che svolge questa funzione: https://packagist.org/packages/grasmash/yaml-expander

Esempio di file 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}

Logica di esempio:

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

Matrice risultante:

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

Amare il concetto di expander!
Guillaume Roderick,

2

In alcune lingue, è possibile utilizzare una libreria alternativa, ad esempio tampax è un'implementazione delle variabili di gestione 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."

1

Che il tuo esempio non sia valido è solo perché hai scelto un carattere riservato con cui iniziare i tuoi scalari. Se lo sostituisci *con qualche altro carattere non riservato (tendo a usare caratteri non ASCII per quello poiché sono usati raramente come parte di alcune specifiche), finisci con YAML perfettamente legale:

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

Questo verrà caricato nella rappresentazione standard per i mapping nella lingua utilizzata dal tuo parser e non espanderà magicamente nulla.
Per fare ciò usa un tipo di oggetto predefinito localmente come nel seguente programma 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]))

che stamperà:

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

L'espansione viene eseguita al volo e gestisce le definizioni nidificate, ma è necessario fare attenzione a non invocare la ricorsione infinita.

Specificando il dumper, è possibile scaricare lo YAML originale dai dati caricati, a causa dell'espansione al volo:

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

questo cambierà l'ordinamento della chiave di mappatura. Se questo è un problema devi fare self.dun CommentedMap(importato da ruamel.yaml.comments.py)


0

Ho scritto la mia libreria su Python per espandere le variabili caricate dalle directory con una gerarchia come:

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

La differenza chiave qui è che l'espansione deve essere applicata solo dopo che tutti i config.yamlfile sono stati caricati, in cui le variabili del file successivo possono sovrascrivere le variabili dal precedente, quindi lo pseudocodice dovrebbe assomigliare a questo:

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

Come opzione aggiuntiva lo xonshscript può esportare le variabili risultanti in variabili d'ambiente (vedere la yaml_update_global_varsfunzione).

Gli script:

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

Pro :

  • semplice, non supporta la ricorsione e le variabili nidificate
  • può sostituire una variabile non definita in un segnaposto ( ${MYUNDEFINEDVAR}-> *$/{MYUNDEFINEDVAR})
  • può espandere un riferimento dalla variabile di ambiente ( ${env:MYVAR})
  • può sostituire tutto \\ad /una variabile di percorso ( ${env:MYVAR:path})

Contro :

  • non supporta le variabili nidificate, quindi non è possibile espandere i valori nei dizionari nidificati (qualcosa di simile ${MYSCOPE.MYVAR}non è implementato)
  • non rileva la ricorsione dell'espansione, inclusa la ricorsione dopo l'inserimento di un segnaposto

0

Con Yglu , puoi scrivere il tuo esempio come:

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

Disclaimer: sono l'autore di Yglu.


È bene essere a conoscenza di una libreria che aggiunge questa funzionalità oltre a YAML
Dhiraj il
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.