##// END OF EJS Templates
config: track "source" along side value...
marmoute -
r47366:d3df397e default
parent child Browse files
Show More
@@ -1,326 +1,325 b''
1 1 # config.py - configuration parsing for Mercurial
2 2 #
3 3 # Copyright 2009 Matt Mackall <mpm@selenic.com> and others
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 import errno
11 11 import os
12 12
13 13 from .i18n import _
14 14 from .pycompat import getattr
15 15 from . import (
16 16 encoding,
17 17 error,
18 18 pycompat,
19 19 util,
20 20 )
21 21
22 22
23 23 class config(object):
24 24 def __init__(self, data=None):
25 25 self._data = {}
26 26 self._unset = []
27 27 if data:
28 28 for k in data._data:
29 29 self._data[k] = data[k].copy()
30 self._source = data._source.copy()
31 else:
32 self._source = util.cowdict()
33 30
34 31 def copy(self):
35 32 return config(self)
36 33
37 34 def __contains__(self, section):
38 35 return section in self._data
39 36
40 37 def hasitem(self, section, item):
41 38 return item in self._data.get(section, {})
42 39
43 40 def __getitem__(self, section):
44 41 return self._data.get(section, {})
45 42
46 43 def __iter__(self):
47 44 for d in self.sections():
48 45 yield d
49 46
50 47 def update(self, src):
51 self._source = self._source.preparewrite()
52 48 for s, n in src._unset:
53 49 ds = self._data.get(s, None)
54 50 if ds is not None and n in ds:
55 51 self._data[s] = ds.preparewrite()
56 52 del self._data[s][n]
57 del self._source[(s, n)]
58 53 for s in src:
59 54 ds = self._data.get(s, None)
60 55 if ds:
61 56 self._data[s] = ds.preparewrite()
62 57 else:
63 58 self._data[s] = util.cowsortdict()
64 59 self._data[s].update(src._data[s])
65 self._source.update(src._source)
60
61 def _get(self, section, item):
62 return self._data.get(section, {}).get(item)
66 63
67 64 def get(self, section, item, default=None):
68 return self._data.get(section, {}).get(item, default)
65 result = self._get(section, item)
66 if result is None:
67 return default
68 return result[0]
69 69
70 def backup(self, section, item):
70 def backup(self, section, key):
71 71 """return a tuple allowing restore to reinstall a previous value
72 72
73 73 The main reason we need it is because it handles the "no data" case.
74 74 """
75 75 try:
76 value = self._data[section][item]
77 source = self.source(section, item)
78 return (section, item, value, source)
76 item = self._data[section][key]
79 77 except KeyError:
80 return (section, item)
78 return (section, key)
79 else:
80 return (section, key) + item
81 81
82 82 def source(self, section, item):
83 return self._source.get((section, item), b"")
83 result = self._get(section, item)
84 if result is None:
85 return b""
86 return result[1]
84 87
85 88 def sections(self):
86 89 return sorted(self._data.keys())
87 90
88 91 def items(self, section):
89 return list(pycompat.iteritems(self._data.get(section, {})))
92 items = pycompat.iteritems(self._data.get(section, {}))
93 return [(k, v) for (k, (v, s)) in items]
90 94
91 95 def set(self, section, item, value, source=b""):
92 96 if pycompat.ispy3:
93 97 assert not isinstance(
94 98 section, str
95 99 ), b'config section may not be unicode strings on Python 3'
96 100 assert not isinstance(
97 101 item, str
98 102 ), b'config item may not be unicode strings on Python 3'
99 103 assert not isinstance(
100 104 value, str
101 105 ), b'config values may not be unicode strings on Python 3'
102 106 if section not in self:
103 107 self._data[section] = util.cowsortdict()
104 108 else:
105 109 self._data[section] = self._data[section].preparewrite()
106 self._data[section][item] = value
107 if source:
108 self._source = self._source.preparewrite()
109 self._source[(section, item)] = source
110 self._data[section][item] = (value, source)
110 111
111 112 def alter(self, section, key, new_value):
112 113 """alter a value without altering its source or level
113 114
114 115 This method is meant to be used by `ui.fixconfig` only."""
115 116 item = self._data[section][key]
116 117 size = len(item)
117 118 new_item = (new_value,) + item[1:]
118 119 assert len(new_item) == size
119 120 self._data[section][key] = new_item
120 121
121 122 def restore(self, data):
122 123 """restore data returned by self.backup"""
123 self._source = self._source.preparewrite()
124 if len(data) == 4:
124 if len(data) != 2:
125 125 # restore old data
126 section, item, value, source = data
126 section, key = data[:2]
127 item = data[2:]
127 128 self._data[section] = self._data[section].preparewrite()
128 self._data[section][item] = value
129 self._source[(section, item)] = source
129 self._data[section][key] = item
130 130 else:
131 131 # no data before, remove everything
132 132 section, item = data
133 133 if section in self._data:
134 134 self._data[section].pop(item, None)
135 self._source.pop((section, item), None)
136 135
137 136 def parse(self, src, data, sections=None, remap=None, include=None):
138 137 sectionre = util.re.compile(br'\[([^\[]+)\]')
139 138 itemre = util.re.compile(br'([^=\s][^=]*?)\s*=\s*(.*\S|)')
140 139 contre = util.re.compile(br'\s+(\S|\S.*\S)\s*$')
141 140 emptyre = util.re.compile(br'(;|#|\s*$)')
142 141 commentre = util.re.compile(br'(;|#)')
143 142 unsetre = util.re.compile(br'%unset\s+(\S+)')
144 143 includere = util.re.compile(br'%include\s+(\S|\S.*\S)\s*$')
145 144 section = b""
146 145 item = None
147 146 line = 0
148 147 cont = False
149 148
150 149 if remap:
151 150 section = remap.get(section, section)
152 151
153 152 for l in data.splitlines(True):
154 153 line += 1
155 154 if line == 1 and l.startswith(b'\xef\xbb\xbf'):
156 155 # Someone set us up the BOM
157 156 l = l[3:]
158 157 if cont:
159 158 if commentre.match(l):
160 159 continue
161 160 m = contre.match(l)
162 161 if m:
163 162 if sections and section not in sections:
164 163 continue
165 164 v = self.get(section, item) + b"\n" + m.group(1)
166 165 self.set(section, item, v, b"%s:%d" % (src, line))
167 166 continue
168 167 item = None
169 168 cont = False
170 169 m = includere.match(l)
171 170
172 171 if m and include:
173 172 expanded = util.expandpath(m.group(1))
174 173 try:
175 174 include(expanded, remap=remap, sections=sections)
176 175 except IOError as inst:
177 176 if inst.errno != errno.ENOENT:
178 177 raise error.ConfigError(
179 178 _(b"cannot include %s (%s)")
180 179 % (expanded, encoding.strtolocal(inst.strerror)),
181 180 b"%s:%d" % (src, line),
182 181 )
183 182 continue
184 183 if emptyre.match(l):
185 184 continue
186 185 m = sectionre.match(l)
187 186 if m:
188 187 section = m.group(1)
189 188 if remap:
190 189 section = remap.get(section, section)
191 190 if section not in self:
192 191 self._data[section] = util.cowsortdict()
193 192 continue
194 193 m = itemre.match(l)
195 194 if m:
196 195 item = m.group(1)
197 196 cont = True
198 197 if sections and section not in sections:
199 198 continue
200 199 self.set(section, item, m.group(2), b"%s:%d" % (src, line))
201 200 continue
202 201 m = unsetre.match(l)
203 202 if m:
204 203 name = m.group(1)
205 204 if sections and section not in sections:
206 205 continue
207 206 if self.get(section, name) is not None:
208 207 self._data[section] = self._data[section].preparewrite()
209 208 del self._data[section][name]
210 209 self._unset.append((section, name))
211 210 continue
212 211
213 212 message = l.rstrip()
214 213 if l.startswith(b' '):
215 214 message = b"unexpected leading whitespace: %s" % message
216 215 raise error.ConfigError(message, (b"%s:%d" % (src, line)))
217 216
218 217 def read(self, path, fp=None, sections=None, remap=None):
219 218 if not fp:
220 219 fp = util.posixfile(path, b'rb')
221 220 assert (
222 221 getattr(fp, 'mode', 'rb') == 'rb'
223 222 ), b'config files must be opened in binary mode, got fp=%r mode=%r' % (
224 223 fp,
225 224 fp.mode,
226 225 )
227 226
228 227 dir = os.path.dirname(path)
229 228
230 229 def include(rel, remap, sections):
231 230 abs = os.path.normpath(os.path.join(dir, rel))
232 231 self.read(abs, remap=remap, sections=sections)
233 232
234 233 self.parse(
235 234 path, fp.read(), sections=sections, remap=remap, include=include
236 235 )
237 236
238 237
239 238 def parselist(value):
240 239 """parse a configuration value as a list of comma/space separated strings
241 240
242 241 >>> parselist(b'this,is "a small" ,test')
243 242 ['this', 'is', 'a small', 'test']
244 243 """
245 244
246 245 def _parse_plain(parts, s, offset):
247 246 whitespace = False
248 247 while offset < len(s) and (
249 248 s[offset : offset + 1].isspace() or s[offset : offset + 1] == b','
250 249 ):
251 250 whitespace = True
252 251 offset += 1
253 252 if offset >= len(s):
254 253 return None, parts, offset
255 254 if whitespace:
256 255 parts.append(b'')
257 256 if s[offset : offset + 1] == b'"' and not parts[-1]:
258 257 return _parse_quote, parts, offset + 1
259 258 elif s[offset : offset + 1] == b'"' and parts[-1][-1:] == b'\\':
260 259 parts[-1] = parts[-1][:-1] + s[offset : offset + 1]
261 260 return _parse_plain, parts, offset + 1
262 261 parts[-1] += s[offset : offset + 1]
263 262 return _parse_plain, parts, offset + 1
264 263
265 264 def _parse_quote(parts, s, offset):
266 265 if offset < len(s) and s[offset : offset + 1] == b'"': # ""
267 266 parts.append(b'')
268 267 offset += 1
269 268 while offset < len(s) and (
270 269 s[offset : offset + 1].isspace()
271 270 or s[offset : offset + 1] == b','
272 271 ):
273 272 offset += 1
274 273 return _parse_plain, parts, offset
275 274
276 275 while offset < len(s) and s[offset : offset + 1] != b'"':
277 276 if (
278 277 s[offset : offset + 1] == b'\\'
279 278 and offset + 1 < len(s)
280 279 and s[offset + 1 : offset + 2] == b'"'
281 280 ):
282 281 offset += 1
283 282 parts[-1] += b'"'
284 283 else:
285 284 parts[-1] += s[offset : offset + 1]
286 285 offset += 1
287 286
288 287 if offset >= len(s):
289 288 real_parts = _configlist(parts[-1])
290 289 if not real_parts:
291 290 parts[-1] = b'"'
292 291 else:
293 292 real_parts[0] = b'"' + real_parts[0]
294 293 parts = parts[:-1]
295 294 parts.extend(real_parts)
296 295 return None, parts, offset
297 296
298 297 offset += 1
299 298 while offset < len(s) and s[offset : offset + 1] in [b' ', b',']:
300 299 offset += 1
301 300
302 301 if offset < len(s):
303 302 if offset + 1 == len(s) and s[offset : offset + 1] == b'"':
304 303 parts[-1] += b'"'
305 304 offset += 1
306 305 else:
307 306 parts.append(b'')
308 307 else:
309 308 return None, parts, offset
310 309
311 310 return _parse_plain, parts, offset
312 311
313 312 def _configlist(s):
314 313 s = s.rstrip(b' ,')
315 314 if not s:
316 315 return []
317 316 parser, parts, offset = _parse_plain, [b''], 0
318 317 while parser:
319 318 parser, parts, offset = parser(parts, s, offset)
320 319 return parts
321 320
322 321 if value is not None and isinstance(value, bytes):
323 322 result = _configlist(value.lstrip(b' ,\n'))
324 323 else:
325 324 result = value
326 325 return result or []
General Comments 0
You need to be logged in to leave comments. Login now