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