ini.py 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. """EditorConfig file parser
  2. Based on code from ConfigParser.py file distributed with Python 2.6.
  3. Licensed under PSF License (see LICENSE.txt file).
  4. Changes to original ConfigParser:
  5. - Special characters can be used in section names
  6. - Octothorpe can be used for comments (not just at beginning of line)
  7. - Only track INI options in sections that match target filename
  8. - Stop parsing files with when ``root = true`` is found
  9. """
  10. import re
  11. from codecs import open
  12. import posixpath
  13. from os import sep
  14. from os.path import normpath, dirname
  15. from editorconfig.exceptions import ParsingError
  16. from editorconfig.fnmatch import fnmatch
  17. from editorconfig.odict import OrderedDict
  18. from editorconfig.compat import u
  19. __all__ = ["ParsingError", "EditorConfigParser"]
  20. class EditorConfigParser(object):
  21. """Parser for EditorConfig-style configuration files
  22. Based on RawConfigParser from ConfigParser.py in Python 2.6.
  23. """
  24. # Regular expressions for parsing section headers and options.
  25. # Allow ``]`` and escaped ``;`` and ``#`` characters in section headers
  26. SECTCRE = re.compile(
  27. r"""
  28. \s * # Optional whitespace
  29. \[ # Opening square brace
  30. (?P<header> # One or more characters excluding
  31. ( [^\#;] | \\\# | \\; ) + # unescaped # and ; characters
  32. )
  33. \] # Closing square brace
  34. """, re.VERBOSE
  35. )
  36. # Regular expression for parsing option name/values.
  37. # Allow any amount of whitespaces, followed by separator
  38. # (either ``:`` or ``=``), followed by any amount of whitespace and then
  39. # any characters to eol
  40. OPTCRE = re.compile(
  41. r"""
  42. \s * # Optional whitespace
  43. (?P<option> # One or more characters excluding
  44. [^:=\s] # : a = characters (and first
  45. [^:=] * # must not be whitespace)
  46. )
  47. \s * # Optional whitespace
  48. (?P<vi>
  49. [:=] # Single = or : character
  50. )
  51. \s * # Optional whitespace
  52. (?P<value>
  53. . * # One or more characters
  54. )
  55. $
  56. """, re.VERBOSE
  57. )
  58. def __init__(self, filename):
  59. self.filename = filename
  60. self.options = OrderedDict()
  61. self.root_file = False
  62. def matches_filename(self, config_filename, glob):
  63. """Return True if section glob matches filename"""
  64. config_dirname = normpath(dirname(config_filename)).replace(sep, '/')
  65. glob = glob.replace("\\#", "#")
  66. glob = glob.replace("\\;", ";")
  67. if '/' in glob:
  68. if glob.find('/') == 0:
  69. glob = glob[1:]
  70. glob = posixpath.join(config_dirname, glob)
  71. else:
  72. glob = posixpath.join('**/', glob)
  73. return fnmatch(self.filename, glob)
  74. def read(self, filename):
  75. """Read and parse single EditorConfig file"""
  76. try:
  77. fp = open(filename, encoding='utf-8')
  78. except IOError:
  79. return
  80. self._read(fp, filename)
  81. fp.close()
  82. def _read(self, fp, fpname):
  83. """Parse a sectioned setup file.
  84. The sections in setup file contains a title line at the top,
  85. indicated by a name in square brackets (`[]'), plus key/value
  86. options lines, indicated by `name: value' format lines.
  87. Continuations are represented by an embedded newline then
  88. leading whitespace. Blank lines, lines beginning with a '#',
  89. and just about everything else are ignored.
  90. """
  91. in_section = False
  92. matching_section = False
  93. optname = None
  94. lineno = 0
  95. e = None # None, or an exception
  96. while True:
  97. line = fp.readline()
  98. if not line:
  99. break
  100. if lineno == 0 and line.startswith(u('\ufeff')):
  101. line = line[1:] # Strip UTF-8 BOM
  102. lineno = lineno + 1
  103. # comment or blank line?
  104. if line.strip() == '' or line[0] in '#;':
  105. continue
  106. # a section header or option header?
  107. else:
  108. # is it a section header?
  109. mo = self.SECTCRE.match(line)
  110. if mo:
  111. sectname = mo.group('header')
  112. in_section = True
  113. matching_section = self.matches_filename(fpname, sectname)
  114. # So sections can't start with a continuation line
  115. optname = None
  116. # an option line?
  117. else:
  118. mo = self.OPTCRE.match(line)
  119. if mo:
  120. optname, vi, optval = mo.group('option', 'vi', 'value')
  121. if ';' in optval or '#' in optval:
  122. # ';' and '#' are comment delimiters only if
  123. # preceeded by a spacing character
  124. m = re.search('(.*?) [;#]', optval)
  125. if m:
  126. optval = m.group(1)
  127. optval = optval.strip()
  128. # allow empty values
  129. if optval == '""':
  130. optval = ''
  131. optname = self.optionxform(optname.rstrip())
  132. if not in_section and optname == 'root':
  133. self.root_file = (optval.lower() == 'true')
  134. if matching_section:
  135. self.options[optname] = optval
  136. else:
  137. # a non-fatal parsing error occurred. set up the
  138. # exception but keep going. the exception will be
  139. # raised at the end of the file and will contain a
  140. # list of all bogus lines
  141. if not e:
  142. e = ParsingError(fpname)
  143. e.append(lineno, repr(line))
  144. # if any parsing errors occurred, raise an exception
  145. if e:
  146. raise e
  147. def optionxform(self, optionstr):
  148. return optionstr.lower()