1 | """ |
---|
2 | This is a heavily hacked version of ConfigParser to keep the order in |
---|
3 | which options and sections are read, and allow multiple options with |
---|
4 | the same key. |
---|
5 | """ |
---|
6 | from __future__ import generators |
---|
7 | import string, types |
---|
8 | import re |
---|
9 | |
---|
10 | __all__ = ["NoSectionError","DuplicateSectionError","NoOptionError", |
---|
11 | "InterpolationError","InterpolationDepthError","ParsingError", |
---|
12 | "MissingSectionHeaderError","ConfigParser", |
---|
13 | "MAX_INTERPOLATION_DEPTH"] |
---|
14 | |
---|
15 | DEFAULTSECT = "DEFAULT" |
---|
16 | |
---|
17 | MAX_INTERPOLATION_DEPTH = 10 |
---|
18 | |
---|
19 | # exception classes |
---|
20 | class Error(Exception): |
---|
21 | def __init__(self, msg=''): |
---|
22 | self._msg = msg |
---|
23 | Exception.__init__(self, msg) |
---|
24 | def __repr__(self): |
---|
25 | return self._msg |
---|
26 | __str__ = __repr__ |
---|
27 | |
---|
28 | class NoSectionError(Error): |
---|
29 | def __init__(self, section): |
---|
30 | Error.__init__(self, 'No section: %s' % section) |
---|
31 | self.section = section |
---|
32 | |
---|
33 | class DuplicateSectionError(Error): |
---|
34 | def __init__(self, section): |
---|
35 | Error.__init__(self, "Section %s already exists" % section) |
---|
36 | self.section = section |
---|
37 | |
---|
38 | class NoOptionError(Error): |
---|
39 | def __init__(self, option, section): |
---|
40 | Error.__init__(self, "No option `%s' in section: %s" % |
---|
41 | (option, section)) |
---|
42 | self.option = option |
---|
43 | self.section = section |
---|
44 | |
---|
45 | class InterpolationError(Error): |
---|
46 | def __init__(self, reference, option, section, rawval): |
---|
47 | Error.__init__(self, |
---|
48 | "Bad value substitution:\n" |
---|
49 | "\tsection: [%s]\n" |
---|
50 | "\toption : %s\n" |
---|
51 | "\tkey : %s\n" |
---|
52 | "\trawval : %s\n" |
---|
53 | % (section, option, reference, rawval)) |
---|
54 | self.reference = reference |
---|
55 | self.option = option |
---|
56 | self.section = section |
---|
57 | |
---|
58 | class InterpolationDepthError(Error): |
---|
59 | def __init__(self, option, section, rawval): |
---|
60 | Error.__init__(self, |
---|
61 | "Value interpolation too deeply recursive:\n" |
---|
62 | "\tsection: [%s]\n" |
---|
63 | "\toption : %s\n" |
---|
64 | "\trawval : %s\n" |
---|
65 | % (section, option, rawval)) |
---|
66 | self.option = option |
---|
67 | self.section = section |
---|
68 | |
---|
69 | class ParsingError(Error): |
---|
70 | def __init__(self, filename): |
---|
71 | Error.__init__(self, 'File contains parsing errors: %s' % filename) |
---|
72 | self.filename = filename |
---|
73 | self.errors = [] |
---|
74 | |
---|
75 | def append(self, lineno, line): |
---|
76 | self.errors.append((lineno, line)) |
---|
77 | self._msg = self._msg + '\n\t[line %2d]: %s' % (lineno, line) |
---|
78 | |
---|
79 | class MissingSectionHeaderError(ParsingError): |
---|
80 | def __init__(self, filename, lineno, line): |
---|
81 | Error.__init__( |
---|
82 | self, |
---|
83 | 'File contains no section headers.\nfile: %s, line: %d\n%s' % |
---|
84 | (filename, lineno, line)) |
---|
85 | self.filename = filename |
---|
86 | self.lineno = lineno |
---|
87 | self.line = line |
---|
88 | |
---|
89 | class ConfigParser: |
---|
90 | def __init__(self, defaults=None): |
---|
91 | # Options are stored in __sections_list like this: |
---|
92 | # [(sectname, [(optname, optval), ...]), ...] |
---|
93 | self.__sections_list = [] |
---|
94 | self.__sections_dict = {} |
---|
95 | if defaults is None: |
---|
96 | self.__defaults = {} |
---|
97 | else: |
---|
98 | self.__defaults = defaults |
---|
99 | |
---|
100 | def defaults(self): |
---|
101 | return self.__defaults |
---|
102 | |
---|
103 | def sections(self): |
---|
104 | return self.__sections_dict.keys() |
---|
105 | |
---|
106 | def has_section(self, section): |
---|
107 | return self.__sections_dict.has_key(section) |
---|
108 | |
---|
109 | def options(self, section): |
---|
110 | self.__sections_dict[section] |
---|
111 | try: |
---|
112 | opts = self.__sections_dict[section].keys() |
---|
113 | except KeyError: |
---|
114 | raise NoSectionError(section) |
---|
115 | return self.__defaults.keys()+opts |
---|
116 | |
---|
117 | def read(self, filenames): |
---|
118 | if type(filenames) in types.StringTypes: |
---|
119 | filenames = [filenames] |
---|
120 | for filename in filenames: |
---|
121 | try: |
---|
122 | fp = open(filename) |
---|
123 | except IOError: |
---|
124 | continue |
---|
125 | self.__read(fp, filename) |
---|
126 | fp.close() |
---|
127 | |
---|
128 | def readfp(self, fp, filename=None): |
---|
129 | if filename is None: |
---|
130 | try: |
---|
131 | filename = fp.name |
---|
132 | except AttributeError: |
---|
133 | filename = '<???>' |
---|
134 | self.__read(fp, filename) |
---|
135 | |
---|
136 | def set(self, section, option, value): |
---|
137 | if self.__sections_dict.has_key(section): |
---|
138 | sectdict = self.__sections_dict[section] |
---|
139 | sectlist = [] |
---|
140 | self.__sections_list.append((section, sectlist)) |
---|
141 | elif section == DEFAULTSECT: |
---|
142 | sectdict = self.__defaults |
---|
143 | sectlist = None |
---|
144 | else: |
---|
145 | sectdict = {} |
---|
146 | self.__sections_dict[section] = sectdict |
---|
147 | sectlist = [] |
---|
148 | self.__sections_list.append((section, sectlist)) |
---|
149 | xform = self.optionxform(option) |
---|
150 | sectdict[xform] = value |
---|
151 | if sectlist is not None: |
---|
152 | sectlist.append([xform, value]) |
---|
153 | |
---|
154 | def get(self, section, option, raw=0, vars=None): |
---|
155 | d = self.__defaults.copy() |
---|
156 | try: |
---|
157 | d.update(self.__sections_dict[section]) |
---|
158 | except KeyError: |
---|
159 | if section != DEFAULTSECT: |
---|
160 | raise NoSectionError(section) |
---|
161 | if vars: |
---|
162 | d.update(vars) |
---|
163 | option = self.optionxform(option) |
---|
164 | try: |
---|
165 | rawval = d[option] |
---|
166 | except KeyError: |
---|
167 | raise NoOptionError(option, section) |
---|
168 | if raw: |
---|
169 | return rawval |
---|
170 | return self.__interpolate(rawval, d) |
---|
171 | |
---|
172 | def getall(self, section, option, raw=0, vars=None): |
---|
173 | option = self.optionxform(option) |
---|
174 | values = [] |
---|
175 | d = self.__defaults.copy() |
---|
176 | if section != DEFAULTSECT: |
---|
177 | for sectname, options in self.__sections_list: |
---|
178 | if sectname == section: |
---|
179 | for optname, value in options: |
---|
180 | if optname == option: |
---|
181 | values.append(value) |
---|
182 | d[optname] = value |
---|
183 | if raw: |
---|
184 | return values |
---|
185 | if vars: |
---|
186 | d.update(vars) |
---|
187 | for i in len(values): |
---|
188 | values[i] = self.__interpolate(values[i], d) |
---|
189 | return values |
---|
190 | |
---|
191 | def walk(self, section, option=None, raw=0, vars=None): |
---|
192 | # Build dictionary for interpolation |
---|
193 | try: |
---|
194 | d = self.__sections_dict[section].copy() |
---|
195 | except KeyError: |
---|
196 | if section == DEFAULTSECT: |
---|
197 | d = {} |
---|
198 | else: |
---|
199 | raise NoSectionError(section) |
---|
200 | d.update(self.__defaults) |
---|
201 | if vars: |
---|
202 | d.update(vars) |
---|
203 | |
---|
204 | # Start walking |
---|
205 | if option: |
---|
206 | option = self.optionxform(option) |
---|
207 | if section != DEFAULTSECT: |
---|
208 | for sectname, options in self.__sections_list: |
---|
209 | if sectname == section: |
---|
210 | for optname, value in options: |
---|
211 | if not option or optname == option: |
---|
212 | if not raw: |
---|
213 | value = self.__interpolate(value, d) |
---|
214 | yield (optname, value) |
---|
215 | |
---|
216 | def __interpolate(self, value, vars): |
---|
217 | rawval = value |
---|
218 | depth = 0 |
---|
219 | while depth < 10: |
---|
220 | depth = depth + 1 |
---|
221 | if value.find("%(") >= 0: |
---|
222 | try: |
---|
223 | value = value % vars |
---|
224 | except KeyError, key: |
---|
225 | raise InterpolationError(key, option, section, rawval) |
---|
226 | else: |
---|
227 | break |
---|
228 | if value.find("%(") >= 0: |
---|
229 | raise InterpolationDepthError(option, section, rawval) |
---|
230 | return value |
---|
231 | |
---|
232 | def __get(self, section, conv, option): |
---|
233 | return conv(self.get(section, option)) |
---|
234 | |
---|
235 | def getint(self, section, option): |
---|
236 | return self.__get(section, string.atoi, option) |
---|
237 | |
---|
238 | def getfloat(self, section, option): |
---|
239 | return self.__get(section, string.atof, option) |
---|
240 | |
---|
241 | def getboolean(self, section, option): |
---|
242 | states = {'1': 1, 'yes': 1, 'true': 1, 'on': 1, |
---|
243 | '0': 0, 'no': 0, 'false': 0, 'off': 0} |
---|
244 | v = self.get(section, option) |
---|
245 | if not states.has_key(v.lower()): |
---|
246 | raise ValueError, 'Not a boolean: %s' % v |
---|
247 | return states[v.lower()] |
---|
248 | |
---|
249 | def optionxform(self, optionstr): |
---|
250 | #return optionstr.lower() |
---|
251 | return optionstr |
---|
252 | |
---|
253 | def has_option(self, section, option): |
---|
254 | """Check for the existence of a given option in a given section.""" |
---|
255 | if not section or section == "DEFAULT": |
---|
256 | return self.__defaults.has_key(option) |
---|
257 | elif not self.has_section(section): |
---|
258 | return 0 |
---|
259 | else: |
---|
260 | option = self.optionxform(option) |
---|
261 | return self.__sections_dict[section].has_key(option) |
---|
262 | |
---|
263 | SECTCRE = re.compile(r'\[(?P<header>[^]]+)\]') |
---|
264 | OPTCRE = re.compile(r'(?P<option>\S+)\s*(?P<vi>[:=])\s*(?P<value>.*)$') |
---|
265 | |
---|
266 | def __read(self, fp, fpname): |
---|
267 | cursectdict = None # None, or a dictionary |
---|
268 | optname = None |
---|
269 | lineno = 0 |
---|
270 | e = None # None, or an exception |
---|
271 | while 1: |
---|
272 | line = fp.readline() |
---|
273 | if not line: |
---|
274 | break |
---|
275 | lineno = lineno + 1 |
---|
276 | # comment or blank line? |
---|
277 | if line.strip() == '' or line[0] in '#;': |
---|
278 | continue |
---|
279 | if line.split()[0].lower() == 'rem' \ |
---|
280 | and line[0] in "rR": # no leading whitespace |
---|
281 | continue |
---|
282 | # continuation line? |
---|
283 | if line[0] in ' \t' and cursectdict is not None and optname: |
---|
284 | value = line.strip() |
---|
285 | if value: |
---|
286 | k = self.optionxform(optname) |
---|
287 | cursectdict[k] = "%s\n%s" % (cursectdict[k], value) |
---|
288 | cursectlist[-1][1] = "%s\n%s" % (cursectlist[-1][1], value) |
---|
289 | # a section header or option header? |
---|
290 | else: |
---|
291 | # is it a section header? |
---|
292 | mo = self.SECTCRE.match(line) |
---|
293 | if mo: |
---|
294 | sectname = mo.group('header') |
---|
295 | if self.__sections_dict.has_key(sectname): |
---|
296 | cursectdict = self.__sections_dict[sectname] |
---|
297 | cursectlist = [] |
---|
298 | self.__sections_list.append((sectname, cursectlist)) |
---|
299 | elif sectname == DEFAULTSECT: |
---|
300 | cursectdict = self.__defaults |
---|
301 | cursectlist = None |
---|
302 | else: |
---|
303 | cursectdict = {} |
---|
304 | self.__sections_dict[sectname] = cursectdict |
---|
305 | cursectlist = [] |
---|
306 | self.__sections_list.append((sectname, cursectlist)) |
---|
307 | # So sections can't start with a continuation line |
---|
308 | optname = None |
---|
309 | # no section header in the file? |
---|
310 | elif cursectdict is None: |
---|
311 | raise MissingSectionHeaderError(fpname, lineno, `line`) |
---|
312 | # an option line? |
---|
313 | else: |
---|
314 | mo = self.OPTCRE.match(line) |
---|
315 | if mo: |
---|
316 | optname, vi, optval = mo.group('option', 'vi', 'value') |
---|
317 | if vi in ('=', ':') and ';' in optval: |
---|
318 | # ';' is a comment delimiter only if it follows |
---|
319 | # a spacing character |
---|
320 | pos = optval.find(';') |
---|
321 | if pos and optval[pos-1] in string.whitespace: |
---|
322 | optval = optval[:pos] |
---|
323 | optval = optval.strip() |
---|
324 | # allow empty values |
---|
325 | if optval == '""': |
---|
326 | optval = '' |
---|
327 | xform = self.optionxform(optname) |
---|
328 | cursectdict[xform] = optval |
---|
329 | if cursectlist is not None: |
---|
330 | cursectlist.append([xform, optval]) |
---|
331 | else: |
---|
332 | # a non-fatal parsing error occurred. set up the |
---|
333 | # exception but keep going. the exception will be |
---|
334 | # raised at the end of the file and will contain a |
---|
335 | # list of all bogus lines |
---|
336 | if not e: |
---|
337 | e = ParsingError(fpname) |
---|
338 | e.append(lineno, `line`) |
---|
339 | # if any parsing errors occurred, raise an exception |
---|
340 | if e: |
---|
341 | raise e |
---|
342 | |
---|
343 | # Here we wrap this hacked ConfigParser into something more useful |
---|
344 | # for us. |
---|
345 | |
---|
346 | import os |
---|
347 | |
---|
348 | class Config: |
---|
349 | def __init__(self): |
---|
350 | self._config = ConfigParser() |
---|
351 | self._wrapped = {} |
---|
352 | conffiles = [] |
---|
353 | conffiles.append("/etc/repsys.conf") |
---|
354 | repsys_conf = os.environ.get("REPSYS_CONF") |
---|
355 | if repsys_conf: |
---|
356 | conffiles.append(repsys_conf) |
---|
357 | conffiles.append(os.path.expanduser("~/.repsys/config")) |
---|
358 | for file in conffiles: |
---|
359 | if os.path.isfile(file): |
---|
360 | self._config.read(file) |
---|
361 | |
---|
362 | def wrap(self, section, handler, option=None): |
---|
363 | """Set one wrapper for a given section |
---|
364 | |
---|
365 | The wrapper must be a function |
---|
366 | f(section, option=None, default=None, walk=False). |
---|
367 | """ |
---|
368 | self._wrapped[section] = handler |
---|
369 | |
---|
370 | def sections(self): |
---|
371 | try: |
---|
372 | return self._config.sections() |
---|
373 | except Error: |
---|
374 | return [] |
---|
375 | |
---|
376 | def options(self, section): |
---|
377 | try: |
---|
378 | return self._config.options(section) |
---|
379 | except Error: |
---|
380 | return [] |
---|
381 | |
---|
382 | def set(self, section, option, value): |
---|
383 | return self._config.set(section, option, value) |
---|
384 | |
---|
385 | def walk(self, section, option=None, raw=0, vars=None): |
---|
386 | handler = self._wrapped.get(section) |
---|
387 | if handler: |
---|
388 | return handler(section, option, walk=True) |
---|
389 | return self._config.walk(section, option, raw, vars) |
---|
390 | |
---|
391 | def get(self, section, option, default=None, raw=False, wrap=True): |
---|
392 | if wrap: |
---|
393 | handler = self._wrapped.get(section) |
---|
394 | if handler: |
---|
395 | handler = self._wrapped.get(section) |
---|
396 | return handler(section, option, default) |
---|
397 | try: |
---|
398 | return self._config.get(section, option, raw=raw) |
---|
399 | except Error: |
---|
400 | return default |
---|
401 | |
---|
402 | def getint(self, section, option, default=None): |
---|
403 | ret = self.get(section, option, default) |
---|
404 | if type(ret) == type(""): |
---|
405 | return int(ret) |
---|
406 | |
---|
407 | def getbool(self, section, option, default=None): |
---|
408 | ret = self.get(section, option, default) |
---|
409 | states = {'1': 1, 'yes': 1, 'true': 1, 'on': 1, |
---|
410 | '0': 0, 'no': 0, 'false': 0, 'off': 0} |
---|
411 | if type(ret) == type("") and states.has_key(ret.lower()): |
---|
412 | return states[ret.lower()] |
---|
413 | return default |
---|
414 | |
---|
415 | def test(): |
---|
416 | config = Config() |
---|
417 | def handler(section, option=None, default=None, walk=False): |
---|
418 | d = {"fulano": "ciclano", |
---|
419 | "foolano": "ceeclano"} |
---|
420 | if walk: |
---|
421 | return d.items() |
---|
422 | elif option in d: |
---|
423 | return d[option] |
---|
424 | else: |
---|
425 | return config.get(section, option, default, wrap=False) |
---|
426 | config.wrap("users", handler=handler) |
---|
427 | print config.get("users", "fulano") # found in wrapper |
---|
428 | print config.get("users", "andreas") # found in repsys.conf |
---|
429 | print config.walk("users") |
---|
430 | |
---|
431 | if __name__ == "__main__": |
---|
432 | test() |
---|
433 | # vim:ts=4:sw=4:et |
---|