|
1 """ brain-dead simple parser for ini-style files. |
|
2 (C) Ronny Pfannschmidt, Holger Krekel -- MIT licensed |
|
3 """ |
|
4 __version__ = "0.2.dev2" |
|
5 |
|
6 __all__ = ['IniConfig', 'ParseError'] |
|
7 |
|
8 class ParseError(Exception): |
|
9 def __init__(self, path, lineno, msg): |
|
10 Exception.__init__(self, path, lineno, msg) |
|
11 self.path = path |
|
12 self.lineno = lineno |
|
13 self.msg = msg |
|
14 |
|
15 def __str__(self): |
|
16 return "%s:%s: %s" %(self.path, self.lineno+1, self.msg) |
|
17 |
|
18 class SectionWrapper(object): |
|
19 def __init__(self, config, name): |
|
20 self.config = config |
|
21 self.name = name |
|
22 |
|
23 def lineof(self, name): |
|
24 return self.config.lineof(self.name, name) |
|
25 |
|
26 def get(self, key, default=None, convert=str): |
|
27 return self.config.get(self.name, key, convert=convert, default=default) |
|
28 |
|
29 def __getitem__(self, key): |
|
30 return self.config.sections[self.name][key] |
|
31 |
|
32 def __iter__(self): |
|
33 section = self.config.sections.get(self.name, []) |
|
34 def lineof(key): |
|
35 return self.config.lineof(self.name, key) |
|
36 for name in sorted(section, key=lineof): |
|
37 yield name |
|
38 |
|
39 def items(self): |
|
40 for name in self: |
|
41 yield name, self[name] |
|
42 |
|
43 |
|
44 class IniConfig(object): |
|
45 def __init__(self, path, data=None): |
|
46 self.path = str(path) # convenience |
|
47 if data is None: |
|
48 f = open(self.path) |
|
49 try: |
|
50 tokens = self._parse(iter(f)) |
|
51 finally: |
|
52 f.close() |
|
53 else: |
|
54 tokens = self._parse(data.splitlines(True)) |
|
55 |
|
56 self._sources = {} |
|
57 self.sections = {} |
|
58 |
|
59 for lineno, section, name, value in tokens: |
|
60 if section is None: |
|
61 self._raise(lineno, 'no section header defined') |
|
62 self._sources[section, name] = lineno |
|
63 if name is None: |
|
64 if section in self.sections: |
|
65 self._raise(lineno, 'duplicate section %r'%(section, )) |
|
66 self.sections[section] = {} |
|
67 else: |
|
68 if name in self.sections[section]: |
|
69 self._raise(lineno, 'duplicate name %r'%(name, )) |
|
70 self.sections[section][name] = value |
|
71 |
|
72 def _raise(self, lineno, msg): |
|
73 raise ParseError(self.path, lineno, msg) |
|
74 |
|
75 def _parse(self, line_iter): |
|
76 result = [] |
|
77 section = None |
|
78 for lineno, line in enumerate(line_iter): |
|
79 name, data = self._parseline(line, lineno) |
|
80 # new value |
|
81 if name is not None and data is not None: |
|
82 result.append((lineno, section, name, data)) |
|
83 # new section |
|
84 elif name is not None and data is None: |
|
85 if not name: |
|
86 self._raise(lineno, 'empty section name') |
|
87 section = name |
|
88 result.append((lineno, section, None, None)) |
|
89 # continuation |
|
90 elif name is None and data is not None: |
|
91 if not result: |
|
92 self._raise(lineno, 'unexpected value continuation') |
|
93 last = result.pop() |
|
94 last_name, last_data = last[-2:] |
|
95 if last_name is None: |
|
96 self._raise(lineno, 'unexpected value continuation') |
|
97 |
|
98 if last_data: |
|
99 data = '%s\n%s' % (last_data, data) |
|
100 result.append(last[:-1] + (data,)) |
|
101 return result |
|
102 |
|
103 def _parseline(self, line, lineno): |
|
104 # comments |
|
105 line = line.split('#')[0].rstrip() |
|
106 # blank lines |
|
107 if not line: |
|
108 return None, None |
|
109 # section |
|
110 if line[0] == '[' and line[-1] == ']': |
|
111 return line[1:-1], None |
|
112 # value |
|
113 elif not line[0].isspace(): |
|
114 try: |
|
115 name, value = line.split('=', 1) |
|
116 if ": " in name: |
|
117 raise ValueError() |
|
118 except ValueError: |
|
119 try: |
|
120 name, value = line.split(": ", 1) |
|
121 except ValueError: |
|
122 self._raise(lineno, 'unexpected line: %r' % line) |
|
123 return name.strip(), value.strip() |
|
124 # continuation |
|
125 else: |
|
126 return None, line.strip() |
|
127 |
|
128 def lineof(self, section, name=None): |
|
129 lineno = self._sources.get((section, name)) |
|
130 if lineno is not None: |
|
131 return lineno + 1 |
|
132 |
|
133 def get(self, section, name, default=None, convert=str): |
|
134 try: |
|
135 return convert(self.sections[section][name]) |
|
136 except KeyError: |
|
137 return default |
|
138 |
|
139 def __getitem__(self, name): |
|
140 if name not in self.sections: |
|
141 raise KeyError(name) |
|
142 return SectionWrapper(self, name) |
|
143 |
|
144 def __iter__(self): |
|
145 for name in sorted(self.sections, key=self.lineof): |
|
146 yield SectionWrapper(self, name) |
|
147 |
|
148 def __contains__(self, arg): |
|
149 return arg in self.sections |