YAML预处理器/宏处理器

8
有没有一种简单的方法可以在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进行循环)。

有什么想法吗?


这是一个递归处理变量的部分解决方案:https://bitbucket.org/djarvis/yamlp/。如果您更新它以处理包含文件,请回传一个拉取请求。 - Dave Jarvis
2个回答

6
# 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 }

1
您正在尝试更改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值与操作关键字(replaceinclude)的开头匹配),你希望这个字符被回传(加载到内存中的数据,然后转储到以下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),因为旧版本 choke if construct_scalar 返回非字符串对象。
请注意,注释在具有 m 键的行后面的位置是相对于该行开头的,而在示例中,没有对插入 xyz.yml 文件的节点的缩进级别进行补偿。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接