如何从同一 YAML 文件的其他地方引用 YAML“设置”?
Posted
技术标签:
【中文标题】如何从同一 YAML 文件的其他地方引用 YAML“设置”?【英文标题】:how to reference a YAML "setting" from elsewhere in the same YAML file? 【发布时间】:2011-01-05 00:54:09 【问题描述】:我有以下 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
显然这是无效的,我只是编造的。真正的语法是什么?能做到吗?
【问题讨论】:
另见: ***.com/a/41620747/42223 参见:YAML 规范:yaml.org/spec 【参考方案1】:我在 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/tacklelib/tacklelib/HEAD/tree/trunk/python/cmdoplib/cmdoplib.yaml.xsh https://sourceforge.net/p/tacklelib/tacklelib/HEAD/tree/trunk/python/tacklelib/tacklelib.yaml.py
优点:
简单,不支持递归和嵌套变量 可以将未定义的变量替换为占位符 ($MYUNDEFINEDVAR
-> *$/MYUNDEFINEDVAR
)
可以扩展来自环境变量的引用 ($env:MYVAR
)
可以将路径变量中的所有\\
替换为/
($env:MYVAR:path
)
缺点:
不支持嵌套变量,因此无法扩展嵌套字典中的值(未实现$MYSCOPE.MYVAR
之类的东西)
不检测扩展递归,包括放置占位符后的递归
【讨论】:
【参考方案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."
编者注:海报也是这个包的作者。
【讨论】:
【参考方案3】:使用Yglu,您可以将示例编写为:
paths:
root: /path/to/root/
patha: !? .paths.root + a
pathb: !? .paths.root + b
pathc: !? .paths.root + c
免责声明:我是 Yglu 的作者。
【讨论】:
很高兴知道有一个库在 YAML 之上添加了此功能 感谢您制作这个。 YAML 这些天风靡一时,很高兴看到一个项目来帮助遏制这种疯狂。【参考方案4】:是的,使用自定义标签。 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]
可以满足您的期望。
【讨论】:
我喜欢你的解决方案,编码比我的更简单,但 YAML 的可读性略低。 这个答案值得更多的投票。根据 YAML 规范,它在技术上是最准确的答案。然而,有一个警告,根据实际的 YAML 实现,很少有真正实现完整的 YAML 规范。 Python 的 pyyaml 在与规范的一致性方面超越了许多其他语言。 问题似乎是关于在 yaml 文件中引用一个值。在它周围添加另一层代码不是我的首选解决方案。 @ChrisJohnson 感谢您的回答,我想知道您是否有列出此语法的参考文档。我已经在网络上的多个地方看到了 YAML 规范的解释,所以我只是想确保我正在查看与您相同的参考。谢谢! 这个解决方案对我不起作用(python3
?),但是通过对上面的简单修改它可以按预期工作。具体来说:yaml.SafeLoader.add_constructor(tag='!join', constructor=join)
yaml.load(open(fpth, mode='r'), Loader=yaml.SafeLoader)
【参考方案5】:
YML 定义:
dir:
default: /home/data/in/
proj1: $dir.defaultp1
proj2: $dir.defaultp2
proj3: $dir.defaultp3
百里香叶中的某处
<p th:utext='$@environment.getProperty("dir.default")' />
<p th:utext='$@environment.getProperty("dir.proj1")' />
输出: /家/数据/在/ /home/data/in/p1
【讨论】:
@AndrewBullock 我认为这应该是公认的答案,因为它完全解决了您的问题。 不,它不是 YAML 中变量的原生用法,也没有在任何规范版本中指定。经过一些测试,这不起作用。 这可能对 Pavol 使用了一些预处理 yaml 的东西(即 maven-resources-plugin 过滤) 非标准 Yaml【参考方案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
成为CommentedMap
(从ruamel.yaml.comments.py
导入)
【讨论】:
【参考方案8】:查看此问题的另一种方法是简单地使用另一个字段。
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
【讨论】:
【参考方案9】:我认为这是不可能的。您可以重用“节点”,但不能重用它的一部分。
bill-to: &id001
given : Chris
family : Dumars
ship-to: *id001
这是完全有效的 YAML,given
和 family
字段在 ship-to
块中重复使用。您可以以相同的方式重用标量节点,但您无法更改内部的内容并从 YAML 内部添加路径的最后一部分。
如果重复让您感到困扰,我建议让您的应用程序了解 root
属性并将其添加到每个看起来相对而不是绝对的路径。
【讨论】:
好的,谢谢,是的,我必须在代码中添加root
。没什么大不了的。
接受的答案不准确。请参阅我的答案以获得解决方案。
如何做到这一点,如果 bill-to 在另一个文件中,我们已经导入其中定义了 ship-to 的位置?
@PrateekJain:如果您正在处理多个文件,您可能最好评估一个独立的 YAML 增强库,例如此处列出的一个。 github.com/dreftymac/dynamic.yaml/blob/master/…
见yaml.org/spec/1.2/spec.html中的示例2.9;还可以引用非常棒的标量以上是关于如何从同一 YAML 文件的其他地方引用 YAML“设置”?的主要内容,如果未能解决你的问题,请参考以下文章
当一条线有破折号而同一级别的其他线没有破折号时,啥是 yaml 结构?
如何在 cloudformation 策略文档中引用资源 ARN? (yaml)