##// END OF EJS Templates
config: allow remapping the default section...
Yuya Nishihara -
r34714:e5a2cfc5 default
parent child Browse files
Show More
@@ -1,277 +1,280 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 if remap:
122 section = remap.get(section, section)
123
121 for l in data.splitlines(True):
124 for l in data.splitlines(True):
122 line += 1
125 line += 1
123 if line == 1 and l.startswith('\xef\xbb\xbf'):
126 if line == 1 and l.startswith('\xef\xbb\xbf'):
124 # Someone set us up the BOM
127 # Someone set us up the BOM
125 l = l[3:]
128 l = l[3:]
126 if cont:
129 if cont:
127 if commentre.match(l):
130 if commentre.match(l):
128 continue
131 continue
129 m = contre.match(l)
132 m = contre.match(l)
130 if m:
133 if m:
131 if sections and section not in sections:
134 if sections and section not in sections:
132 continue
135 continue
133 v = self.get(section, item) + "\n" + m.group(1)
136 v = self.get(section, item) + "\n" + m.group(1)
134 self.set(section, item, v, "%s:%d" % (src, line))
137 self.set(section, item, v, "%s:%d" % (src, line))
135 continue
138 continue
136 item = None
139 item = None
137 cont = False
140 cont = False
138 m = includere.match(l)
141 m = includere.match(l)
139
142
140 if m and include:
143 if m and include:
141 expanded = util.expandpath(m.group(1))
144 expanded = util.expandpath(m.group(1))
142 includepaths = [os.path.dirname(src)] + self._includepaths
145 includepaths = [os.path.dirname(src)] + self._includepaths
143
146
144 for base in includepaths:
147 for base in includepaths:
145 inc = os.path.normpath(os.path.join(base, expanded))
148 inc = os.path.normpath(os.path.join(base, expanded))
146
149
147 try:
150 try:
148 include(inc, remap=remap, sections=sections)
151 include(inc, remap=remap, sections=sections)
149 break
152 break
150 except IOError as inst:
153 except IOError as inst:
151 if inst.errno != errno.ENOENT:
154 if inst.errno != errno.ENOENT:
152 raise error.ParseError(_("cannot include %s (%s)")
155 raise error.ParseError(_("cannot include %s (%s)")
153 % (inc, inst.strerror),
156 % (inc, inst.strerror),
154 "%s:%s" % (src, line))
157 "%s:%s" % (src, line))
155 continue
158 continue
156 if emptyre.match(l):
159 if emptyre.match(l):
157 continue
160 continue
158 m = sectionre.match(l)
161 m = sectionre.match(l)
159 if m:
162 if m:
160 section = m.group(1)
163 section = m.group(1)
161 if remap:
164 if remap:
162 section = remap.get(section, section)
165 section = remap.get(section, section)
163 if section not in self:
166 if section not in self:
164 self._data[section] = util.cowsortdict()
167 self._data[section] = util.cowsortdict()
165 continue
168 continue
166 m = itemre.match(l)
169 m = itemre.match(l)
167 if m:
170 if m:
168 item = m.group(1)
171 item = m.group(1)
169 cont = True
172 cont = True
170 if sections and section not in sections:
173 if sections and section not in sections:
171 continue
174 continue
172 self.set(section, item, m.group(2), "%s:%d" % (src, line))
175 self.set(section, item, m.group(2), "%s:%d" % (src, line))
173 continue
176 continue
174 m = unsetre.match(l)
177 m = unsetre.match(l)
175 if m:
178 if m:
176 name = m.group(1)
179 name = m.group(1)
177 if sections and section not in sections:
180 if sections and section not in sections:
178 continue
181 continue
179 if self.get(section, name) is not None:
182 if self.get(section, name) is not None:
180 self._data[section] = self._data[section].preparewrite()
183 self._data[section] = self._data[section].preparewrite()
181 del self._data[section][name]
184 del self._data[section][name]
182 self._unset.append((section, name))
185 self._unset.append((section, name))
183 continue
186 continue
184
187
185 raise error.ParseError(l.rstrip(), ("%s:%s" % (src, line)))
188 raise error.ParseError(l.rstrip(), ("%s:%s" % (src, line)))
186
189
187 def read(self, path, fp=None, sections=None, remap=None):
190 def read(self, path, fp=None, sections=None, remap=None):
188 if not fp:
191 if not fp:
189 fp = util.posixfile(path, 'rb')
192 fp = util.posixfile(path, 'rb')
190 assert getattr(fp, 'mode', r'rb') == r'rb', (
193 assert getattr(fp, 'mode', r'rb') == r'rb', (
191 'config files must be opened in binary mode, got fp=%r mode=%r' % (
194 'config files must be opened in binary mode, got fp=%r mode=%r' % (
192 fp, fp.mode))
195 fp, fp.mode))
193 self.parse(path, fp.read(),
196 self.parse(path, fp.read(),
194 sections=sections, remap=remap, include=self.read)
197 sections=sections, remap=remap, include=self.read)
195
198
196 def parselist(value):
199 def parselist(value):
197 """parse a configuration value as a list of comma/space separated strings
200 """parse a configuration value as a list of comma/space separated strings
198
201
199 >>> parselist(b'this,is "a small" ,test')
202 >>> parselist(b'this,is "a small" ,test')
200 ['this', 'is', 'a small', 'test']
203 ['this', 'is', 'a small', 'test']
201 """
204 """
202
205
203 def _parse_plain(parts, s, offset):
206 def _parse_plain(parts, s, offset):
204 whitespace = False
207 whitespace = False
205 while offset < len(s) and (s[offset:offset + 1].isspace()
208 while offset < len(s) and (s[offset:offset + 1].isspace()
206 or s[offset:offset + 1] == ','):
209 or s[offset:offset + 1] == ','):
207 whitespace = True
210 whitespace = True
208 offset += 1
211 offset += 1
209 if offset >= len(s):
212 if offset >= len(s):
210 return None, parts, offset
213 return None, parts, offset
211 if whitespace:
214 if whitespace:
212 parts.append('')
215 parts.append('')
213 if s[offset:offset + 1] == '"' and not parts[-1]:
216 if s[offset:offset + 1] == '"' and not parts[-1]:
214 return _parse_quote, parts, offset + 1
217 return _parse_quote, parts, offset + 1
215 elif s[offset:offset + 1] == '"' and parts[-1][-1] == '\\':
218 elif s[offset:offset + 1] == '"' and parts[-1][-1] == '\\':
216 parts[-1] = parts[-1][:-1] + s[offset:offset + 1]
219 parts[-1] = parts[-1][:-1] + s[offset:offset + 1]
217 return _parse_plain, parts, offset + 1
220 return _parse_plain, parts, offset + 1
218 parts[-1] += s[offset:offset + 1]
221 parts[-1] += s[offset:offset + 1]
219 return _parse_plain, parts, offset + 1
222 return _parse_plain, parts, offset + 1
220
223
221 def _parse_quote(parts, s, offset):
224 def _parse_quote(parts, s, offset):
222 if offset < len(s) and s[offset:offset + 1] == '"': # ""
225 if offset < len(s) and s[offset:offset + 1] == '"': # ""
223 parts.append('')
226 parts.append('')
224 offset += 1
227 offset += 1
225 while offset < len(s) and (s[offset:offset + 1].isspace() or
228 while offset < len(s) and (s[offset:offset + 1].isspace() or
226 s[offset:offset + 1] == ','):
229 s[offset:offset + 1] == ','):
227 offset += 1
230 offset += 1
228 return _parse_plain, parts, offset
231 return _parse_plain, parts, offset
229
232
230 while offset < len(s) and s[offset:offset + 1] != '"':
233 while offset < len(s) and s[offset:offset + 1] != '"':
231 if (s[offset:offset + 1] == '\\' and offset + 1 < len(s)
234 if (s[offset:offset + 1] == '\\' and offset + 1 < len(s)
232 and s[offset + 1:offset + 2] == '"'):
235 and s[offset + 1:offset + 2] == '"'):
233 offset += 1
236 offset += 1
234 parts[-1] += '"'
237 parts[-1] += '"'
235 else:
238 else:
236 parts[-1] += s[offset:offset + 1]
239 parts[-1] += s[offset:offset + 1]
237 offset += 1
240 offset += 1
238
241
239 if offset >= len(s):
242 if offset >= len(s):
240 real_parts = _configlist(parts[-1])
243 real_parts = _configlist(parts[-1])
241 if not real_parts:
244 if not real_parts:
242 parts[-1] = '"'
245 parts[-1] = '"'
243 else:
246 else:
244 real_parts[0] = '"' + real_parts[0]
247 real_parts[0] = '"' + real_parts[0]
245 parts = parts[:-1]
248 parts = parts[:-1]
246 parts.extend(real_parts)
249 parts.extend(real_parts)
247 return None, parts, offset
250 return None, parts, offset
248
251
249 offset += 1
252 offset += 1
250 while offset < len(s) and s[offset:offset + 1] in [' ', ',']:
253 while offset < len(s) and s[offset:offset + 1] in [' ', ',']:
251 offset += 1
254 offset += 1
252
255
253 if offset < len(s):
256 if offset < len(s):
254 if offset + 1 == len(s) and s[offset:offset + 1] == '"':
257 if offset + 1 == len(s) and s[offset:offset + 1] == '"':
255 parts[-1] += '"'
258 parts[-1] += '"'
256 offset += 1
259 offset += 1
257 else:
260 else:
258 parts.append('')
261 parts.append('')
259 else:
262 else:
260 return None, parts, offset
263 return None, parts, offset
261
264
262 return _parse_plain, parts, offset
265 return _parse_plain, parts, offset
263
266
264 def _configlist(s):
267 def _configlist(s):
265 s = s.rstrip(' ,')
268 s = s.rstrip(' ,')
266 if not s:
269 if not s:
267 return []
270 return []
268 parser, parts, offset = _parse_plain, [''], 0
271 parser, parts, offset = _parse_plain, [''], 0
269 while parser:
272 while parser:
270 parser, parts, offset = parser(parts, s, offset)
273 parser, parts, offset = parser(parts, s, offset)
271 return parts
274 return parts
272
275
273 if value is not None and isinstance(value, bytes):
276 if value is not None and isinstance(value, bytes):
274 result = _configlist(value.lstrip(' ,\n'))
277 result = _configlist(value.lstrip(' ,\n'))
275 else:
278 else:
276 result = value
279 result = value
277 return result or []
280 return result or []
General Comments 0
You need to be logged in to leave comments. Login now