--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ThirdParty/EditorConfig/editorconfig/ini.py Thu Feb 01 19:26:11 2018 +0100 @@ -0,0 +1,174 @@ +"""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()