Wed, 13 Jul 2022 15:34:50 +0200
Revisions <no_multi_processing, Variables Viewer, with_python2> closed.
"""EditorConfig file parser Based on code from ConfigParser.py file distributed with Python 2.6. Licensed under PSF License (see LICENSE.PSF 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 posixpath import re from codecs import open from collections import OrderedDict from os import sep from os.path import dirname, normpath from editorconfig.compat import u from editorconfig.exceptions import ParsingError from editorconfig.fnmatch import fnmatch __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()