是否有一种简单的方法可以将预处理器/宏处理器与 YAML 文件一起使用? (也就是说,我正在考虑类似于 C 预处理器的东西)?
我们有很多描述各种数据结构的平面文本文件。它们目前采用我们自己的内部格式,并使用内部解析器读取。我想切换到 YAML 文件以利用各种预先存在的库进行读写。
但是我们的文件是分层的,因为我们将主文件“包含”到子文件中,并使用变量替换生成新的数据结构。
作为一个玩具示例,我想要类似的东西:
country_master.yaml
name: $COUNTRY$
file: C:\data\$COUNTRY$
UK_country.yaml
#define $COUNTRY$ UK
#include <country_master.yaml>
USA_country.yaml
#define $COUNTRY$ USA
#include <country_master.yaml>
经过预处理后,我们会得到类似的结果:
name: USA
file: C:\data\USA
C 预处理器无法处理 YAML 注释中使用的 # 字符。另外,理想情况下,我们希望拥有由预处理器扩展的循环,因此在上面的示例中,我们将创建 UK 和 USA 以及循环(我不相信您可以使用
cpp
进行循环)。
有什么想法吗?
# Yamp - YAML Macro-Processor
# https://github.com/birchb1024/yamp
# in master.yaml
defmacro:
name: country
args: [$COUNTRY$]
value:
name: $COUNTRY$
file: C:\data\{{$COUNTRY$}}
---
# in some file
- include: [master.yaml]
# Call with wherever needed:
{ country: USA }
您正在尝试在 YAML 的字符串表示级别上进行更改,但我认为您不应该这样做。 YAML 可以加载对象,并且这些对象可以通过挂钩到解析器来影响后来加载的元素。这样您就可以用数据替换完整的节点,更改标量内的值等。
假设您有这个 YAML 文件
main.yml
:
- !YAMLPreProcessor
verbose: '3'
escape: ♦
- ♦replace(verbose)
- abcd
- ♦include(xyz.yml)
- xyz
xyz.yml
是:
k: 9
l: 8
m: [7. 6] # can be either
并且您有
♦
作为特殊字符(它可以是任何内容,只要特殊的 YAMLPreProcessor 值与操作关键字的开头匹配(replace
和 include
)。您希望它是往返的(加载到内存中的数据,然后转储到以下 YAML:
- !YAMLPreProcessor
verbose: '3'
escape: ♦
- '3'
- abcd
- k: 9
l: 8
m: [7. 6] # can be either
- xyz
您可以通过重载为每个标量调用的标量构造函数和适当的
YAMLPreProcessor
类来做到这一点:
# coding: utf-8
from __future__ import print_function
import ruamel.yaml as yaml
def construct_scalar(loader, node):
self = getattr(loader, '_yaml_preprocessor', None)
if self and self.d.get('escape'):
if node.value and node.value.startswith(self.d['escape']):
key_word, rest = node.value[1:].split('(', 1)
args, rest = rest.split(')', 1)
if key_word == 'replace':
res = u''
for arg in args.split(','):
res += str(self.d[arg])
node.value = res + rest
elif key_word == 'include':
inc_yml = yaml.load(
open(args),
Loader=yaml.RoundTripLoader
)
# this needs ruamel.yaml>=0.9.6
return inc_yml
else:
print('keyword not found:', key_word)
ret_val = loader._org_construct_scalar(node)
# print('ret_val', type(ret_val), ret_val)
return ret_val
class YAMLPreProcessor:
def __init__(self, escape=None, verbose=0):
self.d = dict(escape=escape, verbose=verbose)
def __repr__(self):
return "YAMLPreProcessor({escape!r}, {verbose})".format(**self.d)
@staticmethod
def __yaml_out__(dumper, self):
return dumper.represent_mapping('!YAMLPreProcessor', self.d)
@staticmethod
def __yaml_in__(loader, data):
from ruamel.yaml.comments import CommentedMap
result = YAMLPreProcessor()
loader._yaml_preprocessor = result
z = dict()
loader.construct_mapping(data, z)
result.d = z
yield result
def __delete__(self):
loader._yaml_preprocessor = None
def construct_yaml_str(self, node):
value = self.construct_scalar(node)
if isinstance(value, ScalarString):
return value
if PY3:
return value
try:
return value.encode('ascii')
except AttributeError:
# in case you replace the node dynamically e.g. with a dict
return value
except UnicodeEncodeError:
return value
loader = yaml.RoundTripLoader
loader.add_constructor('!YAMLPreProcessor', YAMLPreProcessor.__yaml_in__)
loader._org_construct_scalar = loader.construct_scalar
loader.construct_scalar = construct_scalar
data_from_yaml = yaml.load(open('main.yml'), Loader=loader)
#print ('out', data_from_yaml)
dumper = yaml.RoundTripDumper
# need to be able to represent '!YAMLPreProcessor'
# but you can of course also remove the first element
# from data_from_yaml if you don't want the preprocessor in your output
dumper.add_representer(YAMLPreProcessor, YAMLPreProcessor.__yaml_out__)
print(yaml.dump(data_from_yaml, Dumper=dumper, allow_unicode=True))
以上需要最新版本的 ruamel.yaml (0.9.6) 作为旧版本 如果construct_scalar返回一个非字符串对象,则阻塞。
请注意行后面的注释位置
m
键相对于行的开头,在示例中
不补偿插入 xyz.yml
文件的节点的缩进级别。
Github 项目 cpphash 可以在任何文件上运行 C 预处理器 使用哈希风格的注释,包括 yaml。 举个它运行的例子 关于您的文件中的问题,请参阅此目录。
要运行该示例,请执行以下操作:
git clone [email protected]:maartenSXM/cpphash.git
cd cpphash
./install.sh -s
cd examples/stackoverflow
make