26 for k in data._data: |
27 for k in data._data: |
27 self._data[k] = data[k].copy() |
28 self._data[k] = data[k].copy() |
28 self._source = data._source.copy() |
29 self._source = data._source.copy() |
29 else: |
30 else: |
30 self._source = util.cowdict() |
31 self._source = util.cowdict() |
|
32 |
31 def copy(self): |
33 def copy(self): |
32 return config(self) |
34 return config(self) |
|
35 |
33 def __contains__(self, section): |
36 def __contains__(self, section): |
34 return section in self._data |
37 return section in self._data |
|
38 |
35 def hasitem(self, section, item): |
39 def hasitem(self, section, item): |
36 return item in self._data.get(section, {}) |
40 return item in self._data.get(section, {}) |
|
41 |
37 def __getitem__(self, section): |
42 def __getitem__(self, section): |
38 return self._data.get(section, {}) |
43 return self._data.get(section, {}) |
|
44 |
39 def __iter__(self): |
45 def __iter__(self): |
40 for d in self.sections(): |
46 for d in self.sections(): |
41 yield d |
47 yield d |
|
48 |
42 def update(self, src): |
49 def update(self, src): |
43 self._source = self._source.preparewrite() |
50 self._source = self._source.preparewrite() |
44 for s, n in src._unset: |
51 for s, n in src._unset: |
45 ds = self._data.get(s, None) |
52 ds = self._data.get(s, None) |
46 if ds is not None and n in ds: |
53 if ds is not None and n in ds: |
53 self._data[s] = ds.preparewrite() |
60 self._data[s] = ds.preparewrite() |
54 else: |
61 else: |
55 self._data[s] = util.cowsortdict() |
62 self._data[s] = util.cowsortdict() |
56 self._data[s].update(src._data[s]) |
63 self._data[s].update(src._data[s]) |
57 self._source.update(src._source) |
64 self._source.update(src._source) |
|
65 |
58 def get(self, section, item, default=None): |
66 def get(self, section, item, default=None): |
59 return self._data.get(section, {}).get(item, default) |
67 return self._data.get(section, {}).get(item, default) |
60 |
68 |
61 def backup(self, section, item): |
69 def backup(self, section, item): |
62 """return a tuple allowing restore to reinstall a previous value |
70 """return a tuple allowing restore to reinstall a previous value |
70 except KeyError: |
78 except KeyError: |
71 return (section, item) |
79 return (section, item) |
72 |
80 |
73 def source(self, section, item): |
81 def source(self, section, item): |
74 return self._source.get((section, item), "") |
82 return self._source.get((section, item), "") |
|
83 |
75 def sections(self): |
84 def sections(self): |
76 return sorted(self._data.keys()) |
85 return sorted(self._data.keys()) |
|
86 |
77 def items(self, section): |
87 def items(self, section): |
78 return list(self._data.get(section, {}).iteritems()) |
88 return list(self._data.get(section, {}).iteritems()) |
|
89 |
79 def set(self, section, item, value, source=""): |
90 def set(self, section, item, value, source=""): |
80 if pycompat.ispy3: |
91 if pycompat.ispy3: |
81 assert not isinstance(section, str), ( |
92 assert not isinstance( |
82 'config section may not be unicode strings on Python 3') |
93 section, str |
83 assert not isinstance(item, str), ( |
94 ), 'config section may not be unicode strings on Python 3' |
84 'config item may not be unicode strings on Python 3') |
95 assert not isinstance( |
85 assert not isinstance(value, str), ( |
96 item, str |
86 'config values may not be unicode strings on Python 3') |
97 ), 'config item may not be unicode strings on Python 3' |
|
98 assert not isinstance( |
|
99 value, str |
|
100 ), 'config values may not be unicode strings on Python 3' |
87 if section not in self: |
101 if section not in self: |
88 self._data[section] = util.cowsortdict() |
102 self._data[section] = util.cowsortdict() |
89 else: |
103 else: |
90 self._data[section] = self._data[section].preparewrite() |
104 self._data[section] = self._data[section].preparewrite() |
91 self._data[section][item] = value |
105 self._data[section][item] = value |
192 raise error.ParseError(l.rstrip(), ("%s:%d" % (src, line))) |
208 raise error.ParseError(l.rstrip(), ("%s:%d" % (src, line))) |
193 |
209 |
194 def read(self, path, fp=None, sections=None, remap=None): |
210 def read(self, path, fp=None, sections=None, remap=None): |
195 if not fp: |
211 if not fp: |
196 fp = util.posixfile(path, 'rb') |
212 fp = util.posixfile(path, 'rb') |
197 assert getattr(fp, 'mode', r'rb') == r'rb', ( |
213 assert ( |
198 'config files must be opened in binary mode, got fp=%r mode=%r' % ( |
214 getattr(fp, 'mode', r'rb') == r'rb' |
199 fp, fp.mode)) |
215 ), 'config files must be opened in binary mode, got fp=%r mode=%r' % ( |
200 self.parse(path, fp.read(), |
216 fp, |
201 sections=sections, remap=remap, include=self.read) |
217 fp.mode, |
|
218 ) |
|
219 self.parse( |
|
220 path, fp.read(), sections=sections, remap=remap, include=self.read |
|
221 ) |
|
222 |
202 |
223 |
203 def parselist(value): |
224 def parselist(value): |
204 """parse a configuration value as a list of comma/space separated strings |
225 """parse a configuration value as a list of comma/space separated strings |
205 |
226 |
206 >>> parselist(b'this,is "a small" ,test') |
227 >>> parselist(b'this,is "a small" ,test') |
207 ['this', 'is', 'a small', 'test'] |
228 ['this', 'is', 'a small', 'test'] |
208 """ |
229 """ |
209 |
230 |
210 def _parse_plain(parts, s, offset): |
231 def _parse_plain(parts, s, offset): |
211 whitespace = False |
232 whitespace = False |
212 while offset < len(s) and (s[offset:offset + 1].isspace() |
233 while offset < len(s) and ( |
213 or s[offset:offset + 1] == ','): |
234 s[offset : offset + 1].isspace() or s[offset : offset + 1] == ',' |
|
235 ): |
214 whitespace = True |
236 whitespace = True |
215 offset += 1 |
237 offset += 1 |
216 if offset >= len(s): |
238 if offset >= len(s): |
217 return None, parts, offset |
239 return None, parts, offset |
218 if whitespace: |
240 if whitespace: |
219 parts.append('') |
241 parts.append('') |
220 if s[offset:offset + 1] == '"' and not parts[-1]: |
242 if s[offset : offset + 1] == '"' and not parts[-1]: |
221 return _parse_quote, parts, offset + 1 |
243 return _parse_quote, parts, offset + 1 |
222 elif s[offset:offset + 1] == '"' and parts[-1][-1:] == '\\': |
244 elif s[offset : offset + 1] == '"' and parts[-1][-1:] == '\\': |
223 parts[-1] = parts[-1][:-1] + s[offset:offset + 1] |
245 parts[-1] = parts[-1][:-1] + s[offset : offset + 1] |
224 return _parse_plain, parts, offset + 1 |
246 return _parse_plain, parts, offset + 1 |
225 parts[-1] += s[offset:offset + 1] |
247 parts[-1] += s[offset : offset + 1] |
226 return _parse_plain, parts, offset + 1 |
248 return _parse_plain, parts, offset + 1 |
227 |
249 |
228 def _parse_quote(parts, s, offset): |
250 def _parse_quote(parts, s, offset): |
229 if offset < len(s) and s[offset:offset + 1] == '"': # "" |
251 if offset < len(s) and s[offset : offset + 1] == '"': # "" |
230 parts.append('') |
252 parts.append('') |
231 offset += 1 |
253 offset += 1 |
232 while offset < len(s) and (s[offset:offset + 1].isspace() or |
254 while offset < len(s) and ( |
233 s[offset:offset + 1] == ','): |
255 s[offset : offset + 1].isspace() |
|
256 or s[offset : offset + 1] == ',' |
|
257 ): |
234 offset += 1 |
258 offset += 1 |
235 return _parse_plain, parts, offset |
259 return _parse_plain, parts, offset |
236 |
260 |
237 while offset < len(s) and s[offset:offset + 1] != '"': |
261 while offset < len(s) and s[offset : offset + 1] != '"': |
238 if (s[offset:offset + 1] == '\\' and offset + 1 < len(s) |
262 if ( |
239 and s[offset + 1:offset + 2] == '"'): |
263 s[offset : offset + 1] == '\\' |
|
264 and offset + 1 < len(s) |
|
265 and s[offset + 1 : offset + 2] == '"' |
|
266 ): |
240 offset += 1 |
267 offset += 1 |
241 parts[-1] += '"' |
268 parts[-1] += '"' |
242 else: |
269 else: |
243 parts[-1] += s[offset:offset + 1] |
270 parts[-1] += s[offset : offset + 1] |
244 offset += 1 |
271 offset += 1 |
245 |
272 |
246 if offset >= len(s): |
273 if offset >= len(s): |
247 real_parts = _configlist(parts[-1]) |
274 real_parts = _configlist(parts[-1]) |
248 if not real_parts: |
275 if not real_parts: |