77 return (section, item, value, source) |
77 return (section, item, value, source) |
78 except KeyError: |
78 except KeyError: |
79 return (section, item) |
79 return (section, item) |
80 |
80 |
81 def source(self, section, item): |
81 def source(self, section, item): |
82 return self._source.get((section, item), "") |
82 return self._source.get((section, item), b"") |
83 |
83 |
84 def sections(self): |
84 def sections(self): |
85 return sorted(self._data.keys()) |
85 return sorted(self._data.keys()) |
86 |
86 |
87 def items(self, section): |
87 def items(self, section): |
88 return list(self._data.get(section, {}).iteritems()) |
88 return list(self._data.get(section, {}).iteritems()) |
89 |
89 |
90 def set(self, section, item, value, source=""): |
90 def set(self, section, item, value, source=b""): |
91 if pycompat.ispy3: |
91 if pycompat.ispy3: |
92 assert not isinstance( |
92 assert not isinstance( |
93 section, str |
93 section, str |
94 ), 'config section may not be unicode strings on Python 3' |
94 ), b'config section may not be unicode strings on Python 3' |
95 assert not isinstance( |
95 assert not isinstance( |
96 item, str |
96 item, str |
97 ), 'config item may not be unicode strings on Python 3' |
97 ), b'config item may not be unicode strings on Python 3' |
98 assert not isinstance( |
98 assert not isinstance( |
99 value, str |
99 value, str |
100 ), 'config values may not be unicode strings on Python 3' |
100 ), b'config values may not be unicode strings on Python 3' |
101 if section not in self: |
101 if section not in self: |
102 self._data[section] = util.cowsortdict() |
102 self._data[section] = util.cowsortdict() |
103 else: |
103 else: |
104 self._data[section] = self._data[section].preparewrite() |
104 self._data[section] = self._data[section].preparewrite() |
105 self._data[section][item] = value |
105 self._data[section][item] = value |
129 contre = util.re.compile(br'\s+(\S|\S.*\S)\s*$') |
129 contre = util.re.compile(br'\s+(\S|\S.*\S)\s*$') |
130 emptyre = util.re.compile(br'(;|#|\s*$)') |
130 emptyre = util.re.compile(br'(;|#|\s*$)') |
131 commentre = util.re.compile(br'(;|#)') |
131 commentre = util.re.compile(br'(;|#)') |
132 unsetre = util.re.compile(br'%unset\s+(\S+)') |
132 unsetre = util.re.compile(br'%unset\s+(\S+)') |
133 includere = util.re.compile(br'%include\s+(\S|\S.*\S)\s*$') |
133 includere = util.re.compile(br'%include\s+(\S|\S.*\S)\s*$') |
134 section = "" |
134 section = b"" |
135 item = None |
135 item = None |
136 line = 0 |
136 line = 0 |
137 cont = False |
137 cont = False |
138 |
138 |
139 if remap: |
139 if remap: |
140 section = remap.get(section, section) |
140 section = remap.get(section, section) |
141 |
141 |
142 for l in data.splitlines(True): |
142 for l in data.splitlines(True): |
143 line += 1 |
143 line += 1 |
144 if line == 1 and l.startswith('\xef\xbb\xbf'): |
144 if line == 1 and l.startswith(b'\xef\xbb\xbf'): |
145 # Someone set us up the BOM |
145 # Someone set us up the BOM |
146 l = l[3:] |
146 l = l[3:] |
147 if cont: |
147 if cont: |
148 if commentre.match(l): |
148 if commentre.match(l): |
149 continue |
149 continue |
150 m = contre.match(l) |
150 m = contre.match(l) |
151 if m: |
151 if m: |
152 if sections and section not in sections: |
152 if sections and section not in sections: |
153 continue |
153 continue |
154 v = self.get(section, item) + "\n" + m.group(1) |
154 v = self.get(section, item) + b"\n" + m.group(1) |
155 self.set(section, item, v, "%s:%d" % (src, line)) |
155 self.set(section, item, v, b"%s:%d" % (src, line)) |
156 continue |
156 continue |
157 item = None |
157 item = None |
158 cont = False |
158 cont = False |
159 m = includere.match(l) |
159 m = includere.match(l) |
160 |
160 |
169 include(inc, remap=remap, sections=sections) |
169 include(inc, remap=remap, sections=sections) |
170 break |
170 break |
171 except IOError as inst: |
171 except IOError as inst: |
172 if inst.errno != errno.ENOENT: |
172 if inst.errno != errno.ENOENT: |
173 raise error.ParseError( |
173 raise error.ParseError( |
174 _("cannot include %s (%s)") |
174 _(b"cannot include %s (%s)") |
175 % (inc, inst.strerror), |
175 % (inc, inst.strerror), |
176 "%s:%d" % (src, line), |
176 b"%s:%d" % (src, line), |
177 ) |
177 ) |
178 continue |
178 continue |
179 if emptyre.match(l): |
179 if emptyre.match(l): |
180 continue |
180 continue |
181 m = sectionre.match(l) |
181 m = sectionre.match(l) |
203 self._data[section] = self._data[section].preparewrite() |
203 self._data[section] = self._data[section].preparewrite() |
204 del self._data[section][name] |
204 del self._data[section][name] |
205 self._unset.append((section, name)) |
205 self._unset.append((section, name)) |
206 continue |
206 continue |
207 |
207 |
208 raise error.ParseError(l.rstrip(), ("%s:%d" % (src, line))) |
208 raise error.ParseError(l.rstrip(), (b"%s:%d" % (src, line))) |
209 |
209 |
210 def read(self, path, fp=None, sections=None, remap=None): |
210 def read(self, path, fp=None, sections=None, remap=None): |
211 if not fp: |
211 if not fp: |
212 fp = util.posixfile(path, 'rb') |
212 fp = util.posixfile(path, b'rb') |
213 assert ( |
213 assert ( |
214 getattr(fp, 'mode', r'rb') == r'rb' |
214 getattr(fp, 'mode', r'rb') == r'rb' |
215 ), 'config files must be opened in binary mode, got fp=%r mode=%r' % ( |
215 ), b'config files must be opened in binary mode, got fp=%r mode=%r' % ( |
216 fp, |
216 fp, |
217 fp.mode, |
217 fp.mode, |
218 ) |
218 ) |
219 self.parse( |
219 self.parse( |
220 path, fp.read(), sections=sections, remap=remap, include=self.read |
220 path, fp.read(), sections=sections, remap=remap, include=self.read |
229 """ |
229 """ |
230 |
230 |
231 def _parse_plain(parts, s, offset): |
231 def _parse_plain(parts, s, offset): |
232 whitespace = False |
232 whitespace = False |
233 while offset < len(s) and ( |
233 while offset < len(s) and ( |
234 s[offset : offset + 1].isspace() or s[offset : offset + 1] == ',' |
234 s[offset : offset + 1].isspace() or s[offset : offset + 1] == b',' |
235 ): |
235 ): |
236 whitespace = True |
236 whitespace = True |
237 offset += 1 |
237 offset += 1 |
238 if offset >= len(s): |
238 if offset >= len(s): |
239 return None, parts, offset |
239 return None, parts, offset |
240 if whitespace: |
240 if whitespace: |
241 parts.append('') |
241 parts.append(b'') |
242 if s[offset : offset + 1] == '"' and not parts[-1]: |
242 if s[offset : offset + 1] == b'"' and not parts[-1]: |
243 return _parse_quote, parts, offset + 1 |
243 return _parse_quote, parts, offset + 1 |
244 elif s[offset : offset + 1] == '"' and parts[-1][-1:] == '\\': |
244 elif s[offset : offset + 1] == b'"' and parts[-1][-1:] == b'\\': |
245 parts[-1] = parts[-1][:-1] + s[offset : offset + 1] |
245 parts[-1] = parts[-1][:-1] + s[offset : offset + 1] |
246 return _parse_plain, parts, offset + 1 |
246 return _parse_plain, parts, offset + 1 |
247 parts[-1] += s[offset : offset + 1] |
247 parts[-1] += s[offset : offset + 1] |
248 return _parse_plain, parts, offset + 1 |
248 return _parse_plain, parts, offset + 1 |
249 |
249 |
250 def _parse_quote(parts, s, offset): |
250 def _parse_quote(parts, s, offset): |
251 if offset < len(s) and s[offset : offset + 1] == '"': # "" |
251 if offset < len(s) and s[offset : offset + 1] == b'"': # "" |
252 parts.append('') |
252 parts.append(b'') |
253 offset += 1 |
253 offset += 1 |
254 while offset < len(s) and ( |
254 while offset < len(s) and ( |
255 s[offset : offset + 1].isspace() |
255 s[offset : offset + 1].isspace() |
256 or s[offset : offset + 1] == ',' |
256 or s[offset : offset + 1] == b',' |
257 ): |
257 ): |
258 offset += 1 |
258 offset += 1 |
259 return _parse_plain, parts, offset |
259 return _parse_plain, parts, offset |
260 |
260 |
261 while offset < len(s) and s[offset : offset + 1] != '"': |
261 while offset < len(s) and s[offset : offset + 1] != b'"': |
262 if ( |
262 if ( |
263 s[offset : offset + 1] == '\\' |
263 s[offset : offset + 1] == b'\\' |
264 and offset + 1 < len(s) |
264 and offset + 1 < len(s) |
265 and s[offset + 1 : offset + 2] == '"' |
265 and s[offset + 1 : offset + 2] == b'"' |
266 ): |
266 ): |
267 offset += 1 |
267 offset += 1 |
268 parts[-1] += '"' |
268 parts[-1] += b'"' |
269 else: |
269 else: |
270 parts[-1] += s[offset : offset + 1] |
270 parts[-1] += s[offset : offset + 1] |
271 offset += 1 |
271 offset += 1 |
272 |
272 |
273 if offset >= len(s): |
273 if offset >= len(s): |
274 real_parts = _configlist(parts[-1]) |
274 real_parts = _configlist(parts[-1]) |
275 if not real_parts: |
275 if not real_parts: |
276 parts[-1] = '"' |
276 parts[-1] = b'"' |
277 else: |
277 else: |
278 real_parts[0] = '"' + real_parts[0] |
278 real_parts[0] = b'"' + real_parts[0] |
279 parts = parts[:-1] |
279 parts = parts[:-1] |
280 parts.extend(real_parts) |
280 parts.extend(real_parts) |
281 return None, parts, offset |
281 return None, parts, offset |
282 |
282 |
283 offset += 1 |
283 offset += 1 |
284 while offset < len(s) and s[offset : offset + 1] in [' ', ',']: |
284 while offset < len(s) and s[offset : offset + 1] in [b' ', b',']: |
285 offset += 1 |
285 offset += 1 |
286 |
286 |
287 if offset < len(s): |
287 if offset < len(s): |
288 if offset + 1 == len(s) and s[offset : offset + 1] == '"': |
288 if offset + 1 == len(s) and s[offset : offset + 1] == b'"': |
289 parts[-1] += '"' |
289 parts[-1] += b'"' |
290 offset += 1 |
290 offset += 1 |
291 else: |
291 else: |
292 parts.append('') |
292 parts.append(b'') |
293 else: |
293 else: |
294 return None, parts, offset |
294 return None, parts, offset |
295 |
295 |
296 return _parse_plain, parts, offset |
296 return _parse_plain, parts, offset |
297 |
297 |
298 def _configlist(s): |
298 def _configlist(s): |
299 s = s.rstrip(' ,') |
299 s = s.rstrip(b' ,') |
300 if not s: |
300 if not s: |
301 return [] |
301 return [] |
302 parser, parts, offset = _parse_plain, [''], 0 |
302 parser, parts, offset = _parse_plain, [b''], 0 |
303 while parser: |
303 while parser: |
304 parser, parts, offset = parser(parts, s, offset) |
304 parser, parts, offset = parser(parts, s, offset) |
305 return parts |
305 return parts |
306 |
306 |
307 if value is not None and isinstance(value, bytes): |
307 if value is not None and isinstance(value, bytes): |
308 result = _configlist(value.lstrip(' ,\n')) |
308 result = _configlist(value.lstrip(b' ,\n')) |
309 else: |
309 else: |
310 result = value |
310 result = value |
311 return result or [] |
311 return result or [] |