Robot Framework Integrated Development Environment (RIDE)
configobj.py
Go to the documentation of this file.
1 # configobj.py
2 # A config file reader/writer that supports nested sections in config files.
3 # Copyright (C) 2005-2014:
4 # (name) : (email)
5 # Michael Foord: fuzzyman AT voidspace DOT org DOT uk
6 # Nicola Larosa: nico AT tekNico DOT net
7 # Rob Dennis: rdennis AT gmail DOT com
8 # Eli Courtwright: eli AT courtwright DOT org
9 
10 # This software is licensed under the terms of the BSD license.
11 # http://opensource.org/licenses/BSD-3-Clause
12 
13 # ConfigObj 5 - main repository for documentation and issue tracking:
14 # https://github.com/DiffSK/configobj
15 
16 import os
17 import re
18 import sys
19 from codecs import BOM_UTF8, BOM_UTF16, BOM_UTF16_BE, BOM_UTF16_LE
20 
21 import six
22 
23 # imported lazily to avoid startup performance hit if it isn't used
24 compiler = None
25 
26 # A dictionary mapping BOM to
27 # the encoding to decode with, and what to set the
28 # encoding attribute to.
29 BOMS = {
30  BOM_UTF8: ('utf_8', None),
31  BOM_UTF16_BE: ('utf16_be', 'utf_16'),
32  BOM_UTF16_LE: ('utf16_le', 'utf_16'),
33  BOM_UTF16: ('utf_16', 'utf_16'),
34  }
35 # All legal variants of the BOM codecs.
36 # TODO: the list of aliases is not meant to be exhaustive, is there a
37 # better way ?
38 BOM_LIST = {
39  'utf_16': 'utf_16',
40  'u16': 'utf_16',
41  'utf16': 'utf_16',
42  'utf-16': 'utf_16',
43  'utf16_be': 'utf16_be',
44  'utf_16_be': 'utf16_be',
45  'utf-16be': 'utf16_be',
46  'utf16_le': 'utf16_le',
47  'utf_16_le': 'utf16_le',
48  'utf-16le': 'utf16_le',
49  'utf_8': 'utf_8',
50  'u8': 'utf_8',
51  'utf': 'utf_8',
52  'utf8': 'utf_8',
53  'utf-8': 'utf_8',
54  }
55 
56 # Map of encodings to the BOM to write.
57 BOM_SET = {
58  'utf_8': BOM_UTF8,
59  'utf_16': BOM_UTF16,
60  'utf16_be': BOM_UTF16_BE,
61  'utf16_le': BOM_UTF16_LE,
62  None: BOM_UTF8
63  }
64 
65 
66 def match_utf8(encoding):
67  return BOM_LIST.get(encoding.lower()) == 'utf_8'
68 
69 
70 # Quote strings used for writing values
71 squot = "'%s'"
72 dquot = '"%s"'
73 noquot = "%s"
74 wspace_plus = ' \r\n\v\t\'"'
75 tsquot = '"""%s"""'
76 tdquot = "'''%s'''"
77 
78 # Sentinel for use in getattr calls to replace hasattr
79 MISSING = object()
80 
81 __all__ = (
82  'DEFAULT_INDENT_TYPE',
83  'DEFAULT_INTERPOLATION',
84  'ConfigObjError',
85  'NestingError',
86  'ParseError',
87  'DuplicateError',
88  'ConfigspecError',
89  'ConfigObj',
90  'SimpleVal',
91  'InterpolationError',
92  'InterpolationLoopError',
93  'MissingInterpolationOption',
94  'RepeatSectionError',
95  'ReloadError',
96  'UnreprError',
97  'UnknownType',
98  'flatten_errors',
99  'get_extra_values'
100 )
101 
102 DEFAULT_INTERPOLATION = 'configparser'
103 DEFAULT_INDENT_TYPE = ' '
104 MAX_INTERPOL_DEPTH = 10
105 
106 OPTION_DEFAULTS = {
107  'interpolation': True,
108  'raise_errors': False,
109  'list_values': True,
110  'create_empty': False,
111  'file_error': False,
112  'configspec': None,
113  'stringify': True,
114  # option may be set to one of ('', ' ', '\t')
115  'indent_type': None,
116  'encoding': None,
117  'default_encoding': None,
118  'unrepr': False,
119  'write_empty_values': False,
120 }
121 
122 # this could be replaced if six is used for compatibility, or there are no
123 # more assertions about items being a string
124 
125 
126 def getObj(s):
127  global compiler
128  if compiler is None:
129  import compiler
130  s = "a=" + s
131  p = compiler.parse(s)
132  return p.getChildren()[1].getChildren()[0].getChildren()[1]
133 
134 
136  pass
137 
138 
139 class Builder():
140 
141  def build(self, o):
142  if m is None:
143  raise UnknownType(o.__class__.__name__)
144  return m(o)
145 
146  def build_List(self, o):
147  return list(map(self.buildbuild, o.getChildren()))
148 
149  def build_Const(self, o):
150  return o.value
151 
152  def build_Dict(self, o):
153  d = {}
154  i = iter(map(self.buildbuild, o.getChildren()))
155  for el in i:
156  d[el] = next(i)
157  return d
158 
159  def build_Tuple(self, o):
160  return tuple(self.build_Listbuild_List(o))
161 
162  def build_Name(self, o):
163  if o.name == 'None':
164  return None
165  if o.name == 'True':
166  return True
167  if o.name == 'False':
168  return False
169 
170  # An undefined Name
171  raise UnknownType('Undefined Name')
172 
173  def build_Add(self, o):
174  real, imag = list(map(self.build_Constbuild_Const, o.getChildren()))
175  try:
176  real = float(real)
177  except TypeError:
178  raise UnknownType('Add')
179  if not isinstance(imag, complex) or imag.real != 0.0:
180  raise UnknownType('Add')
181  return real+imag
182 
183  def build_Getattr(self, o):
184  parent = self.buildbuild(o.expr)
185  return getattr(parent, o.attrname)
186 
187  def build_UnarySub(self, o):
188  return -self.build_Constbuild_Const(o.getChildren()[0])
189 
190  def build_UnaryAdd(self, o):
191  return self.build_Constbuild_Const(o.getChildren()[0])
192 
193 
194 
197 _builder = Builder()
198 
199 
200 def unrepr(s):
201  if not s:
202  return s
203 
204  # this is supposed to be safe
205  import ast
206  return ast.literal_eval(s)
207 
208 
209 
214  def __init__(self, message='', line_number=None, line=''):
215  self.lineline = line
216  self.line_numberline_number = line_number
217  SyntaxError.__init__(self, message)
218 
219 
220 
224 
225 
226 
231 class ParseError(ConfigObjError):
232 
233 
234 
239  def __init__(self):
240  IOError.__init__(self, 'reload failed, filename is not set.')
241 
242 
243 
247 
248 
249 
252 class ConfigspecError(ConfigObjError):
253 
254 
255 
257 
258 
259 
260 class InterpolationLoopError(InterpolationError):
261 
262  def __init__(self, option):
263  InterpolationError.__init__(
264  self,
265  'interpolation loop detected in value "%s".' % option)
266 
267 
268 
273 
274 
275 
276 class MissingInterpolationOption(InterpolationError):
277  def __init__(self, option):
278  msg = 'missing option "%s" in interpolation.' % option
279  InterpolationError.__init__(self, msg)
280 
281 
282 
284 
285 
286 
287 
294 
295  # compiled regexp to use in self.interpolate()
296 
299  _KEYCRE = re.compile(r"%\‍(([^)]*)\‍)s")
300 
303  _cookie = '%'
304 
305  def __init__(self, section):
306  # the Section instance that "owns" this engine
307  self.sectionsection = section
308 
309 
310  def interpolate(self, key, value):
311  # short-cut
312  if not self._cookie_cookie in value:
313  return value
314 
315 
324  def recursive_interpolate(key, value, section, backtrail):
325  # Have we been here already?
326  if (key, section.name) in backtrail:
327  # Yes - infinite loop detected
328  raise InterpolationLoopError(key)
329  # Place a marker on our backtrail so we won't come back here again
330  backtrail[(key, section.name)] = 1
331 
332  # Now start the actual work
333  match = self._KEYCRE_KEYCRE.search(value)
334  while match:
335  # The actual parsing of the match is implementation-dependent,
336  # so delegate to our helper function
337  k, v, s = self._parse_match_parse_match(match)
338  if k is None:
339  # That's the signal that no further interpolation is needed
340  replacement = v
341  else:
342  # Further interpolation may be needed to obtain final value
343  replacement = recursive_interpolate(k, v, s, backtrail)
344  # Replace the matched string with its final value
345  start, end = match.span()
346  value = ''.join((value[:start], replacement, value[end:]))
347  new_search_start = start + len(replacement)
348  # Pick up the next interpolation key, if any, for next time
349  # through the while loop
350  match = self._KEYCRE_KEYCRE.search(value, new_search_start)
351 
352  # Now safe to come back here again; remove marker from backtrail
353  del backtrail[(key, section.name)]
354 
355  return value
356 
357  # Back in interpolate(), all we have to do is kick off the recursive
358  # function with appropriate starting values
359  value = recursive_interpolate(key, value, self.sectionsection, {})
360  return value
361 
362 
363 
369  def _fetch(self, key):
370  # switch off interpolation before we try and fetch anything !
371  save_interp = self.sectionsection.main.interpolation
372  self.sectionsection.main.interpolation = False
373 
374  # Start at section that "owns" this InterpolationEngine
375  current_section = self.sectionsection
376  while True:
377  # try the current section first
378  val = current_section.get(key)
379  if val is not None and not isinstance(val, Section):
380  break
381  # try "DEFAULT" next
382  val = current_section.get('DEFAULT', {}).get(key)
383  if val is not None and not isinstance(val, Section):
384  break
385  # move up to parent and try again
386  # top-level's parent is itself
387  if current_section.parent is current_section:
388  # reached top level, time to give up
389  break
390  current_section = current_section.parent
391 
392  # restore interpolation to previous value before returning
393  self.sectionsection.main.interpolation = save_interp
394  if val is None:
395  raise MissingInterpolationOption(key)
396  return val, current_section
397 
398 
399 
416  def _parse_match(self, match):
417  raise NotImplementedError()
418 
419 
420 
422 
425  _cookie = '%'
426 
429  _KEYCRE = re.compile(r"%\‍(([^)]*)\‍)s")
430 
431  def _parse_match(self, match):
432  key = match.group(1)
433  value, section = self._fetch_fetch(key)
434  return key, value, section
435 
436 
437 
438 
440 
443  _cookie = '$'
444 
447  _delimiter = '$'
448 
451  _KEYCRE = re.compile(r"""
452  \$(?:
453  (?P<escaped>\$) | # Two $ signs
454  (?P<named>[_a-z][_a-z0-9]*) | # $name format
455  {(?P<braced>[^}]*)} # ${name} format
456  )
457  """, re.IGNORECASE | re.VERBOSE)
458 
459  def _parse_match(self, match):
460  # Valid name (in or out of braces): fetch value from section
461  key = match.group('named') or match.group('braced')
462  if key is not None:
463  value, section = self._fetch_fetch(key)
464  return key, value, section
465  # Escaped delimiter (e.g., $$): return single delimiter
466  if match.group('escaped') is not None:
467  # Return None for key and section to indicate it's time to stop
468  return None, self._delimiter_delimiter, None
469  # Anything else: ignore completely, just return it unchanged
470  return None, match.group(), None
471 
472 
473 interpolation_engines = {
474  'configparser': ConfigParserInterpolation,
475  'template': TemplateInterpolation,
476 }
477 
478 
479 def __newobj__(cls, *args):
480  # Hack for pickle
481  return cls.__new__(cls, *args)
482 
483 
484 
500 class Section(dict):
501 
502 
503  def __setstate__(self, state):
504  dict.update(self, state[0])
505  self.__dict__.update(state[1])
506 
507  def __reduce__(self):
508  state = (dict(self), self.__dict__)
509  return (__newobj__, (self.__class__,), state)
510 
511 
512 
518  def __init__(self, parent, depth, main, indict=None, name=None):
519  if indict is None:
520  indict = {}
521  dict.__init__(self)
522  # used for nesting level *and* interpolation
523  self.parentparent = parent
524  # used for the interpolation attribute
525  self.mainmain = main
526  # level of nesting depth of this Section
527  self.depthdepth = depth
528  # purely for information
529  self.namename = name
530  #
531  self._initialise_initialise()
532  # we do this explicitly so that __setitem__ is used properly
533  # (rather than just passing to ``dict.__init__``)
534  for entry, value in indict.items():
535  self[entry] = value
536 
537 
538  def _initialise(self):
539  # the sequence of scalar values in this Section
540  self.scalarsscalars = []
541  # the sequence of sections in this Section
542  self.sectionssections = []
543  # for comments :-)
544  self.commentscomments = {}
545  self.inline_commentsinline_comments = {}
546  # the configspec
547  self.configspecconfigspec = None
548  # for defaults
549  self.defaultsdefaults = []
550  self.default_valuesdefault_values = {}
551  self.extra_valuesextra_values = []
552  self._created_created = False
553 
554 
555  def _interpolate(self, key, value):
556  try:
557  # do we already have an interpolation engine?
558  engine = self._interpolation_engine_interpolation_engine
559  except AttributeError:
560  # not yet: first time running _interpolate(), so pick the engine
561  name = self.mainmain.interpolation
562  if name == True: # note that "if name:" would be incorrect here
563  # backwards-compatibility: interpolation=True means use default
564  name = DEFAULT_INTERPOLATION
565  name = name.lower() # so that "Template", "template", etc. all work
566  class_ = interpolation_engines.get(name, None)
567  if class_ is None:
568  # invalid value for self.main.interpolation
569  self.mainmain.interpolation = False
570  return value
571  else:
572  # save reference to engine so we don't have to do this again
573  engine = self._interpolation_engine_interpolation_engine = class_(self)
574  # let the engine do the actual work
575  return engine.interpolate(key, value)
576 
577 
578 
579  def __getitem__(self, key):
580  val = dict.__getitem__(self, key)
581  if self.mainmain.interpolation:
582  if isinstance(val, six.string_types):
583  return self._interpolate_interpolate(key, val)
584  if isinstance(val, list):
585  def _check(entry):
586  if isinstance(entry, six.string_types):
587  return self._interpolate_interpolate(key, entry)
588  return entry
589  new = [_check(entry) for entry in val]
590  if new != val:
591  return new
592  return val
593 
594 
595 
608  def __setitem__(self, key, value, unrepr=False):
609  if not isinstance(key, six.string_types):
610  raise ValueError('The key "%s" is not a string.' % key)
611 
612  # add the comment
613  if key not in self.commentscomments:
614  self.commentscomments[key] = []
615  self.inline_commentsinline_comments[key] = ''
616  # remove the entry from defaults
617  if key in self.defaultsdefaults:
618  self.defaultsdefaults.remove(key)
619  #
620  if isinstance(value, Section):
621  if key not in self:
622  self.sectionssections.append(key)
623  dict.__setitem__(self, key, value)
624  elif isinstance(value, dict) and not unrepr:
625  # First create the new depth level,
626  # then create the section
627  if key not in self:
628  self.sectionssections.append(key)
629  new_depth = self.depthdepth + 1
630  dict.__setitem__(
631  self,
632  key,
633  Section(
634  self,
635  new_depth,
636  self.mainmain,
637  indict=value,
638  name=key))
639  else:
640  if key not in self:
641  self.scalarsscalars.append(key)
642  if not self.mainmain.stringify:
643  if isinstance(value, six.string_types):
644  pass
645  elif isinstance(value, (list, tuple)):
646  for entry in value:
647  if not isinstance(entry, six.string_types):
648  raise TypeError('Value is not a string "%s".' % entry)
649  else:
650  raise TypeError('Value is not a string "%s".' % value)
651  dict.__setitem__(self, key, value)
652 
653 
654 
655  def __delitem__(self, key):
656  dict. __delitem__(self, key)
657  if key in self.scalarsscalars:
658  self.scalarsscalars.remove(key)
659  else:
660  self.sectionssections.remove(key)
661  del self.commentscomments[key]
662  del self.inline_commentsinline_comments[key]
663 
664 
665 
666  def get(self, key, default=None):
667  try:
668  return self[key]
669  except KeyError:
670  return default
671 
672 
673 
676  def update(self, indict):
677  for entry in indict:
678  self[entry] = indict[entry]
679 
680 
681 
685  def pop(self, key, default=MISSING):
686  try:
687  val = self[key]
688  except KeyError:
689  if default is MISSING:
690  raise
691  val = default
692  else:
693  del self[key]
694  return val
695 
696 
697 
698  def popitem(self):
699  sequence = (self.scalarsscalars + self.sectionssections)
700  if not sequence:
701  raise KeyError(": 'popitem(): dictionary is empty'")
702  key = sequence[0]
703  val = self[key]
704  del self[key]
705  return key, val
706 
707 
708 
715  def clear(self):
716  dict.clear(self)
717  self.scalarsscalars = []
718  self.sectionssections = []
719  self.commentscomments = {}
720  self.inline_commentsinline_comments = {}
721  self.configspecconfigspec = None
722  self.defaultsdefaults = []
723  self.extra_valuesextra_values = []
724 
725 
726 
727  def setdefault(self, key, default=None):
728  try:
729  return self[key]
730  except KeyError:
731  self[key] = default
732  return self[key]
733 
734 
735 
736  def items(self):
737  return list(zip((self.scalarsscalars + self.sectionssections), list(self.valuesvalues())))
738 
739 
740 
741  def keys(self):
742  return (self.scalarsscalars + self.sectionssections)
743 
744 
745 
746  def values(self):
747  return [self[key] for key in (self.scalarsscalars + self.sectionssections)]
748 
749 
750 
751  def iteritems(self):
752  return iter(list(self.itemsitems()))
753 
754 
755 
756  def iterkeys(self):
757  return iter((self.scalarsscalars + self.sectionssections))
758 
759  __iter__ = iterkeys
760 
761 
762 
763  def itervalues(self):
764  return iter(list(self.valuesvalues()))
765 
766 
767 
768  def __repr__(self):
769  def _getval(key):
770  try:
771  return self[key]
772  except MissingInterpolationOption:
773  return dict.__getitem__(self, key)
774  return '{%s}' % ', '.join([('%s: %s' % (repr(key), repr(_getval(key))))
775  for key in (self.scalarsscalars + self.sectionssections)])
776 
777  __str__ = __repr__
778  __str__.__doc__ = "x.__str__() <==> str(x)"
779 
780 
781  # Extra methods - not in a normal dictionary
782 
783 
795  def dict(self):
796  newdict = {}
797  for entry in self:
798  this_entry = self[entry]
799  if isinstance(this_entry, Section):
800  this_entry = this_entry.dict()
801  elif isinstance(this_entry, list):
802  # create a copy rather than a reference
803  this_entry = list(this_entry)
804  elif isinstance(this_entry, tuple):
805  # create a copy rather than a reference
806  this_entry = tuple(this_entry)
807  newdict[entry] = this_entry
808  return newdict
809 
810 
811 
829  def merge(self, indict):
830  for key, val in list(indict.items()):
831  if (key in self and isinstance(self[key], dict) and
832  isinstance(val, dict)):
833  self[key].merge(val)
834  else:
835  self[key] = val
836 
837 
838 
846  def rename(self, oldkey, newkey):
847  if oldkey in self.scalarsscalars:
848  the_list = self.scalarsscalars
849  elif oldkey in self.sectionssections:
850  the_list = self.sectionssections
851  else:
852  raise KeyError('Key "%s" not found.' % oldkey)
853  pos = the_list.index(oldkey)
854  #
855  val = self[oldkey]
856  dict.__delitem__(self, oldkey)
857  dict.__setitem__(self, newkey, val)
858  the_list.remove(oldkey)
859  the_list.insert(pos, newkey)
860  comm = self.commentscomments[oldkey]
861  inline_comment = self.inline_commentsinline_comments[oldkey]
862  del self.commentscomments[oldkey]
863  del self.inline_commentsinline_comments[oldkey]
864  self.commentscomments[newkey] = comm
865  self.inline_commentsinline_comments[newkey] = inline_comment
866 
867 
868 
913  def walk(self, function, raise_errors=True,
914  call_on_sections=False, **keywargs):
915  out = {}
916  # scalars first
917  for i in range(len(self.scalarsscalars)):
918  entry = self.scalarsscalars[i]
919  try:
920  val = function(self, entry, **keywargs)
921  # bound again in case name has changed
922  entry = self.scalarsscalars[i]
923  out[entry] = val
924  except Exception:
925  if raise_errors:
926  raise
927  else:
928  entry = self.scalarsscalars[i]
929  out[entry] = False
930  # then sections
931  for i in range(len(self.sectionssections)):
932  entry = self.sectionssections[i]
933  if call_on_sections:
934  try:
935  function(self, entry, **keywargs)
936  except Exception:
937  if raise_errors:
938  raise
939  else:
940  entry = self.sectionssections[i]
941  out[entry] = False
942  # bound again in case name has changed
943  entry = self.sectionssections[i]
944  # previous result is discarded
945  out[entry] = self[entry].walk(
946  function,
947  raise_errors=raise_errors,
948  call_on_sections=call_on_sections,
949  **keywargs)
950  return out
951 
952 
953 
980  def as_bool(self, key):
981  val = self[key]
982  if val == True:
983  return True
984  elif val == False:
985  return False
986  else:
987  try:
988  if not isinstance(val, six.string_types):
989  # TODO: Why do we raise a KeyError here?
990  raise KeyError()
991  else:
992  return self.mainmain._bools[val.lower()]
993  except KeyError:
994  raise ValueError('Value "%s" is neither True nor False' % val)
995 
996 
997 
1016  def as_int(self, key):
1017  return int(self[key])
1018 
1019 
1020 
1038  def as_float(self, key):
1039  return float(self[key])
1040 
1041 
1042 
1057  def as_list(self, key):
1058  result = self[key]
1059  if isinstance(result, (tuple, list)):
1060  return list(result)
1061  return [result]
1062 
1063 
1064 
1072  def restore_default(self, key):
1073  default = self.default_valuesdefault_values[key]
1074  dict.__setitem__(self, key, default)
1075  if key not in self.defaultsdefaults:
1076  self.defaultsdefaults.append(key)
1077  return default
1078 
1079 
1080 
1089  def restore_defaults(self):
1090  for key in self.default_valuesdefault_values:
1091  self.restore_defaultrestore_default(key)
1092 
1093  for section in self.sectionssections:
1094  self[section].restore_defaults()
1095 
1096 
1097 
1099 
1100 
1103  _keyword = re.compile(r'''^ # line start
1104  (\s*) # indentation
1105  ( # keyword
1106  (?:".*?")| # double quotes
1107  (?:'.*?')| # single quotes
1108  (?:[^'"=].*?) # no quotes
1109  )
1110  \s*=\s* # divider
1111  (.*) # value (including list values and comments)
1112  $ # line end
1113  ''',
1114  re.VERBOSE)
1115 
1116 
1119  _sectionmarker = re.compile(r'''^
1120  (\s*) # 1: indentation
1121  ((?:\[\s*)+) # 2: section marker open
1122  ( # 3: section name open
1123  (?:"\s*\S.*?\s*")| # at least one non-space with double quotes
1124  (?:'\s*\S.*?\s*')| # at least one non-space with single quotes
1125  (?:[^'"\s].*?) # at least one non-space unquoted
1126  ) # section name close
1127  ((?:\s*\])+) # 4: section marker close
1128  \s*(\#.*)? # 5: optional comment
1129  $''',
1130  re.VERBOSE)
1131 
1132  # this regexp pulls list values out as a single string
1133  # or single values and comments
1134  # FIXME: this regex adds a '' to the end of comma terminated lists
1135  # workaround in ``_handle_value``
1136 
1139  _valueexp = re.compile(r'''^
1140  (?:
1141  (?:
1142  (
1143  (?:
1144  (?:
1145  (?:".*?")| # double quotes
1146  (?:'.*?')| # single quotes
1147  (?:[^'",\#][^,\#]*?) # unquoted
1148  )
1149  \s*,\s* # comma
1150  )* # match all list items ending in a comma (if any)
1151  )
1152  (
1153  (?:".*?")| # double quotes
1154  (?:'.*?')| # single quotes
1155  (?:[^'",\#\s][^,]*?)| # unquoted
1156  (?:(?<!,)) # Empty value
1157  )? # last item in a list - or string value
1158  )|
1159  (,) # alternatively a single comma - empty list
1160  )
1161  \s*(\#.*)? # optional comment
1162  $''',
1163  re.VERBOSE)
1164 
1165  # use findall to get the members of a list value
1166 
1169  _listvalueexp = re.compile(r'''
1170  (
1171  (?:".*?")| # double quotes
1172  (?:'.*?')| # single quotes
1173  (?:[^'",\#]?.*?) # unquoted
1174  )
1175  \s*,\s* # comma
1176  ''',
1177  re.VERBOSE)
1178 
1179  # this regexp is used for the value
1180  # when lists are switched off
1181 
1184  _nolistvalue = re.compile(r'''^
1185  (
1186  (?:".*?")| # double quotes
1187  (?:'.*?')| # single quotes
1188  (?:[^'"\#].*?)| # unquoted
1189  (?:) # Empty value
1190  )
1191  \s*(\#.*)? # optional comment
1192  $''',
1193  re.VERBOSE)
1194 
1195  # regexes for finding triple quoted values on one line
1196 
1199  _single_line_single = re.compile(r"^'''(.*?)'''\s*(#.*)?$")
1200 
1203  _single_line_double = re.compile(r'^"""(.*?)"""\s*(#.*)?$')
1204 
1207  _multi_line_single = re.compile(r"^(.*?)'''\s*(#.*)?$")
1208 
1211  _multi_line_double = re.compile(r'^(.*?)"""\s*(#.*)?$')
1212 
1213 
1216  _triple_quote = {
1217  "'''": (_single_line_single, _multi_line_single),
1218  '"""': (_single_line_double, _multi_line_double),
1219  }
1220 
1221  # Used by the ``istrue`` Section method
1222 
1225  _bools = {
1226  'yes': True, 'no': False,
1227  'on': True, 'off': False,
1228  '1': True, '0': False,
1229  'true': True, 'false': False,
1230  }
1231 
1232 
1233 
1242  def __init__(self, infile=None, options=None, configspec=None, encoding=None,
1243  interpolation=True, raise_errors=False, list_values=True,
1244  create_empty=False, file_error=False, stringify=True,
1245  indent_type=None, default_encoding=None, unrepr=False,
1246  write_empty_values=False, _inspec=False):
1247  self._inspec_inspec = _inspec
1248  # init the superclass
1249  Section.__init__(self, self, 0, self)
1250 
1251  infile = infile or []
1252 
1253 
1256  _options = {'configspec': configspec,
1257  'encoding': encoding, 'interpolation': interpolation,
1258  'raise_errors': raise_errors, 'list_values': list_values,
1259  'create_empty': create_empty, 'file_error': file_error,
1260  'stringify': stringify, 'indent_type': indent_type,
1261  'default_encoding': default_encoding, 'unrepr': unrepr,
1262  'write_empty_values': write_empty_values}
1263 
1264  if options is None:
1265  options = _options
1266  else:
1267  import warnings
1268  warnings.warn('Passing in an options dictionary to ConfigObj() is '
1269  'deprecated. Use **options instead.',
1270  DeprecationWarning, stacklevel=2)
1271 
1272  # TODO: check the values too.
1273  for entry in options:
1274  if entry not in OPTION_DEFAULTS:
1275  raise TypeError('Unrecognised option "%s".' % entry)
1276  for entry, value in list(OPTION_DEFAULTS.items()):
1277  if entry not in options:
1278  options[entry] = value
1279  keyword_value = _options[entry]
1280  if value != keyword_value:
1281  options[entry] = keyword_value
1282 
1283  # XXXX this ignores an explicit list_values = True in combination
1284  # with _inspec. The user should *never* do that anyway, but still...
1285  if _inspec:
1286  options['list_values'] = False
1287 
1288  self._initialise_initialise_initialise(options)
1289  configspec = options['configspec']
1290  self._original_configspec_original_configspec = configspec
1291  self._load_load(infile, configspec)
1292 
1293 
1294  def _load(self, infile, configspec):
1295  if isinstance(infile, six.string_types):
1296  self.filenamefilename = infile
1297  if os.path.isfile(infile):
1298  with open(infile, 'rb') as h:
1299  content = h.readlines() or []
1300  elif self.file_errorfile_error:
1301  # raise an error if the file doesn't exist
1302  raise IOError('Config file not found: "%s".' % self.filenamefilename)
1303  else:
1304  # file doesn't already exist
1305  if self.create_emptycreate_empty:
1306  # this is a good test that the filename specified
1307  # isn't impossible - like on a non-existent device
1308  with open(infile, 'w') as h:
1309  h.write('')
1310  content = []
1311 
1312  elif isinstance(infile, (list, tuple)):
1313  content = list(infile)
1314 
1315  elif isinstance(infile, dict):
1316  # initialise self
1317  # the Section class handles creating subsections
1318  if isinstance(infile, ConfigObj):
1319  # get a copy of our ConfigObj
1320  def set_section(in_section, this_section):
1321  for entry in in_section.scalars:
1322  this_section[entry] = in_section[entry]
1323  for section in in_section.sections:
1324  this_section[section] = {}
1325  set_section(in_section[section], this_section[section])
1326  set_section(infile, self)
1327 
1328  else:
1329  for entry in infile:
1330  self[entry] = infile[entry]
1331  del self._errors_errors
1332 
1333  if configspec is not None:
1334  self._handle_configspec_handle_configspec(configspec)
1335  else:
1336  self.configspecconfigspecconfigspec = None
1337  return
1338 
1339  elif getattr(infile, 'read', MISSING) is not MISSING:
1340  # This supports file like objects
1341  content = infile.read() or []
1342  # needs splitting into lines - but needs doing *after* decoding
1343  # in case it's not an 8 bit encoding
1344  else:
1345  raise TypeError('infile must be a filename, file like object, or list of lines.')
1346 
1347  if content:
1348  # don't do it for the empty ConfigObj
1349  content = self._handle_bom_handle_bom(content)
1350  # infile is now *always* a list
1351  #
1352  # Set the newlines attribute (first line ending it finds)
1353  # and strip trailing '\n' or '\r' from lines
1354  for line in content:
1355  if (not line) or (line[-1] not in ('\r', '\n')):
1356  continue
1357  for end in ('\r\n', '\n', '\r'):
1358  if line.endswith(end):
1359  self.newlinesnewlines = end
1360  break
1361  break
1362 
1363  assert all(isinstance(line, six.string_types) for line in content), repr(content)
1364  content = [line.rstrip('\r\n') for line in content]
1365 
1366  self._parse_parse(content)
1367  # if we had any errors, now is the time to raise them
1368  if self._errors_errors:
1369  info = "at line %s." % self._errors_errors[0].line_number
1370  if len(self._errors_errors) > 1:
1371  msg = "Parsing failed with several errors.\nFirst error %s" % info
1372  error = ConfigObjError(msg)
1373  else:
1374  error = self._errors_errors[0]
1375  # set the errors attribute; it's a list of tuples:
1376  # (error_type, message, line_number)
1377  error.errors = self._errors_errors
1378  # set the config attribute
1379  error.config = self
1380  raise error
1381  # delete private attributes
1382  del self._errors_errors
1383 
1384  if configspec is None:
1385  self.configspecconfigspecconfigspec = None
1386  else:
1387  self._handle_configspec_handle_configspec(configspec)
1388 
1389 
1390  def _initialise(self, options=None):
1391  if options is None:
1392  options = OPTION_DEFAULTS
1393 
1394  # initialise a few variables
1395  self.filenamefilename = None
1396  self._errors_errors = []
1397  self.raise_errorsraise_errors = options['raise_errors']
1398  self.interpolationinterpolation = options['interpolation']
1399  self.list_valueslist_values = options['list_values']
1400  self.create_emptycreate_empty = options['create_empty']
1401  self.file_errorfile_error = options['file_error']
1402  self.stringifystringify = options['stringify']
1403  self.indent_typeindent_type = options['indent_type']
1404  self.encodingencoding = options['encoding']
1405  self.default_encodingdefault_encoding = options['default_encoding']
1406  self.BOMBOM = False
1407  self.newlinesnewlines = None
1408  self.write_empty_valueswrite_empty_values = options['write_empty_values']
1409  self.unreprunrepr = options['unrepr']
1410 
1411  self.initial_commentinitial_comment = []
1412  self.final_commentfinal_comment = []
1413  self.configspecconfigspecconfigspec = None
1414 
1415  if self._inspec_inspec:
1416  self.list_valueslist_values = False
1417 
1418  # Clear section attributes as well
1419  Section._initialise(self)
1420 
1421 
1422  def __repr__(self):
1423  def _getval(key):
1424  try:
1425  return self[key]
1426  except MissingInterpolationOption:
1427  return dict.__getitem__(self, key)
1428  return ('ConfigObj({%s})' %
1429  ', '.join([('%s: %s' % (repr(key), repr(_getval(key))))
1430  for key in (self.scalarsscalars + self.sectionssections)]))
1431 
1432 
1433 
1456  def _handle_bom(self, infile):
1457 
1458  if ((self.encodingencoding is not None) and
1459  (self.encodingencoding.lower() not in BOM_LIST)):
1460  # No need to check for a BOM
1461  # the encoding specified doesn't have one
1462  # just decode
1463  return self._decode_decode(infile, self.encodingencoding)
1464 
1465  if isinstance(infile, (list, tuple)):
1466  line = infile[0]
1467  else:
1468  line = infile
1469 
1470  if isinstance(line, six.text_type):
1471  # it's already decoded and there's no need to do anything
1472  # else, just use the _decode utility method to handle
1473  # listifying appropriately
1474  return self._decode_decode(infile, self.encodingencoding)
1475 
1476  if self.encodingencoding is not None:
1477  # encoding explicitly supplied
1478  # And it could have an associated BOM
1479  # TODO: if encoding is just UTF16 - we ought to check for both
1480  # TODO: big endian and little endian versions.
1481  enc = BOM_LIST[self.encodingencoding.lower()]
1482  if enc == 'utf_16':
1483  # For UTF16 we try big endian and little endian
1484  for BOM, (encoding, final_encoding) in list(BOMS.items()):
1485  if not final_encoding:
1486  # skip UTF8
1487  continue
1488  if infile.startswith(BOM):
1489 
1492  return self._decode_decode(infile, encoding)
1493 
1494  # If we get this far, will *probably* raise a DecodeError
1495  # As it doesn't appear to start with a BOM
1496  return self._decode_decode(infile, self.encodingencoding)
1497 
1498  # Must be UTF8
1499  BOM = BOM_SET[enc]
1500  if not line.startswith(BOM):
1501  return self._decode_decode(infile, self.encodingencoding)
1502 
1503  newline = line[len(BOM):]
1504 
1505  # BOM removed
1506  if isinstance(infile, (list, tuple)):
1507  infile[0] = newline
1508  else:
1509  infile = newline
1510  self.BOMBOM = True
1511  return self._decode_decode(infile, self.encodingencoding)
1512 
1513  # No encoding specified - so we need to check for UTF8/UTF16
1514  for BOM, (encoding, final_encoding) in list(BOMS.items()):
1515  if not isinstance(line, six.binary_type) or not line.startswith(BOM):
1516  # didn't specify a BOM, or it's not a bytestring
1517  continue
1518  else:
1519  # BOM discovered
1520  self.encodingencoding = final_encoding
1521  if not final_encoding:
1522  self.BOMBOM = True
1523  # UTF8
1524  # remove BOM
1525  newline = line[len(BOM):]
1526  if isinstance(infile, (list, tuple)):
1527  infile[0] = newline
1528  else:
1529  infile = newline
1530  # UTF-8
1531  if isinstance(infile, six.text_type):
1532  return infile.splitlines(True)
1533  elif isinstance(infile, six.binary_type):
1534  return infile.decode('utf-8').splitlines(True)
1535  else:
1536  return self._decode_decode(infile, 'utf-8')
1537  # UTF16 - have to decode
1538  return self._decode_decode(infile, encoding)
1539 
1540 
1541  if six.PY2 and isinstance(line, str):
1542  # don't actually do any decoding, since we're on python 2 and
1543  # returning a bytestring is fine
1544  return self._decode_decode(infile, None)
1545  # No BOM discovered and no encoding specified, default to UTF-8
1546  if isinstance(infile, six.binary_type):
1547  return infile.decode('utf-8').splitlines(True)
1548  else:
1549  return self._decode_decode(infile, 'utf-8')
1550 
1551 
1552 
1555  def _a_to_u(self, aString):
1556  if isinstance(aString, six.binary_type) and self.encodingencoding:
1557  return aString.decode(self.encodingencoding)
1558  else:
1559  return aString
1560 
1561 
1562 
1569  def _decode(self, infile, encoding):
1570  if isinstance(infile, six.string_types):
1571  return infile.splitlines(True)
1572  if isinstance(infile, six.binary_type):
1573  # NOTE: Could raise a ``UnicodeDecodeError``
1574  if encoding:
1575  return infile.decode(encoding).splitlines(True)
1576  else:
1577  return infile.splitlines(True)
1578 
1579  if encoding:
1580  for i, line in enumerate(infile):
1581  if isinstance(line, six.binary_type):
1582  # NOTE: The isinstance test here handles mixed lists of unicode/string
1583  # NOTE: But the decode will break on any non-string values
1584  # NOTE: Or could raise a ``UnicodeDecodeError``
1585  infile[i] = line.decode(encoding)
1586  return infile
1587 
1588 
1589 
1592  def _decode_element(self, line):
1593  if isinstance(line, six.binary_type) and self.default_encodingdefault_encoding:
1594  return line.decode(self.default_encodingdefault_encoding)
1595  else:
1596  return line
1597 
1598 
1599  # TODO: this may need to be modified
1600 
1606  def _str(self, value):
1607  if not isinstance(value, six.string_types):
1608  # intentially 'str' because it's just whatever the "normal"
1609  # string type is for the python version we're dealing with
1610  return str(value)
1611  else:
1612  return value
1613 
1614 
1615 
1618  def _parse(self, infile):
1619  temp_list_values = self.list_valueslist_values
1620  if self.unreprunrepr:
1621  self.list_valueslist_values = False
1622 
1623  comment_list = []
1624  done_start = False
1625  this_section = self
1626  maxline = len(infile) - 1
1627  cur_index = -1
1628  reset_comment = False
1629 
1630  while cur_index < maxline:
1631  if reset_comment:
1632  comment_list = []
1633  cur_index += 1
1634  line = infile[cur_index]
1635  sline = line.strip()
1636  # do we have anything on the line ?
1637  if not sline or sline.startswith('#'):
1638  reset_comment = False
1639  comment_list.append(line)
1640  continue
1641 
1642  if not done_start:
1643  # preserve initial comment
1644  self.initial_commentinitial_comment = comment_list
1645  comment_list = []
1646  done_start = True
1647 
1648  reset_comment = True
1649  # first we check if it's a section marker
1650  mat = self._sectionmarker_sectionmarker.match(line)
1651  if mat is not None:
1652  # is a section line
1653  (indent, sect_open, sect_name, sect_close, comment) = mat.groups()
1654  if indent and (self.indent_typeindent_type is None):
1655  self.indent_typeindent_type = indent
1656  cur_depth = sect_open.count('[')
1657  if cur_depth != sect_close.count(']'):
1658  self._handle_error_handle_error("Cannot compute the section depth",
1659  NestingError, infile, cur_index)
1660  continue
1661 
1662  if cur_depth < this_section.depth:
1663  # the new section is dropping back to a previous level
1664  try:
1665  parent = self._match_depth_match_depth(this_section,
1666  cur_depth).parent
1667  except SyntaxError:
1668  self._handle_error_handle_error("Cannot compute nesting level",
1669  NestingError, infile, cur_index)
1670  continue
1671  elif cur_depth == this_section.depth:
1672  # the new section is a sibling of the current section
1673  parent = this_section.parent
1674  elif cur_depth == this_section.depth + 1:
1675  # the new section is a child the current section
1676  parent = this_section
1677  else:
1678  self._handle_error_handle_error("Section too nested",
1679  NestingError, infile, cur_index)
1680  continue
1681 
1682  sect_name = self._unquote_unquote(sect_name)
1683  if sect_name in parent:
1684  self._handle_error_handle_error('Duplicate section name',
1685  DuplicateError, infile, cur_index)
1686  continue
1687 
1688  # create the new section
1689  this_section = Section(
1690  parent,
1691  cur_depth,
1692  self,
1693  name=sect_name)
1694  parent[sect_name] = this_section
1695  parent.inline_comments[sect_name] = comment
1696  parent.comments[sect_name] = comment_list
1697  continue
1698  #
1699  # it's not a section marker,
1700  # so it should be a valid ``key = value`` line
1701  mat = self._keyword_keyword.match(line)
1702  if mat is None:
1703  self._handle_error_handle_error(
1704  'Invalid line ({0!r}) (matched as neither section nor keyword)'.format(line),
1705  ParseError, infile, cur_index)
1706  else:
1707  # is a keyword value
1708  # value will include any inline comment
1709  (indent, key, value) = mat.groups()
1710  if indent and (self.indent_typeindent_type is None):
1711  self.indent_typeindent_type = indent
1712  # check for a multiline value
1713  if value[:3] in ['"""', "'''"]:
1714  try:
1715  value, comment, cur_index = self._multiline_multiline(
1716  value, infile, cur_index, maxline)
1717  except SyntaxError:
1718  self._handle_error_handle_error(
1719  'Parse error in multiline value',
1720  ParseError, infile, cur_index)
1721  continue
1722  else:
1723  if self.unreprunrepr:
1724  comment = ''
1725  try:
1726  value = unrepr(value)
1727  except Exception as e:
1728  if type(e) == UnknownType:
1729  msg = 'Unknown name or type in value'
1730  else:
1731  msg = 'Parse error from unrepr-ing multiline value'
1732  self._handle_error_handle_error(msg, UnreprError, infile,
1733  cur_index)
1734  continue
1735  else:
1736  if self.unreprunrepr:
1737  comment = ''
1738  try:
1739  value = unrepr(value)
1740  except Exception as e:
1741  if isinstance(e, UnknownType):
1742  msg = 'Unknown name or type in value'
1743  else:
1744  msg = 'Parse error from unrepr-ing value'
1745  self._handle_error_handle_error(msg, UnreprError, infile,
1746  cur_index)
1747  continue
1748  else:
1749  # extract comment and lists
1750  try:
1751  (value, comment) = self._handle_value_handle_value(value)
1752  except SyntaxError:
1753  self._handle_error_handle_error(
1754  'Parse error in value',
1755  ParseError, infile, cur_index)
1756  continue
1757  #
1758  key = self._unquote_unquote(key)
1759  if key in this_section:
1760  self._handle_error_handle_error(
1761  'Duplicate keyword name',
1762  DuplicateError, infile, cur_index)
1763  continue
1764  # add the key.
1765  # we set unrepr because if we have got this far we will never
1766  # be creating a new section
1767  this_section.__setitem__(key, value, unrepr=True)
1768  this_section.inline_comments[key] = comment
1769  this_section.comments[key] = comment_list
1770  continue
1771  #
1772  if self.indent_typeindent_type is None:
1773  # no indentation used, set the type accordingly
1774  self.indent_typeindent_type = ''
1775 
1776  # preserve the final comment
1777  if not self and not self.initial_commentinitial_comment:
1778  self.initial_commentinitial_comment = comment_list
1779  elif not reset_comment:
1780  self.final_commentfinal_comment = comment_list
1781  self.list_valueslist_values = temp_list_values
1782 
1783 
1784 
1793  def _match_depth(self, sect, depth):
1794  while depth < sect.depth:
1795  if sect is sect.parent:
1796  # we've reached the top level already
1797  raise SyntaxError()
1798  sect = sect.parent
1799  if sect.depth == depth:
1800  return sect
1801  # shouldn't get here
1802  raise SyntaxError()
1803 
1804 
1805 
1813  def _handle_error(self, text, ErrorClass, infile, cur_index):
1814  line = infile[cur_index]
1815  cur_index += 1
1816  message = '{0} at line {1}.'.format(text, cur_index)
1817  error = ErrorClass(message, cur_index, line)
1818  if self.raise_errorsraise_errors:
1819  # raise the error - parsing stops here
1820  raise error
1821  # store the error
1822  # reraise when parsing has finished
1823  self._errors_errors.append(error)
1824 
1825 
1826 
1829  def _unquote(self, value):
1830  if not value:
1831  # should only happen during parsing of lists
1832  raise SyntaxError
1833  if (value[0] == value[-1]) and (value[0] in ('"', "'")):
1834  value = value[1:-1]
1835  return value
1836 
1837 
1838 
1858  def _quote(self, value, multiline=True):
1859  if multiline and self.write_empty_valueswrite_empty_values and value == '':
1860  # Only if multiline is set, so that it is used for values not
1861  # keys, and not values that are part of a list
1862  return ''
1863 
1864  if multiline and isinstance(value, (list, tuple)):
1865  if not value:
1866  return ','
1867  elif len(value) == 1:
1868  return self._quote_quote(value[0], multiline=False) + ','
1869  return ', '.join([self._quote_quote(val, multiline=False)
1870  for val in value])
1871  if not isinstance(value, six.string_types):
1872  if self.stringifystringify:
1873  # intentially 'str' because it's just whatever the "normal"
1874  # string type is for the python version we're dealing with
1875  value = str(value)
1876  else:
1877  raise TypeError('Value "%s" is not a string.' % value)
1878 
1879  if not value:
1880  return '""'
1881 
1882  no_lists_no_quotes = not self.list_valueslist_values and '\n' not in value and '#' not in value
1883  need_triple = multiline and ((("'" in value) and ('"' in value)) or ('\n' in value ))
1884  hash_triple_quote = multiline and not need_triple and ("'" in value) and ('"' in value) and ('#' in value)
1885  check_for_single = (no_lists_no_quotes or not need_triple) and not hash_triple_quote
1886 
1887  if check_for_single:
1888  if not self.list_valueslist_values:
1889  # we don't quote if ``list_values=False``
1890  quot = noquot
1891  # for normal values either single or double quotes will do
1892  elif '\n' in value:
1893  # will only happen if multiline is off - e.g. '\n' in key
1894  raise ConfigObjError('Value "%s" cannot be safely quoted.' % value)
1895  elif ((value[0] not in wspace_plus) and
1896  (value[-1] not in wspace_plus) and
1897  (',' not in value)):
1898  quot = noquot
1899  else:
1900  quot = self._get_single_quote_get_single_quote(value)
1901  else:
1902  # if value has '\n' or "'" *and* '"', it will need triple quotes
1903  quot = self._get_triple_quote_get_triple_quote(value)
1904 
1905  if quot == noquot and '#' in value and self.list_valueslist_values:
1906  quot = self._get_single_quote_get_single_quote(value)
1907 
1908  return quot % value
1909 
1910 
1911  def _get_single_quote(self, value):
1912  if ("'" in value) and ('"' in value):
1913  raise ConfigObjError('Value "%s" cannot be safely quoted.' % value)
1914  elif '"' in value:
1915  quot = squot
1916  else:
1917  quot = dquot
1918  return quot
1919 
1920 
1921  def _get_triple_quote(self, value):
1922  if (value.find('"""') != -1) and (value.find("'''") != -1):
1923  raise ConfigObjError('Value "%s" cannot be safely quoted.' % value)
1924  if value.find('"""') == -1:
1925  quot = tdquot
1926  else:
1927  quot = tsquot
1928  return quot
1929 
1930 
1931 
1937  def _handle_value(self, value):
1938  if self._inspec_inspec:
1939  # Parsing a configspec so don't handle comments
1940  return (value, '')
1941  # do we look for lists in values ?
1942  if not self.list_valueslist_values:
1943  mat = self._nolistvalue_nolistvalue.match(value)
1944  if mat is None:
1945  raise SyntaxError()
1946  # NOTE: we don't unquote here
1947  return mat.groups()
1948  #
1949  mat = self._valueexp_valueexp.match(value)
1950  if mat is None:
1951  # the value is badly constructed, probably badly quoted,
1952  # or an invalid list
1953  raise SyntaxError()
1954  (list_values, single, empty_list, comment) = mat.groups()
1955  if (list_values == '') and (single is None):
1956  # change this if you want to accept empty values
1957  raise SyntaxError()
1958  # NOTE: note there is no error handling from here if the regex
1959  # is wrong: then incorrect values will slip through
1960  if empty_list is not None:
1961  # the single comma - meaning an empty list
1962  return ([], comment)
1963  if single is not None:
1964  # handle empty values
1965  if list_values and not single:
1966  # FIXME: the '' is a workaround because our regex now matches
1967  # '' at the end of a list if it has a trailing comma
1968  single = None
1969  else:
1970  single = single or '""'
1971  single = self._unquote_unquote(single)
1972  if list_values == '':
1973  # not a list value
1974  return (single, comment)
1975  the_list = self._listvalueexp_listvalueexp.findall(list_values)
1976  the_list = [self._unquote_unquote(val) for val in the_list]
1977  if single is not None:
1978  the_list += [single]
1979  return (the_list, comment)
1980 
1981 
1982 
1985  def _multiline(self, value, infile, cur_index, maxline):
1986  quot = value[:3]
1987  newvalue = value[3:]
1988  single_line = self._triple_quote_triple_quote[quot][0]
1989  multi_line = self._triple_quote_triple_quote[quot][1]
1990  mat = single_line.match(value)
1991  if mat is not None:
1992  retval = list(mat.groups())
1993  retval.append(cur_index)
1994  return retval
1995  elif newvalue.find(quot) != -1:
1996  # somehow the triple quote is missing
1997  raise SyntaxError()
1998  #
1999  while cur_index < maxline:
2000  cur_index += 1
2001  newvalue += '\n'
2002  line = infile[cur_index]
2003  if line.find(quot) == -1:
2004  newvalue += line
2005  else:
2006  # end of multiline, process it
2007  break
2008  else:
2009  # we've got to the end of the config, oops...
2010  raise SyntaxError()
2011  mat = multi_line.match(line)
2012  if mat is None:
2013  # a badly formed line
2014  raise SyntaxError()
2015  (value, comment) = mat.groups()
2016  return (newvalue + value, comment, cur_index)
2017 
2018 
2019 
2022  def _handle_configspec(self, configspec):
2023  # FIXME: Should we check that the configspec was created with the
2024  # correct settings ? (i.e. ``list_values=False``)
2025  if not isinstance(configspec, ConfigObj):
2026  try:
2027  configspec = ConfigObj(configspec,
2028  raise_errors=True,
2029  file_error=True,
2030  _inspec=True)
2031  except ConfigObjError as e:
2032  # FIXME: Should these errors have a reference
2033  # to the already parsed ConfigObj ?
2034  raise ConfigspecError('Parsing configspec failed: %s' % e)
2035  except IOError as e:
2036  raise IOError('Reading configspec failed: %s' % e)
2037 
2038  self.configspecconfigspecconfigspec = configspec
2039 
2040 
2041 
2042 
2048  def _set_configspec(self, section, copy):
2049  configspec = section.configspec
2050  many = configspec.get('__many__')
2051  if isinstance(many, dict):
2052  for entry in section.sections:
2053  if entry not in configspec:
2054  section[entry].configspec = many
2055 
2056  for entry in configspec.sections:
2057  if entry == '__many__':
2058  continue
2059  if entry not in section:
2060  section[entry] = {}
2061  section[entry]._created = True
2062  if copy:
2063  # copy comments
2064  section.comments[entry] = configspec.comments.get(entry, [])
2065  section.inline_comments[entry] = configspec.inline_comments.get(entry, '')
2066 
2067  # Could be a scalar when we expect a section
2068  if isinstance(section[entry], Section):
2069  section[entry].configspec = configspec[entry]
2070 
2071 
2072 
2075  def _write_line(self, indent_string, entry, this_entry, comment):
2076  # NOTE: the calls to self._quote here handles non-StringType values.
2077  if not self.unreprunrepr:
2078  val = self._decode_element_decode_element(self._quote_quote(this_entry))
2079  else:
2080  val = repr(this_entry)
2081  return '%s%s%s%s%s' % (indent_string,
2082  self._decode_element_decode_element(self._quote_quote(entry, multiline=False)),
2083  self._a_to_u_a_to_u(' = '),
2084  val,
2085  self._decode_element_decode_element(comment))
2086 
2087 
2088 
2091  def _write_marker(self, indent_string, depth, entry, comment):
2092  return '%s%s%s%s%s' % (indent_string,
2093  self._a_to_u_a_to_u('[' * depth),
2094  self._quote_quote(self._decode_element_decode_element(entry), multiline=False),
2095  self._a_to_u_a_to_u(']' * depth),
2096  self._decode_element_decode_element(comment))
2097 
2098 
2099 
2102  def _handle_comment(self, comment):
2103  if not comment:
2104  return ''
2105  start = self.indent_typeindent_type
2106  if not comment.startswith('#'):
2107  start += self._a_to_u_a_to_u(' # ')
2108  return (start + comment)
2109 
2110 
2111  # Public methods
2112 
2113 
2127  def write(self, outfile=None, section=None):
2128  if self.indent_typeindent_type is None:
2129  # this can be true if initialised from a dictionary
2130  self.indent_typeindent_type = DEFAULT_INDENT_TYPE
2131 
2132  out = []
2133  cs = self._a_to_u_a_to_u('#')
2134  csp = self._a_to_u_a_to_u('# ')
2135  if section is None:
2136  int_val = self.interpolationinterpolation
2137  self.interpolationinterpolation = False
2138  section = self
2139  for line in self.initial_commentinitial_comment:
2140  line = self._decode_element_decode_element(line)
2141  stripped_line = line.strip()
2142  if stripped_line and not stripped_line.startswith(cs):
2143  line = csp + line
2144  out.append(line)
2145 
2146  indent_string = self.indent_typeindent_type * section.depth
2147  for entry in (section.scalars + section.sections):
2148  if entry in section.defaults:
2149  # don't write out default values
2150  continue
2151  for comment_line in section.comments[entry]:
2152  comment_line = self._decode_element_decode_element(comment_line.lstrip())
2153  if comment_line and not comment_line.startswith(cs):
2154  comment_line = csp + comment_line
2155  out.append(indent_string + comment_line)
2156  this_entry = section[entry]
2157  comment = self._handle_comment_handle_comment(section.inline_comments[entry])
2158 
2159  if isinstance(this_entry, Section):
2160  # a section
2161  out.append(self._write_marker_write_marker(
2162  indent_string,
2163  this_entry.depth,
2164  entry,
2165  comment))
2166  out.extend(self.writewrite(section=this_entry))
2167  else:
2168  out.append(self._write_line_write_line(
2169  indent_string,
2170  entry,
2171  this_entry,
2172  comment))
2173 
2174  if section is self:
2175  for line in self.final_commentfinal_comment:
2176  line = self._decode_element_decode_element(line)
2177  stripped_line = line.strip()
2178  if stripped_line and not stripped_line.startswith(cs):
2179  line = csp + line
2180  out.append(line)
2181  self.interpolationinterpolation = int_val
2182 
2183  if section is not self:
2184  return out
2185 
2186  if (self.filenamefilename is None) and (outfile is None):
2187  # output a list of lines
2188  # might need to encode
2189  # NOTE: This will *screw* UTF16, each line will start with the BOM
2190  if self.encodingencoding:
2191  out = [l.encode(self.encodingencoding) for l in out]
2192  if (self.BOMBOM and ((self.encodingencoding is None) or
2193  (BOM_LIST.get(self.encodingencoding.lower()) == 'utf_8'))):
2194  # Add the UTF8 BOM
2195  if not out:
2196  out.append('')
2197  out[0] = BOM_UTF8 + out[0]
2198  return out
2199 
2200  # Turn the list to a string, joined with correct newlines
2201  newline = self.newlinesnewlines or os.linesep
2202  if (getattr(outfile, 'mode', None) is not None and outfile.mode == 'w'
2203  and sys.platform == 'win32' and newline == '\r\n'):
2204  # Windows specific hack to avoid writing '\r\r\n'
2205  newline = '\n'
2206  output = self._a_to_u_a_to_u(newline).join(out)
2207  if not output.endswith(newline):
2208  output += newline
2209 
2210  if isinstance(output, six.binary_type):
2211  output_bytes = output
2212  else:
2213  output_bytes = output.encode(self.encodingencoding or
2214  self.default_encodingdefault_encoding or
2215  'utf-8') # DEBUG was 'ascii'
2216 
2217 
2218  if self.BOMBOM and ((self.encodingencoding is None) or match_utf8(self.encodingencoding)):
2219  # Add the UTF8 BOM
2220  output_bytes = BOM_UTF8 + output_bytes
2221 
2222  if outfile is not None:
2223  outfile.write(output_bytes)
2224  else:
2225  with open(self.filenamefilename, 'wb') as h:
2226  h.write(output_bytes)
2227 
2228 
2262  def validate(self, validator, preserve_errors=False, copy=False,
2263  section=None):
2264  if section is None:
2265  if self.configspecconfigspecconfigspec is None:
2266  raise ValueError('No configspec supplied.')
2267  if preserve_errors:
2268  # We do this once to remove a top level dependency on the validate module
2269  # Which makes importing configobj faster
2270  from validate import VdtMissingValue
2271  self._vdtMissingValue_vdtMissingValue = VdtMissingValue
2272 
2273  section = self
2274 
2275  if copy:
2276  section.initial_comment = section.configspec.initial_comment
2277  section.final_comment = section.configspec.final_comment
2278  section.encoding = section.configspec.encoding
2279  section.BOM = section.configspec.BOM
2280  section.newlines = section.configspec.newlines
2281  section.indent_type = section.configspec.indent_type
2282 
2283  #
2284  # section.default_values.clear() #??
2285  configspec = section.configspec
2286  self._set_configspec_set_configspec(section, copy)
2287 
2288 
2289  def validate_entry(entry, spec, val, missing, ret_true, ret_false):
2290  section.default_values.pop(entry, None)
2291 
2292  try:
2293  section.default_values[entry] = validator.get_default_value(configspec[entry])
2294  except (KeyError, AttributeError, validator.baseErrorClass):
2295  # No default, bad default or validator has no 'get_default_value'
2296  # (e.g. SimpleVal)
2297  pass
2298 
2299  try:
2300  check = validator.check(spec,
2301  val,
2302  missing=missing
2303  )
2304  except validator.baseErrorClass as e:
2305  if not preserve_errors or isinstance(e, self._vdtMissingValue_vdtMissingValue):
2306  out[entry] = False
2307  else:
2308  # preserve the error
2309  out[entry] = e
2310  ret_false = False
2311  ret_true = False
2312  else:
2313  ret_false = False
2314  out[entry] = True
2315  if self.stringifystringify or missing:
2316  # if we are doing type conversion
2317  # or the value is a supplied default
2318  if not self.stringifystringify:
2319  if isinstance(check, (list, tuple)):
2320  # preserve lists
2321  check = [self._str_str(item) for item in check]
2322  elif missing and check is None:
2323  # convert the None from a default to a ''
2324  check = ''
2325  else:
2326  check = self._str_str(check)
2327  if (check != val) or missing:
2328  section[entry] = check
2329  if not copy and missing and entry not in section.defaults:
2330  section.defaults.append(entry)
2331  return ret_true, ret_false
2332 
2333  #
2334  out = {}
2335  ret_true = True
2336  ret_false = True
2337 
2338  unvalidated = [k for k in section.scalars if k not in configspec]
2339  incorrect_sections = [k for k in configspec.sections if k in section.scalars]
2340  incorrect_scalars = [k for k in configspec.scalars if k in section.sections]
2341 
2342  for entry in configspec.scalars:
2343  if entry in ('__many__', '___many___'):
2344  # reserved names
2345  continue
2346  if (not entry in section.scalars) or (entry in section.defaults):
2347  # missing entries
2348  # or entries from defaults
2349  missing = True
2350  val = None
2351  if copy and entry not in section.scalars:
2352  # copy comments
2353  section.comments[entry] = (
2354  configspec.comments.get(entry, []))
2355  section.inline_comments[entry] = (
2356  configspec.inline_comments.get(entry, ''))
2357  #
2358  else:
2359  missing = False
2360  val = section[entry]
2361 
2362  ret_true, ret_false = validate_entry(entry, configspec[entry], val,
2363  missing, ret_true, ret_false)
2364 
2365  many = None
2366  if '__many__' in configspec.scalars:
2367  many = configspec['__many__']
2368  elif '___many___' in configspec.scalars:
2369  many = configspec['___many___']
2370 
2371  if many is not None:
2372  for entry in unvalidated:
2373  val = section[entry]
2374  ret_true, ret_false = validate_entry(entry, many, val, False,
2375  ret_true, ret_false)
2376  unvalidated = []
2377 
2378  for entry in incorrect_scalars:
2379  ret_true = False
2380  if not preserve_errors:
2381  out[entry] = False
2382  else:
2383  ret_false = False
2384  msg = 'Value %r was provided as a section' % entry
2385  out[entry] = validator.baseErrorClass(msg)
2386  for entry in incorrect_sections:
2387  ret_true = False
2388  if not preserve_errors:
2389  out[entry] = False
2390  else:
2391  ret_false = False
2392  msg = 'Section %r was provided as a single value' % entry
2393  out[entry] = validator.baseErrorClass(msg)
2394 
2395  # Missing sections will have been created as empty ones when the
2396  # configspec was read.
2397  for entry in section.sections:
2398  # FIXME: this means DEFAULT is not copied in copy mode
2399  if section is self and entry == 'DEFAULT':
2400  continue
2401  if section[entry].configspec is None:
2402  unvalidated.append(entry)
2403  continue
2404  if copy:
2405  section.comments[entry] = configspec.comments.get(entry, [])
2406  section.inline_comments[entry] = configspec.inline_comments.get(entry, '')
2407  check = self.validatevalidate(validator, preserve_errors=preserve_errors, copy=copy, section=section[entry])
2408  out[entry] = check
2409  if check == False:
2410  ret_true = False
2411  elif check == True:
2412  ret_false = False
2413  else:
2414  ret_true = False
2415 
2416  section.extra_values = unvalidated
2417  if preserve_errors and not section._created:
2418  # If the section wasn't created (i.e. it wasn't missing)
2419  # then we can't return False, we need to preserve errors
2420  ret_false = False
2421  #
2422  if ret_false and preserve_errors and out:
2423  # If we are preserving errors, but all
2424  # the failures are from missing sections / values
2425  # then we can return False. Otherwise there is a
2426  # real failure that we need to preserve.
2427  ret_false = not any(out.values())
2428  if ret_true:
2429  return True
2430  elif ret_false:
2431  return False
2432  return out
2433 
2434 
2435 
2436  def reset(self):
2437  self.clearclear()
2438  self._initialise_initialise_initialise()
2439  # FIXME: Should be done by '_initialise', but ConfigObj constructor (and reload)
2440  # requires an empty dictionary
2441  self.configspecconfigspecconfigspec = None
2442  # Just to be sure ;-)
2443  self._original_configspec_original_configspec = None
2444 
2445 
2446 
2452  def reload(self):
2453  if not isinstance(self.filenamefilename, six.string_types):
2454  raise ReloadError()
2455 
2456  filename = self.filenamefilename
2457  current_options = {}
2458  for entry in OPTION_DEFAULTS:
2459  if entry == 'configspec':
2460  continue
2461  current_options[entry] = getattr(self, entry)
2462 
2463  configspec = self._original_configspec_original_configspec
2464  current_options['configspec'] = configspec
2465 
2466  self.clearclear()
2467  self._initialise_initialise_initialise(current_options)
2468  self._load_load(filename, configspec)
2469 
2470 
2471 
2481 class SimpleVal():
2482 
2483  def __init__(self):
2484  self.baseErrorClassbaseErrorClass = ConfigObjError
2485 
2486 
2487  def check(self, check, member, missing=False):
2488  if missing:
2489  raise self.baseErrorClassbaseErrorClass()
2490  return member
2491 
2492 
2493 
2524 def flatten_errors(cfg, res, levels=None, results=None):
2525  if levels is None:
2526  # first time called
2527  levels = []
2528  results = []
2529  if res == True:
2530  return sorted(results)
2531  if res == False or isinstance(res, Exception):
2532  results.append((levels[:], None, res))
2533  if levels:
2534  levels.pop()
2535  return sorted(results)
2536  for (key, val) in list(res.items()):
2537  if val == True:
2538  continue
2539  if isinstance(cfg.get(key), dict):
2540  # Go down one level
2541  levels.append(key)
2542  flatten_errors(cfg[key], val, levels, results)
2543  continue
2544  results.append((levels[:], key, val))
2545  #
2546  # Go up one level
2547  if levels:
2548  levels.pop()
2549  #
2550  return sorted(results)
2551 
2552 
2553 
2569 def get_extra_values(conf, _prepend=()):
2570  out = []
2571 
2572  out.extend([(_prepend, name) for name in conf.extra_values])
2573  for name in conf.sections:
2574  if name not in conf.extra_values:
2575  out.extend(get_extra_values(conf[name], _prepend + (name,)))
2576  return out
2577 
2578 
2579 """*A programming language is a medium of expression.* - Paul Graham"""
This is the base class for all errors that ConfigObj raises.
Definition: configobj.py:213
def __init__(self, message='', line_number=None, line='')
Definition: configobj.py:214
An object to read, create, and write config files.
Definition: configobj.py:1098
def write(self, outfile=None, section=None)
Write the current ConfigObj as a file.
Definition: configobj.py:2127
def _handle_error(self, text, ErrorClass, infile, cur_index)
Definition: configobj.py:1813
def reset(self)
Clear ConfigObj instance and restore to 'freshly created' state.
Definition: configobj.py:2436
def __repr__(self)
x.__repr__() <==> repr(x)
Definition: configobj.py:1422
def _decode_element(self, line)
Decode element to unicode if necessary.
Definition: configobj.py:1592
def _decode(self, infile, encoding)
Definition: configobj.py:1569
def _write_marker(self, indent_string, depth, entry, comment)
Write a section marker line.
Definition: configobj.py:2091
def _set_configspec(self, section, copy)
Definition: configobj.py:2048
def _match_depth(self, sect, depth)
Definition: configobj.py:1793
def _a_to_u(self, aString)
Decode ASCII strings to unicode if a self.encoding is specified.
Definition: configobj.py:1555
BOM
BOM discovered self.BOM = True Don't need to remove BOM.
Definition: configobj.py:1406
def _multiline(self, value, infile, cur_index, maxline)
Extract the value, where we are in a multiline situation.
Definition: configobj.py:1985
def _handle_comment(self, comment)
Deal with a comment.
Definition: configobj.py:2102
def validate(self, validator, preserve_errors=False, copy=False, section=None)
Test the ConfigObj against a configspec.
Definition: configobj.py:2263
def _quote(self, value, multiline=True)
Definition: configobj.py:1858
def _initialise(self, options=None)
Definition: configobj.py:1390
def reload(self)
Reload a ConfigObj from file.
Definition: configobj.py:2452
def _parse(self, infile)
Actually parse the config file.
Definition: configobj.py:1618
def _handle_configspec(self, configspec)
Parse the configspec.
Definition: configobj.py:2022
def _write_line(self, indent_string, entry, this_entry, comment)
Write an individual line, for the write method.
Definition: configobj.py:2075
def __init__(self, infile=None, options=None, configspec=None, encoding=None, interpolation=True, raise_errors=False, list_values=True, create_empty=False, file_error=False, stringify=True, indent_type=None, default_encoding=None, unrepr=False, write_empty_values=False, _inspec=False)
Parse a config file or create a config file object.
Definition: configobj.py:1246
def _load(self, infile, configspec)
Definition: configobj.py:1294
def _unquote(self, value)
Return an unquoted version of a value.
Definition: configobj.py:1829
def _parse_match(self, match)
Implementation-dependent helper function.
Definition: configobj.py:431
Base class for the two interpolation errors.
Definition: configobj.py:252
The keyword or section specified already exists.
Definition: configobj.py:246
def _fetch(self, key)
Helper function to fetch values from owning section.
Definition: configobj.py:369
def _parse_match(self, match)
Implementation-dependent helper function.
Definition: configobj.py:416
Maximum interpolation depth exceeded in string interpolation.
Definition: configobj.py:256
This error indicates a level of nesting that doesn't match.
Definition: configobj.py:223
A 'reload' operation failed.
Definition: configobj.py:231
This error indicates additional sections in a section with a __many__ (repeated) section.
Definition: configobj.py:272
A dictionary-like object that represents a section in a config file.
Definition: configobj.py:500
def setdefault(self, key, default=None)
A version of setdefault that sets sequence if appropriate.
Definition: configobj.py:727
def as_int(self, key)
A convenience method which coerces the specified value to an integer.
Definition: configobj.py:1016
def iteritems(self)
D.iteritems() -> an iterator over the (key, value) items of D.
Definition: configobj.py:751
def iterkeys(self)
D.iterkeys() -> an iterator over the keys of D.
Definition: configobj.py:756
def __init__(self, parent, depth, main, indict=None, name=None)
Definition: configobj.py:518
def update(self, indict)
A version of update that uses our __setitem__.
Definition: configobj.py:676
def popitem(self)
Pops the first (key,val)
Definition: configobj.py:698
def clear(self)
A version of clear that also affects scalars/sections Also clears comments and configspec.
Definition: configobj.py:715
def __setitem__(self, key, value, unrepr=False)
Correctly set a value.
Definition: configobj.py:608
def restore_default(self, key)
Restore (and return) default value for the specified key.
Definition: configobj.py:1072
def dict(self)
Return a deepcopy of self as a dictionary.
Definition: configobj.py:795
def keys(self)
D.keys() -> list of D's keys.
Definition: configobj.py:741
def pop(self, key, default=MISSING)
'D.pop(k[,d]) -> v, remove specified key and return the corresponding value.
Definition: configobj.py:685
def itervalues(self)
D.itervalues() -> an iterator over the values of D.
Definition: configobj.py:763
def restore_defaults(self)
Recursively restore default values to all members that have them.
Definition: configobj.py:1089
def rename(self, oldkey, newkey)
Change a keyname to another, without changing position in sequence.
Definition: configobj.py:846
def merge(self, indict)
A recursive update - useful for merging config files.
Definition: configobj.py:829
def values(self)
D.values() -> list of D's values.
Definition: configobj.py:746
def get(self, key, default=None)
A version of get that doesn't bypass string interpolation.
Definition: configobj.py:666
def walk(self, function, raise_errors=True, call_on_sections=False, **keywargs)
Walk every member and call a function on the keyword and value.
Definition: configobj.py:914
def as_bool(self, key)
Accepts a key as input.
Definition: configobj.py:980
def __delitem__(self, key)
Remove items from the sequence when deleting.
Definition: configobj.py:655
def items(self)
D.items() -> list of D's (key, value) pairs, as 2-tuples.
Definition: configobj.py:736
def __repr__(self)
x.__repr__() <==> repr(x)
Definition: configobj.py:768
def as_float(self, key)
A convenience method which coerces the specified value to a float.
Definition: configobj.py:1038
def __getitem__(self, key)
Fetch the item and do string interpolation.
Definition: configobj.py:579
def as_list(self, key)
A convenience method which fetches the specified value, guaranteeing that it is a list.
Definition: configobj.py:1057
def _interpolate(self, key, value)
Definition: configobj.py:555
def check(self, check, member, missing=False)
A dummy check method, always returns the value unchanged.
Definition: configobj.py:2487
def _parse_match(self, match)
Implementation-dependent helper function.
Definition: configobj.py:459
An error parsing in unrepr mode.
Definition: configobj.py:283
def __newobj__(cls, *args)
Definition: configobj.py:479
def get_extra_values(conf, _prepend=())
Find all the values and sections not in the configspec from a validated ConfigObj.
Definition: configobj.py:2569
def flatten_errors(cfg, res, levels=None, results=None)
An example function that will turn a nested dictionary of results (as returned by ConfigObj....
Definition: configobj.py:2524