bagaimana cara mereferensikan "pengaturan" YAML dari tempat lain dalam file YAML yang sama?


150

Saya memiliki YAML berikut:

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

Bagaimana saya bisa "menormalkan" ini, dengan menghapus /path/to/root/dari tiga jalur, dan menjadikannya sebagai pengaturannya sendiri, seperti:

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

Jelas itu tidak valid, saya hanya mengada-ada. Apa sintaks sebenarnya? Bisakah itu dilakukan?


Jawaban:


134

Saya rasa itu tidak mungkin. Anda dapat menggunakan kembali "node" tapi bukan bagian darinya.

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

Ini adalah YAML dan kolom yang benar-benar valid givendan familydigunakan kembali dalam ship-toblok. Anda dapat menggunakan kembali node skalar dengan cara yang sama tetapi tidak ada cara untuk mengubah apa yang ada di dalamnya dan menambahkan bagian terakhir dari jalur ke sana dari dalam YAML.

Jika pengulangan sangat mengganggu Anda, saya sarankan untuk membuat aplikasi Anda sadar akan rootproperti dan menambahkannya ke setiap jalur yang terlihat relatif tidak absolut.


1
Ok terima kasih, ya saya harus menambahkan rootkode di awal. bukan masalah besar.
Andrew Bullock

2
Jawaban yang diterima tidak akurat. Lihat jawaban saya untuk solusinya.
Chris Johnson

bagaimana melakukan ini, jika tagih ke ada di file lain, yang telah kita impor di mana tujuan pengiriman ditentukan?
Prateek Jain

@PrateekJain: jika Anda berurusan dengan banyak file, Anda mungkin akan melakukan yang terbaik untuk mengevaluasi pustaka penyempurnaan YAML mandiri, seperti yang tercantum di sini. github.com/dreftymac/dynamic.yaml/blob/master/...
dreftymac

1
Lihat contoh 2.9 di yaml.org/spec/1.2/spec.html ; Anda juga dapat mereferensikan skalar yang mengagumkan
akostadinov

77

Ya, menggunakan tag kustom. Contoh di Python, membuat !jointag menggabungkan string dalam sebuah 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]
""")

Yang mengakibatkan:

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

Larik argumen untuk !joindapat memiliki sejumlah elemen dari tipe data apa pun, selama mereka dapat dikonversi menjadi string, begitu !join [*a, "/", *b, "/", *c]juga yang Anda harapkan.


2
Saya suka solusi Anda, lebih sederhana dalam pengkodean daripada solusi saya dengan biaya YAML yang sedikit kurang dapat dibaca.
Anthon

7
Jawaban ini layak mendapat lebih banyak suara. Secara teknis, ini adalah jawaban paling akurat menurut spesifikasi YAML. Namun, ada satu peringatan, berdasarkan implementasi YAML yang sebenarnya , ada beberapa yang benar-benar mengimplementasikan spesifikasi YAML lengkap. Pyyaml ​​Python berada di atas dan melampaui banyak lainnya dalam hal keseragamannya dengan spesifikasinya.
dreftymac

6
Pertanyaannya sepertinya tentang mereferensikan nilai DALAM file yaml. Menambahkan lapisan kode lain di sekitarnya bukanlah solusi pilihan saya.
pengguna2020056

1
@ChrisJohnson Terima kasih atas jawaban ini, saya ingin tahu apakah Anda memiliki dokumen referensi yang mencantumkan sintaks ini. Saya telah melihat spesifikasi YAML dijelaskan di banyak tempat di web jadi saya hanya ingin memastikan saya melihat referensi yang sama dengan Anda. Terima kasih!
pengguna5359531

3
Solusi ini tidak berfungsi untuk saya ( python3?) Namun dengan modifikasi sederhana di atas, solusi ini berfungsi seperti yang diharapkan. Khususnya:yaml.SafeLoader.add_constructor(tag='!join', constructor=join) yaml.load(open(fpth, mode='r'), Loader=yaml.SafeLoader)
benjaminmgross

20

Cara lain untuk melihatnya adalah dengan menggunakan bidang lain.

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

6

Definisi YML:

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

Di suatu tempat di daun timele

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

Keluaran: / home / data / in / / home / data / in / p1


@AndrewBullock Saya pikir ini harus menjadi jawaban yang diterima, karena persis memecahkan masalah Anda.
Honza Zidek

5
Tidak, ini bukan penggunaan variabel asli di YAML dan tidak ditentukan dalam versi spesifikasi apa pun. Setelah beberapa pengujian, ini tidak berhasil.
Arthur Lacoste

2
Ini mungkin berhasil untuk Pavol menggunakan sesuatu yang pra-proses yaml (yaitu maven-resources-plugin filtering)
DeezCashews

2
Bukan standar Yaml
Dan Niero

3

Saya telah membuat perpustakaan, tersedia di Packagist, yang menjalankan fungsi ini: https://packagist.org/packages/grasmash/yaml-expander

Contoh 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}

Contoh logika:

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

Array yang dihasilkan:

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

Menyukai konsep expander!
Guillaume Roderick

2

Dalam beberapa bahasa, Anda bisa menggunakan library alternatif. Misalnya, tampax adalah implementasi dari variabel penanganan 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

Bahwa contoh Anda tidak valid hanya karena Anda memilih karakter khusus untuk memulai skalar Anda. Jika Anda mengganti *dengan beberapa karakter non-cadangan lainnya (saya cenderung menggunakan karakter non-ASCII untuk itu karena jarang digunakan sebagai bagian dari beberapa spesifikasi), Anda akan mendapatkan YAML yang legal:

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

Ini akan dimuat ke dalam representasi standar untuk pemetaan dalam bahasa yang digunakan parser Anda dan tidak memperluas apa pun secara ajaib.
Untuk melakukan itu gunakan tipe objek default lokal seperti pada program Python berikut:

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

yang akan mencetak:

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

Perluasan dilakukan dengan cepat dan menangani definisi bertingkat, tetapi Anda harus berhati-hati agar tidak meminta rekursi tak terbatas.

Dengan menentukan dumper, Anda dapat membuang YAML asli dari data yang dimuat, karena ekspansi on-the-fly:

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

ini akan mengubah urutan kunci pemetaan. Jika itu adalah masalah Anda harus membuat self.dsebuah CommentedMap(diimpor dari ruamel.yaml.comments.py)


1

Dengan Yglu , Anda dapat menuliskan contoh Anda sebagai:

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

Penafian: Saya adalah penulis Yglu.


Ada baiknya untuk mengetahui perpustakaan yang menambahkan fungsionalitas ini di atas YAML
Dhiraj

Terima kasih telah membuat ini. YAML sangat populer akhir-akhir ini dan senang melihat proyek untuk membantu mengatasi kegilaan itu.
dbaumann

0

Saya telah menulis perpustakaan saya sendiri di Python untuk memperluas variabel yang dimuat dari direktori dengan hierarki seperti:

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

Perbedaan utamanya di sini adalah bahwa perluasan harus diterapkan hanya setelah semua config.yamlfile dimuat, di mana variabel dari file berikutnya dapat menimpa variabel dari sebelumnya, jadi pseudocode akan terlihat seperti ini:

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

Sebagai opsi tambahan, xonshskrip dapat mengekspor variabel yang dihasilkan menjadi variabel lingkungan (lihat yaml_update_global_varsfungsinya).

Scriptnya:

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

Kelebihan :

  • sederhana, tidak mendukung rekursi dan variabel bersarang
  • dapat mengganti variabel yang tidak ditentukan menjadi placeholder ( ${MYUNDEFINEDVAR}-> *$/{MYUNDEFINEDVAR})
  • dapat memperluas referensi dari variabel lingkungan ( ${env:MYVAR})
  • dapat mengganti semua \\ke /dalam variabel jalur ( ${env:MYVAR:path})

Kekurangan :

  • tidak mendukung variabel bertingkat, jadi tidak dapat memperluas nilai dalam kamus bertingkat (sesuatu seperti ${MYSCOPE.MYVAR}itu tidak diterapkan)
  • tidak mendeteksi rekursi perluasan, termasuk rekursi setelah penempatan placeholder
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.