##// END OF EJS Templates
py3: encode underlying error message during parse error of %include
Denis Laxalde -
r43570:d201a637 default
parent child Browse files
Show More
@@ -1,312 +1,313 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 encoding,
16 17 error,
17 18 pycompat,
18 19 util,
19 20 )
20 21
21 22
22 23 class config(object):
23 24 def __init__(self, data=None, includepaths=None):
24 25 self._data = {}
25 26 self._unset = []
26 27 self._includepaths = includepaths or []
27 28 if data:
28 29 for k in data._data:
29 30 self._data[k] = data[k].copy()
30 31 self._source = data._source.copy()
31 32 else:
32 33 self._source = util.cowdict()
33 34
34 35 def copy(self):
35 36 return config(self)
36 37
37 38 def __contains__(self, section):
38 39 return section in self._data
39 40
40 41 def hasitem(self, section, item):
41 42 return item in self._data.get(section, {})
42 43
43 44 def __getitem__(self, section):
44 45 return self._data.get(section, {})
45 46
46 47 def __iter__(self):
47 48 for d in self.sections():
48 49 yield d
49 50
50 51 def update(self, src):
51 52 self._source = self._source.preparewrite()
52 53 for s, n in src._unset:
53 54 ds = self._data.get(s, None)
54 55 if ds is not None and n in ds:
55 56 self._data[s] = ds.preparewrite()
56 57 del self._data[s][n]
57 58 del self._source[(s, n)]
58 59 for s in src:
59 60 ds = self._data.get(s, None)
60 61 if ds:
61 62 self._data[s] = ds.preparewrite()
62 63 else:
63 64 self._data[s] = util.cowsortdict()
64 65 self._data[s].update(src._data[s])
65 66 self._source.update(src._source)
66 67
67 68 def get(self, section, item, default=None):
68 69 return self._data.get(section, {}).get(item, default)
69 70
70 71 def backup(self, section, item):
71 72 """return a tuple allowing restore to reinstall a previous value
72 73
73 74 The main reason we need it is because it handles the "no data" case.
74 75 """
75 76 try:
76 77 value = self._data[section][item]
77 78 source = self.source(section, item)
78 79 return (section, item, value, source)
79 80 except KeyError:
80 81 return (section, item)
81 82
82 83 def source(self, section, item):
83 84 return self._source.get((section, item), b"")
84 85
85 86 def sections(self):
86 87 return sorted(self._data.keys())
87 88
88 89 def items(self, section):
89 90 return list(pycompat.iteritems(self._data.get(section, {})))
90 91
91 92 def set(self, section, item, value, source=b""):
92 93 if pycompat.ispy3:
93 94 assert not isinstance(
94 95 section, str
95 96 ), b'config section may not be unicode strings on Python 3'
96 97 assert not isinstance(
97 98 item, str
98 99 ), b'config item may not be unicode strings on Python 3'
99 100 assert not isinstance(
100 101 value, str
101 102 ), b'config values may not be unicode strings on Python 3'
102 103 if section not in self:
103 104 self._data[section] = util.cowsortdict()
104 105 else:
105 106 self._data[section] = self._data[section].preparewrite()
106 107 self._data[section][item] = value
107 108 if source:
108 109 self._source = self._source.preparewrite()
109 110 self._source[(section, item)] = source
110 111
111 112 def restore(self, data):
112 113 """restore data returned by self.backup"""
113 114 self._source = self._source.preparewrite()
114 115 if len(data) == 4:
115 116 # restore old data
116 117 section, item, value, source = data
117 118 self._data[section] = self._data[section].preparewrite()
118 119 self._data[section][item] = value
119 120 self._source[(section, item)] = source
120 121 else:
121 122 # no data before, remove everything
122 123 section, item = data
123 124 if section in self._data:
124 125 self._data[section].pop(item, None)
125 126 self._source.pop((section, item), None)
126 127
127 128 def parse(self, src, data, sections=None, remap=None, include=None):
128 129 sectionre = util.re.compile(br'\[([^\[]+)\]')
129 130 itemre = util.re.compile(br'([^=\s][^=]*?)\s*=\s*(.*\S|)')
130 131 contre = util.re.compile(br'\s+(\S|\S.*\S)\s*$')
131 132 emptyre = util.re.compile(br'(;|#|\s*$)')
132 133 commentre = util.re.compile(br'(;|#)')
133 134 unsetre = util.re.compile(br'%unset\s+(\S+)')
134 135 includere = util.re.compile(br'%include\s+(\S|\S.*\S)\s*$')
135 136 section = b""
136 137 item = None
137 138 line = 0
138 139 cont = False
139 140
140 141 if remap:
141 142 section = remap.get(section, section)
142 143
143 144 for l in data.splitlines(True):
144 145 line += 1
145 146 if line == 1 and l.startswith(b'\xef\xbb\xbf'):
146 147 # Someone set us up the BOM
147 148 l = l[3:]
148 149 if cont:
149 150 if commentre.match(l):
150 151 continue
151 152 m = contre.match(l)
152 153 if m:
153 154 if sections and section not in sections:
154 155 continue
155 156 v = self.get(section, item) + b"\n" + m.group(1)
156 157 self.set(section, item, v, b"%s:%d" % (src, line))
157 158 continue
158 159 item = None
159 160 cont = False
160 161 m = includere.match(l)
161 162
162 163 if m and include:
163 164 expanded = util.expandpath(m.group(1))
164 165 includepaths = [os.path.dirname(src)] + self._includepaths
165 166
166 167 for base in includepaths:
167 168 inc = os.path.normpath(os.path.join(base, expanded))
168 169
169 170 try:
170 171 include(inc, remap=remap, sections=sections)
171 172 break
172 173 except IOError as inst:
173 174 if inst.errno != errno.ENOENT:
174 175 raise error.ParseError(
175 176 _(b"cannot include %s (%s)")
176 % (inc, inst.strerror),
177 % (inc, encoding.strtolocal(inst.strerror)),
177 178 b"%s:%d" % (src, line),
178 179 )
179 180 continue
180 181 if emptyre.match(l):
181 182 continue
182 183 m = sectionre.match(l)
183 184 if m:
184 185 section = m.group(1)
185 186 if remap:
186 187 section = remap.get(section, section)
187 188 if section not in self:
188 189 self._data[section] = util.cowsortdict()
189 190 continue
190 191 m = itemre.match(l)
191 192 if m:
192 193 item = m.group(1)
193 194 cont = True
194 195 if sections and section not in sections:
195 196 continue
196 197 self.set(section, item, m.group(2), b"%s:%d" % (src, line))
197 198 continue
198 199 m = unsetre.match(l)
199 200 if m:
200 201 name = m.group(1)
201 202 if sections and section not in sections:
202 203 continue
203 204 if self.get(section, name) is not None:
204 205 self._data[section] = self._data[section].preparewrite()
205 206 del self._data[section][name]
206 207 self._unset.append((section, name))
207 208 continue
208 209
209 210 raise error.ParseError(l.rstrip(), (b"%s:%d" % (src, line)))
210 211
211 212 def read(self, path, fp=None, sections=None, remap=None):
212 213 if not fp:
213 214 fp = util.posixfile(path, b'rb')
214 215 assert (
215 216 getattr(fp, 'mode', r'rb') == r'rb'
216 217 ), b'config files must be opened in binary mode, got fp=%r mode=%r' % (
217 218 fp,
218 219 fp.mode,
219 220 )
220 221 self.parse(
221 222 path, fp.read(), sections=sections, remap=remap, include=self.read
222 223 )
223 224
224 225
225 226 def parselist(value):
226 227 """parse a configuration value as a list of comma/space separated strings
227 228
228 229 >>> parselist(b'this,is "a small" ,test')
229 230 ['this', 'is', 'a small', 'test']
230 231 """
231 232
232 233 def _parse_plain(parts, s, offset):
233 234 whitespace = False
234 235 while offset < len(s) and (
235 236 s[offset : offset + 1].isspace() or s[offset : offset + 1] == b','
236 237 ):
237 238 whitespace = True
238 239 offset += 1
239 240 if offset >= len(s):
240 241 return None, parts, offset
241 242 if whitespace:
242 243 parts.append(b'')
243 244 if s[offset : offset + 1] == b'"' and not parts[-1]:
244 245 return _parse_quote, parts, offset + 1
245 246 elif s[offset : offset + 1] == b'"' and parts[-1][-1:] == b'\\':
246 247 parts[-1] = parts[-1][:-1] + s[offset : offset + 1]
247 248 return _parse_plain, parts, offset + 1
248 249 parts[-1] += s[offset : offset + 1]
249 250 return _parse_plain, parts, offset + 1
250 251
251 252 def _parse_quote(parts, s, offset):
252 253 if offset < len(s) and s[offset : offset + 1] == b'"': # ""
253 254 parts.append(b'')
254 255 offset += 1
255 256 while offset < len(s) and (
256 257 s[offset : offset + 1].isspace()
257 258 or s[offset : offset + 1] == b','
258 259 ):
259 260 offset += 1
260 261 return _parse_plain, parts, offset
261 262
262 263 while offset < len(s) and s[offset : offset + 1] != b'"':
263 264 if (
264 265 s[offset : offset + 1] == b'\\'
265 266 and offset + 1 < len(s)
266 267 and s[offset + 1 : offset + 2] == b'"'
267 268 ):
268 269 offset += 1
269 270 parts[-1] += b'"'
270 271 else:
271 272 parts[-1] += s[offset : offset + 1]
272 273 offset += 1
273 274
274 275 if offset >= len(s):
275 276 real_parts = _configlist(parts[-1])
276 277 if not real_parts:
277 278 parts[-1] = b'"'
278 279 else:
279 280 real_parts[0] = b'"' + real_parts[0]
280 281 parts = parts[:-1]
281 282 parts.extend(real_parts)
282 283 return None, parts, offset
283 284
284 285 offset += 1
285 286 while offset < len(s) and s[offset : offset + 1] in [b' ', b',']:
286 287 offset += 1
287 288
288 289 if offset < len(s):
289 290 if offset + 1 == len(s) and s[offset : offset + 1] == b'"':
290 291 parts[-1] += b'"'
291 292 offset += 1
292 293 else:
293 294 parts.append(b'')
294 295 else:
295 296 return None, parts, offset
296 297
297 298 return _parse_plain, parts, offset
298 299
299 300 def _configlist(s):
300 301 s = s.rstrip(b' ,')
301 302 if not s:
302 303 return []
303 304 parser, parts, offset = _parse_plain, [b''], 0
304 305 while parser:
305 306 parser, parts, offset = parser(parts, s, offset)
306 307 return parts
307 308
308 309 if value is not None and isinstance(value, bytes):
309 310 result = _configlist(value.lstrip(b' ,\n'))
310 311 else:
311 312 result = value
312 313 return result or []
@@ -1,249 +1,261 b''
1 1 Use hgrc within $TESTTMP
2 2
3 3 $ HGRCPATH=`pwd`/hgrc
4 4 $ export HGRCPATH
5 5
6 6 hide outer repo
7 7 $ hg init
8 8
9 9 Use an alternate var for scribbling on hgrc to keep check-code from
10 10 complaining about the important settings we may be overwriting:
11 11
12 12 $ HGRC=`pwd`/hgrc
13 13 $ export HGRC
14 14
15 15 Basic syntax error
16 16
17 17 $ echo "invalid" > $HGRC
18 18 $ hg version
19 19 hg: parse error at $TESTTMP/hgrc:1: invalid
20 20 [255]
21 21 $ echo "" > $HGRC
22 22
23 23 Issue1199: Can't use '%' in hgrc (eg url encoded username)
24 24
25 25 $ hg init "foo%bar"
26 26 $ hg clone "foo%bar" foobar
27 27 updating to branch default
28 28 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
29 29 $ cd foobar
30 30 $ cat .hg/hgrc
31 31 # example repository config (see 'hg help config' for more info)
32 32 [paths]
33 33 default = $TESTTMP/foo%bar
34 34
35 35 # path aliases to other clones of this repo in URLs or filesystem paths
36 36 # (see 'hg help config.paths' for more info)
37 37 #
38 38 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
39 39 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
40 40 # my-clone = /home/jdoe/jdoes-clone
41 41
42 42 [ui]
43 43 # name and email (local to this repository, optional), e.g.
44 44 # username = Jane Doe <jdoe@example.com>
45 45 $ hg paths
46 46 default = $TESTTMP/foo%bar
47 47 $ hg showconfig
48 48 bundle.mainreporoot=$TESTTMP/foobar
49 49 paths.default=$TESTTMP/foo%bar
50 50 $ cd ..
51 51
52 Check %include
53
54 $ echo '[section]' > $TESTTMP/included
55 $ echo 'option = value' >> $TESTTMP/included
56 $ echo '%include $TESTTMP/included' >> $HGRC
57 $ hg showconfig section
58 section.option=value
59 $ chmod u-r $TESTTMP/included
60 $ hg showconfig section
61 hg: parse error at $TESTTMP/hgrc:2: cannot include $TESTTMP/included (Permission denied)
62 [255]
63
52 64 issue1829: wrong indentation
53 65
54 66 $ echo '[foo]' > $HGRC
55 67 $ echo ' x = y' >> $HGRC
56 68 $ hg version
57 69 hg: parse error at $TESTTMP/hgrc:2: x = y
58 70 unexpected leading whitespace
59 71 [255]
60 72
61 73 $ "$PYTHON" -c "from __future__ import print_function; print('[foo]\nbar = a\n b\n c \n de\n fg \nbaz = bif cb \n')" \
62 74 > > $HGRC
63 75 $ hg showconfig foo
64 76 foo.bar=a\nb\nc\nde\nfg
65 77 foo.baz=bif cb
66 78
67 79 $ FAKEPATH=/path/to/nowhere
68 80 $ export FAKEPATH
69 81 $ echo '%include $FAKEPATH/no-such-file' > $HGRC
70 82 $ hg version
71 83 Mercurial Distributed SCM (version *) (glob)
72 84 (see https://mercurial-scm.org for more information)
73 85
74 86 Copyright (C) 2005-* Matt Mackall and others (glob)
75 87 This is free software; see the source for copying conditions. There is NO
76 88 warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
77 89 $ unset FAKEPATH
78 90
79 91 make sure global options given on the cmdline take precedence
80 92
81 93 $ hg showconfig --config ui.verbose=True --quiet
82 94 bundle.mainreporoot=$TESTTMP
83 95 ui.verbose=False
84 96 ui.debug=False
85 97 ui.quiet=True
86 98
87 99 $ touch foobar/untracked
88 100 $ cat >> foobar/.hg/hgrc <<EOF
89 101 > [ui]
90 102 > verbose=True
91 103 > EOF
92 104 $ hg -R foobar st -q
93 105
94 106 username expansion
95 107
96 108 $ olduser=$HGUSER
97 109 $ unset HGUSER
98 110
99 111 $ FAKEUSER='John Doe'
100 112 $ export FAKEUSER
101 113 $ echo '[ui]' > $HGRC
102 114 $ echo 'username = $FAKEUSER' >> $HGRC
103 115
104 116 $ hg init usertest
105 117 $ cd usertest
106 118 $ touch bar
107 119 $ hg commit --addremove --quiet -m "added bar"
108 120 $ hg log --template "{author}\n"
109 121 John Doe
110 122 $ cd ..
111 123
112 124 $ hg showconfig
113 125 bundle.mainreporoot=$TESTTMP
114 126 ui.username=$FAKEUSER
115 127
116 128 $ unset FAKEUSER
117 129 $ HGUSER=$olduser
118 130 $ export HGUSER
119 131
120 132 showconfig with multiple arguments
121 133
122 134 $ echo "[alias]" > $HGRC
123 135 $ echo "log = log -g" >> $HGRC
124 136 $ echo "[defaults]" >> $HGRC
125 137 $ echo "identify = -n" >> $HGRC
126 138 $ hg showconfig alias defaults
127 139 alias.log=log -g
128 140 defaults.identify=-n
129 141 $ hg showconfig alias alias
130 142 alias.log=log -g
131 143 $ hg showconfig alias.log alias.log
132 144 alias.log=log -g
133 145 $ hg showconfig alias defaults.identify
134 146 alias.log=log -g
135 147 defaults.identify=-n
136 148 $ hg showconfig alias.log defaults.identify
137 149 alias.log=log -g
138 150 defaults.identify=-n
139 151
140 152 HGPLAIN
141 153
142 154 $ echo "[ui]" > $HGRC
143 155 $ echo "debug=true" >> $HGRC
144 156 $ echo "fallbackencoding=ASCII" >> $HGRC
145 157 $ echo "quiet=true" >> $HGRC
146 158 $ echo "slash=true" >> $HGRC
147 159 $ echo "traceback=true" >> $HGRC
148 160 $ echo "verbose=true" >> $HGRC
149 161 $ echo "style=~/.hgstyle" >> $HGRC
150 162 $ echo "logtemplate={node}" >> $HGRC
151 163 $ echo "[defaults]" >> $HGRC
152 164 $ echo "identify=-n" >> $HGRC
153 165 $ echo "[alias]" >> $HGRC
154 166 $ echo "log=log -g" >> $HGRC
155 167
156 168 customized hgrc
157 169
158 170 $ hg showconfig
159 171 read config from: $TESTTMP/hgrc
160 172 $TESTTMP/hgrc:13: alias.log=log -g
161 173 repo: bundle.mainreporoot=$TESTTMP
162 174 $TESTTMP/hgrc:11: defaults.identify=-n
163 175 $TESTTMP/hgrc:2: ui.debug=true
164 176 $TESTTMP/hgrc:3: ui.fallbackencoding=ASCII
165 177 $TESTTMP/hgrc:4: ui.quiet=true
166 178 $TESTTMP/hgrc:5: ui.slash=true
167 179 $TESTTMP/hgrc:6: ui.traceback=true
168 180 $TESTTMP/hgrc:7: ui.verbose=true
169 181 $TESTTMP/hgrc:8: ui.style=~/.hgstyle
170 182 $TESTTMP/hgrc:9: ui.logtemplate={node}
171 183
172 184 plain hgrc
173 185
174 186 $ HGPLAIN=; export HGPLAIN
175 187 $ hg showconfig --config ui.traceback=True --debug
176 188 read config from: $TESTTMP/hgrc
177 189 repo: bundle.mainreporoot=$TESTTMP
178 190 --config: ui.traceback=True
179 191 --verbose: ui.verbose=False
180 192 --debug: ui.debug=True
181 193 --quiet: ui.quiet=False
182 194
183 195 with environment variables
184 196
185 197 $ PAGER=p1 EDITOR=e1 VISUAL=e2 hg showconfig --debug
186 198 set config by: $EDITOR
187 199 set config by: $VISUAL
188 200 set config by: $PAGER
189 201 read config from: $TESTTMP/hgrc
190 202 repo: bundle.mainreporoot=$TESTTMP
191 203 $PAGER: pager.pager=p1
192 204 $VISUAL: ui.editor=e2
193 205 --verbose: ui.verbose=False
194 206 --debug: ui.debug=True
195 207 --quiet: ui.quiet=False
196 208
197 209 plain mode with exceptions
198 210
199 211 $ cat > plain.py <<EOF
200 212 > from mercurial import commands, extensions
201 213 > def _config(orig, ui, repo, *values, **opts):
202 214 > ui.write(b'plain: %r\n' % ui.plain())
203 215 > return orig(ui, repo, *values, **opts)
204 216 > def uisetup(ui):
205 217 > extensions.wrapcommand(commands.table, b'config', _config)
206 218 > EOF
207 219 $ echo "[extensions]" >> $HGRC
208 220 $ echo "plain=./plain.py" >> $HGRC
209 221 $ HGPLAINEXCEPT=; export HGPLAINEXCEPT
210 222 $ hg showconfig --config ui.traceback=True --debug
211 223 plain: True
212 224 read config from: $TESTTMP/hgrc
213 225 repo: bundle.mainreporoot=$TESTTMP
214 226 $TESTTMP/hgrc:15: extensions.plain=./plain.py
215 227 --config: ui.traceback=True
216 228 --verbose: ui.verbose=False
217 229 --debug: ui.debug=True
218 230 --quiet: ui.quiet=False
219 231 $ unset HGPLAIN
220 232 $ hg showconfig --config ui.traceback=True --debug
221 233 plain: True
222 234 read config from: $TESTTMP/hgrc
223 235 repo: bundle.mainreporoot=$TESTTMP
224 236 $TESTTMP/hgrc:15: extensions.plain=./plain.py
225 237 --config: ui.traceback=True
226 238 --verbose: ui.verbose=False
227 239 --debug: ui.debug=True
228 240 --quiet: ui.quiet=False
229 241 $ HGPLAINEXCEPT=i18n; export HGPLAINEXCEPT
230 242 $ hg showconfig --config ui.traceback=True --debug
231 243 plain: True
232 244 read config from: $TESTTMP/hgrc
233 245 repo: bundle.mainreporoot=$TESTTMP
234 246 $TESTTMP/hgrc:15: extensions.plain=./plain.py
235 247 --config: ui.traceback=True
236 248 --verbose: ui.verbose=False
237 249 --debug: ui.debug=True
238 250 --quiet: ui.quiet=False
239 251
240 252 source of paths is not mangled
241 253
242 254 $ cat >> $HGRCPATH <<EOF
243 255 > [paths]
244 256 > foo = bar
245 257 > EOF
246 258 $ hg showconfig --debug paths
247 259 plain: True
248 260 read config from: $TESTTMP/hgrc
249 261 $TESTTMP/hgrc:17: paths.foo=$TESTTMP/bar
General Comments 0
You need to be logged in to leave comments. Login now