| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174 |
- """EditorConfig file parser
- Based on code from ConfigParser.py file distributed with Python 2.6.
- Licensed under PSF License (see LICENSE.txt file).
- Changes to original ConfigParser:
- - Special characters can be used in section names
- - Octothorpe can be used for comments (not just at beginning of line)
- - Only track INI options in sections that match target filename
- - Stop parsing files with when ``root = true`` is found
- """
- import re
- from codecs import open
- import posixpath
- from os import sep
- from os.path import normpath, dirname
- from editorconfig.exceptions import ParsingError
- from editorconfig.fnmatch import fnmatch
- from editorconfig.odict import OrderedDict
- from editorconfig.compat import u
- __all__ = ["ParsingError", "EditorConfigParser"]
- class EditorConfigParser(object):
- """Parser for EditorConfig-style configuration files
- Based on RawConfigParser from ConfigParser.py in Python 2.6.
- """
- # Regular expressions for parsing section headers and options.
- # Allow ``]`` and escaped ``;`` and ``#`` characters in section headers
- SECTCRE = re.compile(
- r"""
- \s * # Optional whitespace
- \[ # Opening square brace
- (?P<header> # One or more characters excluding
- ( [^\#;] | \\\# | \\; ) + # unescaped # and ; characters
- )
- \] # Closing square brace
- """, re.VERBOSE
- )
- # Regular expression for parsing option name/values.
- # Allow any amount of whitespaces, followed by separator
- # (either ``:`` or ``=``), followed by any amount of whitespace and then
- # any characters to eol
- OPTCRE = re.compile(
- r"""
- \s * # Optional whitespace
- (?P<option> # One or more characters excluding
- [^:=\s] # : a = characters (and first
- [^:=] * # must not be whitespace)
- )
- \s * # Optional whitespace
- (?P<vi>
- [:=] # Single = or : character
- )
- \s * # Optional whitespace
- (?P<value>
- . * # One or more characters
- )
- $
- """, re.VERBOSE
- )
- def __init__(self, filename):
- self.filename = filename
- self.options = OrderedDict()
- self.root_file = False
- def matches_filename(self, config_filename, glob):
- """Return True if section glob matches filename"""
- config_dirname = normpath(dirname(config_filename)).replace(sep, '/')
- glob = glob.replace("\\#", "#")
- glob = glob.replace("\\;", ";")
- if '/' in glob:
- if glob.find('/') == 0:
- glob = glob[1:]
- glob = posixpath.join(config_dirname, glob)
- else:
- glob = posixpath.join('**/', glob)
- return fnmatch(self.filename, glob)
- def read(self, filename):
- """Read and parse single EditorConfig file"""
- try:
- fp = open(filename, encoding='utf-8')
- except IOError:
- return
- self._read(fp, filename)
- fp.close()
- def _read(self, fp, fpname):
- """Parse a sectioned setup file.
- The sections in setup file contains a title line at the top,
- indicated by a name in square brackets (`[]'), plus key/value
- options lines, indicated by `name: value' format lines.
- Continuations are represented by an embedded newline then
- leading whitespace. Blank lines, lines beginning with a '#',
- and just about everything else are ignored.
- """
- in_section = False
- matching_section = False
- optname = None
- lineno = 0
- e = None # None, or an exception
- while True:
- line = fp.readline()
- if not line:
- break
- if lineno == 0 and line.startswith(u('\ufeff')):
- line = line[1:] # Strip UTF-8 BOM
- lineno = lineno + 1
- # comment or blank line?
- if line.strip() == '' or line[0] in '#;':
- continue
- # a section header or option header?
- else:
- # is it a section header?
- mo = self.SECTCRE.match(line)
- if mo:
- sectname = mo.group('header')
- in_section = True
- matching_section = self.matches_filename(fpname, sectname)
- # So sections can't start with a continuation line
- optname = None
- # an option line?
- else:
- mo = self.OPTCRE.match(line)
- if mo:
- optname, vi, optval = mo.group('option', 'vi', 'value')
- if ';' in optval or '#' in optval:
- # ';' and '#' are comment delimiters only if
- # preceeded by a spacing character
- m = re.search('(.*?) [;#]', optval)
- if m:
- optval = m.group(1)
- optval = optval.strip()
- # allow empty values
- if optval == '""':
- optval = ''
- optname = self.optionxform(optname.rstrip())
- if not in_section and optname == 'root':
- self.root_file = (optval.lower() == 'true')
- if matching_section:
- self.options[optname] = optval
- else:
- # a non-fatal parsing error occurred. set up the
- # exception but keep going. the exception will be
- # raised at the end of the file and will contain a
- # list of all bogus lines
- if not e:
- e = ParsingError(fpname)
- e.append(lineno, repr(line))
- # if any parsing errors occurred, raise an exception
- if e:
- raise e
- def optionxform(self, optionstr):
- return optionstr.lower()
|