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