1 # Copyright (c) 2001, 2002, 2003 Python Software Foundation
2 # Copyright (c) 2004 Paramjit Oberoi <param.cs.wisc.edu>
3 # All Rights Reserved. See LICENSE-PSF & LICENSE for details.
5 """Access and/or modify INI files
7 * Compatiable with ConfigParser
8 * Preserves order of sections & options
9 * Preserves comments/blank lines/etc
10 * More convenient access to data
14 >>> from StringIO import StringIO
15 >>> sio = StringIO('''# configure foo-application
22 >>> cfg = INIConfig(sio)
23 >>> print cfg.foo.bar1
25 >>> print cfg['foo-ext'].special
27 >>> cfg.foo.newopt = 'hi!'
30 # configure foo-application
40 # An ini parser that supports ordered sections/options
41 # Also supports updates, while preserving structure
42 # Backward-compatiable with ConfigParser
45 from iniparse
import config
47 from ConfigParser
import DEFAULTSECT
, ParsingError
, MissingSectionHeaderError
49 class LineType(object):
52 def __init__(self
, line
=None):
54 self
.line
= line
.strip('\n')
56 # Return the original line for unmodified objects
57 # Otherwise construct using the current attribute values
59 if self
.line
is not None:
62 return self
.to_string()
64 # If an attribute is modified after initialization
65 # set line to None since it is no longer accurate.
66 def __setattr__(self
, name
, value
):
67 if hasattr(self
,name
):
68 self
.__dict
__['line'] = None
69 self
.__dict
__[name
] = value
72 raise Exception('This method must be overridden in derived classes')
75 class SectionLine(LineType
):
76 regex
= re
.compile(r
'^\['
79 r
'((?P<csep>;|#)(?P<comment>.*))?$')
81 def __init__(self
, name
, comment
=None, comment_separator
=None,
82 comment_offset
=-1, line
=None):
83 super(SectionLine
, self
).__init
__(line
)
85 self
.comment
= comment
86 self
.comment_separator
= comment_separator
87 self
.comment_offset
= comment_offset
90 out
= '[' + self
.name
+ ']'
91 if self
.comment
is not None:
92 # try to preserve indentation of comments
93 out
= (out
+' ').ljust(self
.comment_offset
)
94 out
= out
+ self
.comment_separator
+ self
.comment
98 m
= cls
.regex
.match(line
.rstrip())
101 return cls(m
.group('name'), m
.group('comment'),
102 m
.group('csep'), m
.start('csep'),
104 parse
= classmethod(parse
)
107 class OptionLine(LineType
):
108 def __init__(self
, name
, value
, separator
=' = ', comment
=None,
109 comment_separator
=None, comment_offset
=-1, line
=None):
110 super(OptionLine
, self
).__init
__(line
)
113 self
.separator
= separator
114 self
.comment
= comment
115 self
.comment_separator
= comment_separator
116 self
.comment_offset
= comment_offset
119 out
= '%s%s%s' % (self
.name
, self
.separator
, self
.value
)
120 if self
.comment
is not None:
121 # try to preserve indentation of comments
122 out
= (out
+' ').ljust(self
.comment_offset
)
123 out
= out
+ self
.comment_separator
+ self
.comment
126 regex
= re
.compile(r
'^(?P<name>[^:=\s[][^:=\s]*)'
127 r
'(?P<sep>\s*[:=]\s*)'
130 def parse(cls
, line
):
131 m
= cls
.regex
.match(line
.rstrip())
135 name
= m
.group('name').rstrip()
136 value
= m
.group('value')
139 # comments are not detected in the regex because
140 # ensuring total compatibility with ConfigParser
142 # option = value ;comment // value=='value'
143 # option = value;1 ;comment // value=='value;1 ;comment'
145 # Doing this in a regex would be complicated. I
146 # think this is a bug. The whole issue of how to
147 # include ';' in the value needs to be addressed.
148 # Also, '#' doesn't mark comments in options...
150 coff
= value
.find(';')
151 if coff
!= -1 and value
[coff
-1].isspace():
152 comment
= value
[coff
+1:]
154 value
= value
[:coff
].rstrip()
155 coff
= m
.start('value') + coff
161 return cls(name
, value
, sep
, comment
, csep
, coff
, line
)
162 parse
= classmethod(parse
)
165 class CommentLine(LineType
):
166 regex
= re
.compile(r
'^(?P<csep>[;#]|[rR][eE][mM])'
169 def __init__(self
, comment
='', separator
='#', line
=None):
170 super(CommentLine
, self
).__init
__(line
)
171 self
.comment
= comment
172 self
.separator
= separator
175 return self
.separator
+ self
.comment
177 def parse(cls
, line
):
178 m
= cls
.regex
.match(line
.rstrip())
181 return cls(m
.group('comment'), m
.group('csep'), line
)
182 parse
= classmethod(parse
)
185 class EmptyLine(LineType
):
186 # could make this a singleton
190 def parse(cls
, line
):
191 if line
.strip(): return None
193 parse
= classmethod(parse
)
196 class ContinuationLine(LineType
):
197 regex
= re
.compile(r
'^\s+(?P<value>.*)$')
199 def __init__(self
, value
, value_offset
=8, line
=None):
200 super(ContinuationLine
, self
).__init
__(line
)
202 self
.value_offset
= value_offset
205 return ' '*self
.value_offset
+ self
.value
207 def parse(cls
, line
):
208 m
= cls
.regex
.match(line
.rstrip())
211 return cls(m
.group('value'), m
.start('value'), line
)
212 parse
= classmethod(parse
)
215 class LineContainer(object):
216 def __init__(self
, d
=None):
220 if isinstance(d
, list): self
.extend(d
)
224 self
.contents
.append(x
)
227 for i
in x
: self
.add(i
)
230 return self
.contents
[0].name
232 def set_name(self
, data
):
233 self
.contents
[0].name
= data
236 if self
.orgvalue
is not None:
238 elif len(self
.contents
) == 1:
239 return self
.contents
[0].value
241 return '\n'.join([str(x
.value
) for x
in self
.contents
242 if not isinstance(x
, (CommentLine
, EmptyLine
))])
244 def set_value(self
, data
):
246 lines
= str(data
).split('\n')
247 linediff
= len(lines
) - len(self
.contents
)
249 for _
in range(linediff
):
250 self
.add(ContinuationLine(''))
252 self
.contents
= self
.contents
[:linediff
]
253 for i
,v
in enumerate(lines
):
254 self
.contents
[i
].value
= v
256 name
= property(get_name
, set_name
)
257 value
= property(get_value
, set_value
)
260 s
= [str(x
) for x
in self
.contents
]
263 def finditer(self
, key
):
264 for x
in self
.contents
[::-1]:
265 if hasattr(x
, 'name') and x
.name
==key
:
269 for x
in self
.finditer(key
):
274 def _make_xform_property(myattrname
, srcattrname
=None):
275 private_attrname
= myattrname
+ 'value'
276 private_srcname
= myattrname
+ 'source'
277 if srcattrname
is None:
278 srcattrname
= myattrname
281 srcobj
= getattr(self
, private_srcname
)
282 if srcobj
is not None:
283 return getattr(srcobj
, srcattrname
)
285 return getattr(self
, private_attrname
)
287 def setfn(self
, value
):
288 srcobj
= getattr(self
, private_srcname
)
289 if srcobj
is not None:
290 setattr(srcobj
, srcattrname
, value
)
292 setattr(self
, private_attrname
, value
)
294 return property(getfn
, setfn
)
297 class INISection(config
.ConfigNamespace
):
301 _optionxformvalue
= None
302 _optionxformsource
= None
303 def __init__(self
, lineobj
, defaults
= None,
304 optionxformvalue
=None, optionxformsource
=None):
305 self
._lines
= [lineobj
]
306 self
._defaults
= defaults
307 self
._optionxformvalue
= optionxformvalue
308 self
._optionxformsource
= optionxformsource
311 _optionxform
= _make_xform_property('_optionxform')
313 def __getitem__(self
, key
):
314 if key
== '__name__':
315 return self
._lines
[-1].name
316 if self
._optionxform
: key
= self
._optionxform
(key
)
318 return self
._options
[key
].value
320 if self
._defaults
and key
in self
._defaults
._options
:
321 return self
._defaults
._options
[key
].value
325 def __setitem__(self
, key
, value
):
326 if self
._optionxform
: xkey
= self
._optionxform
(key
)
328 if xkey
not in self
._options
:
329 # create a dummy object - value may have multiple lines
330 obj
= LineContainer(OptionLine(key
, ''))
331 self
._lines
[-1].add(obj
)
332 self
._options
[xkey
] = obj
333 # the set_value() function in LineContainer
334 # automatically handles multi-line values
335 self
._options
[xkey
].value
= value
337 def __delitem__(self
, key
):
338 if self
._optionxform
: key
= self
._optionxform
(key
)
339 for l
in self
._lines
:
342 if isinstance(o
, LineContainer
):
344 if self
._optionxform
: n
= self
._optionxform
(n
)
345 if key
!= n
: remaining
.append(o
)
348 l
.contents
= remaining
349 del self
._options
[key
]
353 for l
in self
._lines
:
355 if isinstance(x
, LineContainer
):
356 if self
._optionxform
:
357 ans
= self
._optionxform
(x
.name
)
364 for x
in self
._defaults
:
369 def new_namespace(self
, name
):
370 raise Exception('No sub-sections allowed', name
)
373 def make_comment(line
):
374 return CommentLine(line
.rstrip())
377 def readline_iterator(f
):
378 """iterate over a file by only using the file object's readline method"""
389 if line
.endswith('\n'):
397 class INIConfig(config
.ConfigNamespace
):
401 _optionxformvalue
= None
402 _optionxformsource
= None
403 _sectionxformvalue
= None
404 _sectionxformsource
= None
406 def __init__(self
, fp
=None, defaults
= None, parse_exc
=True,
407 optionxformvalue
=str.lower
, optionxformsource
=None,
408 sectionxformvalue
=None, sectionxformsource
=None):
409 self
._data
= LineContainer()
410 self
._parse
_exc
= parse_exc
411 self
._optionxformvalue
= optionxformvalue
412 self
._optionxformsource
= optionxformsource
413 self
._sectionxformvalue
= sectionxformvalue
414 self
._sectionxformsource
= sectionxformsource
416 if defaults
is None: defaults
= {}
417 self
._defaults
= INISection(LineContainer(), optionxformsource
=self
)
418 for name
, value
in defaults
.iteritems():
419 self
._defaults
[name
] = value
423 _optionxform
= _make_xform_property('_optionxform', 'optionxform')
424 _sectionxform
= _make_xform_property('_sectionxform', 'optionxform')
426 def __getitem__(self
, key
):
427 if key
== DEFAULTSECT
:
428 return self
._defaults
429 if self
._sectionxform
: key
= self
._sectionxform
(key
)
430 return self
._sections
[key
]
432 def __setitem__(self
, key
, value
):
433 raise Exception('Values must be inside sections', key
, value
)
435 def __delitem__(self
, key
):
436 if self
._sectionxform
: key
= self
._sectionxform
(key
)
437 for line
in self
._sections
[key
]._lines
:
438 self
._data
.contents
.remove(line
)
439 del self
._sections
[key
]
443 for x
in self
._data
.contents
:
444 if isinstance(x
, LineContainer
):
449 def new_namespace(self
, name
):
450 if self
._data
.contents
:
451 self
._data
.add(EmptyLine())
452 obj
= LineContainer(SectionLine(name
))
454 if self
._sectionxform
: name
= self
._sectionxform
(name
)
455 if name
in self
._sections
:
456 ns
= self
._sections
[name
]
457 ns
._lines
.append(obj
)
459 ns
= INISection(obj
, defaults
=self
._defaults
,
460 optionxformsource
=self
)
461 self
._sections
[name
] = ns
465 return str(self
._data
)
467 _line_types
= [EmptyLine
, CommentLine
,
468 SectionLine
, OptionLine
,
471 def _parse(self
, line
):
472 for linetype
in self
._line
_types
:
473 lineobj
= linetype
.parse(line
)
480 def readfp(self
, fp
):
483 cur_section_name
= None
484 cur_option_name
= None
488 except AttributeError:
494 for line
in readline_iterator(fp
):
495 lineobj
= self
._parse
(line
)
498 if not cur_section
and not isinstance(lineobj
,
499 (CommentLine
, EmptyLine
, SectionLine
)):
501 raise MissingSectionHeaderError(fname
, linecount
, line
)
503 lineobj
= make_comment(line
)
507 if exc
is None: exc
= ParsingError(fname
)
508 exc
.append(linecount
, line
)
509 lineobj
= make_comment(line
)
511 if isinstance(lineobj
, ContinuationLine
):
513 cur_option
.extend(pending_lines
)
515 cur_option
.add(lineobj
)
517 # illegal continuation line - convert to comment
519 if exc
is None: exc
= ParsingError(fname
)
520 exc
.append(linecount
, line
)
521 lineobj
= make_comment(line
)
523 if isinstance(lineobj
, OptionLine
):
524 cur_section
.extend(pending_lines
)
526 cur_option
= LineContainer(lineobj
)
527 cur_section
.add(cur_option
)
528 if self
._optionxform
:
529 cur_option_name
= self
._optionxform
(cur_option
.name
)
531 cur_option_name
= cur_option
.name
532 if cur_section_name
== DEFAULTSECT
:
533 optobj
= self
._defaults
535 optobj
= self
._sections
[cur_section_name
]
536 optobj
._options
[cur_option_name
] = cur_option
538 if isinstance(lineobj
, SectionLine
):
539 self
._data
.extend(pending_lines
)
541 cur_section
= LineContainer(lineobj
)
542 self
._data
.add(cur_section
)
544 cur_option_name
= None
545 if cur_section
.name
== DEFAULTSECT
:
546 self
._defaults
._lines
.append(cur_section
)
547 cur_section_name
= DEFAULTSECT
549 if self
._sectionxform
:
550 cur_section_name
= self
._sectionxform
(cur_section
.name
)
552 cur_section_name
= cur_section
.name
553 if not self
._sections
.has_key(cur_section_name
):
554 self
._sections
[cur_section_name
] = \
555 INISection(cur_section
, defaults
=self
._defaults
,
556 optionxformsource
=self
)
558 self
._sections
[cur_section_name
]._lines
.append(cur_section
)
560 if isinstance(lineobj
, (CommentLine
, EmptyLine
)):
561 pending_lines
.append(lineobj
)
563 self
._data
.extend(pending_lines
)
564 if line
and line
[-1]=='\n':
565 self
._data
.add(EmptyLine())