如何从同一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

显然那是无效的,我只是弥补了。真正的语法是什么?能做到吗


Answers:


127

我认为不可能。您可以重用“节点”,但不能重用。

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

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

如果重复使您感到困扰,我建议使您的应用程序了解root属性,并将其添加到看起来相对(不是绝对)的路径中。


1
好的,谢谢,我必须root在代码中加上前缀。没关系
安德鲁·布洛克

2
接受的答案不正确。请参阅我的答案以寻求解决方案。
克里斯·约翰逊

如果bill-to在另一个文件中,我们已经在定义Ship-to的地方导入了该文件,该 怎么做?
Prateek Jain 2015年

@PrateekJain:如果要处理多个文件,则可能最好评估一个独立的YAML增强库,例如此处列出的一个。github.com/dreftymac/dynamic.yaml/blob/master/...
dreftymac

1
参见yaml.org/spec/1.2/spec.html中的示例2.9 ;一个人也可以引用很棒的标量
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为代价。
Anthon

7
这个答案值得更多的投票。根据YAML规范,从技术上讲,这是最准确的答案。需要注意的是,根据实际的YAML 实现,很少有实际实现完整的YAML规范。就其与规范的一致性而言,Python的pyyaml超越了许多其他。
dreftymac

5
问题似乎与在yaml文件中引用值有关。在它周围添加另一层代码不是我的首选解决方案。
user2020056

1
@ChrisJohnson感谢您的答复,我想知道您是否有列出此语法的参考文档。我已经在网络上的多个地方看到了YAML规范的解释,所以我只想确保我要查找的是相同的参考文献。谢谢!
user5359531 '10

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
这可能使用的东西,预处理的YAML(即Maven的资源-插件过滤)工作了帕沃尔
DeezCashews

1
非标准Yaml
Dan

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.d一个CommentedMap(从导入ruamel.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 /工具/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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.