toml不支持继承? 手撸一个继承机制

配置文件的格式有很多:ini,json,xml,yaml,toml等。 

ini功能过于简单;
json手写不够方便;
xml复杂且难于编辑和阅读;
yaml规则太多而且坑也不少(比如不支持多行字符串);
因此toml横空出世,语法优雅,易于阅读,灵活且严谨,它可能是目前配置文件这个场景下最合适的解决方案。

但它也有不够完美的地方,笔者在使用过程中就发现,yaml中支持的继承功能(虽然用起来很恶心),在toml中是不支持的。

比如如下两个toml:

Default:
>[openconfig-optical-amplifier]
name.len = [2, 20]
gain_tilt.range = [-1, 1]
"a:b/c/d".len = [1, 20]

Ver1:
>[openconfig-optical-amplifier]
name.len = [2, 30]

#### 如果想在Ver1中复用Default,目前似乎没有很好的办法。
所以笔者动手做了一些改造,使用一个 '>' 标记将多个toml块分开,命名并在其后括号中注明继承自哪个父块,分别去读这些块,再跟继承的块合并,格式如下:
```
>Default:
[openconfig-optical-amplifier]
name.len = [2, 20]
gain_tilt.range = [-1, 1]
"a:b/c/d".len = [1, 20]

>Ver1(Default):
[openconfig-optical-amplifier]
name.len = [2, 30]

>Ver2(Default, Ver1):
a = 1
```
最终输出结果:
```
{
 "openconfig-optical-amplifier": {
"name": {"len": [2, 20]},
"gain_tilt":{"range": [-1, 1]},
"a:b/c/d": {"len" = [1, 20]}
},

 "Ver1": {
"name": {"len": [2, 30]},
"gain_tilt":{"range": [-1, 1]},
"a:b/c/d": {"len" = [1, 20]}
},

 "Ver2": {
"a": 1,
"name": {"len": [2, 30]},
"gain_tilt":{"range": [-1, 1]},
"a:b/c/d": {"len" = [1, 20]}
}
}
```
## 代码实现:
```python
"""
@Author: Sanmao.Wu
@File: inheritableToml.py
@Time: 2022/2/24 22:12 
@FileDesc: 
"""

from copy import deepcopy
import toml
import pprint


def merge(source, destination):
    """
    Function: Merge two dict by recursion way.

    run me with nosetests --with-doctest file.py
    >>> a = { 'first' : { 'all_rows' : { 'pass' : 'dog', 'number' : '1' } } }
    >>> b = { 'first' : { 'all_rows' : { 'fail' : 'cat', 'number' : '5' } } }
    >>> merge(b, a) == { 'first' : { 'all_rows' : { 'pass' : 'dog', 'fail' : 'cat', 'number' : '5' } } }
    True
    """
    for key, value in source.items():
        if isinstance(value, dict):
            # get node or create one
            node = destination.setdefault(key, {})
            merge(value, node)
        else:
            destination[key] = value
    return destination


class InheritableToml:
    def __init__(self):
        self.text = ''
        self.dict = {}

    def __parse_title(self, title):
        """
        Parse title like:
        e.g: 
             blockA(blockB, blockC)
             or
             blockA
        """
        if '(' in title:
            class_name, others = title.split('(', maxsplit=1)
            parents = [item.strip() for item in others.rstrip(')').split(',')]
        else:
            class_name = title
            parents = []
        return class_name, parents

    def iter_block(self):
        """
        A toml block is guided by  '>'  symbol, following which is block name. 
        e.g: >blockA:
             a.b = 1
              
             >blockB:
             c = 2
        """
        block, class_name, parents = [], '', []
        for line in self.text.split('\n'):
            print(line)
            if not line:
                continue
            line = line.strip()
            if line.startswith('>'):
                if block:
                    toml_block = '\n'.join(block)
                    yield class_name, parents, toml_block
                    block = []
                title = line.strip('>:').strip()
                class_name, parents = self.__parse_title(title)
            else:
                block.append(line)
        toml_block = '\n'.join(block)
        yield class_name, parents, toml_block

    def gather_blocks(self):
        """
        Gather all blocks into one dict, thus we can reference any block without thinking about appearance order
        """
        blocks = {}
        for class_name, parents, toml_block in self.iter_block():
            blocks[class_name] = {}
            blocks[class_name]['parents'] = parents
            blocks[class_name]['dict'] = toml.loads(toml_block)
        return blocks

    def inherit(self):
        """
        Process inherit relationship.
        e.g: >blockB(blockA):
        blockB will inherit from blockA and then update its own paths.  
        """
        blocks = self.gather_blocks()
        for class_name, block in blocks.items():
            parents = block['parents']
            self.dict[class_name] = {}
            for parent in parents:
                parent_dict = blocks[parent].get('dict')
                if parent_dict is None:
                    raise NameError(f"No such parent block: '{parent}'")
                self.dict[class_name] = merge(parent_dict, self.dict[class_name])
            self.dict[class_name] = merge(block['dict'], self.dict[class_name])

    def load(self, io):
        self.text = io.read()
        self.inherit()
        return self.dict

    def loads(self, text):
        self.text = text
        self.inherit()
        return self.dict


if __name__ == '__main__':
    s = '''
>Default:
[openconfig-optical-amplifier]
name.len = [2, 20]
gain_tilt.range = [-1, 1]
"a:b/c/d".len = [1, 20]

>Ver1(Default):
[openconfig-optical-amplifier]
name.len = [2, 30]

>Ver2(Default, Ver1):
a = 1
'''
    pprint.pprint(InheritableToml().loads(s))
```

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值