我有以下YAML:

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

我如何“正常化”它,通过从三个路径中删除/path/到/root/,并将其作为自己的设置,类似于:

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

显然这是无效的,我瞎编的。真正的语法是什么?这能做到吗?


当前回答

是的,使用自定义标签。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]就是您所期望的那样。

其他回答

我认为这是不可能的。您可以重用“节点”,但不能重用它的一部分。

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

这是完全有效的YAML,并且给出的字段和家族在ship-to block中被重用。您可以以同样的方式重用标量节点,但您无法更改内部内容并从YAML中添加路径的最后一部分。

如果重复让你很困扰,我建议让你的应用程序意识到根属性,并将它添加到每个看起来相对而不是绝对的路径。

是的,使用自定义标签。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]就是您所期望的那样。

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 /数据/ in / / home /数据/ in / p1

使用OmegaConf

OmegaConf是一个基于yaml的分层配置系统,在变量插值功能下支持此功能。使用OmegaConf v2.2.2:

创建YAML文件路径。Yaml如下:

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

然后我们可以读取可变路径的文件:

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'

深度和交叉裁判

可以定义更复杂的(嵌套的)结构,使用变量的相对深度来引用其他变量:

创建另一个文件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}

我们可以再次检查:

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'

在无效引用的情况下(例如错误的深度,错误的变量名),OmegaConf将抛出错误OmegaConf .errors. interpolationresolutionerror。在Hydra中,它还用于配置复杂的应用程序。

我已经创建了一个库,在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',
);