PageRenderTime 42ms CodeModel.GetById 16ms RepoModel.GetById 1ms app.codeStats 0ms

/src/zc/buildout/configparser.py

https://github.com/koodaamo/buildout
Python | 166 lines | 112 code | 8 blank | 46 comment | 24 complexity | 6a99219c8b55ff4819fa1ff0aa7a654e MD5 | raw file
Possible License(s): GPL-2.0
  1. ##############################################################################
  2. #
  3. # Copyright Zope Foundation and Contributors.
  4. # All Rights Reserved.
  5. #
  6. # This software is subject to the provisions of the Zope Public License,
  7. # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
  8. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
  9. # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  10. # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
  11. # FOR A PARTICULAR PURPOSE.
  12. #
  13. ##############################################################################
  14. # The following copied from Python 2 config parser because:
  15. # - The py3 configparser isn't backward compatible
  16. # - Both strip option values in undesireable ways
  17. # - dict of dicts is a much simpler api
  18. import re
  19. import textwrap
  20. class Error(Exception):
  21. """Base class for ConfigParser exceptions."""
  22. def _get_message(self):
  23. """Getter for 'message'; needed only to override deprecation in
  24. BaseException."""
  25. return self.__message
  26. def _set_message(self, value):
  27. """Setter for 'message'; needed only to override deprecation in
  28. BaseException."""
  29. self.__message = value
  30. # BaseException.message has been deprecated since Python 2.6. To prevent
  31. # DeprecationWarning from popping up over this pre-existing attribute, use
  32. # a new property that takes lookup precedence.
  33. message = property(_get_message, _set_message)
  34. def __init__(self, msg=''):
  35. self.message = msg
  36. Exception.__init__(self, msg)
  37. def __repr__(self):
  38. return self.message
  39. __str__ = __repr__
  40. class ParsingError(Error):
  41. """Raised when a configuration file does not follow legal syntax."""
  42. def __init__(self, filename):
  43. Error.__init__(self, 'File contains parsing errors: %s' % filename)
  44. self.filename = filename
  45. self.errors = []
  46. def append(self, lineno, line):
  47. self.errors.append((lineno, line))
  48. self.message += '\n\t[line %2d]: %s' % (lineno, line)
  49. class MissingSectionHeaderError(ParsingError):
  50. """Raised when a key-value pair is found before any section header."""
  51. def __init__(self, filename, lineno, line):
  52. Error.__init__(
  53. self,
  54. 'File contains no section headers.\nfile: %s, line: %d\n%r' %
  55. (filename, lineno, line))
  56. self.filename = filename
  57. self.lineno = lineno
  58. self.line = line
  59. section_header = re.compile(
  60. r'\[\s*(?P<header>[^\s[\]:{}]+)\s*]\s*([#;].*)?$').match
  61. option_start = re.compile(
  62. r'(?P<name>[^\s{}[\]=:]+\s*[-+]?)'
  63. r'='
  64. r'(?P<value>.*)$').match
  65. leading_blank_lines = re.compile(r"^(\s*\n)+")
  66. def parse(fp, fpname):
  67. """Parse a sectioned setup file.
  68. The sections in setup file contains a title line at the top,
  69. indicated by a name in square brackets (`[]'), plus key/value
  70. options lines, indicated by `name: value' format lines.
  71. Continuations are represented by an embedded newline then
  72. leading whitespace. Blank lines, lines beginning with a '#',
  73. and just about everything else are ignored.
  74. """
  75. sections = {}
  76. cursect = None # None, or a dictionary
  77. blockmode = None
  78. optname = None
  79. lineno = 0
  80. e = None # None, or an exception
  81. while True:
  82. line = fp.readline()
  83. if not line:
  84. break # EOF
  85. lineno = lineno + 1
  86. if line[0] in '#;':
  87. continue # comment
  88. if line[0].isspace() and cursect is not None and optname:
  89. # continuation line
  90. if blockmode:
  91. line = line.rstrip()
  92. else:
  93. line = line.strip()
  94. if not line:
  95. continue
  96. cursect[optname] = "%s\n%s" % (cursect[optname], line)
  97. else:
  98. mo = section_header(line)
  99. if mo:
  100. # section header
  101. sectname = mo.group('header')
  102. if sectname in sections:
  103. cursect = sections[sectname]
  104. else:
  105. sections[sectname] = cursect = {}
  106. # So sections can't start with a continuation line
  107. optname = None
  108. elif cursect is None:
  109. if not line.strip():
  110. continue
  111. # no section header in the file?
  112. raise MissingSectionHeaderError(fpname, lineno, line)
  113. else:
  114. mo = option_start(line)
  115. if mo:
  116. # option start line
  117. optname, optval = mo.group('name', 'value')
  118. optname = optname.rstrip()
  119. optval = optval.strip()
  120. cursect[optname] = optval
  121. blockmode = not optval
  122. elif not (optname or line.strip()):
  123. # blank line after section start
  124. continue
  125. else:
  126. # a non-fatal parsing error occurred. set up the
  127. # exception but keep going. the exception will be
  128. # raised at the end of the file and will contain a
  129. # list of all bogus lines
  130. if not e:
  131. e = ParsingError(fpname)
  132. e.append(lineno, repr(line))
  133. # if any parsing errors occurred, raise an exception
  134. if e:
  135. raise e
  136. for sectname in sections:
  137. section = sections[sectname]
  138. for name in section:
  139. value = section[name]
  140. if value[:1].isspace():
  141. section[name] = leading_blank_lines.sub(
  142. '', textwrap.dedent(value.rstrip()))
  143. return sections