但行好事,莫问前程

挖了太多坑,一点点填回来

类似Zend_Config_Ini风格的python Parser

ini, linux, python

写完上一篇文章之后,做了一个类似Zend_Config_Ini的Python parser,可以用来解析标准和非标准格式的ini文件,但是不能写文件。如果只用来读取文件内容的话,还是挺方便的。

ini文件内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
master.host                 = stats10.mezimedia.com
master.username             = scltrk
master.password             = Mhok8homL7
master.dbname               = tracking

slave.host                  = stats11.mezimedia.com
slave.port                  = 3308
slave.username              = scltrk
slave.password              = Mhok8homL7
slave.dbname                = tracking

msgsite                     = smarter
offset                      = 20000

源代码:

(pyparseini.py) download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
#coding: utf-8

from collections import OrderedDict
from copy import deepcopy
from ast import literal_eval
import re

__all__ = ['parse_ini']

default = '__default__'

# [ production ]
REGEX_SEC = re.compile('\[\s?([\w]+)\s?\]', re.IGNORECASE)
# [ dev : production ]
REGEX_I_SEC = re.compile('\[\s?([\w]+)\s?:\s?([\w]+)\s?\]', re.IGNORECASE)

def parse_ini(ini_path, env=None):
    ini = _Obj({default: _Obj()})
    current_section = default

    with open(ini_path) as f:
        for line in f:
            line = line.strip()
            # empty line or a comment line
            if not line or line.isspace() or line[0]==';' or line[0]=='#':
                continue

            # section
            if line[0]=='[':
                # a common section 
                result = REGEX_SEC.search(line)
                if result is not None:
                    section = result.group(1)
                    ini[section] = deepcopy(ini[default])
                else:
                    # a inherited section 
                    result = REGEX_I_SEC.search(line)
                    if result in None:
                        raise SyntaxError("Invalid section declaration")
                    section = result.group(1)
                    parent = result.group(2)

                    # wrong parent section
                    if parent not in ini:
                        raise MissingSectionError("'%s' inherits from '%s' which hasn't been declared." % (section, parent))
                    ini[section] = deepcopy(ini[parent])

                current_section = section

            # option
            else:
                pieces = line.split("=")
                # master.foo.bar
                vals = pieces[0].strip().split('.')
                # ['bar', 'foo', 'master']
                vals.reverse()
                data = _cast(pieces[1].strip())
                working_obj = ini[current_section]

                while vals:
                    if len(vals)==1:
                        working_obj[vals.pop()] = data
                    else:
                        val = vals.pop()
                        if val not in working_obj:
                            working_obj[val] = _Obj()
                        working_obj = working_obj[val]

        if env is not None:
            if env not in ini:
                raise MissingSectionError("The section being loaded does not exist.")
            return ini[env]

        return ini

def _cast(val):
    try:
        val = literal_eval(val)
    except:
        pass
    return val


class _Obj(OrderedDict):
    """
    A dict that allows for object-like property access syntax.
    """
    def __copy__(self):
        data = self.__dict__.copy()
        return _Obj(data)

    def __getattr__(self, name):
        try:
            return self[name]
        except KeyError:
            try:
                return self[default][name]
            except KeyError:
                raise AttributeError(name)

    def __contains__(self, name):
        try:
            self.__getattr__(name)
        except AttributeError:
            return False
        return True


class MissingSectionError(Exception):
    """
    Thrown when a section header inherits from a section that has yet been undeclared.
    """
    pass

测试代码:

(test_parseini.py) download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#coding: utf-8

import unittest
import os
from ..pyparseini import *

PATH = os.path.dirname(__file__)

class ParseIniTestCase(unittest.TestCase):
    def setUp(self):
        """
        master.host                 = stats10.mezimedia.com
        master.username             = scltrk
        master.password             = Mhok8homL7
        master.dbname               = tracking

        slave.host                  = stats11.mezimedia.com
        slave.port                  = 3308
        slave.username              = scltrk
        slave.password              = Mhok8homL7
        slave.dbname                = tracking

        msgsite                     = smarter
        offset                      = 20000
        """
        self.file = os.path.join(PATH, 'files/smus.ini')
        self.ini = parse_ini(self.file)

    def test_master(self):
        self.assertTrue('master' in self.ini)
        self.assertEqual(self.ini.master.host, 'stats10.mezimedia.com')
        self.assertEqual(self.ini.master.username, 'scltrk')
        self.assertEqual(self.ini.master.password, 'Mhok8homL7')
        self.assertEqual(self.ini.master.dbname, 'tracking')

    def test_slave(self):
        self.assertTrue('slave' in self.ini)
        self.assertEqual(self.ini.slave.host, 'stats11.mezimedia.com')
        self.assertEqual(self.ini.slave.port, 3308)
        self.assertEqual(self.ini.slave.username, 'scltrk')
        self.assertEqual(self.ini.slave.password, 'Mhok8homL7')
        self.assertEqual(self.ini.slave.dbname, 'tracking')

    def test_msgsite(self):
        self.assertEqual(self.ini.msgsite, 'smarter')

    def test_offset(self):
        self.assertEqual(self.ini.offset, 20000)

不知道把Python的东西写成Zend的风格是不是一种好习惯。对我来说,Python很轻巧,我很喜欢,Zend虽然很重,但是大气的架构和丰富的功能以及各种设计模式的运用都非常值得学习。艺不压身,努力学习才是关键。

Have a nice day!