ThirdParty/EditorConfig/editorconfig/ini.py

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

eric ide

mercurial