2015-05-19 140 views
3

有没有简单的方法来使用预处理器/宏处理器与YAML文件? (也就是说,我正在考虑沿着C预处理器的方向)?YAML预处理器/宏处理器

我们有很多描述各种数据结构的平面文本文件。它们目前采用我们自己的内部格式,并且使用内部解析器进行阅读。我想切换到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注释中使用#字符工作。此外,理想情况下,我们希望有预处理器扩展的循环,因此在上面的示例中,我们将创建英国和美国以及循环(并且我不相信您可以使用cpp循环)。

任何想法?

+0

下面是预处理递归定义变量的部分解决方案:https://bitbucket.org/djarvis/yamlp/。如果您更新它以处理包含文件,请回滚拉取请求。 –

回答

0

你正试图改变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)的版本,旧版本 扼流圈如果construct_scalar返回非字符串对象。

请注意, 的m键行后面的注释的位置是相对于该行的开始,并在那里 的例子是在xyz.yml文件是节点的缩进级别不赔偿插入。