##// END OF EJS Templates
config: avoid using a mutable default...
Martijn Pieters -
r31374:d30fb3de default
parent child Browse files
Show More
@@ -1,181 +1,181 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=[]):
21 def __init__(self, data=None, includepaths=None):
22 self._data = {}
22 self._data = {}
23 self._source = {}
23 self._source = {}
24 self._unset = []
24 self._unset = []
25 self._includepaths = includepaths
25 self._includepaths = includepaths or []
26 if data:
26 if data:
27 for k in data._data:
27 for k in data._data:
28 self._data[k] = data[k].copy()
28 self._data[k] = data[k].copy()
29 self._source = data._source.copy()
29 self._source = data._source.copy()
30 def copy(self):
30 def copy(self):
31 return config(self)
31 return config(self)
32 def __contains__(self, section):
32 def __contains__(self, section):
33 return section in self._data
33 return section in self._data
34 def hasitem(self, section, item):
34 def hasitem(self, section, item):
35 return item in self._data.get(section, {})
35 return item in self._data.get(section, {})
36 def __getitem__(self, section):
36 def __getitem__(self, section):
37 return self._data.get(section, {})
37 return self._data.get(section, {})
38 def __iter__(self):
38 def __iter__(self):
39 for d in self.sections():
39 for d in self.sections():
40 yield d
40 yield d
41 def update(self, src):
41 def update(self, src):
42 for s, n in src._unset:
42 for s, n in src._unset:
43 if s in self and n in self._data[s]:
43 if s in self and n in self._data[s]:
44 del self._data[s][n]
44 del self._data[s][n]
45 del self._source[(s, n)]
45 del self._source[(s, n)]
46 for s in src:
46 for s in src:
47 if s not in self:
47 if s not in self:
48 self._data[s] = util.sortdict()
48 self._data[s] = util.sortdict()
49 self._data[s].update(src._data[s])
49 self._data[s].update(src._data[s])
50 self._source.update(src._source)
50 self._source.update(src._source)
51 def get(self, section, item, default=None):
51 def get(self, section, item, default=None):
52 return self._data.get(section, {}).get(item, default)
52 return self._data.get(section, {}).get(item, default)
53
53
54 def backup(self, section, item):
54 def backup(self, section, item):
55 """return a tuple allowing restore to reinstall a previous value
55 """return a tuple allowing restore to reinstall a previous value
56
56
57 The main reason we need it is because it handles the "no data" case.
57 The main reason we need it is because it handles the "no data" case.
58 """
58 """
59 try:
59 try:
60 value = self._data[section][item]
60 value = self._data[section][item]
61 source = self.source(section, item)
61 source = self.source(section, item)
62 return (section, item, value, source)
62 return (section, item, value, source)
63 except KeyError:
63 except KeyError:
64 return (section, item)
64 return (section, item)
65
65
66 def source(self, section, item):
66 def source(self, section, item):
67 return self._source.get((section, item), "")
67 return self._source.get((section, item), "")
68 def sections(self):
68 def sections(self):
69 return sorted(self._data.keys())
69 return sorted(self._data.keys())
70 def items(self, section):
70 def items(self, section):
71 return self._data.get(section, {}).items()
71 return self._data.get(section, {}).items()
72 def set(self, section, item, value, source=""):
72 def set(self, section, item, value, source=""):
73 if pycompat.ispy3:
73 if pycompat.ispy3:
74 assert not isinstance(value, str), (
74 assert not isinstance(value, str), (
75 'config values may not be unicode strings on Python 3')
75 'config values may not be unicode strings on Python 3')
76 if section not in self:
76 if section not in self:
77 self._data[section] = util.sortdict()
77 self._data[section] = util.sortdict()
78 self._data[section][item] = value
78 self._data[section][item] = value
79 if source:
79 if source:
80 self._source[(section, item)] = source
80 self._source[(section, item)] = source
81
81
82 def restore(self, data):
82 def restore(self, data):
83 """restore data returned by self.backup"""
83 """restore data returned by self.backup"""
84 if len(data) == 4:
84 if len(data) == 4:
85 # restore old data
85 # restore old data
86 section, item, value, source = data
86 section, item, value, source = data
87 self._data[section][item] = value
87 self._data[section][item] = value
88 self._source[(section, item)] = source
88 self._source[(section, item)] = source
89 else:
89 else:
90 # no data before, remove everything
90 # no data before, remove everything
91 section, item = data
91 section, item = data
92 if section in self._data:
92 if section in self._data:
93 self._data[section].pop(item, None)
93 self._data[section].pop(item, None)
94 self._source.pop((section, item), None)
94 self._source.pop((section, item), None)
95
95
96 def parse(self, src, data, sections=None, remap=None, include=None):
96 def parse(self, src, data, sections=None, remap=None, include=None):
97 sectionre = util.re.compile(br'\[([^\[]+)\]')
97 sectionre = util.re.compile(br'\[([^\[]+)\]')
98 itemre = util.re.compile(br'([^=\s][^=]*?)\s*=\s*(.*\S|)')
98 itemre = util.re.compile(br'([^=\s][^=]*?)\s*=\s*(.*\S|)')
99 contre = util.re.compile(br'\s+(\S|\S.*\S)\s*$')
99 contre = util.re.compile(br'\s+(\S|\S.*\S)\s*$')
100 emptyre = util.re.compile(br'(;|#|\s*$)')
100 emptyre = util.re.compile(br'(;|#|\s*$)')
101 commentre = util.re.compile(br'(;|#)')
101 commentre = util.re.compile(br'(;|#)')
102 unsetre = util.re.compile(br'%unset\s+(\S+)')
102 unsetre = util.re.compile(br'%unset\s+(\S+)')
103 includere = util.re.compile(br'%include\s+(\S|\S.*\S)\s*$')
103 includere = util.re.compile(br'%include\s+(\S|\S.*\S)\s*$')
104 section = ""
104 section = ""
105 item = None
105 item = None
106 line = 0
106 line = 0
107 cont = False
107 cont = False
108
108
109 for l in data.splitlines(True):
109 for l in data.splitlines(True):
110 line += 1
110 line += 1
111 if line == 1 and l.startswith('\xef\xbb\xbf'):
111 if line == 1 and l.startswith('\xef\xbb\xbf'):
112 # Someone set us up the BOM
112 # Someone set us up the BOM
113 l = l[3:]
113 l = l[3:]
114 if cont:
114 if cont:
115 if commentre.match(l):
115 if commentre.match(l):
116 continue
116 continue
117 m = contre.match(l)
117 m = contre.match(l)
118 if m:
118 if m:
119 if sections and section not in sections:
119 if sections and section not in sections:
120 continue
120 continue
121 v = self.get(section, item) + "\n" + m.group(1)
121 v = self.get(section, item) + "\n" + m.group(1)
122 self.set(section, item, v, "%s:%d" % (src, line))
122 self.set(section, item, v, "%s:%d" % (src, line))
123 continue
123 continue
124 item = None
124 item = None
125 cont = False
125 cont = False
126 m = includere.match(l)
126 m = includere.match(l)
127
127
128 if m and include:
128 if m and include:
129 expanded = util.expandpath(m.group(1))
129 expanded = util.expandpath(m.group(1))
130 includepaths = [os.path.dirname(src)] + self._includepaths
130 includepaths = [os.path.dirname(src)] + self._includepaths
131
131
132 for base in includepaths:
132 for base in includepaths:
133 inc = os.path.normpath(os.path.join(base, expanded))
133 inc = os.path.normpath(os.path.join(base, expanded))
134
134
135 try:
135 try:
136 include(inc, remap=remap, sections=sections)
136 include(inc, remap=remap, sections=sections)
137 break
137 break
138 except IOError as inst:
138 except IOError as inst:
139 if inst.errno != errno.ENOENT:
139 if inst.errno != errno.ENOENT:
140 raise error.ParseError(_("cannot include %s (%s)")
140 raise error.ParseError(_("cannot include %s (%s)")
141 % (inc, inst.strerror),
141 % (inc, inst.strerror),
142 "%s:%s" % (src, line))
142 "%s:%s" % (src, line))
143 continue
143 continue
144 if emptyre.match(l):
144 if emptyre.match(l):
145 continue
145 continue
146 m = sectionre.match(l)
146 m = sectionre.match(l)
147 if m:
147 if m:
148 section = m.group(1)
148 section = m.group(1)
149 if remap:
149 if remap:
150 section = remap.get(section, section)
150 section = remap.get(section, section)
151 if section not in self:
151 if section not in self:
152 self._data[section] = util.sortdict()
152 self._data[section] = util.sortdict()
153 continue
153 continue
154 m = itemre.match(l)
154 m = itemre.match(l)
155 if m:
155 if m:
156 item = m.group(1)
156 item = m.group(1)
157 cont = True
157 cont = True
158 if sections and section not in sections:
158 if sections and section not in sections:
159 continue
159 continue
160 self.set(section, item, m.group(2), "%s:%d" % (src, line))
160 self.set(section, item, m.group(2), "%s:%d" % (src, line))
161 continue
161 continue
162 m = unsetre.match(l)
162 m = unsetre.match(l)
163 if m:
163 if m:
164 name = m.group(1)
164 name = m.group(1)
165 if sections and section not in sections:
165 if sections and section not in sections:
166 continue
166 continue
167 if self.get(section, name) is not None:
167 if self.get(section, name) is not None:
168 del self._data[section][name]
168 del self._data[section][name]
169 self._unset.append((section, name))
169 self._unset.append((section, name))
170 continue
170 continue
171
171
172 raise error.ParseError(l.rstrip(), ("%s:%s" % (src, line)))
172 raise error.ParseError(l.rstrip(), ("%s:%s" % (src, line)))
173
173
174 def read(self, path, fp=None, sections=None, remap=None):
174 def read(self, path, fp=None, sections=None, remap=None):
175 if not fp:
175 if not fp:
176 fp = util.posixfile(path, 'rb')
176 fp = util.posixfile(path, 'rb')
177 assert getattr(fp, 'mode', r'rb') == r'rb', (
177 assert getattr(fp, 'mode', r'rb') == r'rb', (
178 'config files must be opened in binary mode, got fp=%r mode=%r' % (
178 'config files must be opened in binary mode, got fp=%r mode=%r' % (
179 fp, fp.mode))
179 fp, fp.mode))
180 self.parse(path, fp.read(),
180 self.parse(path, fp.read(),
181 sections=sections, remap=remap, include=self.read)
181 sections=sections, remap=remap, include=self.read)
General Comments 0
You need to be logged in to leave comments. Login now