简体   繁体   English

如何从同一 YAML 文件中的其他地方引用 YAML“设置”?

[英]how to reference a YAML "setting" from elsewhere in the same YAML file?

I have the following YAML:我有以下 YAML:

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

How can I "normalise" this, by removing /path/to/root/ from the three paths, and have it as its own setting, something like:如何通过从三个路径中删除/path/to/root/ “规范化”它,并将其作为自己的设置,例如:

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

Obviously that's invalid, I just made it up.显然这是无效的,我只是编造的。 What's the real syntax?真正的语法是什么? Can it be done?可以做到吗?

I don't think it is possible.我不认为这是可能的。 You can reuse "node" but not part of it.您可以重用“节点”,但不能重用它的一部分。

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

This is perfectly valid YAML and fields given and family are reused in ship-to block.这是完全有效的 YAML,并且given的字段和familyship-to块中被重用。 You can reuse a scalar node the same way but there's no way you can change what's inside and add that last part of a path to it from inside YAML.您可以以相同的方式重用标量节点,但您无法更改内部的内容并从 YAML 内部添加路径的最后一部分。

If repetition bother you that much I suggest to make your application aware of root property and add it to every path that looks relative not absolute.如果重复让您感到困扰,我建议让您的应用程序了解root属性并将其添加到每个看起来相对而不是绝对的路径中。

Yes, using custom tags.是的,使用自定义标签。 Example in Python, making the !join tag join strings in an array: 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]
""")

Which results in:结果是:

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

The array of arguments to !join can have any number of elements of any data type, as long as they can be converted to string, so !join [*a, "/", *b, "/", *c] does what you would expect. !join的参数数组可以包含任意数量的任何数据类型的元素,只要它们可以转换为字符串,因此!join [*a, "/", *b, "/", *c]可以你会期待什么。

Another way to look at this is to simply use another field.另一种看待这一点的方法是简单地使用另一个字段。

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

I've create a library, available on Packagist, that performs this function: https://packagist.org/packages/grasmash/yaml-expander我创建了一个库,可在 Packagist 上使用,它执行此功能: https ://packagist.org/packages/grasmash/yaml-expander

Example YAML 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}

Example logic:示例逻辑:

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

Resultant array:结果数组:

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

YML definition: YML 定义:

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

Somewhere in thymeleaf在百里香叶的某个地方

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

Output: /home/data/in/ /home/data/in/p1输出: /home/data/in/ /home/data/in/p1

In some languages, you can use an alternative library, For example, tampax is an implementation of YAML handling variables:在某些语言中,您可以使用替代库,例如, 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."

Editor's Note: poster is also the author of this package.编者注:海报也是这个包的作者。

That your example is invalid is only because you chose a reserved character to start your scalars with.您的示例无效只是因为您选择了一个保留字符来开始您的标量。 If you replace the * with some other non-reserved character (I tend to use non-ASCII characters for that as they are seldom used as part of some specification), you end up with perfectly legal YAML:如果您将*替换为其他一些非保留字符(我倾向于使用非 ASCII 字符,因为它们很少用作某些规范的一部分),您最终会得到完全合法的 YAML:

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

This will load into the standard representation for mappings in the language your parser uses and does not magically expand anything.这将加载到解析器使用的语言映射的标准表示中,并且不会神奇地扩展任何内容。
To do that use a locally default object type as in the following Python program:为此,请使用以下 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]))

which will print:这将打印:

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

The expanding is done on the fly and handles nested definitions, but you have to be careful about not invoking infinite recursion.扩展是即时完成的并处理嵌套定义,但您必须小心不要调用无限递归。

By specifying the dumper, you can dump the original YAML from the data loaded in, because of the on-the-fly expansion:通过指定转储程序,您可以从加载的数据中转储原始 YAML,因为动态扩展:

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

this will change the mapping key ordering.这将改变映射键的顺序。 If that is a problem you have to make self.d a CommentedMap (imported from ruamel.yaml.comments.py )如果这是一个问题,你必须让self.d成为CommentedMap (从ruamel.yaml.comments.py导入)

With Yglu , you can write your example as:使用Yglu ,您可以将示例编写为:

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

Disclaimer: I am the author of Yglu.免责声明:我是 Yglu 的作者。

Using OmegaConf使用 OmegaConf

OmegaConf is a YAML-based hierarchical configuration system that has support for this under the functionality Variable interpolation . OmegaConf 是一个基于 YAML 的分层配置系统,在功能变量插值下对此提供支持。 Using OmegaConf v2.2.2:使用 OmegaConf v2.2.2:

Create a YAML file paths.yaml as follows:创建一个 YAML 文件paths.yaml ,如下所示:

paths:
  root: /path/to/root/
  patha: ${.root}a
  pathb: ${.root}b
  pathc: ${.root}c

then we can read the file with variable paths:然后我们可以读取带有可变路径的文件:

from omegaconf import OmegaConf
conf = OmegaConf.load("test_paths.yaml")

>>> conf.paths.root
'/path/to/root/'

>>> conf.paths.patha
'/path/to/root/a'
>>> conf.paths.pathb
'/path/to/root/b'
>>> conf.paths.pathc
'/path/to/root/c'

Deep and Cross -Ref深度和交叉参考

It is possible to define more complex (nested) structures with a relative depth of your variable in reference to other variables:可以参考其他变量定义更复杂(嵌套)的结构,其中变量的相对深度:

Create another file nested_paths.yaml :创建另一个文件nested_paths.yaml

data:
    base: data
    sub_dir_A:
        name: a
        # here we note that `base` is two levels above this variable
        # hence we will use `..base` two dots but the `name` variable is
        # at the same level hence a single dot `.name`
        nested_dir: ${..base}/sub_dir/${.name}/last_dir 
    sub_dir_B:
        # add another level of depth
        - name: b
          # due to another level of depth, we have to use three dots
          # to access `base` variable as `...base`
          nested_file: ${...base}/sub_dir/${.name}/dirs.txt
        - name: c
          # we can also make cross-references to other variables
          cross_ref_dir: ${...sub_dir_A.nested_dir}/${.name}

again we can check:我们可以再次检查:

conf = OmegaConf.load("nested_paths.yaml")

# 1-level of depth reference
>>> conf.data.sub_dir_A.nested_dir
'data/sub_dir/a/last_dir'

# 2-levels of depth reference
>>> conf.data.sub_dir_B[0].nested_file
'data/sub_dir/b/dirs.txt'

# cross-reference example
>>> conf.data.sub_dir_B[1].cross_ref_dir
'data/sub_dir/a/last_dir/c'

In case of invalid references (such as wrong depth, wrong variable name), OmegaConf will throw an error omegaconf.errors.InterpolationResolutionError .如果引用无效(例如错误的深度、错误的变量名),OmegaConf 将抛出错误omegaconf.errors.InterpolationResolutionError It is also used in Hydra for configuring complex applications.它还在Hydra中用于配置复杂的应用程序。

I have written my own library on Python to expand variables being loaded from directories with a hierarchy like:我在 Python 上编写了自己的库,以扩展从目录加载的变量,其层次结构如下:

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

The key difference here is that the expansion must be applied only after all the config.yaml files is loaded, where the variables from the next file can override the variables from the previous, so the pseudocode should look like this:这里的关键区别在于,扩展必须仅在加载所有config.yaml文件后应用,其中下一个文件中的变量可以覆盖上一个文件中的变量,因此伪代码应如下所示:

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

As an additional option the xonsh script can export the resulting variables into environment variables (see the yaml_update_global_vars function).作为附加选项, xonsh脚本可以将结果变量导出到环境变量中(请参阅yaml_update_global_vars函数)。

The scripts:脚本:

https://sourceforge.net/p/tacklelib/tacklelib/HEAD/tree/trunk/python/cmdoplib/cmdoplib.yaml.xsh https://sourceforge.net/p/tacklelib/tacklelib/HEAD/tree/trunk/python/tacklelib/tacklelib.yaml.py https://sourceforge.net/p/tacklelib/tacklelib/HEAD/tree/trunk/python/cmdoplib/cmdoplib.yaml.xsh https://sourceforge.net/p/tacklelib/tacklelib/HEAD/tree/trunk/python /tacklelib/tacklelib.yaml.py

Pros :优点

  • simple, does not support recursion and nested variables简单,不支持递归和嵌套变量
  • can replace an undefined variable to a placeholder ( ${MYUNDEFINEDVAR} -> *$/{MYUNDEFINEDVAR} )可以将未定义的变量替换为占位符( ${MYUNDEFINEDVAR} -> *$/{MYUNDEFINEDVAR}
  • can expand a reference from environment variable ( ${env:MYVAR} )可以从环境变量( ${env:MYVAR} )扩展引用
  • can replace all \\ to / in a path variable ( ${env:MYVAR:path} )可以将路径变量中的所有\\替换为/${env:MYVAR:path}

Cons :缺点

  • does not support nested variables, so can not expand values in nested dictionaries (something like ${MYSCOPE.MYVAR} is not implemented)不支持嵌套变量,因此无法扩展嵌套字典中的值(未实现${MYSCOPE.MYVAR}类的东西)
  • does not detect expansion recursion, including recursion after a placeholder put不检测扩展递归,包括占位符放置后的递归

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM