|
1 # configobj.py |
|
2 # A config file reader/writer that supports nested sections in config files. |
|
3 # Copyright (C) 2005-2006 Michael Foord, Nicola Larosa |
|
4 # E-mail: fuzzyman AT voidspace DOT org DOT uk |
|
5 # nico AT tekNico DOT net |
|
6 |
|
7 # ConfigObj 4 |
|
8 # http://www.voidspace.org.uk/python/configobj.html |
|
9 |
|
10 # Released subject to the BSD License |
|
11 # Please see http://www.voidspace.org.uk/python/license.shtml |
|
12 |
|
13 # Scripts maintained at http://www.voidspace.org.uk/python/index.shtml |
|
14 # For information about bugfixes, updates and support, please join the |
|
15 # ConfigObj mailing list: |
|
16 # http://lists.sourceforge.net/lists/listinfo/configobj-develop |
|
17 # Comments, suggestions and bug reports welcome. |
|
18 |
|
19 from __future__ import generators |
|
20 |
|
21 import sys |
|
22 INTP_VER = sys.version_info[:2] |
|
23 if INTP_VER < (2, 2): |
|
24 raise RuntimeError("Python v.2.2 or later needed") |
|
25 |
|
26 import os, re |
|
27 compiler = None |
|
28 try: |
|
29 import compiler |
|
30 except ImportError: |
|
31 # for IronPython |
|
32 pass |
|
33 from types import StringTypes |
|
34 from warnings import warn |
|
35 try: |
|
36 from codecs import BOM_UTF8, BOM_UTF16, BOM_UTF16_BE, BOM_UTF16_LE |
|
37 except ImportError: |
|
38 # Python 2.2 does not have these |
|
39 # UTF-8 |
|
40 BOM_UTF8 = '\xef\xbb\xbf' |
|
41 # UTF-16, little endian |
|
42 BOM_UTF16_LE = '\xff\xfe' |
|
43 # UTF-16, big endian |
|
44 BOM_UTF16_BE = '\xfe\xff' |
|
45 if sys.byteorder == 'little': |
|
46 # UTF-16, native endianness |
|
47 BOM_UTF16 = BOM_UTF16_LE |
|
48 else: |
|
49 # UTF-16, native endianness |
|
50 BOM_UTF16 = BOM_UTF16_BE |
|
51 |
|
52 # A dictionary mapping BOM to |
|
53 # the encoding to decode with, and what to set the |
|
54 # encoding attribute to. |
|
55 BOMS = { |
|
56 BOM_UTF8: ('utf_8', None), |
|
57 BOM_UTF16_BE: ('utf16_be', 'utf_16'), |
|
58 BOM_UTF16_LE: ('utf16_le', 'utf_16'), |
|
59 BOM_UTF16: ('utf_16', 'utf_16'), |
|
60 } |
|
61 # All legal variants of the BOM codecs. |
|
62 # TODO: the list of aliases is not meant to be exhaustive, is there a |
|
63 # better way ? |
|
64 BOM_LIST = { |
|
65 'utf_16': 'utf_16', |
|
66 'u16': 'utf_16', |
|
67 'utf16': 'utf_16', |
|
68 'utf-16': 'utf_16', |
|
69 'utf16_be': 'utf16_be', |
|
70 'utf_16_be': 'utf16_be', |
|
71 'utf-16be': 'utf16_be', |
|
72 'utf16_le': 'utf16_le', |
|
73 'utf_16_le': 'utf16_le', |
|
74 'utf-16le': 'utf16_le', |
|
75 'utf_8': 'utf_8', |
|
76 'u8': 'utf_8', |
|
77 'utf': 'utf_8', |
|
78 'utf8': 'utf_8', |
|
79 'utf-8': 'utf_8', |
|
80 } |
|
81 |
|
82 # Map of encodings to the BOM to write. |
|
83 BOM_SET = { |
|
84 'utf_8': BOM_UTF8, |
|
85 'utf_16': BOM_UTF16, |
|
86 'utf16_be': BOM_UTF16_BE, |
|
87 'utf16_le': BOM_UTF16_LE, |
|
88 None: BOM_UTF8 |
|
89 } |
|
90 |
|
91 try: |
|
92 from validate import VdtMissingValue |
|
93 except ImportError: |
|
94 VdtMissingValue = None |
|
95 |
|
96 try: |
|
97 enumerate |
|
98 except NameError: |
|
99 def enumerate(obj): |
|
100 """enumerate for Python 2.2.""" |
|
101 i = -1 |
|
102 for item in obj: |
|
103 i += 1 |
|
104 yield i, item |
|
105 |
|
106 try: |
|
107 True, False |
|
108 except NameError: |
|
109 True, False = 1, 0 |
|
110 |
|
111 |
|
112 __version__ = '4.4.0' |
|
113 |
|
114 __revision__ = '$Id: configobj.py,v 3.5 2007/07/02 18:20:24 benjamin%smedbergs.us Exp $' |
|
115 |
|
116 __docformat__ = "restructuredtext en" |
|
117 |
|
118 __all__ = ( |
|
119 '__version__', |
|
120 'DEFAULT_INDENT_TYPE', |
|
121 'DEFAULT_INTERPOLATION', |
|
122 'ConfigObjError', |
|
123 'NestingError', |
|
124 'ParseError', |
|
125 'DuplicateError', |
|
126 'ConfigspecError', |
|
127 'ConfigObj', |
|
128 'SimpleVal', |
|
129 'InterpolationError', |
|
130 'InterpolationLoopError', |
|
131 'MissingInterpolationOption', |
|
132 'RepeatSectionError', |
|
133 'UnreprError', |
|
134 'UnknownType', |
|
135 '__docformat__', |
|
136 'flatten_errors', |
|
137 ) |
|
138 |
|
139 DEFAULT_INTERPOLATION = 'configparser' |
|
140 DEFAULT_INDENT_TYPE = ' ' |
|
141 MAX_INTERPOL_DEPTH = 10 |
|
142 |
|
143 OPTION_DEFAULTS = { |
|
144 'interpolation': True, |
|
145 'raise_errors': False, |
|
146 'list_values': True, |
|
147 'create_empty': False, |
|
148 'file_error': False, |
|
149 'configspec': None, |
|
150 'stringify': True, |
|
151 # option may be set to one of ('', ' ', '\t') |
|
152 'indent_type': None, |
|
153 'encoding': None, |
|
154 'default_encoding': None, |
|
155 'unrepr': False, |
|
156 'write_empty_values': False, |
|
157 } |
|
158 |
|
159 |
|
160 def getObj(s): |
|
161 s = "a=" + s |
|
162 if compiler is None: |
|
163 raise ImportError('compiler module not available') |
|
164 p = compiler.parse(s) |
|
165 return p.getChildren()[1].getChildren()[0].getChildren()[1] |
|
166 |
|
167 class UnknownType(Exception): |
|
168 pass |
|
169 |
|
170 class Builder: |
|
171 |
|
172 def build(self, o): |
|
173 m = getattr(self, 'build_' + o.__class__.__name__, None) |
|
174 if m is None: |
|
175 raise UnknownType(o.__class__.__name__) |
|
176 return m(o) |
|
177 |
|
178 def build_List(self, o): |
|
179 return map(self.build, o.getChildren()) |
|
180 |
|
181 def build_Const(self, o): |
|
182 return o.value |
|
183 |
|
184 def build_Dict(self, o): |
|
185 d = {} |
|
186 i = iter(map(self.build, o.getChildren())) |
|
187 for el in i: |
|
188 d[el] = i.next() |
|
189 return d |
|
190 |
|
191 def build_Tuple(self, o): |
|
192 return tuple(self.build_List(o)) |
|
193 |
|
194 def build_Name(self, o): |
|
195 if o.name == 'None': |
|
196 return None |
|
197 if o.name == 'True': |
|
198 return True |
|
199 if o.name == 'False': |
|
200 return False |
|
201 |
|
202 # An undefinted Name |
|
203 raise UnknownType('Undefined Name') |
|
204 |
|
205 def build_Add(self, o): |
|
206 real, imag = map(self.build_Const, o.getChildren()) |
|
207 try: |
|
208 real = float(real) |
|
209 except TypeError: |
|
210 raise UnknownType('Add') |
|
211 if not isinstance(imag, complex) or imag.real != 0.0: |
|
212 raise UnknownType('Add') |
|
213 return real+imag |
|
214 |
|
215 def build_Getattr(self, o): |
|
216 parent = self.build(o.expr) |
|
217 return getattr(parent, o.attrname) |
|
218 |
|
219 def build_UnarySub(self, o): |
|
220 return -self.build_Const(o.getChildren()[0]) |
|
221 |
|
222 def build_UnaryAdd(self, o): |
|
223 return self.build_Const(o.getChildren()[0]) |
|
224 |
|
225 def unrepr(s): |
|
226 if not s: |
|
227 return s |
|
228 return Builder().build(getObj(s)) |
|
229 |
|
230 def _splitlines(instring): |
|
231 """Split a string on lines, without losing line endings or truncating.""" |
|
232 |
|
233 |
|
234 class ConfigObjError(SyntaxError): |
|
235 """ |
|
236 This is the base class for all errors that ConfigObj raises. |
|
237 It is a subclass of SyntaxError. |
|
238 """ |
|
239 def __init__(self, message='', line_number=None, line=''): |
|
240 self.line = line |
|
241 self.line_number = line_number |
|
242 self.message = message |
|
243 SyntaxError.__init__(self, message) |
|
244 |
|
245 class NestingError(ConfigObjError): |
|
246 """ |
|
247 This error indicates a level of nesting that doesn't match. |
|
248 """ |
|
249 |
|
250 class ParseError(ConfigObjError): |
|
251 """ |
|
252 This error indicates that a line is badly written. |
|
253 It is neither a valid ``key = value`` line, |
|
254 nor a valid section marker line. |
|
255 """ |
|
256 |
|
257 class DuplicateError(ConfigObjError): |
|
258 """ |
|
259 The keyword or section specified already exists. |
|
260 """ |
|
261 |
|
262 class ConfigspecError(ConfigObjError): |
|
263 """ |
|
264 An error occurred whilst parsing a configspec. |
|
265 """ |
|
266 |
|
267 class InterpolationError(ConfigObjError): |
|
268 """Base class for the two interpolation errors.""" |
|
269 |
|
270 class InterpolationLoopError(InterpolationError): |
|
271 """Maximum interpolation depth exceeded in string interpolation.""" |
|
272 |
|
273 def __init__(self, option): |
|
274 InterpolationError.__init__( |
|
275 self, |
|
276 'interpolation loop detected in value "%s".' % option) |
|
277 |
|
278 class RepeatSectionError(ConfigObjError): |
|
279 """ |
|
280 This error indicates additional sections in a section with a |
|
281 ``__many__`` (repeated) section. |
|
282 """ |
|
283 |
|
284 class MissingInterpolationOption(InterpolationError): |
|
285 """A value specified for interpolation was missing.""" |
|
286 |
|
287 def __init__(self, option): |
|
288 InterpolationError.__init__( |
|
289 self, |
|
290 'missing option "%s" in interpolation.' % option) |
|
291 |
|
292 class UnreprError(ConfigObjError): |
|
293 """An error parsing in unrepr mode.""" |
|
294 |
|
295 |
|
296 class InterpolationEngine(object): |
|
297 """ |
|
298 A helper class to help perform string interpolation. |
|
299 |
|
300 This class is an abstract base class; its descendants perform |
|
301 the actual work. |
|
302 """ |
|
303 |
|
304 # compiled regexp to use in self.interpolate() |
|
305 _KEYCRE = re.compile(r"%\(([^)]*)\)s") |
|
306 |
|
307 def __init__(self, section): |
|
308 # the Section instance that "owns" this engine |
|
309 self.section = section |
|
310 |
|
311 def interpolate(self, key, value): |
|
312 def recursive_interpolate(key, value, section, backtrail): |
|
313 """The function that does the actual work. |
|
314 |
|
315 ``value``: the string we're trying to interpolate. |
|
316 ``section``: the section in which that string was found |
|
317 ``backtrail``: a dict to keep track of where we've been, |
|
318 to detect and prevent infinite recursion loops |
|
319 |
|
320 This is similar to a depth-first-search algorithm. |
|
321 """ |
|
322 # Have we been here already? |
|
323 if backtrail.has_key((key, section.name)): |
|
324 # Yes - infinite loop detected |
|
325 raise InterpolationLoopError(key) |
|
326 # Place a marker on our backtrail so we won't come back here again |
|
327 backtrail[(key, section.name)] = 1 |
|
328 |
|
329 # Now start the actual work |
|
330 match = self._KEYCRE.search(value) |
|
331 while match: |
|
332 # The actual parsing of the match is implementation-dependent, |
|
333 # so delegate to our helper function |
|
334 k, v, s = self._parse_match(match) |
|
335 if k is None: |
|
336 # That's the signal that no further interpolation is needed |
|
337 replacement = v |
|
338 else: |
|
339 # Further interpolation may be needed to obtain final value |
|
340 replacement = recursive_interpolate(k, v, s, backtrail) |
|
341 # Replace the matched string with its final value |
|
342 start, end = match.span() |
|
343 value = ''.join((value[:start], replacement, value[end:])) |
|
344 new_search_start = start + len(replacement) |
|
345 # Pick up the next interpolation key, if any, for next time |
|
346 # through the while loop |
|
347 match = self._KEYCRE.search(value, new_search_start) |
|
348 |
|
349 # Now safe to come back here again; remove marker from backtrail |
|
350 del backtrail[(key, section.name)] |
|
351 |
|
352 return value |
|
353 |
|
354 # Back in interpolate(), all we have to do is kick off the recursive |
|
355 # function with appropriate starting values |
|
356 value = recursive_interpolate(key, value, self.section, {}) |
|
357 return value |
|
358 |
|
359 def _fetch(self, key): |
|
360 """Helper function to fetch values from owning section. |
|
361 |
|
362 Returns a 2-tuple: the value, and the section where it was found. |
|
363 """ |
|
364 # switch off interpolation before we try and fetch anything ! |
|
365 save_interp = self.section.main.interpolation |
|
366 self.section.main.interpolation = False |
|
367 |
|
368 # Start at section that "owns" this InterpolationEngine |
|
369 current_section = self.section |
|
370 while True: |
|
371 # try the current section first |
|
372 val = current_section.get(key) |
|
373 if val is not None: |
|
374 break |
|
375 # try "DEFAULT" next |
|
376 val = current_section.get('DEFAULT', {}).get(key) |
|
377 if val is not None: |
|
378 break |
|
379 # move up to parent and try again |
|
380 # top-level's parent is itself |
|
381 if current_section.parent is current_section: |
|
382 # reached top level, time to give up |
|
383 break |
|
384 current_section = current_section.parent |
|
385 |
|
386 # restore interpolation to previous value before returning |
|
387 self.section.main.interpolation = save_interp |
|
388 if val is None: |
|
389 raise MissingInterpolationOption(key) |
|
390 return val, current_section |
|
391 |
|
392 def _parse_match(self, match): |
|
393 """Implementation-dependent helper function. |
|
394 |
|
395 Will be passed a match object corresponding to the interpolation |
|
396 key we just found (e.g., "%(foo)s" or "$foo"). Should look up that |
|
397 key in the appropriate config file section (using the ``_fetch()`` |
|
398 helper function) and return a 3-tuple: (key, value, section) |
|
399 |
|
400 ``key`` is the name of the key we're looking for |
|
401 ``value`` is the value found for that key |
|
402 ``section`` is a reference to the section where it was found |
|
403 |
|
404 ``key`` and ``section`` should be None if no further |
|
405 interpolation should be performed on the resulting value |
|
406 (e.g., if we interpolated "$$" and returned "$"). |
|
407 """ |
|
408 raise NotImplementedError |
|
409 |
|
410 |
|
411 class ConfigParserInterpolation(InterpolationEngine): |
|
412 """Behaves like ConfigParser.""" |
|
413 _KEYCRE = re.compile(r"%\(([^)]*)\)s") |
|
414 |
|
415 def _parse_match(self, match): |
|
416 key = match.group(1) |
|
417 value, section = self._fetch(key) |
|
418 return key, value, section |
|
419 |
|
420 |
|
421 class TemplateInterpolation(InterpolationEngine): |
|
422 """Behaves like string.Template.""" |
|
423 _delimiter = '$' |
|
424 _KEYCRE = re.compile(r""" |
|
425 \$(?: |
|
426 (?P<escaped>\$) | # Two $ signs |
|
427 (?P<named>[_a-z][_a-z0-9]*) | # $name format |
|
428 {(?P<braced>[^}]*)} # ${name} format |
|
429 ) |
|
430 """, re.IGNORECASE | re.VERBOSE) |
|
431 |
|
432 def _parse_match(self, match): |
|
433 # Valid name (in or out of braces): fetch value from section |
|
434 key = match.group('named') or match.group('braced') |
|
435 if key is not None: |
|
436 value, section = self._fetch(key) |
|
437 return key, value, section |
|
438 # Escaped delimiter (e.g., $$): return single delimiter |
|
439 if match.group('escaped') is not None: |
|
440 # Return None for key and section to indicate it's time to stop |
|
441 return None, self._delimiter, None |
|
442 # Anything else: ignore completely, just return it unchanged |
|
443 return None, match.group(), None |
|
444 |
|
445 interpolation_engines = { |
|
446 'configparser': ConfigParserInterpolation, |
|
447 'template': TemplateInterpolation, |
|
448 } |
|
449 |
|
450 class Section(dict): |
|
451 """ |
|
452 A dictionary-like object that represents a section in a config file. |
|
453 |
|
454 It does string interpolation if the 'interpolation' attribute |
|
455 of the 'main' object is set to True. |
|
456 |
|
457 Interpolation is tried first from this object, then from the 'DEFAULT' |
|
458 section of this object, next from the parent and its 'DEFAULT' section, |
|
459 and so on until the main object is reached. |
|
460 |
|
461 A Section will behave like an ordered dictionary - following the |
|
462 order of the ``scalars`` and ``sections`` attributes. |
|
463 You can use this to change the order of members. |
|
464 |
|
465 Iteration follows the order: scalars, then sections. |
|
466 """ |
|
467 |
|
468 def __init__(self, parent, depth, main, indict=None, name=None): |
|
469 """ |
|
470 * parent is the section above |
|
471 * depth is the depth level of this section |
|
472 * main is the main ConfigObj |
|
473 * indict is a dictionary to initialise the section with |
|
474 """ |
|
475 if indict is None: |
|
476 indict = {} |
|
477 dict.__init__(self) |
|
478 # used for nesting level *and* interpolation |
|
479 self.parent = parent |
|
480 # used for the interpolation attribute |
|
481 self.main = main |
|
482 # level of nesting depth of this Section |
|
483 self.depth = depth |
|
484 # the sequence of scalar values in this Section |
|
485 self.scalars = [] |
|
486 # the sequence of sections in this Section |
|
487 self.sections = [] |
|
488 # purely for information |
|
489 self.name = name |
|
490 # for comments :-) |
|
491 self.comments = {} |
|
492 self.inline_comments = {} |
|
493 # for the configspec |
|
494 self.configspec = {} |
|
495 self._order = [] |
|
496 self._configspec_comments = {} |
|
497 self._configspec_inline_comments = {} |
|
498 self._cs_section_comments = {} |
|
499 self._cs_section_inline_comments = {} |
|
500 # for defaults |
|
501 self.defaults = [] |
|
502 # |
|
503 # we do this explicitly so that __setitem__ is used properly |
|
504 # (rather than just passing to ``dict.__init__``) |
|
505 for entry in indict: |
|
506 self[entry] = indict[entry] |
|
507 |
|
508 def _interpolate(self, key, value): |
|
509 try: |
|
510 # do we already have an interpolation engine? |
|
511 engine = self._interpolation_engine |
|
512 except AttributeError: |
|
513 # not yet: first time running _interpolate(), so pick the engine |
|
514 name = self.main.interpolation |
|
515 if name == True: # note that "if name:" would be incorrect here |
|
516 # backwards-compatibility: interpolation=True means use default |
|
517 name = DEFAULT_INTERPOLATION |
|
518 name = name.lower() # so that "Template", "template", etc. all work |
|
519 class_ = interpolation_engines.get(name, None) |
|
520 if class_ is None: |
|
521 # invalid value for self.main.interpolation |
|
522 self.main.interpolation = False |
|
523 return value |
|
524 else: |
|
525 # save reference to engine so we don't have to do this again |
|
526 engine = self._interpolation_engine = class_(self) |
|
527 # let the engine do the actual work |
|
528 return engine.interpolate(key, value) |
|
529 |
|
530 def __getitem__(self, key): |
|
531 """Fetch the item and do string interpolation.""" |
|
532 val = dict.__getitem__(self, key) |
|
533 if self.main.interpolation and isinstance(val, StringTypes): |
|
534 return self._interpolate(key, val) |
|
535 return val |
|
536 |
|
537 def __setitem__(self, key, value, unrepr=False): |
|
538 """ |
|
539 Correctly set a value. |
|
540 |
|
541 Making dictionary values Section instances. |
|
542 (We have to special case 'Section' instances - which are also dicts) |
|
543 |
|
544 Keys must be strings. |
|
545 Values need only be strings (or lists of strings) if |
|
546 ``main.stringify`` is set. |
|
547 |
|
548 `unrepr`` must be set when setting a value to a dictionary, without |
|
549 creating a new sub-section. |
|
550 """ |
|
551 if not isinstance(key, StringTypes): |
|
552 raise ValueError, 'The key "%s" is not a string.' % key |
|
553 # add the comment |
|
554 if not self.comments.has_key(key): |
|
555 self.comments[key] = [] |
|
556 self.inline_comments[key] = '' |
|
557 # remove the entry from defaults |
|
558 if key in self.defaults: |
|
559 self.defaults.remove(key) |
|
560 # |
|
561 if isinstance(value, Section): |
|
562 if not self.has_key(key): |
|
563 self.sections.append(key) |
|
564 dict.__setitem__(self, key, value) |
|
565 elif isinstance(value, dict) and not unrepr: |
|
566 # First create the new depth level, |
|
567 # then create the section |
|
568 if not self.has_key(key): |
|
569 self.sections.append(key) |
|
570 new_depth = self.depth + 1 |
|
571 dict.__setitem__( |
|
572 self, |
|
573 key, |
|
574 Section( |
|
575 self, |
|
576 new_depth, |
|
577 self.main, |
|
578 indict=value, |
|
579 name=key)) |
|
580 else: |
|
581 if not self.has_key(key): |
|
582 self.scalars.append(key) |
|
583 if not self.main.stringify: |
|
584 if isinstance(value, StringTypes): |
|
585 pass |
|
586 elif isinstance(value, (list, tuple)): |
|
587 for entry in value: |
|
588 if not isinstance(entry, StringTypes): |
|
589 raise TypeError, ( |
|
590 'Value is not a string "%s".' % entry) |
|
591 else: |
|
592 raise TypeError, 'Value is not a string "%s".' % value |
|
593 dict.__setitem__(self, key, value) |
|
594 |
|
595 def __delitem__(self, key): |
|
596 """Remove items from the sequence when deleting.""" |
|
597 dict. __delitem__(self, key) |
|
598 if key in self.scalars: |
|
599 self.scalars.remove(key) |
|
600 else: |
|
601 self.sections.remove(key) |
|
602 del self.comments[key] |
|
603 del self.inline_comments[key] |
|
604 |
|
605 def get(self, key, default=None): |
|
606 """A version of ``get`` that doesn't bypass string interpolation.""" |
|
607 try: |
|
608 return self[key] |
|
609 except KeyError: |
|
610 return default |
|
611 |
|
612 def update(self, indict): |
|
613 """ |
|
614 A version of update that uses our ``__setitem__``. |
|
615 """ |
|
616 for entry in indict: |
|
617 self[entry] = indict[entry] |
|
618 |
|
619 def pop(self, key, *args): |
|
620 """ """ |
|
621 val = dict.pop(self, key, *args) |
|
622 if key in self.scalars: |
|
623 del self.comments[key] |
|
624 del self.inline_comments[key] |
|
625 self.scalars.remove(key) |
|
626 elif key in self.sections: |
|
627 del self.comments[key] |
|
628 del self.inline_comments[key] |
|
629 self.sections.remove(key) |
|
630 if self.main.interpolation and isinstance(val, StringTypes): |
|
631 return self._interpolate(key, val) |
|
632 return val |
|
633 |
|
634 def popitem(self): |
|
635 """Pops the first (key,val)""" |
|
636 sequence = (self.scalars + self.sections) |
|
637 if not sequence: |
|
638 raise KeyError, ": 'popitem(): dictionary is empty'" |
|
639 key = sequence[0] |
|
640 val = self[key] |
|
641 del self[key] |
|
642 return key, val |
|
643 |
|
644 def clear(self): |
|
645 """ |
|
646 A version of clear that also affects scalars/sections |
|
647 Also clears comments and configspec. |
|
648 |
|
649 Leaves other attributes alone : |
|
650 depth/main/parent are not affected |
|
651 """ |
|
652 dict.clear(self) |
|
653 self.scalars = [] |
|
654 self.sections = [] |
|
655 self.comments = {} |
|
656 self.inline_comments = {} |
|
657 self.configspec = {} |
|
658 |
|
659 def setdefault(self, key, default=None): |
|
660 """A version of setdefault that sets sequence if appropriate.""" |
|
661 try: |
|
662 return self[key] |
|
663 except KeyError: |
|
664 self[key] = default |
|
665 return self[key] |
|
666 |
|
667 def items(self): |
|
668 """ """ |
|
669 return zip((self.scalars + self.sections), self.values()) |
|
670 |
|
671 def keys(self): |
|
672 """ """ |
|
673 return (self.scalars + self.sections) |
|
674 |
|
675 def values(self): |
|
676 """ """ |
|
677 return [self[key] for key in (self.scalars + self.sections)] |
|
678 |
|
679 def iteritems(self): |
|
680 """ """ |
|
681 return iter(self.items()) |
|
682 |
|
683 def iterkeys(self): |
|
684 """ """ |
|
685 return iter((self.scalars + self.sections)) |
|
686 |
|
687 __iter__ = iterkeys |
|
688 |
|
689 def itervalues(self): |
|
690 """ """ |
|
691 return iter(self.values()) |
|
692 |
|
693 def __repr__(self): |
|
694 return '{%s}' % ', '.join([('%s: %s' % (repr(key), repr(self[key]))) |
|
695 for key in (self.scalars + self.sections)]) |
|
696 |
|
697 __str__ = __repr__ |
|
698 |
|
699 # Extra methods - not in a normal dictionary |
|
700 |
|
701 def dict(self): |
|
702 """ |
|
703 Return a deepcopy of self as a dictionary. |
|
704 |
|
705 All members that are ``Section`` instances are recursively turned to |
|
706 ordinary dictionaries - by calling their ``dict`` method. |
|
707 |
|
708 >>> n = a.dict() |
|
709 >>> n == a |
|
710 1 |
|
711 >>> n is a |
|
712 0 |
|
713 """ |
|
714 newdict = {} |
|
715 for entry in self: |
|
716 this_entry = self[entry] |
|
717 if isinstance(this_entry, Section): |
|
718 this_entry = this_entry.dict() |
|
719 elif isinstance(this_entry, list): |
|
720 # create a copy rather than a reference |
|
721 this_entry = list(this_entry) |
|
722 elif isinstance(this_entry, tuple): |
|
723 # create a copy rather than a reference |
|
724 this_entry = tuple(this_entry) |
|
725 newdict[entry] = this_entry |
|
726 return newdict |
|
727 |
|
728 def merge(self, indict): |
|
729 """ |
|
730 A recursive update - useful for merging config files. |
|
731 |
|
732 >>> a = '''[section1] |
|
733 ... option1 = True |
|
734 ... [[subsection]] |
|
735 ... more_options = False |
|
736 ... # end of file'''.splitlines() |
|
737 >>> b = '''# File is user.ini |
|
738 ... [section1] |
|
739 ... option1 = False |
|
740 ... # end of file'''.splitlines() |
|
741 >>> c1 = ConfigObj(b) |
|
742 >>> c2 = ConfigObj(a) |
|
743 >>> c2.merge(c1) |
|
744 >>> c2 |
|
745 {'section1': {'option1': 'False', 'subsection': {'more_options': 'False'}}} |
|
746 """ |
|
747 for key, val in indict.items(): |
|
748 if (key in self and isinstance(self[key], dict) and |
|
749 isinstance(val, dict)): |
|
750 self[key].merge(val) |
|
751 else: |
|
752 self[key] = val |
|
753 |
|
754 def rename(self, oldkey, newkey): |
|
755 """ |
|
756 Change a keyname to another, without changing position in sequence. |
|
757 |
|
758 Implemented so that transformations can be made on keys, |
|
759 as well as on values. (used by encode and decode) |
|
760 |
|
761 Also renames comments. |
|
762 """ |
|
763 if oldkey in self.scalars: |
|
764 the_list = self.scalars |
|
765 elif oldkey in self.sections: |
|
766 the_list = self.sections |
|
767 else: |
|
768 raise KeyError, 'Key "%s" not found.' % oldkey |
|
769 pos = the_list.index(oldkey) |
|
770 # |
|
771 val = self[oldkey] |
|
772 dict.__delitem__(self, oldkey) |
|
773 dict.__setitem__(self, newkey, val) |
|
774 the_list.remove(oldkey) |
|
775 the_list.insert(pos, newkey) |
|
776 comm = self.comments[oldkey] |
|
777 inline_comment = self.inline_comments[oldkey] |
|
778 del self.comments[oldkey] |
|
779 del self.inline_comments[oldkey] |
|
780 self.comments[newkey] = comm |
|
781 self.inline_comments[newkey] = inline_comment |
|
782 |
|
783 def walk(self, function, raise_errors=True, |
|
784 call_on_sections=False, **keywargs): |
|
785 """ |
|
786 Walk every member and call a function on the keyword and value. |
|
787 |
|
788 Return a dictionary of the return values |
|
789 |
|
790 If the function raises an exception, raise the errror |
|
791 unless ``raise_errors=False``, in which case set the return value to |
|
792 ``False``. |
|
793 |
|
794 Any unrecognised keyword arguments you pass to walk, will be pased on |
|
795 to the function you pass in. |
|
796 |
|
797 Note: if ``call_on_sections`` is ``True`` then - on encountering a |
|
798 subsection, *first* the function is called for the *whole* subsection, |
|
799 and then recurses into its members. This means your function must be |
|
800 able to handle strings, dictionaries and lists. This allows you |
|
801 to change the key of subsections as well as for ordinary members. The |
|
802 return value when called on the whole subsection has to be discarded. |
|
803 |
|
804 See the encode and decode methods for examples, including functions. |
|
805 |
|
806 .. caution:: |
|
807 |
|
808 You can use ``walk`` to transform the names of members of a section |
|
809 but you mustn't add or delete members. |
|
810 |
|
811 >>> config = '''[XXXXsection] |
|
812 ... XXXXkey = XXXXvalue'''.splitlines() |
|
813 >>> cfg = ConfigObj(config) |
|
814 >>> cfg |
|
815 {'XXXXsection': {'XXXXkey': 'XXXXvalue'}} |
|
816 >>> def transform(section, key): |
|
817 ... val = section[key] |
|
818 ... newkey = key.replace('XXXX', 'CLIENT1') |
|
819 ... section.rename(key, newkey) |
|
820 ... if isinstance(val, (tuple, list, dict)): |
|
821 ... pass |
|
822 ... else: |
|
823 ... val = val.replace('XXXX', 'CLIENT1') |
|
824 ... section[newkey] = val |
|
825 >>> cfg.walk(transform, call_on_sections=True) |
|
826 {'CLIENT1section': {'CLIENT1key': None}} |
|
827 >>> cfg |
|
828 {'CLIENT1section': {'CLIENT1key': 'CLIENT1value'}} |
|
829 """ |
|
830 out = {} |
|
831 # scalars first |
|
832 for i in range(len(self.scalars)): |
|
833 entry = self.scalars[i] |
|
834 try: |
|
835 val = function(self, entry, **keywargs) |
|
836 # bound again in case name has changed |
|
837 entry = self.scalars[i] |
|
838 out[entry] = val |
|
839 except Exception: |
|
840 if raise_errors: |
|
841 raise |
|
842 else: |
|
843 entry = self.scalars[i] |
|
844 out[entry] = False |
|
845 # then sections |
|
846 for i in range(len(self.sections)): |
|
847 entry = self.sections[i] |
|
848 if call_on_sections: |
|
849 try: |
|
850 function(self, entry, **keywargs) |
|
851 except Exception: |
|
852 if raise_errors: |
|
853 raise |
|
854 else: |
|
855 entry = self.sections[i] |
|
856 out[entry] = False |
|
857 # bound again in case name has changed |
|
858 entry = self.sections[i] |
|
859 # previous result is discarded |
|
860 out[entry] = self[entry].walk( |
|
861 function, |
|
862 raise_errors=raise_errors, |
|
863 call_on_sections=call_on_sections, |
|
864 **keywargs) |
|
865 return out |
|
866 |
|
867 def decode(self, encoding): |
|
868 """ |
|
869 Decode all strings and values to unicode, using the specified encoding. |
|
870 |
|
871 Works with subsections and list values. |
|
872 |
|
873 Uses the ``walk`` method. |
|
874 |
|
875 Testing ``encode`` and ``decode``. |
|
876 >>> m = ConfigObj(a) |
|
877 >>> m.decode('ascii') |
|
878 >>> def testuni(val): |
|
879 ... for entry in val: |
|
880 ... if not isinstance(entry, unicode): |
|
881 ... print >> sys.stderr, type(entry) |
|
882 ... raise AssertionError, 'decode failed.' |
|
883 ... if isinstance(val[entry], dict): |
|
884 ... testuni(val[entry]) |
|
885 ... elif not isinstance(val[entry], unicode): |
|
886 ... raise AssertionError, 'decode failed.' |
|
887 >>> testuni(m) |
|
888 >>> m.encode('ascii') |
|
889 >>> a == m |
|
890 1 |
|
891 """ |
|
892 warn('use of ``decode`` is deprecated.', DeprecationWarning) |
|
893 def decode(section, key, encoding=encoding, warn=True): |
|
894 """ """ |
|
895 val = section[key] |
|
896 if isinstance(val, (list, tuple)): |
|
897 newval = [] |
|
898 for entry in val: |
|
899 newval.append(entry.decode(encoding)) |
|
900 elif isinstance(val, dict): |
|
901 newval = val |
|
902 else: |
|
903 newval = val.decode(encoding) |
|
904 newkey = key.decode(encoding) |
|
905 section.rename(key, newkey) |
|
906 section[newkey] = newval |
|
907 # using ``call_on_sections`` allows us to modify section names |
|
908 self.walk(decode, call_on_sections=True) |
|
909 |
|
910 def encode(self, encoding): |
|
911 """ |
|
912 Encode all strings and values from unicode, |
|
913 using the specified encoding. |
|
914 |
|
915 Works with subsections and list values. |
|
916 Uses the ``walk`` method. |
|
917 """ |
|
918 warn('use of ``encode`` is deprecated.', DeprecationWarning) |
|
919 def encode(section, key, encoding=encoding): |
|
920 """ """ |
|
921 val = section[key] |
|
922 if isinstance(val, (list, tuple)): |
|
923 newval = [] |
|
924 for entry in val: |
|
925 newval.append(entry.encode(encoding)) |
|
926 elif isinstance(val, dict): |
|
927 newval = val |
|
928 else: |
|
929 newval = val.encode(encoding) |
|
930 newkey = key.encode(encoding) |
|
931 section.rename(key, newkey) |
|
932 section[newkey] = newval |
|
933 self.walk(encode, call_on_sections=True) |
|
934 |
|
935 def istrue(self, key): |
|
936 """A deprecated version of ``as_bool``.""" |
|
937 warn('use of ``istrue`` is deprecated. Use ``as_bool`` method ' |
|
938 'instead.', DeprecationWarning) |
|
939 return self.as_bool(key) |
|
940 |
|
941 def as_bool(self, key): |
|
942 """ |
|
943 Accepts a key as input. The corresponding value must be a string or |
|
944 the objects (``True`` or 1) or (``False`` or 0). We allow 0 and 1 to |
|
945 retain compatibility with Python 2.2. |
|
946 |
|
947 If the string is one of ``True``, ``On``, ``Yes``, or ``1`` it returns |
|
948 ``True``. |
|
949 |
|
950 If the string is one of ``False``, ``Off``, ``No``, or ``0`` it returns |
|
951 ``False``. |
|
952 |
|
953 ``as_bool`` is not case sensitive. |
|
954 |
|
955 Any other input will raise a ``ValueError``. |
|
956 |
|
957 >>> a = ConfigObj() |
|
958 >>> a['a'] = 'fish' |
|
959 >>> a.as_bool('a') |
|
960 Traceback (most recent call last): |
|
961 ValueError: Value "fish" is neither True nor False |
|
962 >>> a['b'] = 'True' |
|
963 >>> a.as_bool('b') |
|
964 1 |
|
965 >>> a['b'] = 'off' |
|
966 >>> a.as_bool('b') |
|
967 0 |
|
968 """ |
|
969 val = self[key] |
|
970 if val == True: |
|
971 return True |
|
972 elif val == False: |
|
973 return False |
|
974 else: |
|
975 try: |
|
976 if not isinstance(val, StringTypes): |
|
977 raise KeyError |
|
978 else: |
|
979 return self.main._bools[val.lower()] |
|
980 except KeyError: |
|
981 raise ValueError('Value "%s" is neither True nor False' % val) |
|
982 |
|
983 def as_int(self, key): |
|
984 """ |
|
985 A convenience method which coerces the specified value to an integer. |
|
986 |
|
987 If the value is an invalid literal for ``int``, a ``ValueError`` will |
|
988 be raised. |
|
989 |
|
990 >>> a = ConfigObj() |
|
991 >>> a['a'] = 'fish' |
|
992 >>> a.as_int('a') |
|
993 Traceback (most recent call last): |
|
994 ValueError: invalid literal for int(): fish |
|
995 >>> a['b'] = '1' |
|
996 >>> a.as_int('b') |
|
997 1 |
|
998 >>> a['b'] = '3.2' |
|
999 >>> a.as_int('b') |
|
1000 Traceback (most recent call last): |
|
1001 ValueError: invalid literal for int(): 3.2 |
|
1002 """ |
|
1003 return int(self[key]) |
|
1004 |
|
1005 def as_float(self, key): |
|
1006 """ |
|
1007 A convenience method which coerces the specified value to a float. |
|
1008 |
|
1009 If the value is an invalid literal for ``float``, a ``ValueError`` will |
|
1010 be raised. |
|
1011 |
|
1012 >>> a = ConfigObj() |
|
1013 >>> a['a'] = 'fish' |
|
1014 >>> a.as_float('a') |
|
1015 Traceback (most recent call last): |
|
1016 ValueError: invalid literal for float(): fish |
|
1017 >>> a['b'] = '1' |
|
1018 >>> a.as_float('b') |
|
1019 1.0 |
|
1020 >>> a['b'] = '3.2' |
|
1021 >>> a.as_float('b') |
|
1022 3.2000000000000002 |
|
1023 """ |
|
1024 return float(self[key]) |
|
1025 |
|
1026 |
|
1027 class ConfigObj(Section): |
|
1028 """An object to read, create, and write config files.""" |
|
1029 |
|
1030 _keyword = re.compile(r'''^ # line start |
|
1031 (\s*) # indentation |
|
1032 ( # keyword |
|
1033 (?:".*?")| # double quotes |
|
1034 (?:'.*?')| # single quotes |
|
1035 (?:[^'"=].*?) # no quotes |
|
1036 ) |
|
1037 \s*=\s* # divider |
|
1038 (.*) # value (including list values and comments) |
|
1039 $ # line end |
|
1040 ''', |
|
1041 re.VERBOSE) |
|
1042 |
|
1043 _sectionmarker = re.compile(r'''^ |
|
1044 (\s*) # 1: indentation |
|
1045 ((?:\[\s*)+) # 2: section marker open |
|
1046 ( # 3: section name open |
|
1047 (?:"\s*\S.*?\s*")| # at least one non-space with double quotes |
|
1048 (?:'\s*\S.*?\s*')| # at least one non-space with single quotes |
|
1049 (?:[^'"\s].*?) # at least one non-space unquoted |
|
1050 ) # section name close |
|
1051 ((?:\s*\])+) # 4: section marker close |
|
1052 \s*(\#.*)? # 5: optional comment |
|
1053 $''', |
|
1054 re.VERBOSE) |
|
1055 |
|
1056 # this regexp pulls list values out as a single string |
|
1057 # or single values and comments |
|
1058 # FIXME: this regex adds a '' to the end of comma terminated lists |
|
1059 # workaround in ``_handle_value`` |
|
1060 _valueexp = re.compile(r'''^ |
|
1061 (?: |
|
1062 (?: |
|
1063 ( |
|
1064 (?: |
|
1065 (?: |
|
1066 (?:".*?")| # double quotes |
|
1067 (?:'.*?')| # single quotes |
|
1068 (?:[^'",\#][^,\#]*?) # unquoted |
|
1069 ) |
|
1070 \s*,\s* # comma |
|
1071 )* # match all list items ending in a comma (if any) |
|
1072 ) |
|
1073 ( |
|
1074 (?:".*?")| # double quotes |
|
1075 (?:'.*?')| # single quotes |
|
1076 (?:[^'",\#\s][^,]*?)| # unquoted |
|
1077 (?:(?<!,)) # Empty value |
|
1078 )? # last item in a list - or string value |
|
1079 )| |
|
1080 (,) # alternatively a single comma - empty list |
|
1081 ) |
|
1082 \s*(\#.*)? # optional comment |
|
1083 $''', |
|
1084 re.VERBOSE) |
|
1085 |
|
1086 # use findall to get the members of a list value |
|
1087 _listvalueexp = re.compile(r''' |
|
1088 ( |
|
1089 (?:".*?")| # double quotes |
|
1090 (?:'.*?')| # single quotes |
|
1091 (?:[^'",\#].*?) # unquoted |
|
1092 ) |
|
1093 \s*,\s* # comma |
|
1094 ''', |
|
1095 re.VERBOSE) |
|
1096 |
|
1097 # this regexp is used for the value |
|
1098 # when lists are switched off |
|
1099 _nolistvalue = re.compile(r'''^ |
|
1100 ( |
|
1101 (?:".*?")| # double quotes |
|
1102 (?:'.*?')| # single quotes |
|
1103 (?:[^'"\#].*?)| # unquoted |
|
1104 (?:) # Empty value |
|
1105 ) |
|
1106 \s*(\#.*)? # optional comment |
|
1107 $''', |
|
1108 re.VERBOSE) |
|
1109 |
|
1110 # regexes for finding triple quoted values on one line |
|
1111 _single_line_single = re.compile(r"^'''(.*?)'''\s*(#.*)?$") |
|
1112 _single_line_double = re.compile(r'^"""(.*?)"""\s*(#.*)?$') |
|
1113 _multi_line_single = re.compile(r"^(.*?)'''\s*(#.*)?$") |
|
1114 _multi_line_double = re.compile(r'^(.*?)"""\s*(#.*)?$') |
|
1115 |
|
1116 _triple_quote = { |
|
1117 "'''": (_single_line_single, _multi_line_single), |
|
1118 '"""': (_single_line_double, _multi_line_double), |
|
1119 } |
|
1120 |
|
1121 # Used by the ``istrue`` Section method |
|
1122 _bools = { |
|
1123 'yes': True, 'no': False, |
|
1124 'on': True, 'off': False, |
|
1125 '1': True, '0': False, |
|
1126 'true': True, 'false': False, |
|
1127 } |
|
1128 |
|
1129 def __init__(self, infile=None, options=None, **kwargs): |
|
1130 """ |
|
1131 Parse or create a config file object. |
|
1132 |
|
1133 ``ConfigObj(infile=None, options=None, **kwargs)`` |
|
1134 """ |
|
1135 if infile is None: |
|
1136 infile = [] |
|
1137 if options is None: |
|
1138 options = {} |
|
1139 else: |
|
1140 options = dict(options) |
|
1141 # keyword arguments take precedence over an options dictionary |
|
1142 options.update(kwargs) |
|
1143 # init the superclass |
|
1144 Section.__init__(self, self, 0, self) |
|
1145 # |
|
1146 defaults = OPTION_DEFAULTS.copy() |
|
1147 for entry in options.keys(): |
|
1148 if entry not in defaults.keys(): |
|
1149 raise TypeError, 'Unrecognised option "%s".' % entry |
|
1150 # TODO: check the values too. |
|
1151 # |
|
1152 # Add any explicit options to the defaults |
|
1153 defaults.update(options) |
|
1154 # |
|
1155 # initialise a few variables |
|
1156 self.filename = None |
|
1157 self._errors = [] |
|
1158 self.raise_errors = defaults['raise_errors'] |
|
1159 self.interpolation = defaults['interpolation'] |
|
1160 self.list_values = defaults['list_values'] |
|
1161 self.create_empty = defaults['create_empty'] |
|
1162 self.file_error = defaults['file_error'] |
|
1163 self.stringify = defaults['stringify'] |
|
1164 self.indent_type = defaults['indent_type'] |
|
1165 self.encoding = defaults['encoding'] |
|
1166 self.default_encoding = defaults['default_encoding'] |
|
1167 self.BOM = False |
|
1168 self.newlines = None |
|
1169 self.write_empty_values = defaults['write_empty_values'] |
|
1170 self.unrepr = defaults['unrepr'] |
|
1171 # |
|
1172 self.initial_comment = [] |
|
1173 self.final_comment = [] |
|
1174 # |
|
1175 self._terminated = False |
|
1176 # |
|
1177 if isinstance(infile, StringTypes): |
|
1178 self.filename = infile |
|
1179 if os.path.isfile(infile): |
|
1180 infile = open(infile).read() or [] |
|
1181 elif self.file_error: |
|
1182 # raise an error if the file doesn't exist |
|
1183 raise IOError, 'Config file not found: "%s".' % self.filename |
|
1184 else: |
|
1185 # file doesn't already exist |
|
1186 if self.create_empty: |
|
1187 # this is a good test that the filename specified |
|
1188 # isn't impossible - like on a non existent device |
|
1189 h = open(infile, 'w') |
|
1190 h.write('') |
|
1191 h.close() |
|
1192 infile = [] |
|
1193 elif isinstance(infile, (list, tuple)): |
|
1194 infile = list(infile) |
|
1195 elif isinstance(infile, dict): |
|
1196 # initialise self |
|
1197 # the Section class handles creating subsections |
|
1198 if isinstance(infile, ConfigObj): |
|
1199 # get a copy of our ConfigObj |
|
1200 infile = infile.dict() |
|
1201 for entry in infile: |
|
1202 self[entry] = infile[entry] |
|
1203 del self._errors |
|
1204 if defaults['configspec'] is not None: |
|
1205 self._handle_configspec(defaults['configspec']) |
|
1206 else: |
|
1207 self.configspec = None |
|
1208 return |
|
1209 elif hasattr(infile, 'read'): |
|
1210 # This supports file like objects |
|
1211 infile = infile.read() or [] |
|
1212 # needs splitting into lines - but needs doing *after* decoding |
|
1213 # in case it's not an 8 bit encoding |
|
1214 else: |
|
1215 raise TypeError, ('infile must be a filename,' |
|
1216 ' file like object, or list of lines.') |
|
1217 # |
|
1218 if infile: |
|
1219 # don't do it for the empty ConfigObj |
|
1220 infile = self._handle_bom(infile) |
|
1221 # infile is now *always* a list |
|
1222 # |
|
1223 # Set the newlines attribute (first line ending it finds) |
|
1224 # and strip trailing '\n' or '\r' from lines |
|
1225 for line in infile: |
|
1226 if (not line) or (line[-1] not in ('\r', '\n', '\r\n')): |
|
1227 continue |
|
1228 for end in ('\r\n', '\n', '\r'): |
|
1229 if line.endswith(end): |
|
1230 self.newlines = end |
|
1231 break |
|
1232 break |
|
1233 if infile[-1] and infile[-1] in ('\r', '\n', '\r\n'): |
|
1234 self._terminated = True |
|
1235 infile = [line.rstrip('\r\n') for line in infile] |
|
1236 # |
|
1237 self._parse(infile) |
|
1238 # if we had any errors, now is the time to raise them |
|
1239 if self._errors: |
|
1240 info = "at line %s." % self._errors[0].line_number |
|
1241 if len(self._errors) > 1: |
|
1242 msg = ("Parsing failed with several errors.\nFirst error %s" % |
|
1243 info) |
|
1244 error = ConfigObjError(msg) |
|
1245 else: |
|
1246 error = self._errors[0] |
|
1247 # set the errors attribute; it's a list of tuples: |
|
1248 # (error_type, message, line_number) |
|
1249 error.errors = self._errors |
|
1250 # set the config attribute |
|
1251 error.config = self |
|
1252 raise error |
|
1253 # delete private attributes |
|
1254 del self._errors |
|
1255 # |
|
1256 if defaults['configspec'] is None: |
|
1257 self.configspec = None |
|
1258 else: |
|
1259 self._handle_configspec(defaults['configspec']) |
|
1260 |
|
1261 def __repr__(self): |
|
1262 return 'ConfigObj({%s})' % ', '.join( |
|
1263 [('%s: %s' % (repr(key), repr(self[key]))) for key in |
|
1264 (self.scalars + self.sections)]) |
|
1265 |
|
1266 def _handle_bom(self, infile): |
|
1267 """ |
|
1268 Handle any BOM, and decode if necessary. |
|
1269 |
|
1270 If an encoding is specified, that *must* be used - but the BOM should |
|
1271 still be removed (and the BOM attribute set). |
|
1272 |
|
1273 (If the encoding is wrongly specified, then a BOM for an alternative |
|
1274 encoding won't be discovered or removed.) |
|
1275 |
|
1276 If an encoding is not specified, UTF8 or UTF16 BOM will be detected and |
|
1277 removed. The BOM attribute will be set. UTF16 will be decoded to |
|
1278 unicode. |
|
1279 |
|
1280 NOTE: This method must not be called with an empty ``infile``. |
|
1281 |
|
1282 Specifying the *wrong* encoding is likely to cause a |
|
1283 ``UnicodeDecodeError``. |
|
1284 |
|
1285 ``infile`` must always be returned as a list of lines, but may be |
|
1286 passed in as a single string. |
|
1287 """ |
|
1288 if ((self.encoding is not None) and |
|
1289 (self.encoding.lower() not in BOM_LIST)): |
|
1290 # No need to check for a BOM |
|
1291 # the encoding specified doesn't have one |
|
1292 # just decode |
|
1293 return self._decode(infile, self.encoding) |
|
1294 # |
|
1295 if isinstance(infile, (list, tuple)): |
|
1296 line = infile[0] |
|
1297 else: |
|
1298 line = infile |
|
1299 if self.encoding is not None: |
|
1300 # encoding explicitly supplied |
|
1301 # And it could have an associated BOM |
|
1302 # TODO: if encoding is just UTF16 - we ought to check for both |
|
1303 # TODO: big endian and little endian versions. |
|
1304 enc = BOM_LIST[self.encoding.lower()] |
|
1305 if enc == 'utf_16': |
|
1306 # For UTF16 we try big endian and little endian |
|
1307 for BOM, (encoding, final_encoding) in BOMS.items(): |
|
1308 if not final_encoding: |
|
1309 # skip UTF8 |
|
1310 continue |
|
1311 if infile.startswith(BOM): |
|
1312 ### BOM discovered |
|
1313 ##self.BOM = True |
|
1314 # Don't need to remove BOM |
|
1315 return self._decode(infile, encoding) |
|
1316 # |
|
1317 # If we get this far, will *probably* raise a DecodeError |
|
1318 # As it doesn't appear to start with a BOM |
|
1319 return self._decode(infile, self.encoding) |
|
1320 # |
|
1321 # Must be UTF8 |
|
1322 BOM = BOM_SET[enc] |
|
1323 if not line.startswith(BOM): |
|
1324 return self._decode(infile, self.encoding) |
|
1325 # |
|
1326 newline = line[len(BOM):] |
|
1327 # |
|
1328 # BOM removed |
|
1329 if isinstance(infile, (list, tuple)): |
|
1330 infile[0] = newline |
|
1331 else: |
|
1332 infile = newline |
|
1333 self.BOM = True |
|
1334 return self._decode(infile, self.encoding) |
|
1335 # |
|
1336 # No encoding specified - so we need to check for UTF8/UTF16 |
|
1337 for BOM, (encoding, final_encoding) in BOMS.items(): |
|
1338 if not line.startswith(BOM): |
|
1339 continue |
|
1340 else: |
|
1341 # BOM discovered |
|
1342 self.encoding = final_encoding |
|
1343 if not final_encoding: |
|
1344 self.BOM = True |
|
1345 # UTF8 |
|
1346 # remove BOM |
|
1347 newline = line[len(BOM):] |
|
1348 if isinstance(infile, (list, tuple)): |
|
1349 infile[0] = newline |
|
1350 else: |
|
1351 infile = newline |
|
1352 # UTF8 - don't decode |
|
1353 if isinstance(infile, StringTypes): |
|
1354 return infile.splitlines(True) |
|
1355 else: |
|
1356 return infile |
|
1357 # UTF16 - have to decode |
|
1358 return self._decode(infile, encoding) |
|
1359 # |
|
1360 # No BOM discovered and no encoding specified, just return |
|
1361 if isinstance(infile, StringTypes): |
|
1362 # infile read from a file will be a single string |
|
1363 return infile.splitlines(True) |
|
1364 else: |
|
1365 return infile |
|
1366 |
|
1367 def _a_to_u(self, aString): |
|
1368 """Decode ASCII strings to unicode if a self.encoding is specified.""" |
|
1369 if self.encoding: |
|
1370 return aString.decode('ascii') |
|
1371 else: |
|
1372 return aString |
|
1373 |
|
1374 def _decode(self, infile, encoding): |
|
1375 """ |
|
1376 Decode infile to unicode. Using the specified encoding. |
|
1377 |
|
1378 if is a string, it also needs converting to a list. |
|
1379 """ |
|
1380 if isinstance(infile, StringTypes): |
|
1381 # can't be unicode |
|
1382 # NOTE: Could raise a ``UnicodeDecodeError`` |
|
1383 return infile.decode(encoding).splitlines(True) |
|
1384 for i, line in enumerate(infile): |
|
1385 if not isinstance(line, unicode): |
|
1386 # NOTE: The isinstance test here handles mixed lists of unicode/string |
|
1387 # NOTE: But the decode will break on any non-string values |
|
1388 # NOTE: Or could raise a ``UnicodeDecodeError`` |
|
1389 infile[i] = line.decode(encoding) |
|
1390 return infile |
|
1391 |
|
1392 def _decode_element(self, line): |
|
1393 """Decode element to unicode if necessary.""" |
|
1394 if not self.encoding: |
|
1395 return line |
|
1396 if isinstance(line, str) and self.default_encoding: |
|
1397 return line.decode(self.default_encoding) |
|
1398 return line |
|
1399 |
|
1400 def _str(self, value): |
|
1401 """ |
|
1402 Used by ``stringify`` within validate, to turn non-string values |
|
1403 into strings. |
|
1404 """ |
|
1405 if not isinstance(value, StringTypes): |
|
1406 return str(value) |
|
1407 else: |
|
1408 return value |
|
1409 |
|
1410 def _parse(self, infile): |
|
1411 """Actually parse the config file.""" |
|
1412 temp_list_values = self.list_values |
|
1413 if self.unrepr: |
|
1414 self.list_values = False |
|
1415 comment_list = [] |
|
1416 done_start = False |
|
1417 this_section = self |
|
1418 maxline = len(infile) - 1 |
|
1419 cur_index = -1 |
|
1420 reset_comment = False |
|
1421 while cur_index < maxline: |
|
1422 if reset_comment: |
|
1423 comment_list = [] |
|
1424 cur_index += 1 |
|
1425 line = infile[cur_index] |
|
1426 sline = line.strip() |
|
1427 # do we have anything on the line ? |
|
1428 if not sline or sline.startswith('#') or sline.startswith(';'): |
|
1429 reset_comment = False |
|
1430 comment_list.append(line) |
|
1431 continue |
|
1432 if not done_start: |
|
1433 # preserve initial comment |
|
1434 self.initial_comment = comment_list |
|
1435 comment_list = [] |
|
1436 done_start = True |
|
1437 reset_comment = True |
|
1438 # first we check if it's a section marker |
|
1439 mat = self._sectionmarker.match(line) |
|
1440 if mat is not None: |
|
1441 # is a section line |
|
1442 (indent, sect_open, sect_name, sect_close, comment) = ( |
|
1443 mat.groups()) |
|
1444 if indent and (self.indent_type is None): |
|
1445 self.indent_type = indent |
|
1446 cur_depth = sect_open.count('[') |
|
1447 if cur_depth != sect_close.count(']'): |
|
1448 self._handle_error( |
|
1449 "Cannot compute the section depth at line %s.", |
|
1450 NestingError, infile, cur_index) |
|
1451 continue |
|
1452 # |
|
1453 if cur_depth < this_section.depth: |
|
1454 # the new section is dropping back to a previous level |
|
1455 try: |
|
1456 parent = self._match_depth( |
|
1457 this_section, |
|
1458 cur_depth).parent |
|
1459 except SyntaxError: |
|
1460 self._handle_error( |
|
1461 "Cannot compute nesting level at line %s.", |
|
1462 NestingError, infile, cur_index) |
|
1463 continue |
|
1464 elif cur_depth == this_section.depth: |
|
1465 # the new section is a sibling of the current section |
|
1466 parent = this_section.parent |
|
1467 elif cur_depth == this_section.depth + 1: |
|
1468 # the new section is a child the current section |
|
1469 parent = this_section |
|
1470 else: |
|
1471 self._handle_error( |
|
1472 "Section too nested at line %s.", |
|
1473 NestingError, infile, cur_index) |
|
1474 # |
|
1475 sect_name = self._unquote(sect_name) |
|
1476 if parent.has_key(sect_name): |
|
1477 self._handle_error( |
|
1478 'Duplicate section name at line %s.', |
|
1479 DuplicateError, infile, cur_index) |
|
1480 continue |
|
1481 # create the new section |
|
1482 this_section = Section( |
|
1483 parent, |
|
1484 cur_depth, |
|
1485 self, |
|
1486 name=sect_name) |
|
1487 parent[sect_name] = this_section |
|
1488 parent.inline_comments[sect_name] = comment |
|
1489 parent.comments[sect_name] = comment_list |
|
1490 continue |
|
1491 # |
|
1492 # it's not a section marker, |
|
1493 # so it should be a valid ``key = value`` line |
|
1494 mat = self._keyword.match(line) |
|
1495 if mat is None: |
|
1496 # it neither matched as a keyword |
|
1497 # or a section marker |
|
1498 self._handle_error( |
|
1499 'Invalid line at line "%s".', |
|
1500 ParseError, infile, cur_index) |
|
1501 else: |
|
1502 # is a keyword value |
|
1503 # value will include any inline comment |
|
1504 (indent, key, value) = mat.groups() |
|
1505 if indent and (self.indent_type is None): |
|
1506 self.indent_type = indent |
|
1507 # check for a multiline value |
|
1508 if value[:3] in ['"""', "'''"]: |
|
1509 try: |
|
1510 (value, comment, cur_index) = self._multiline( |
|
1511 value, infile, cur_index, maxline) |
|
1512 except SyntaxError: |
|
1513 self._handle_error( |
|
1514 'Parse error in value at line %s.', |
|
1515 ParseError, infile, cur_index) |
|
1516 continue |
|
1517 else: |
|
1518 if self.unrepr: |
|
1519 comment = '' |
|
1520 try: |
|
1521 value = unrepr(value) |
|
1522 except Exception, e: |
|
1523 if type(e) == UnknownType: |
|
1524 msg = 'Unknown name or type in value at line %s.' |
|
1525 else: |
|
1526 msg = 'Parse error in value at line %s.' |
|
1527 self._handle_error(msg, UnreprError, infile, |
|
1528 cur_index) |
|
1529 continue |
|
1530 else: |
|
1531 if self.unrepr: |
|
1532 comment = '' |
|
1533 try: |
|
1534 value = unrepr(value) |
|
1535 except Exception, e: |
|
1536 if isinstance(e, UnknownType): |
|
1537 msg = 'Unknown name or type in value at line %s.' |
|
1538 else: |
|
1539 msg = 'Parse error in value at line %s.' |
|
1540 self._handle_error(msg, UnreprError, infile, |
|
1541 cur_index) |
|
1542 continue |
|
1543 else: |
|
1544 # extract comment and lists |
|
1545 try: |
|
1546 (value, comment) = self._handle_value(value) |
|
1547 except SyntaxError: |
|
1548 self._handle_error( |
|
1549 'Parse error in value at line %s.', |
|
1550 ParseError, infile, cur_index) |
|
1551 continue |
|
1552 # |
|
1553 key = self._unquote(key) |
|
1554 if this_section.has_key(key): |
|
1555 self._handle_error( |
|
1556 'Duplicate keyword name at line %s.', |
|
1557 DuplicateError, infile, cur_index) |
|
1558 continue |
|
1559 # add the key. |
|
1560 # we set unrepr because if we have got this far we will never |
|
1561 # be creating a new section |
|
1562 this_section.__setitem__(key, value, unrepr=True) |
|
1563 this_section.inline_comments[key] = comment |
|
1564 this_section.comments[key] = comment_list |
|
1565 continue |
|
1566 # |
|
1567 if self.indent_type is None: |
|
1568 # no indentation used, set the type accordingly |
|
1569 self.indent_type = '' |
|
1570 # |
|
1571 if self._terminated: |
|
1572 comment_list.append('') |
|
1573 # preserve the final comment |
|
1574 if not self and not self.initial_comment: |
|
1575 self.initial_comment = comment_list |
|
1576 elif not reset_comment: |
|
1577 self.final_comment = comment_list |
|
1578 self.list_values = temp_list_values |
|
1579 |
|
1580 def _match_depth(self, sect, depth): |
|
1581 """ |
|
1582 Given a section and a depth level, walk back through the sections |
|
1583 parents to see if the depth level matches a previous section. |
|
1584 |
|
1585 Return a reference to the right section, |
|
1586 or raise a SyntaxError. |
|
1587 """ |
|
1588 while depth < sect.depth: |
|
1589 if sect is sect.parent: |
|
1590 # we've reached the top level already |
|
1591 raise SyntaxError |
|
1592 sect = sect.parent |
|
1593 if sect.depth == depth: |
|
1594 return sect |
|
1595 # shouldn't get here |
|
1596 raise SyntaxError |
|
1597 |
|
1598 def _handle_error(self, text, ErrorClass, infile, cur_index): |
|
1599 """ |
|
1600 Handle an error according to the error settings. |
|
1601 |
|
1602 Either raise the error or store it. |
|
1603 The error will have occurred at ``cur_index`` |
|
1604 """ |
|
1605 line = infile[cur_index] |
|
1606 cur_index += 1 |
|
1607 message = text % cur_index |
|
1608 error = ErrorClass(message, cur_index, line) |
|
1609 if self.raise_errors: |
|
1610 # raise the error - parsing stops here |
|
1611 raise error |
|
1612 # store the error |
|
1613 # reraise when parsing has finished |
|
1614 self._errors.append(error) |
|
1615 |
|
1616 def _unquote(self, value): |
|
1617 """Return an unquoted version of a value""" |
|
1618 if (value[0] == value[-1]) and (value[0] in ('"', "'")): |
|
1619 value = value[1:-1] |
|
1620 return value |
|
1621 |
|
1622 def _quote(self, value, multiline=True): |
|
1623 """ |
|
1624 Return a safely quoted version of a value. |
|
1625 |
|
1626 Raise a ConfigObjError if the value cannot be safely quoted. |
|
1627 If multiline is ``True`` (default) then use triple quotes |
|
1628 if necessary. |
|
1629 |
|
1630 Don't quote values that don't need it. |
|
1631 Recursively quote members of a list and return a comma joined list. |
|
1632 Multiline is ``False`` for lists. |
|
1633 Obey list syntax for empty and single member lists. |
|
1634 |
|
1635 If ``list_values=False`` then the value is only quoted if it contains |
|
1636 a ``\n`` (is multiline). |
|
1637 |
|
1638 If ``write_empty_values`` is set, and the value is an empty string, it |
|
1639 won't be quoted. |
|
1640 """ |
|
1641 if multiline and self.write_empty_values and value == '': |
|
1642 # Only if multiline is set, so that it is used for values not |
|
1643 # keys, and not values that are part of a list |
|
1644 return '' |
|
1645 if multiline and isinstance(value, (list, tuple)): |
|
1646 if not value: |
|
1647 return ',' |
|
1648 elif len(value) == 1: |
|
1649 return self._quote(value[0], multiline=False) + ',' |
|
1650 return ', '.join([self._quote(val, multiline=False) |
|
1651 for val in value]) |
|
1652 if not isinstance(value, StringTypes): |
|
1653 if self.stringify: |
|
1654 value = str(value) |
|
1655 else: |
|
1656 raise TypeError, 'Value "%s" is not a string.' % value |
|
1657 squot = "'%s'" |
|
1658 dquot = '"%s"' |
|
1659 noquot = "%s" |
|
1660 wspace_plus = ' \r\t\n\v\t\'"' |
|
1661 tsquot = '"""%s"""' |
|
1662 tdquot = "'''%s'''" |
|
1663 if not value: |
|
1664 return '""' |
|
1665 if (not self.list_values and '\n' not in value) or not (multiline and |
|
1666 ((("'" in value) and ('"' in value)) or ('\n' in value))): |
|
1667 if not self.list_values: |
|
1668 # we don't quote if ``list_values=False`` |
|
1669 quot = noquot |
|
1670 # for normal values either single or double quotes will do |
|
1671 elif '\n' in value: |
|
1672 # will only happen if multiline is off - e.g. '\n' in key |
|
1673 raise ConfigObjError, ('Value "%s" cannot be safely quoted.' % |
|
1674 value) |
|
1675 elif ((value[0] not in wspace_plus) and |
|
1676 (value[-1] not in wspace_plus) and |
|
1677 (',' not in value)): |
|
1678 quot = noquot |
|
1679 else: |
|
1680 if ("'" in value) and ('"' in value): |
|
1681 raise ConfigObjError, ( |
|
1682 'Value "%s" cannot be safely quoted.' % value) |
|
1683 elif '"' in value: |
|
1684 quot = squot |
|
1685 else: |
|
1686 quot = dquot |
|
1687 else: |
|
1688 # if value has '\n' or "'" *and* '"', it will need triple quotes |
|
1689 if (value.find('"""') != -1) and (value.find("'''") != -1): |
|
1690 raise ConfigObjError, ( |
|
1691 'Value "%s" cannot be safely quoted.' % value) |
|
1692 if value.find('"""') == -1: |
|
1693 quot = tdquot |
|
1694 else: |
|
1695 quot = tsquot |
|
1696 return quot % value |
|
1697 |
|
1698 def _handle_value(self, value): |
|
1699 """ |
|
1700 Given a value string, unquote, remove comment, |
|
1701 handle lists. (including empty and single member lists) |
|
1702 """ |
|
1703 # do we look for lists in values ? |
|
1704 if not self.list_values: |
|
1705 mat = self._nolistvalue.match(value) |
|
1706 if mat is None: |
|
1707 raise SyntaxError |
|
1708 # NOTE: we don't unquote here |
|
1709 return mat.groups() |
|
1710 # |
|
1711 mat = self._valueexp.match(value) |
|
1712 if mat is None: |
|
1713 # the value is badly constructed, probably badly quoted, |
|
1714 # or an invalid list |
|
1715 raise SyntaxError |
|
1716 (list_values, single, empty_list, comment) = mat.groups() |
|
1717 if (list_values == '') and (single is None): |
|
1718 # change this if you want to accept empty values |
|
1719 raise SyntaxError |
|
1720 # NOTE: note there is no error handling from here if the regex |
|
1721 # is wrong: then incorrect values will slip through |
|
1722 if empty_list is not None: |
|
1723 # the single comma - meaning an empty list |
|
1724 return ([], comment) |
|
1725 if single is not None: |
|
1726 # handle empty values |
|
1727 if list_values and not single: |
|
1728 # FIXME: the '' is a workaround because our regex now matches |
|
1729 # '' at the end of a list if it has a trailing comma |
|
1730 single = None |
|
1731 else: |
|
1732 single = single or '""' |
|
1733 single = self._unquote(single) |
|
1734 if list_values == '': |
|
1735 # not a list value |
|
1736 return (single, comment) |
|
1737 the_list = self._listvalueexp.findall(list_values) |
|
1738 the_list = [self._unquote(val) for val in the_list] |
|
1739 if single is not None: |
|
1740 the_list += [single] |
|
1741 return (the_list, comment) |
|
1742 |
|
1743 def _multiline(self, value, infile, cur_index, maxline): |
|
1744 """Extract the value, where we are in a multiline situation.""" |
|
1745 quot = value[:3] |
|
1746 newvalue = value[3:] |
|
1747 single_line = self._triple_quote[quot][0] |
|
1748 multi_line = self._triple_quote[quot][1] |
|
1749 mat = single_line.match(value) |
|
1750 if mat is not None: |
|
1751 retval = list(mat.groups()) |
|
1752 retval.append(cur_index) |
|
1753 return retval |
|
1754 elif newvalue.find(quot) != -1: |
|
1755 # somehow the triple quote is missing |
|
1756 raise SyntaxError |
|
1757 # |
|
1758 while cur_index < maxline: |
|
1759 cur_index += 1 |
|
1760 newvalue += '\n' |
|
1761 line = infile[cur_index] |
|
1762 if line.find(quot) == -1: |
|
1763 newvalue += line |
|
1764 else: |
|
1765 # end of multiline, process it |
|
1766 break |
|
1767 else: |
|
1768 # we've got to the end of the config, oops... |
|
1769 raise SyntaxError |
|
1770 mat = multi_line.match(line) |
|
1771 if mat is None: |
|
1772 # a badly formed line |
|
1773 raise SyntaxError |
|
1774 (value, comment) = mat.groups() |
|
1775 return (newvalue + value, comment, cur_index) |
|
1776 |
|
1777 def _handle_configspec(self, configspec): |
|
1778 """Parse the configspec.""" |
|
1779 # FIXME: Should we check that the configspec was created with the |
|
1780 # correct settings ? (i.e. ``list_values=False``) |
|
1781 if not isinstance(configspec, ConfigObj): |
|
1782 try: |
|
1783 configspec = ConfigObj( |
|
1784 configspec, |
|
1785 raise_errors=True, |
|
1786 file_error=True, |
|
1787 list_values=False) |
|
1788 except ConfigObjError, e: |
|
1789 # FIXME: Should these errors have a reference |
|
1790 # to the already parsed ConfigObj ? |
|
1791 raise ConfigspecError('Parsing configspec failed: %s' % e) |
|
1792 except IOError, e: |
|
1793 raise IOError('Reading configspec failed: %s' % e) |
|
1794 self._set_configspec_value(configspec, self) |
|
1795 |
|
1796 def _set_configspec_value(self, configspec, section): |
|
1797 """Used to recursively set configspec values.""" |
|
1798 if '__many__' in configspec.sections: |
|
1799 section.configspec['__many__'] = configspec['__many__'] |
|
1800 if len(configspec.sections) > 1: |
|
1801 # FIXME: can we supply any useful information here ? |
|
1802 raise RepeatSectionError |
|
1803 if hasattr(configspec, 'initial_comment'): |
|
1804 section._configspec_initial_comment = configspec.initial_comment |
|
1805 section._configspec_final_comment = configspec.final_comment |
|
1806 section._configspec_encoding = configspec.encoding |
|
1807 section._configspec_BOM = configspec.BOM |
|
1808 section._configspec_newlines = configspec.newlines |
|
1809 section._configspec_indent_type = configspec.indent_type |
|
1810 for entry in configspec.scalars: |
|
1811 section._configspec_comments[entry] = configspec.comments[entry] |
|
1812 section._configspec_inline_comments[entry] = ( |
|
1813 configspec.inline_comments[entry]) |
|
1814 section.configspec[entry] = configspec[entry] |
|
1815 section._order.append(entry) |
|
1816 for entry in configspec.sections: |
|
1817 if entry == '__many__': |
|
1818 continue |
|
1819 section._cs_section_comments[entry] = configspec.comments[entry] |
|
1820 section._cs_section_inline_comments[entry] = ( |
|
1821 configspec.inline_comments[entry]) |
|
1822 if not section.has_key(entry): |
|
1823 section[entry] = {} |
|
1824 self._set_configspec_value(configspec[entry], section[entry]) |
|
1825 |
|
1826 def _handle_repeat(self, section, configspec): |
|
1827 """Dynamically assign configspec for repeated section.""" |
|
1828 try: |
|
1829 section_keys = configspec.sections |
|
1830 scalar_keys = configspec.scalars |
|
1831 except AttributeError: |
|
1832 section_keys = [entry for entry in configspec |
|
1833 if isinstance(configspec[entry], dict)] |
|
1834 scalar_keys = [entry for entry in configspec |
|
1835 if not isinstance(configspec[entry], dict)] |
|
1836 if '__many__' in section_keys and len(section_keys) > 1: |
|
1837 # FIXME: can we supply any useful information here ? |
|
1838 raise RepeatSectionError |
|
1839 scalars = {} |
|
1840 sections = {} |
|
1841 for entry in scalar_keys: |
|
1842 val = configspec[entry] |
|
1843 scalars[entry] = val |
|
1844 for entry in section_keys: |
|
1845 val = configspec[entry] |
|
1846 if entry == '__many__': |
|
1847 scalars[entry] = val |
|
1848 continue |
|
1849 sections[entry] = val |
|
1850 # |
|
1851 section.configspec = scalars |
|
1852 for entry in sections: |
|
1853 if not section.has_key(entry): |
|
1854 section[entry] = {} |
|
1855 self._handle_repeat(section[entry], sections[entry]) |
|
1856 |
|
1857 def _write_line(self, indent_string, entry, this_entry, comment): |
|
1858 """Write an individual line, for the write method""" |
|
1859 # NOTE: the calls to self._quote here handles non-StringType values. |
|
1860 if not self.unrepr: |
|
1861 val = self._decode_element(self._quote(this_entry)) |
|
1862 else: |
|
1863 val = repr(this_entry) |
|
1864 return '%s%s%s%s%s' % ( |
|
1865 indent_string, |
|
1866 self._decode_element(self._quote(entry, multiline=False)), |
|
1867 self._a_to_u(' = '), |
|
1868 val, |
|
1869 self._decode_element(comment)) |
|
1870 |
|
1871 def _write_marker(self, indent_string, depth, entry, comment): |
|
1872 """Write a section marker line""" |
|
1873 return '%s%s%s%s%s' % ( |
|
1874 indent_string, |
|
1875 self._a_to_u('[' * depth), |
|
1876 self._quote(self._decode_element(entry), multiline=False), |
|
1877 self._a_to_u(']' * depth), |
|
1878 self._decode_element(comment)) |
|
1879 |
|
1880 def _handle_comment(self, comment): |
|
1881 """Deal with a comment.""" |
|
1882 if not comment: |
|
1883 return '' |
|
1884 start = self.indent_type |
|
1885 if not comment.startswith('#'): |
|
1886 start += self._a_to_u(' # ') |
|
1887 return (start + comment) |
|
1888 |
|
1889 # Public methods |
|
1890 |
|
1891 def write(self, outfile=None, section=None): |
|
1892 """ |
|
1893 Write the current ConfigObj as a file |
|
1894 |
|
1895 tekNico: FIXME: use StringIO instead of real files |
|
1896 |
|
1897 >>> filename = a.filename |
|
1898 >>> a.filename = 'test.ini' |
|
1899 >>> a.write() |
|
1900 >>> a.filename = filename |
|
1901 >>> a == ConfigObj('test.ini', raise_errors=True) |
|
1902 1 |
|
1903 """ |
|
1904 if self.indent_type is None: |
|
1905 # this can be true if initialised from a dictionary |
|
1906 self.indent_type = DEFAULT_INDENT_TYPE |
|
1907 # |
|
1908 out = [] |
|
1909 cs = self._a_to_u('#') |
|
1910 csp = self._a_to_u('# ') |
|
1911 if section is None: |
|
1912 int_val = self.interpolation |
|
1913 self.interpolation = False |
|
1914 section = self |
|
1915 for line in self.initial_comment: |
|
1916 line = self._decode_element(line) |
|
1917 stripped_line = line.strip() |
|
1918 if stripped_line and not stripped_line.startswith(cs): |
|
1919 line = csp + line |
|
1920 out.append(line) |
|
1921 # |
|
1922 indent_string = self.indent_type * section.depth |
|
1923 for entry in (section.scalars + section.sections): |
|
1924 if entry in section.defaults: |
|
1925 # don't write out default values |
|
1926 continue |
|
1927 for comment_line in section.comments[entry]: |
|
1928 comment_line = self._decode_element(comment_line.lstrip()) |
|
1929 if comment_line and not comment_line.startswith(cs): |
|
1930 comment_line = csp + comment_line |
|
1931 out.append(indent_string + comment_line) |
|
1932 this_entry = section[entry] |
|
1933 comment = self._handle_comment(section.inline_comments[entry]) |
|
1934 # |
|
1935 if isinstance(this_entry, dict): |
|
1936 # a section |
|
1937 out.append(self._write_marker( |
|
1938 indent_string, |
|
1939 this_entry.depth, |
|
1940 entry, |
|
1941 comment)) |
|
1942 out.extend(self.write(section=this_entry)) |
|
1943 else: |
|
1944 out.append(self._write_line( |
|
1945 indent_string, |
|
1946 entry, |
|
1947 this_entry, |
|
1948 comment)) |
|
1949 # |
|
1950 if section is self: |
|
1951 for line in self.final_comment: |
|
1952 line = self._decode_element(line) |
|
1953 stripped_line = line.strip() |
|
1954 if stripped_line and not stripped_line.startswith(cs): |
|
1955 line = csp + line |
|
1956 out.append(line) |
|
1957 self.interpolation = int_val |
|
1958 # |
|
1959 if section is not self: |
|
1960 return out |
|
1961 # |
|
1962 if (self.filename is None) and (outfile is None): |
|
1963 # output a list of lines |
|
1964 # might need to encode |
|
1965 # NOTE: This will *screw* UTF16, each line will start with the BOM |
|
1966 if self.encoding: |
|
1967 out = [l.encode(self.encoding) for l in out] |
|
1968 if (self.BOM and ((self.encoding is None) or |
|
1969 (BOM_LIST.get(self.encoding.lower()) == 'utf_8'))): |
|
1970 # Add the UTF8 BOM |
|
1971 if not out: |
|
1972 out.append('') |
|
1973 out[0] = BOM_UTF8 + out[0] |
|
1974 return out |
|
1975 # |
|
1976 # Turn the list to a string, joined with correct newlines |
|
1977 output = (self._a_to_u(self.newlines or os.linesep) |
|
1978 ).join(out) |
|
1979 if self.encoding: |
|
1980 output = output.encode(self.encoding) |
|
1981 if (self.BOM and ((self.encoding is None) or |
|
1982 (BOM_LIST.get(self.encoding.lower()) == 'utf_8'))): |
|
1983 # Add the UTF8 BOM |
|
1984 output = BOM_UTF8 + output |
|
1985 if outfile is not None: |
|
1986 outfile.write(output) |
|
1987 else: |
|
1988 h = open(self.filename, 'wb') |
|
1989 h.write(output) |
|
1990 h.close() |
|
1991 |
|
1992 def validate(self, validator, preserve_errors=False, copy=False, |
|
1993 section=None): |
|
1994 """ |
|
1995 Test the ConfigObj against a configspec. |
|
1996 |
|
1997 It uses the ``validator`` object from *validate.py*. |
|
1998 |
|
1999 To run ``validate`` on the current ConfigObj, call: :: |
|
2000 |
|
2001 test = config.validate(validator) |
|
2002 |
|
2003 (Normally having previously passed in the configspec when the ConfigObj |
|
2004 was created - you can dynamically assign a dictionary of checks to the |
|
2005 ``configspec`` attribute of a section though). |
|
2006 |
|
2007 It returns ``True`` if everything passes, or a dictionary of |
|
2008 pass/fails (True/False). If every member of a subsection passes, it |
|
2009 will just have the value ``True``. (It also returns ``False`` if all |
|
2010 members fail). |
|
2011 |
|
2012 In addition, it converts the values from strings to their native |
|
2013 types if their checks pass (and ``stringify`` is set). |
|
2014 |
|
2015 If ``preserve_errors`` is ``True`` (``False`` is default) then instead |
|
2016 of a marking a fail with a ``False``, it will preserve the actual |
|
2017 exception object. This can contain info about the reason for failure. |
|
2018 For example the ``VdtValueTooSmallError`` indeicates that the value |
|
2019 supplied was too small. If a value (or section) is missing it will |
|
2020 still be marked as ``False``. |
|
2021 |
|
2022 You must have the validate module to use ``preserve_errors=True``. |
|
2023 |
|
2024 You can then use the ``flatten_errors`` function to turn your nested |
|
2025 results dictionary into a flattened list of failures - useful for |
|
2026 displaying meaningful error messages. |
|
2027 """ |
|
2028 if section is None: |
|
2029 if self.configspec is None: |
|
2030 raise ValueError, 'No configspec supplied.' |
|
2031 if preserve_errors: |
|
2032 if VdtMissingValue is None: |
|
2033 raise ImportError('Missing validate module.') |
|
2034 section = self |
|
2035 # |
|
2036 spec_section = section.configspec |
|
2037 if copy and hasattr(section, '_configspec_initial_comment'): |
|
2038 section.initial_comment = section._configspec_initial_comment |
|
2039 section.final_comment = section._configspec_final_comment |
|
2040 section.encoding = section._configspec_encoding |
|
2041 section.BOM = section._configspec_BOM |
|
2042 section.newlines = section._configspec_newlines |
|
2043 section.indent_type = section._configspec_indent_type |
|
2044 if '__many__' in section.configspec: |
|
2045 many = spec_section['__many__'] |
|
2046 # dynamically assign the configspecs |
|
2047 # for the sections below |
|
2048 for entry in section.sections: |
|
2049 self._handle_repeat(section[entry], many) |
|
2050 # |
|
2051 out = {} |
|
2052 ret_true = True |
|
2053 ret_false = True |
|
2054 order = [k for k in section._order if k in spec_section] |
|
2055 order += [k for k in spec_section if k not in order] |
|
2056 for entry in order: |
|
2057 if entry == '__many__': |
|
2058 continue |
|
2059 if (not entry in section.scalars) or (entry in section.defaults): |
|
2060 # missing entries |
|
2061 # or entries from defaults |
|
2062 missing = True |
|
2063 val = None |
|
2064 if copy and not entry in section.scalars: |
|
2065 # copy comments |
|
2066 section.comments[entry] = ( |
|
2067 section._configspec_comments.get(entry, [])) |
|
2068 section.inline_comments[entry] = ( |
|
2069 section._configspec_inline_comments.get(entry, '')) |
|
2070 # |
|
2071 else: |
|
2072 missing = False |
|
2073 val = section[entry] |
|
2074 try: |
|
2075 check = validator.check(spec_section[entry], |
|
2076 val, |
|
2077 missing=missing |
|
2078 ) |
|
2079 except validator.baseErrorClass, e: |
|
2080 if not preserve_errors or isinstance(e, VdtMissingValue): |
|
2081 out[entry] = False |
|
2082 else: |
|
2083 # preserve the error |
|
2084 out[entry] = e |
|
2085 ret_false = False |
|
2086 ret_true = False |
|
2087 else: |
|
2088 ret_false = False |
|
2089 out[entry] = True |
|
2090 if self.stringify or missing: |
|
2091 # if we are doing type conversion |
|
2092 # or the value is a supplied default |
|
2093 if not self.stringify: |
|
2094 if isinstance(check, (list, tuple)): |
|
2095 # preserve lists |
|
2096 check = [self._str(item) for item in check] |
|
2097 elif missing and check is None: |
|
2098 # convert the None from a default to a '' |
|
2099 check = '' |
|
2100 else: |
|
2101 check = self._str(check) |
|
2102 if (check != val) or missing: |
|
2103 section[entry] = check |
|
2104 if not copy and missing and entry not in section.defaults: |
|
2105 section.defaults.append(entry) |
|
2106 # |
|
2107 # Missing sections will have been created as empty ones when the |
|
2108 # configspec was read. |
|
2109 for entry in section.sections: |
|
2110 # FIXME: this means DEFAULT is not copied in copy mode |
|
2111 if section is self and entry == 'DEFAULT': |
|
2112 continue |
|
2113 if copy: |
|
2114 section.comments[entry] = section._cs_section_comments[entry] |
|
2115 section.inline_comments[entry] = ( |
|
2116 section._cs_section_inline_comments[entry]) |
|
2117 check = self.validate(validator, preserve_errors=preserve_errors, |
|
2118 copy=copy, section=section[entry]) |
|
2119 out[entry] = check |
|
2120 if check == False: |
|
2121 ret_true = False |
|
2122 elif check == True: |
|
2123 ret_false = False |
|
2124 else: |
|
2125 ret_true = False |
|
2126 ret_false = False |
|
2127 # |
|
2128 if ret_true: |
|
2129 return True |
|
2130 elif ret_false: |
|
2131 return False |
|
2132 else: |
|
2133 return out |
|
2134 |
|
2135 class SimpleVal(object): |
|
2136 """ |
|
2137 A simple validator. |
|
2138 Can be used to check that all members expected are present. |
|
2139 |
|
2140 To use it, provide a configspec with all your members in (the value given |
|
2141 will be ignored). Pass an instance of ``SimpleVal`` to the ``validate`` |
|
2142 method of your ``ConfigObj``. ``validate`` will return ``True`` if all |
|
2143 members are present, or a dictionary with True/False meaning |
|
2144 present/missing. (Whole missing sections will be replaced with ``False``) |
|
2145 """ |
|
2146 |
|
2147 def __init__(self): |
|
2148 self.baseErrorClass = ConfigObjError |
|
2149 |
|
2150 def check(self, check, member, missing=False): |
|
2151 """A dummy check method, always returns the value unchanged.""" |
|
2152 if missing: |
|
2153 raise self.baseErrorClass |
|
2154 return member |
|
2155 |
|
2156 # Check / processing functions for options |
|
2157 def flatten_errors(cfg, res, levels=None, results=None): |
|
2158 """ |
|
2159 An example function that will turn a nested dictionary of results |
|
2160 (as returned by ``ConfigObj.validate``) into a flat list. |
|
2161 |
|
2162 ``cfg`` is the ConfigObj instance being checked, ``res`` is the results |
|
2163 dictionary returned by ``validate``. |
|
2164 |
|
2165 (This is a recursive function, so you shouldn't use the ``levels`` or |
|
2166 ``results`` arguments - they are used by the function. |
|
2167 |
|
2168 Returns a list of keys that failed. Each member of the list is a tuple : |
|
2169 :: |
|
2170 |
|
2171 ([list of sections...], key, result) |
|
2172 |
|
2173 If ``validate`` was called with ``preserve_errors=False`` (the default) |
|
2174 then ``result`` will always be ``False``. |
|
2175 |
|
2176 *list of sections* is a flattened list of sections that the key was found |
|
2177 in. |
|
2178 |
|
2179 If the section was missing then key will be ``None``. |
|
2180 |
|
2181 If the value (or section) was missing then ``result`` will be ``False``. |
|
2182 |
|
2183 If ``validate`` was called with ``preserve_errors=True`` and a value |
|
2184 was present, but failed the check, then ``result`` will be the exception |
|
2185 object returned. You can use this as a string that describes the failure. |
|
2186 |
|
2187 For example *The value "3" is of the wrong type*. |
|
2188 |
|
2189 >>> import validate |
|
2190 >>> vtor = validate.Validator() |
|
2191 >>> my_ini = ''' |
|
2192 ... option1 = True |
|
2193 ... [section1] |
|
2194 ... option1 = True |
|
2195 ... [section2] |
|
2196 ... another_option = Probably |
|
2197 ... [section3] |
|
2198 ... another_option = True |
|
2199 ... [[section3b]] |
|
2200 ... value = 3 |
|
2201 ... value2 = a |
|
2202 ... value3 = 11 |
|
2203 ... ''' |
|
2204 >>> my_cfg = ''' |
|
2205 ... option1 = boolean() |
|
2206 ... option2 = boolean() |
|
2207 ... option3 = boolean(default=Bad_value) |
|
2208 ... [section1] |
|
2209 ... option1 = boolean() |
|
2210 ... option2 = boolean() |
|
2211 ... option3 = boolean(default=Bad_value) |
|
2212 ... [section2] |
|
2213 ... another_option = boolean() |
|
2214 ... [section3] |
|
2215 ... another_option = boolean() |
|
2216 ... [[section3b]] |
|
2217 ... value = integer |
|
2218 ... value2 = integer |
|
2219 ... value3 = integer(0, 10) |
|
2220 ... [[[section3b-sub]]] |
|
2221 ... value = string |
|
2222 ... [section4] |
|
2223 ... another_option = boolean() |
|
2224 ... ''' |
|
2225 >>> cs = my_cfg.split('\\n') |
|
2226 >>> ini = my_ini.split('\\n') |
|
2227 >>> cfg = ConfigObj(ini, configspec=cs) |
|
2228 >>> res = cfg.validate(vtor, preserve_errors=True) |
|
2229 >>> errors = [] |
|
2230 >>> for entry in flatten_errors(cfg, res): |
|
2231 ... section_list, key, error = entry |
|
2232 ... section_list.insert(0, '[root]') |
|
2233 ... if key is not None: |
|
2234 ... section_list.append(key) |
|
2235 ... else: |
|
2236 ... section_list.append('[missing]') |
|
2237 ... section_string = ', '.join(section_list) |
|
2238 ... errors.append((section_string, ' = ', error)) |
|
2239 >>> errors.sort() |
|
2240 >>> for entry in errors: |
|
2241 ... print entry[0], entry[1], (entry[2] or 0) |
|
2242 [root], option2 = 0 |
|
2243 [root], option3 = the value "Bad_value" is of the wrong type. |
|
2244 [root], section1, option2 = 0 |
|
2245 [root], section1, option3 = the value "Bad_value" is of the wrong type. |
|
2246 [root], section2, another_option = the value "Probably" is of the wrong type. |
|
2247 [root], section3, section3b, section3b-sub, [missing] = 0 |
|
2248 [root], section3, section3b, value2 = the value "a" is of the wrong type. |
|
2249 [root], section3, section3b, value3 = the value "11" is too big. |
|
2250 [root], section4, [missing] = 0 |
|
2251 """ |
|
2252 if levels is None: |
|
2253 # first time called |
|
2254 levels = [] |
|
2255 results = [] |
|
2256 if res is True: |
|
2257 return results |
|
2258 if res is False: |
|
2259 results.append((levels[:], None, False)) |
|
2260 if levels: |
|
2261 levels.pop() |
|
2262 return results |
|
2263 for (key, val) in res.items(): |
|
2264 if val == True: |
|
2265 continue |
|
2266 if isinstance(cfg.get(key), dict): |
|
2267 # Go down one level |
|
2268 levels.append(key) |
|
2269 flatten_errors(cfg[key], val, levels, results) |
|
2270 continue |
|
2271 results.append((levels[:], key, val)) |
|
2272 # |
|
2273 # Go up one level |
|
2274 if levels: |
|
2275 levels.pop() |
|
2276 # |
|
2277 return results |
|
2278 |
|
2279 """*A programming language is a medium of expression.* - Paul Graham""" |