як посилатися на "налаштування" YAML з іншого місця в тому ж файлі YAML?


145

У мене є така YAML:

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

Як я можу "нормалізувати" це, видаляючи /path/to/root/з трьох контурів, і мати це як власне налаштування, щось на зразок:

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

Очевидно, що це недійсно, я просто вигадав. Що таке справжній синтаксис? Чи можна це зробити?


1
Дивіться також: stackoverflow.com/a/41620747/42223
dreftymac

Відповіді:


127

Я не думаю, що це можливо. Ви можете повторно використовувати "вузол", але не його частину.

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

Це цілком дійсна YAML і поля givenі familyповторно використовуються в ship-toблоці. Ви можете повторно використовувати скалярний вузол так само, але немає можливості змінити те, що знаходиться всередині, і додати цю останню частину шляху до неї зсередини YAML.

Якщо повторення вас так сильно турбує, я пропоную ознайомити вашу програму із rootвластивістю та додати її до кожного шляху, який виглядає відносно не абсолютним.


1
Ок, дякую, так погано потрібно додати rootкод. немає великого.
Ендрю Буллок

2
Прийнята відповідь не точна. Дивіться мою відповідь щодо рішення.
Кріс Джонсон

як це зробити, якщо рахунок-ін є в іншому файлі, який ми імпортували, де визначено доставку ?
Prateek Jain

@PrateekJain: якщо ви маєте справу з декількома файлами, ви, ймовірно, найкраще оцінити окрему бібліотеку розширень YAML, таку як перелічена тут. github.com/dreftymac/dynamic.yaml/blob/master/…
dreftymac

1
Дивіться приклад 2.9 в yaml.org/spec/1.2/spec.html ; можна також посилатися на скалярів, що є дивним
akostadinov

72

Так, використовуючи власні теги. Приклад в Python, що робить !joinтег приєднання рядків до масиву:

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

Результати:

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

Масив аргументів !joinможе містити будь-яку кількість елементів будь-якого типу даних, якщо вони можуть бути перетворені в рядок, і це !join [*a, "/", *b, "/", *c]робить те, що ви очікували.


2
Мені подобається ваше рішення, простіше в кодуванні, ніж моє ціною трохи менш читабельної YAML.
Антон

7
Ця відповідь заслуговує на більшу кількість голосів. Технічно це найбільш точна відповідь відповідно до специфікації YAML. Є одне застереження, однак, згідно з фактичними реалізаціями YAML , мало хто реально реалізує повну специфікацію YAML. Pyyhon's pyyaml ​​є вищим за багато інших з точки зору його однорідності із специфікацією.
dreftymac

5
Здається, питання стосується посилання на значення у файлі yaml. Додавання іншого шару коду навколо нього не було б моїм кращим рішенням.
користувач2020056

1
@ChrisJohnson Дякую за цю відповідь, мені було цікаво, чи є у вас довідковий документ, який перераховував цей синтаксис. Я бачив специфікацію YAML, пояснювану в багатьох місцях в Інтернеті, тому просто хочу переконатися, що я дивлюсь ту саму довідку, що і ви. Дякую!
користувач5359531

3
Це рішення не спрацювало для мене ( python3?), Проте з простою модифікацією вищезазначеного воно працює як очікувалося. Конкретно:yaml.SafeLoader.add_constructor(tag='!join', constructor=join) yaml.load(open(fpth, mode='r'), Loader=yaml.SafeLoader)
benjaminmgross

20

Ще один спосіб подивитися на це - просто використовувати інше поле.

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

Визначення YML:

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

Десь у чебрецю

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

Вихід: / home / data / in / / home / data / in / p1


@AndrewBullock Я думаю, що це має бути прийнятою відповіддю, оскільки це точно вирішує вашу проблему.
Honza Zidek

5
Ні, це не вбудоване використання змінної в YAML, і це не вказано в жодних версіях специфікацій. Після деяких тестів це не працює.
Артур Лакост

2
Це, ймовірно, працювало для Pavol, використовуючи щось, що попередньо обробило yaml (тобто фільтрування плагінів Maven-Resources)
DeezCashews

1
Не стандартний Ямл
Дан Нієро

3

Я створив бібліотеку, доступну в Packagist, яка виконує цю функцію: https://packagist.org/packages/grasmash/yaml-expander

Приклад файлу 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}

Приклад логіки:

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

Любляча концепція розширювача!
Гійом Родерік

2

У деяких мовах можна використовувати альтернативну бібліотеку, наприклад, tampax - це реалізація змінних 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

Те, що ваш приклад недійсний, полягає лише в тому, що ви вибрали зарезервований символ, щоб розпочати скаляри. Якщо ви заміните на *якийсь інший незарезервований символ (я, як правило, використовую символи, що не належать до ASCII, оскільки вони рідко використовуються як частина якоїсь специфікації), у вас виходить абсолютно законна YAML:

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

Це завантажиться в стандартне подання для відображень на мові, який використовує ваш аналізатор, і не магічно розширить нічого.
Для цього використовуйте локально типовий тип об'єкта, як у наступній програмі 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]))

який надрукує:

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

Розширення робиться на ходу і обробляє вкладені визначення, але ви повинні бути обережні, щоб не викликати нескінченну рекурсію.

Вказавши самоскид, ви можете скинути оригінальний YAML з завантажених даних через розширення на ходу:

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

це змінить впорядкування ключів відображення. Якщо це проблема , яку ви повинні зробити (імпортований з )self.dCommentedMapruamel.yaml.comments.py


0

Я написав власну бібліотеку на Python для розширення змінних, завантажених з каталогів з ієрархією, як:

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

Ключова відмінність тут полягає в тому, що розширення потрібно застосовувати лише після завантаження всіх config.yamlфайлів, де змінні з наступного файлу можуть заміняти змінні з попереднього, тому псевдокод повинен виглядати так:

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

Як додатковий варіант xonshсценарій може експортувати отримані змінні в змінні середовища (див. Розділyaml_update_global_vars Функцію).

Сценарії:

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

Плюси :

  • простий, не підтримує рекурсію та вкладені змінні
  • може замінити невизначену змінну на заповнювач ( ${MYUNDEFINEDVAR}->*$/{MYUNDEFINEDVAR} )
  • може розширити посилання з змінної середовища (${env:MYVAR} )
  • може замінити все \\на /змінну шляху ( ${env:MYVAR:path})

Мінуси :

  • не підтримує вкладені змінні, тому не може розширювати значення у вкладених словниках (щось на кшталт ${MYSCOPE.MYVAR} не реалізовано)
  • не виявляє рекурсії розширення, включаючи рекурсію після встановлення заповнювача

0

З Yglu ви можете написати свій приклад як:

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

Відмова: Я є автором Yglu.


Добре бути в курсі бібліотеки, яка додає цю функціональність поверх YAML
Dhiraj
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.