1
2
3
4
5 """Access and/or modify INI files
6
7 * Compatiable with ConfigParser
8 * Preserves order of sections & options
9 * Preserves comments/blank lines/etc
10 * More convenient access to data
11
12 Example:
13
14 >>> from StringIO import StringIO
15 >>> sio = StringIO('''# configure foo-application
16 ... [foo]
17 ... bar1 = qualia
18 ... bar2 = 1977
19 ... [foo-ext]
20 ... special = 1''')
21
22 >>> cfg = INIConfig(sio)
23 >>> print cfg.foo.bar1
24 qualia
25 >>> print cfg['foo-ext'].special
26 1
27 >>> cfg.foo.newopt = 'hi!'
28
29 >>> print cfg
30 # configure foo-application
31 [foo]
32 bar1 = qualia
33 bar2 = 1977
34 newopt = hi!
35 [foo-ext]
36 special = 1
37
38 """
39
40
41
42
43
44 import re
45 from sets import Set
46
47 from iniparse import config
48 from ConfigParser import DEFAULTSECT, ParsingError, MissingSectionHeaderError
49
51 line = None
52
56
57
58
64
65
66
68 if hasattr(self,name):
69 self.__dict__['line'] = None
70 self.__dict__[name] = value
71
73 raise Exception('This method must be overridden in derived classes')
74
75
77 regex = re.compile(r'^\['
78 r'(?P<name>[^]]+)'
79 r'\]\s*'
80 r'((?P<csep>;|#)(?P<comment>.*))?$')
81
82 - def __init__(self, name, comment=None, comment_separator=None,
83 comment_offset=-1, line=None):
84 super(SectionLine, self).__init__(line)
85 self.name = name
86 self.comment = comment
87 self.comment_separator = comment_separator
88 self.comment_offset = comment_offset
89
91 out = '[' + self.name + ']'
92 if self.comment is not None:
93
94 out = (out+' ').ljust(self.comment_offset)
95 out = out + self.comment_separator + self.comment
96 return out
97
99 m = cls.regex.match(line.rstrip())
100 if m is None:
101 return None
102 return cls(m.group('name'), m.group('comment'),
103 m.group('csep'), m.start('csep'),
104 line)
105 parse = classmethod(parse)
106
107
109 - def __init__(self, name, value, separator=' = ', comment=None,
110 comment_separator=None, comment_offset=-1, line=None):
111 super(OptionLine, self).__init__(line)
112 self.name = name
113 self.value = value
114 self.separator = separator
115 self.comment = comment
116 self.comment_separator = comment_separator
117 self.comment_offset = comment_offset
118
120 out = '%s%s%s' % (self.name, self.separator, self.value)
121 if self.comment is not None:
122
123 out = (out+' ').ljust(self.comment_offset)
124 out = out + self.comment_separator + self.comment
125 return out
126
127 regex = re.compile(r'^(?P<name>[^:=\s[][^:=\s]*)'
128 r'(?P<sep>\s*[:=]\s*)'
129 r'(?P<value>.*)$')
130
132 m = cls.regex.match(line.rstrip())
133 if m is None:
134 return None
135
136 name = m.group('name').rstrip()
137 value = m.group('value')
138 sep = m.group('sep')
139
140
141
142
143
144
145
146
147
148
149
150
151 coff = value.find(';')
152 if coff != -1 and value[coff-1].isspace():
153 comment = value[coff+1:]
154 csep = value[coff]
155 value = value[:coff].rstrip()
156 coff = m.start('value') + coff
157 else:
158 comment = None
159 csep = None
160 coff = -1
161
162 return cls(name, value, sep, comment, csep, coff, line)
163 parse = classmethod(parse)
164
165
184
185
187
190
192 if line.strip(): return None
193 return cls(line)
194 parse = classmethod(parse)
195
196
198 regex = re.compile(r'^\s+(?P<value>.*)$')
199
200 - def __init__(self, value, value_offset=8, line=None):
204
206 return ' '*self.value_offset + self.value
207
209 m = cls.regex.match(line.rstrip())
210 if m is None:
211 return None
212 return cls(m.group('value'), m.start('value'), line)
213 parse = classmethod(parse)
214
215
218 self.contents = []
219 self.orgvalue = None
220 if d:
221 if isinstance(d, list): self.extend(d)
222 else: self.add(d)
223
225 self.contents.append(x)
226
229
231 return self.contents[0].name
232
235
237 if self.orgvalue is not None:
238 return self.orgvalue
239 elif len(self.contents) == 1:
240 return self.contents[0].value
241 else:
242 return '\n'.join([str(x.value) for x in self.contents
243 if not isinstance(x, (CommentLine, EmptyLine))])
244
246 self.orgvalue = data
247 lines = str(data).split('\n')
248 linediff = len(lines) - len(self.contents)
249 if linediff > 0:
250 for _ in range(linediff):
251 self.add(ContinuationLine(''))
252 elif linediff < 0:
253 self.contents = self.contents[:linediff]
254 for i,v in enumerate(lines):
255 self.contents[i].value = v
256
257 name = property(get_name, set_name)
258 value = property(get_value, set_value)
259
261 s = [str(x) for x in self.contents]
262 return '\n'.join(s)
263
265 for x in self.contents[::-1]:
266 if hasattr(x, 'name') and x.name==key:
267 yield x
268
269 - def find(self, key):
273
274
287
288 def setfn(self, value):
289 srcobj = getattr(self, private_srcname)
290 if srcobj is not None:
291 setattr(srcobj, srcattrname, value)
292 else:
293 setattr(self, private_attrname, value)
294
295 return property(getfn, setfn)
296
297
372
373
376
377
379 """iterate over a file by only using the file object's readline method"""
380
381 have_newline = False
382 while True:
383 line = f.readline()
384
385 if not line:
386 if have_newline:
387 yield ""
388 return
389
390 if line.endswith('\n'):
391 have_newline = True
392 else:
393 have_newline = False
394
395 yield line
396
397
399 _data = None
400 _sections = None
401 _defaults = None
402 _optionxformvalue = None
403 _optionxformsource = None
404 _sectionxformvalue = None
405 _sectionxformsource = None
406 _parse_exc = None
407 - def __init__(self, fp=None, defaults = None, parse_exc=True,
408 optionxformvalue=str.lower, optionxformsource=None,
409 sectionxformvalue=None, sectionxformsource=None):
423
424 _optionxform = _make_xform_property('_optionxform', 'optionxform')
425 _sectionxform = _make_xform_property('_sectionxform', 'optionxform')
426
432
434 raise Exception('Values must be inside sections', key, value)
435
441
449
464
466 return str(self._data)
467
468 _line_types = [EmptyLine, CommentLine,
469 SectionLine, OptionLine,
470 ContinuationLine]
471
473 for linetype in self._line_types:
474 lineobj = linetype.parse(line)
475 if lineobj:
476 return lineobj
477 else:
478
479 return None
480
482 cur_section = None
483 cur_option = None
484 cur_section_name = None
485 cur_option_name = None
486 pending_lines = []
487 try:
488 fname = fp.name
489 except AttributeError:
490 fname = '<???>'
491 linecount = 0
492 exc = None
493 line = None
494
495 for line in readline_iterator(fp):
496 lineobj = self._parse(line)
497 linecount += 1
498
499 if not cur_section and not isinstance(lineobj,
500 (CommentLine, EmptyLine, SectionLine)):
501 if self._parse_exc:
502 raise MissingSectionHeaderError(fname, linecount, line)
503 else:
504 lineobj = make_comment(line)
505
506 if lineobj is None:
507 if self._parse_exc:
508 if exc is None: exc = ParsingError(fname)
509 exc.append(linecount, line)
510 lineobj = make_comment(line)
511
512 if isinstance(lineobj, ContinuationLine):
513 if cur_option:
514 cur_option.extend(pending_lines)
515 pending_lines = []
516 cur_option.add(lineobj)
517 else:
518
519 if self._parse_exc:
520 if exc is None: exc = ParsingError(fname)
521 exc.append(linecount, line)
522 lineobj = make_comment(line)
523
524 if isinstance(lineobj, OptionLine):
525 cur_section.extend(pending_lines)
526 pending_lines = []
527 cur_option = LineContainer(lineobj)
528 cur_section.add(cur_option)
529 if self._optionxform:
530 cur_option_name = self._optionxform(cur_option.name)
531 else:
532 cur_option_name = cur_option.name
533 if cur_section_name == DEFAULTSECT:
534 optobj = self._defaults
535 else:
536 optobj = self._sections[cur_section_name]
537 optobj._options[cur_option_name] = cur_option
538
539 if isinstance(lineobj, SectionLine):
540 self._data.extend(pending_lines)
541 pending_lines = []
542 cur_section = LineContainer(lineobj)
543 self._data.add(cur_section)
544 cur_option = None
545 cur_option_name = None
546 if cur_section.name == DEFAULTSECT:
547 self._defaults._lines.append(cur_section)
548 cur_section_name = DEFAULTSECT
549 else:
550 if self._sectionxform:
551 cur_section_name = self._sectionxform(cur_section.name)
552 else:
553 cur_section_name = cur_section.name
554 if cur_section_name not in self._sections:
555 self._sections[cur_section_name] = \
556 INISection(cur_section, defaults=self._defaults,
557 optionxformsource=self)
558 else:
559 self._sections[cur_section_name]._lines.append(cur_section)
560
561 if isinstance(lineobj, (CommentLine, EmptyLine)):
562 pending_lines.append(lineobj)
563
564 self._data.extend(pending_lines)
565 if line and line[-1]=='\n':
566 self._data.add(EmptyLine())
567
568 if exc:
569 raise exc
570