【问题标题】:YAML preprocessor / macro processorYAML 预处理器/宏处理器
【发布时间】:2015-07-31 10:53:28
【问题描述】:

是否有一种简单的方法可以将预处理器/宏处理器与 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 cmets 中使用的 # 字符。此外,理想情况下,我们希望有由预处理器扩展的循环,因此在上面的示例中,我们将创建 UK 和 USA 以及循环(我不相信您可以使用 cpp 循环)。

有什么想法吗?

【问题讨论】:

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

标签: preprocessor yaml


【解决方案1】:
# 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 }

【讨论】:

    【解决方案2】:

    您正在尝试在 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 的值与 action 关键字的开头匹配(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文件的节点的缩进级别没有补偿。

    【讨论】:

      猜你喜欢
      • 2015-04-15
      • 1970-01-01
      • 2010-09-19
      • 2020-02-09
      • 1970-01-01
      • 2021-12-29
      • 1970-01-01
      • 2013-05-13
      相关资源
      最近更新 更多