Подтвердить что ты не робот

Как ссылаться на YAML "настройка" из другого места в том же файле YAML?

У меня есть следующий 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

Очевидно, что это неверно, я просто это сделал. Какой настоящий синтаксис? Это можно сделать?

4b9b3361

Ответ 1

Я не думаю, что это возможно. Вы можете повторно использовать "node", но не часть его.

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

Это вполне допустимый YAML, а поля given и family повторно используются в блоке ship-to. Вы можете повторно использовать скаляр node одинаково, но вы не можете изменить то, что внутри, и добавить эту последнюю часть пути к нему изнутри YAML.

Если повторение вас так сильно беспокоит, я предлагаю вам узнать ваше приложение о свойстве root и добавить его к каждому пути, который выглядит относительным не абсолютным.

Ответ 2

Да, используя пользовательские теги. Пример на 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] делает то, что вы ожидаете.

Ответ 3

Другой способ взглянуть на это - просто использовать другое поле.

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

Ответ 4

Определение 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/p 1

Ответ 5

В некоторых языках вы можете использовать альтернативную библиотеку, например 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."

Ответ 6

Я создал библиотеку, доступную в 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',
);

Ответ 7

То, что ваш пример недопустим, только, потому что вы выбрали зарезервированный символ для запуска ваших скаляров. Если вы замените * на какой-то другой незарезервированный символ (я обычно использую для него символы, отличные от 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.d a CommentedMap (импортированный из ruamel.yaml.comments.py)

Ответ 8

Я написал свою собственную библиотеку на 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} не реализовано)
  • не обнаруживает рекурсию расширения, включая рекурсию после размещения заполнителя