##// END OF EJS Templates
config: have a way to backup and restore value in config...
Pierre-Yves David -
r15919:69e792cf default
parent child Browse files
Show More
@@ -1,149 +1,176
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 i18n import _
9 9 import error, util
10 10 import re, os, errno
11 11
12 12 class sortdict(dict):
13 13 'a simple sorted dictionary'
14 14 def __init__(self, data=None):
15 15 self._list = []
16 16 if data:
17 17 self.update(data)
18 18 def copy(self):
19 19 return sortdict(self)
20 20 def __setitem__(self, key, val):
21 21 if key in self:
22 22 self._list.remove(key)
23 23 self._list.append(key)
24 24 dict.__setitem__(self, key, val)
25 25 def __iter__(self):
26 26 return self._list.__iter__()
27 27 def update(self, src):
28 28 for k in src:
29 29 self[k] = src[k]
30 30 def clear(self):
31 31 dict.clear(self)
32 32 self._list = []
33 33 def items(self):
34 34 return [(k, self[k]) for k in self._list]
35 35 def __delitem__(self, key):
36 36 dict.__delitem__(self, key)
37 37 self._list.remove(key)
38 38
39 39 class config(object):
40 40 def __init__(self, data=None):
41 41 self._data = {}
42 42 self._source = {}
43 43 if data:
44 44 for k in data._data:
45 45 self._data[k] = data[k].copy()
46 46 self._source = data._source.copy()
47 47 def copy(self):
48 48 return config(self)
49 49 def __contains__(self, section):
50 50 return section in self._data
51 51 def __getitem__(self, section):
52 52 return self._data.get(section, {})
53 53 def __iter__(self):
54 54 for d in self.sections():
55 55 yield d
56 56 def update(self, src):
57 57 for s in src:
58 58 if s not in self:
59 59 self._data[s] = sortdict()
60 60 self._data[s].update(src._data[s])
61 61 self._source.update(src._source)
62 62 def get(self, section, item, default=None):
63 63 return self._data.get(section, {}).get(item, default)
64
65 def backup(self, section, item):
66 """return a tuple allowing restore to reinstall a previous valuesi
67
68 The main reason we need it is because it handle the "no data" case.
69 """
70 try:
71 value = self._data[section][item]
72 source = self.source(section, item)
73 return (section, item, value, source)
74 except KeyError:
75 return (section, item)
76
64 77 def source(self, section, item):
65 78 return self._source.get((section, item), "")
66 79 def sections(self):
67 80 return sorted(self._data.keys())
68 81 def items(self, section):
69 82 return self._data.get(section, {}).items()
70 83 def set(self, section, item, value, source=""):
71 84 if section not in self:
72 85 self._data[section] = sortdict()
73 86 self._data[section][item] = value
74 87 self._source[(section, item)] = source
75 88
89 def restore(self, data):
90 """restore data returned by self.backup"""
91 if len(data) == 4:
92 # restore old data
93 section, item, value, source = data
94 self._data[section][item] = value
95 self._source[(section, item)] = source
96 else:
97 # no data before, remove everything
98 section, item = data
99 if section in self._data:
100 del self._data[section][item]
101 self._source.pop((section, item), None)
102
76 103 def parse(self, src, data, sections=None, remap=None, include=None):
77 104 sectionre = re.compile(r'\[([^\[]+)\]')
78 105 itemre = re.compile(r'([^=\s][^=]*?)\s*=\s*(.*\S|)')
79 106 contre = re.compile(r'\s+(\S|\S.*\S)\s*$')
80 107 emptyre = re.compile(r'(;|#|\s*$)')
81 108 commentre = re.compile(r'(;|#)')
82 109 unsetre = re.compile(r'%unset\s+(\S+)')
83 110 includere = re.compile(r'%include\s+(\S|\S.*\S)\s*$')
84 111 section = ""
85 112 item = None
86 113 line = 0
87 114 cont = False
88 115
89 116 for l in data.splitlines(True):
90 117 line += 1
91 118 if cont:
92 119 if commentre.match(l):
93 120 continue
94 121 m = contre.match(l)
95 122 if m:
96 123 if sections and section not in sections:
97 124 continue
98 125 v = self.get(section, item) + "\n" + m.group(1)
99 126 self.set(section, item, v, "%s:%d" % (src, line))
100 127 continue
101 128 item = None
102 129 cont = False
103 130 m = includere.match(l)
104 131 if m:
105 132 inc = util.expandpath(m.group(1))
106 133 base = os.path.dirname(src)
107 134 inc = os.path.normpath(os.path.join(base, inc))
108 135 if include:
109 136 try:
110 137 include(inc, remap=remap, sections=sections)
111 138 except IOError, inst:
112 139 if inst.errno != errno.ENOENT:
113 140 raise error.ParseError(_("cannot include %s (%s)")
114 141 % (inc, inst.strerror),
115 142 "%s:%s" % (src, line))
116 143 continue
117 144 if emptyre.match(l):
118 145 continue
119 146 m = sectionre.match(l)
120 147 if m:
121 148 section = m.group(1)
122 149 if remap:
123 150 section = remap.get(section, section)
124 151 if section not in self:
125 152 self._data[section] = sortdict()
126 153 continue
127 154 m = itemre.match(l)
128 155 if m:
129 156 item = m.group(1)
130 157 cont = True
131 158 if sections and section not in sections:
132 159 continue
133 160 self.set(section, item, m.group(2), "%s:%d" % (src, line))
134 161 continue
135 162 m = unsetre.match(l)
136 163 if m:
137 164 name = m.group(1)
138 165 if sections and section not in sections:
139 166 continue
140 167 if self.get(section, name) is not None:
141 168 del self._data[section][name]
142 169 continue
143 170
144 171 raise error.ParseError(l.rstrip(), ("%s:%s" % (src, line)))
145 172
146 173 def read(self, path, fp=None, sections=None, remap=None):
147 174 if not fp:
148 175 fp = util.posixfile(path)
149 176 self.parse(path, fp.read(), sections, remap, self.read)
@@ -1,734 +1,743
1 1 # ui.py - user interface bits for mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
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 i18n import _
9 9 import errno, getpass, os, socket, sys, tempfile, traceback
10 10 import config, scmutil, util, error
11 11
12 12 class ui(object):
13 13 def __init__(self, src=None):
14 14 self._buffers = []
15 15 self.quiet = self.verbose = self.debugflag = self.tracebackflag = False
16 16 self._reportuntrusted = True
17 17 self._ocfg = config.config() # overlay
18 18 self._tcfg = config.config() # trusted
19 19 self._ucfg = config.config() # untrusted
20 20 self._trustusers = set()
21 21 self._trustgroups = set()
22 22
23 23 if src:
24 24 self.fout = src.fout
25 25 self.ferr = src.ferr
26 26 self.fin = src.fin
27 27
28 28 self._tcfg = src._tcfg.copy()
29 29 self._ucfg = src._ucfg.copy()
30 30 self._ocfg = src._ocfg.copy()
31 31 self._trustusers = src._trustusers.copy()
32 32 self._trustgroups = src._trustgroups.copy()
33 33 self.environ = src.environ
34 34 self.fixconfig()
35 35 else:
36 36 self.fout = sys.stdout
37 37 self.ferr = sys.stderr
38 38 self.fin = sys.stdin
39 39
40 40 # shared read-only environment
41 41 self.environ = os.environ
42 42 # we always trust global config files
43 43 for f in scmutil.rcpath():
44 44 self.readconfig(f, trust=True)
45 45
46 46 def copy(self):
47 47 return self.__class__(self)
48 48
49 49 def _trusted(self, fp, f):
50 50 st = util.fstat(fp)
51 51 if util.isowner(st):
52 52 return True
53 53
54 54 tusers, tgroups = self._trustusers, self._trustgroups
55 55 if '*' in tusers or '*' in tgroups:
56 56 return True
57 57
58 58 user = util.username(st.st_uid)
59 59 group = util.groupname(st.st_gid)
60 60 if user in tusers or group in tgroups or user == util.username():
61 61 return True
62 62
63 63 if self._reportuntrusted:
64 64 self.warn(_('Not trusting file %s from untrusted '
65 65 'user %s, group %s\n') % (f, user, group))
66 66 return False
67 67
68 68 def readconfig(self, filename, root=None, trust=False,
69 69 sections=None, remap=None):
70 70 try:
71 71 fp = open(filename)
72 72 except IOError:
73 73 if not sections: # ignore unless we were looking for something
74 74 return
75 75 raise
76 76
77 77 cfg = config.config()
78 78 trusted = sections or trust or self._trusted(fp, filename)
79 79
80 80 try:
81 81 cfg.read(filename, fp, sections=sections, remap=remap)
82 82 fp.close()
83 83 except error.ConfigError, inst:
84 84 if trusted:
85 85 raise
86 86 self.warn(_("Ignored: %s\n") % str(inst))
87 87
88 88 if self.plain():
89 89 for k in ('debug', 'fallbackencoding', 'quiet', 'slash',
90 90 'logtemplate', 'style',
91 91 'traceback', 'verbose'):
92 92 if k in cfg['ui']:
93 93 del cfg['ui'][k]
94 94 for k, v in cfg.items('defaults'):
95 95 del cfg['defaults'][k]
96 96 # Don't remove aliases from the configuration if in the exceptionlist
97 97 if self.plain('alias'):
98 98 for k, v in cfg.items('alias'):
99 99 del cfg['alias'][k]
100 100
101 101 if trusted:
102 102 self._tcfg.update(cfg)
103 103 self._tcfg.update(self._ocfg)
104 104 self._ucfg.update(cfg)
105 105 self._ucfg.update(self._ocfg)
106 106
107 107 if root is None:
108 108 root = os.path.expanduser('~')
109 109 self.fixconfig(root=root)
110 110
111 111 def fixconfig(self, root=None, section=None):
112 112 if section in (None, 'paths'):
113 113 # expand vars and ~
114 114 # translate paths relative to root (or home) into absolute paths
115 115 root = root or os.getcwd()
116 116 for c in self._tcfg, self._ucfg, self._ocfg:
117 117 for n, p in c.items('paths'):
118 118 if not p:
119 119 continue
120 120 if '%%' in p:
121 121 self.warn(_("(deprecated '%%' in path %s=%s from %s)\n")
122 122 % (n, p, self.configsource('paths', n)))
123 123 p = p.replace('%%', '%')
124 124 p = util.expandpath(p)
125 125 if not util.hasscheme(p) and not os.path.isabs(p):
126 126 p = os.path.normpath(os.path.join(root, p))
127 127 c.set("paths", n, p)
128 128
129 129 if section in (None, 'ui'):
130 130 # update ui options
131 131 self.debugflag = self.configbool('ui', 'debug')
132 132 self.verbose = self.debugflag or self.configbool('ui', 'verbose')
133 133 self.quiet = not self.debugflag and self.configbool('ui', 'quiet')
134 134 if self.verbose and self.quiet:
135 135 self.quiet = self.verbose = False
136 136 self._reportuntrusted = self.debugflag or self.configbool("ui",
137 137 "report_untrusted", True)
138 138 self.tracebackflag = self.configbool('ui', 'traceback', False)
139 139
140 140 if section in (None, 'trusted'):
141 141 # update trust information
142 142 self._trustusers.update(self.configlist('trusted', 'users'))
143 143 self._trustgroups.update(self.configlist('trusted', 'groups'))
144 144
145 def backupconfig(self, section, item):
146 return (self._ocfg.backup(section, item),
147 self._tcfg.backup(section, item),
148 self._ucfg.backup(section, item),)
149 def restoreconfig(self, data):
150 self._ocfg.restore(data[0])
151 self._tcfg.restore(data[1])
152 self._ucfg.restore(data[2])
153
145 154 def setconfig(self, section, name, value, overlay=True):
146 155 if overlay:
147 156 self._ocfg.set(section, name, value)
148 157 self._tcfg.set(section, name, value)
149 158 self._ucfg.set(section, name, value)
150 159 self.fixconfig(section=section)
151 160
152 161 def _data(self, untrusted):
153 162 return untrusted and self._ucfg or self._tcfg
154 163
155 164 def configsource(self, section, name, untrusted=False):
156 165 return self._data(untrusted).source(section, name) or 'none'
157 166
158 167 def config(self, section, name, default=None, untrusted=False):
159 168 if isinstance(name, list):
160 169 alternates = name
161 170 else:
162 171 alternates = [name]
163 172
164 173 for n in alternates:
165 174 value = self._data(untrusted).get(section, name, None)
166 175 if value is not None:
167 176 name = n
168 177 break
169 178 else:
170 179 value = default
171 180
172 181 if self.debugflag and not untrusted and self._reportuntrusted:
173 182 uvalue = self._ucfg.get(section, name)
174 183 if uvalue is not None and uvalue != value:
175 184 self.debug("ignoring untrusted configuration option "
176 185 "%s.%s = %s\n" % (section, name, uvalue))
177 186 return value
178 187
179 188 def configpath(self, section, name, default=None, untrusted=False):
180 189 'get a path config item, expanded relative to repo root or config file'
181 190 v = self.config(section, name, default, untrusted)
182 191 if v is None:
183 192 return None
184 193 if not os.path.isabs(v) or "://" not in v:
185 194 src = self.configsource(section, name, untrusted)
186 195 if ':' in src:
187 196 base = os.path.dirname(src.rsplit(':')[0])
188 197 v = os.path.join(base, os.path.expanduser(v))
189 198 return v
190 199
191 200 def configbool(self, section, name, default=False, untrusted=False):
192 201 """parse a configuration element as a boolean
193 202
194 203 >>> u = ui(); s = 'foo'
195 204 >>> u.setconfig(s, 'true', 'yes')
196 205 >>> u.configbool(s, 'true')
197 206 True
198 207 >>> u.setconfig(s, 'false', 'no')
199 208 >>> u.configbool(s, 'false')
200 209 False
201 210 >>> u.configbool(s, 'unknown')
202 211 False
203 212 >>> u.configbool(s, 'unknown', True)
204 213 True
205 214 >>> u.setconfig(s, 'invalid', 'somevalue')
206 215 >>> u.configbool(s, 'invalid')
207 216 Traceback (most recent call last):
208 217 ...
209 218 ConfigError: foo.invalid is not a boolean ('somevalue')
210 219 """
211 220
212 221 v = self.config(section, name, None, untrusted)
213 222 if v is None:
214 223 return default
215 224 if isinstance(v, bool):
216 225 return v
217 226 b = util.parsebool(v)
218 227 if b is None:
219 228 raise error.ConfigError(_("%s.%s is not a boolean ('%s')")
220 229 % (section, name, v))
221 230 return b
222 231
223 232 def configint(self, section, name, default=None, untrusted=False):
224 233 """parse a configuration element as an integer
225 234
226 235 >>> u = ui(); s = 'foo'
227 236 >>> u.setconfig(s, 'int1', '42')
228 237 >>> u.configint(s, 'int1')
229 238 42
230 239 >>> u.setconfig(s, 'int2', '-42')
231 240 >>> u.configint(s, 'int2')
232 241 -42
233 242 >>> u.configint(s, 'unknown', 7)
234 243 7
235 244 >>> u.setconfig(s, 'invalid', 'somevalue')
236 245 >>> u.configint(s, 'invalid')
237 246 Traceback (most recent call last):
238 247 ...
239 248 ConfigError: foo.invalid is not an integer ('somevalue')
240 249 """
241 250
242 251 v = self.config(section, name, None, untrusted)
243 252 if v is None:
244 253 return default
245 254 try:
246 255 return int(v)
247 256 except ValueError:
248 257 raise error.ConfigError(_("%s.%s is not an integer ('%s')")
249 258 % (section, name, v))
250 259
251 260 def configlist(self, section, name, default=None, untrusted=False):
252 261 """parse a configuration element as a list of comma/space separated
253 262 strings
254 263
255 264 >>> u = ui(); s = 'foo'
256 265 >>> u.setconfig(s, 'list1', 'this,is "a small" ,test')
257 266 >>> u.configlist(s, 'list1')
258 267 ['this', 'is', 'a small', 'test']
259 268 """
260 269
261 270 def _parse_plain(parts, s, offset):
262 271 whitespace = False
263 272 while offset < len(s) and (s[offset].isspace() or s[offset] == ','):
264 273 whitespace = True
265 274 offset += 1
266 275 if offset >= len(s):
267 276 return None, parts, offset
268 277 if whitespace:
269 278 parts.append('')
270 279 if s[offset] == '"' and not parts[-1]:
271 280 return _parse_quote, parts, offset + 1
272 281 elif s[offset] == '"' and parts[-1][-1] == '\\':
273 282 parts[-1] = parts[-1][:-1] + s[offset]
274 283 return _parse_plain, parts, offset + 1
275 284 parts[-1] += s[offset]
276 285 return _parse_plain, parts, offset + 1
277 286
278 287 def _parse_quote(parts, s, offset):
279 288 if offset < len(s) and s[offset] == '"': # ""
280 289 parts.append('')
281 290 offset += 1
282 291 while offset < len(s) and (s[offset].isspace() or
283 292 s[offset] == ','):
284 293 offset += 1
285 294 return _parse_plain, parts, offset
286 295
287 296 while offset < len(s) and s[offset] != '"':
288 297 if (s[offset] == '\\' and offset + 1 < len(s)
289 298 and s[offset + 1] == '"'):
290 299 offset += 1
291 300 parts[-1] += '"'
292 301 else:
293 302 parts[-1] += s[offset]
294 303 offset += 1
295 304
296 305 if offset >= len(s):
297 306 real_parts = _configlist(parts[-1])
298 307 if not real_parts:
299 308 parts[-1] = '"'
300 309 else:
301 310 real_parts[0] = '"' + real_parts[0]
302 311 parts = parts[:-1]
303 312 parts.extend(real_parts)
304 313 return None, parts, offset
305 314
306 315 offset += 1
307 316 while offset < len(s) and s[offset] in [' ', ',']:
308 317 offset += 1
309 318
310 319 if offset < len(s):
311 320 if offset + 1 == len(s) and s[offset] == '"':
312 321 parts[-1] += '"'
313 322 offset += 1
314 323 else:
315 324 parts.append('')
316 325 else:
317 326 return None, parts, offset
318 327
319 328 return _parse_plain, parts, offset
320 329
321 330 def _configlist(s):
322 331 s = s.rstrip(' ,')
323 332 if not s:
324 333 return []
325 334 parser, parts, offset = _parse_plain, [''], 0
326 335 while parser:
327 336 parser, parts, offset = parser(parts, s, offset)
328 337 return parts
329 338
330 339 result = self.config(section, name, untrusted=untrusted)
331 340 if result is None:
332 341 result = default or []
333 342 if isinstance(result, basestring):
334 343 result = _configlist(result.lstrip(' ,\n'))
335 344 if result is None:
336 345 result = default or []
337 346 return result
338 347
339 348 def has_section(self, section, untrusted=False):
340 349 '''tell whether section exists in config.'''
341 350 return section in self._data(untrusted)
342 351
343 352 def configitems(self, section, untrusted=False):
344 353 items = self._data(untrusted).items(section)
345 354 if self.debugflag and not untrusted and self._reportuntrusted:
346 355 for k, v in self._ucfg.items(section):
347 356 if self._tcfg.get(section, k) != v:
348 357 self.debug("ignoring untrusted configuration option "
349 358 "%s.%s = %s\n" % (section, k, v))
350 359 return items
351 360
352 361 def walkconfig(self, untrusted=False):
353 362 cfg = self._data(untrusted)
354 363 for section in cfg.sections():
355 364 for name, value in self.configitems(section, untrusted):
356 365 yield section, name, value
357 366
358 367 def plain(self, feature=None):
359 368 '''is plain mode active?
360 369
361 370 Plain mode means that all configuration variables which affect
362 371 the behavior and output of Mercurial should be
363 372 ignored. Additionally, the output should be stable,
364 373 reproducible and suitable for use in scripts or applications.
365 374
366 375 The only way to trigger plain mode is by setting either the
367 376 `HGPLAIN' or `HGPLAINEXCEPT' environment variables.
368 377
369 378 The return value can either be
370 379 - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT
371 380 - True otherwise
372 381 '''
373 382 if 'HGPLAIN' not in os.environ and 'HGPLAINEXCEPT' not in os.environ:
374 383 return False
375 384 exceptions = os.environ.get('HGPLAINEXCEPT', '').strip().split(',')
376 385 if feature and exceptions:
377 386 return feature not in exceptions
378 387 return True
379 388
380 389 def username(self):
381 390 """Return default username to be used in commits.
382 391
383 392 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
384 393 and stop searching if one of these is set.
385 394 If not found and ui.askusername is True, ask the user, else use
386 395 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
387 396 """
388 397 user = os.environ.get("HGUSER")
389 398 if user is None:
390 399 user = self.config("ui", "username")
391 400 if user is not None:
392 401 user = os.path.expandvars(user)
393 402 if user is None:
394 403 user = os.environ.get("EMAIL")
395 404 if user is None and self.configbool("ui", "askusername"):
396 405 user = self.prompt(_("enter a commit username:"), default=None)
397 406 if user is None and not self.interactive():
398 407 try:
399 408 user = '%s@%s' % (util.getuser(), socket.getfqdn())
400 409 self.warn(_("No username found, using '%s' instead\n") % user)
401 410 except KeyError:
402 411 pass
403 412 if not user:
404 413 raise util.Abort(_('no username supplied (see "hg help config")'))
405 414 if "\n" in user:
406 415 raise util.Abort(_("username %s contains a newline\n") % repr(user))
407 416 return user
408 417
409 418 def shortuser(self, user):
410 419 """Return a short representation of a user name or email address."""
411 420 if not self.verbose:
412 421 user = util.shortuser(user)
413 422 return user
414 423
415 424 def expandpath(self, loc, default=None):
416 425 """Return repository location relative to cwd or from [paths]"""
417 426 if util.hasscheme(loc) or os.path.isdir(os.path.join(loc, '.hg')):
418 427 return loc
419 428
420 429 path = self.config('paths', loc)
421 430 if not path and default is not None:
422 431 path = self.config('paths', default)
423 432 return path or loc
424 433
425 434 def pushbuffer(self):
426 435 self._buffers.append([])
427 436
428 437 def popbuffer(self, labeled=False):
429 438 '''pop the last buffer and return the buffered output
430 439
431 440 If labeled is True, any labels associated with buffered
432 441 output will be handled. By default, this has no effect
433 442 on the output returned, but extensions and GUI tools may
434 443 handle this argument and returned styled output. If output
435 444 is being buffered so it can be captured and parsed or
436 445 processed, labeled should not be set to True.
437 446 '''
438 447 return "".join(self._buffers.pop())
439 448
440 449 def write(self, *args, **opts):
441 450 '''write args to output
442 451
443 452 By default, this method simply writes to the buffer or stdout,
444 453 but extensions or GUI tools may override this method,
445 454 write_err(), popbuffer(), and label() to style output from
446 455 various parts of hg.
447 456
448 457 An optional keyword argument, "label", can be passed in.
449 458 This should be a string containing label names separated by
450 459 space. Label names take the form of "topic.type". For example,
451 460 ui.debug() issues a label of "ui.debug".
452 461
453 462 When labeling output for a specific command, a label of
454 463 "cmdname.type" is recommended. For example, status issues
455 464 a label of "status.modified" for modified files.
456 465 '''
457 466 if self._buffers:
458 467 self._buffers[-1].extend([str(a) for a in args])
459 468 else:
460 469 for a in args:
461 470 self.fout.write(str(a))
462 471
463 472 def write_err(self, *args, **opts):
464 473 try:
465 474 if not getattr(self.fout, 'closed', False):
466 475 self.fout.flush()
467 476 for a in args:
468 477 self.ferr.write(str(a))
469 478 # stderr may be buffered under win32 when redirected to files,
470 479 # including stdout.
471 480 if not getattr(self.ferr, 'closed', False):
472 481 self.ferr.flush()
473 482 except IOError, inst:
474 483 if inst.errno not in (errno.EPIPE, errno.EIO):
475 484 raise
476 485
477 486 def flush(self):
478 487 try: self.fout.flush()
479 488 except: pass
480 489 try: self.ferr.flush()
481 490 except: pass
482 491
483 492 def interactive(self):
484 493 '''is interactive input allowed?
485 494
486 495 An interactive session is a session where input can be reasonably read
487 496 from `sys.stdin'. If this function returns false, any attempt to read
488 497 from stdin should fail with an error, unless a sensible default has been
489 498 specified.
490 499
491 500 Interactiveness is triggered by the value of the `ui.interactive'
492 501 configuration variable or - if it is unset - when `sys.stdin' points
493 502 to a terminal device.
494 503
495 504 This function refers to input only; for output, see `ui.formatted()'.
496 505 '''
497 506 i = self.configbool("ui", "interactive", None)
498 507 if i is None:
499 508 # some environments replace stdin without implementing isatty
500 509 # usually those are non-interactive
501 510 return util.isatty(self.fin)
502 511
503 512 return i
504 513
505 514 def termwidth(self):
506 515 '''how wide is the terminal in columns?
507 516 '''
508 517 if 'COLUMNS' in os.environ:
509 518 try:
510 519 return int(os.environ['COLUMNS'])
511 520 except ValueError:
512 521 pass
513 522 return util.termwidth()
514 523
515 524 def formatted(self):
516 525 '''should formatted output be used?
517 526
518 527 It is often desirable to format the output to suite the output medium.
519 528 Examples of this are truncating long lines or colorizing messages.
520 529 However, this is not often not desirable when piping output into other
521 530 utilities, e.g. `grep'.
522 531
523 532 Formatted output is triggered by the value of the `ui.formatted'
524 533 configuration variable or - if it is unset - when `sys.stdout' points
525 534 to a terminal device. Please note that `ui.formatted' should be
526 535 considered an implementation detail; it is not intended for use outside
527 536 Mercurial or its extensions.
528 537
529 538 This function refers to output only; for input, see `ui.interactive()'.
530 539 This function always returns false when in plain mode, see `ui.plain()'.
531 540 '''
532 541 if self.plain():
533 542 return False
534 543
535 544 i = self.configbool("ui", "formatted", None)
536 545 if i is None:
537 546 # some environments replace stdout without implementing isatty
538 547 # usually those are non-interactive
539 548 return util.isatty(self.fout)
540 549
541 550 return i
542 551
543 552 def _readline(self, prompt=''):
544 553 if util.isatty(self.fin):
545 554 try:
546 555 # magically add command line editing support, where
547 556 # available
548 557 import readline
549 558 # force demandimport to really load the module
550 559 readline.read_history_file
551 560 # windows sometimes raises something other than ImportError
552 561 except Exception:
553 562 pass
554 563
555 564 # call write() so output goes through subclassed implementation
556 565 # e.g. color extension on Windows
557 566 self.write(prompt)
558 567
559 568 # instead of trying to emulate raw_input, swap (self.fin,
560 569 # self.fout) with (sys.stdin, sys.stdout)
561 570 oldin = sys.stdin
562 571 oldout = sys.stdout
563 572 sys.stdin = self.fin
564 573 sys.stdout = self.fout
565 574 line = raw_input(' ')
566 575 sys.stdin = oldin
567 576 sys.stdout = oldout
568 577
569 578 # When stdin is in binary mode on Windows, it can cause
570 579 # raw_input() to emit an extra trailing carriage return
571 580 if os.linesep == '\r\n' and line and line[-1] == '\r':
572 581 line = line[:-1]
573 582 return line
574 583
575 584 def prompt(self, msg, default="y"):
576 585 """Prompt user with msg, read response.
577 586 If ui is not interactive, the default is returned.
578 587 """
579 588 if not self.interactive():
580 589 self.write(msg, ' ', default, "\n")
581 590 return default
582 591 try:
583 592 r = self._readline(self.label(msg, 'ui.prompt'))
584 593 if not r:
585 594 return default
586 595 return r
587 596 except EOFError:
588 597 raise util.Abort(_('response expected'))
589 598
590 599 def promptchoice(self, msg, choices, default=0):
591 600 """Prompt user with msg, read response, and ensure it matches
592 601 one of the provided choices. The index of the choice is returned.
593 602 choices is a sequence of acceptable responses with the format:
594 603 ('&None', 'E&xec', 'Sym&link') Responses are case insensitive.
595 604 If ui is not interactive, the default is returned.
596 605 """
597 606 resps = [s[s.index('&')+1].lower() for s in choices]
598 607 while True:
599 608 r = self.prompt(msg, resps[default])
600 609 if r.lower() in resps:
601 610 return resps.index(r.lower())
602 611 self.write(_("unrecognized response\n"))
603 612
604 613 def getpass(self, prompt=None, default=None):
605 614 if not self.interactive():
606 615 return default
607 616 try:
608 617 return getpass.getpass(prompt or _('password: '))
609 618 except EOFError:
610 619 raise util.Abort(_('response expected'))
611 620 def status(self, *msg, **opts):
612 621 '''write status message to output (if ui.quiet is False)
613 622
614 623 This adds an output label of "ui.status".
615 624 '''
616 625 if not self.quiet:
617 626 opts['label'] = opts.get('label', '') + ' ui.status'
618 627 self.write(*msg, **opts)
619 628 def warn(self, *msg, **opts):
620 629 '''write warning message to output (stderr)
621 630
622 631 This adds an output label of "ui.warning".
623 632 '''
624 633 opts['label'] = opts.get('label', '') + ' ui.warning'
625 634 self.write_err(*msg, **opts)
626 635 def note(self, *msg, **opts):
627 636 '''write note to output (if ui.verbose is True)
628 637
629 638 This adds an output label of "ui.note".
630 639 '''
631 640 if self.verbose:
632 641 opts['label'] = opts.get('label', '') + ' ui.note'
633 642 self.write(*msg, **opts)
634 643 def debug(self, *msg, **opts):
635 644 '''write debug message to output (if ui.debugflag is True)
636 645
637 646 This adds an output label of "ui.debug".
638 647 '''
639 648 if self.debugflag:
640 649 opts['label'] = opts.get('label', '') + ' ui.debug'
641 650 self.write(*msg, **opts)
642 651 def edit(self, text, user):
643 652 (fd, name) = tempfile.mkstemp(prefix="hg-editor-", suffix=".txt",
644 653 text=True)
645 654 try:
646 655 f = os.fdopen(fd, "w")
647 656 f.write(text)
648 657 f.close()
649 658
650 659 editor = self.geteditor()
651 660
652 661 util.system("%s \"%s\"" % (editor, name),
653 662 environ={'HGUSER': user},
654 663 onerr=util.Abort, errprefix=_("edit failed"),
655 664 out=self.fout)
656 665
657 666 f = open(name)
658 667 t = f.read()
659 668 f.close()
660 669 finally:
661 670 os.unlink(name)
662 671
663 672 return t
664 673
665 674 def traceback(self, exc=None):
666 675 '''print exception traceback if traceback printing enabled.
667 676 only to call in exception handler. returns true if traceback
668 677 printed.'''
669 678 if self.tracebackflag:
670 679 if exc:
671 680 traceback.print_exception(exc[0], exc[1], exc[2], file=self.ferr)
672 681 else:
673 682 traceback.print_exc(file=self.ferr)
674 683 return self.tracebackflag
675 684
676 685 def geteditor(self):
677 686 '''return editor to use'''
678 687 return (os.environ.get("HGEDITOR") or
679 688 self.config("ui", "editor") or
680 689 os.environ.get("VISUAL") or
681 690 os.environ.get("EDITOR", "vi"))
682 691
683 692 def progress(self, topic, pos, item="", unit="", total=None):
684 693 '''show a progress message
685 694
686 695 With stock hg, this is simply a debug message that is hidden
687 696 by default, but with extensions or GUI tools it may be
688 697 visible. 'topic' is the current operation, 'item' is a
689 698 non-numeric marker of the current position (ie the currently
690 699 in-process file), 'pos' is the current numeric position (ie
691 700 revision, bytes, etc.), unit is a corresponding unit label,
692 701 and total is the highest expected pos.
693 702
694 703 Multiple nested topics may be active at a time.
695 704
696 705 All topics should be marked closed by setting pos to None at
697 706 termination.
698 707 '''
699 708
700 709 if pos is None or not self.debugflag:
701 710 return
702 711
703 712 if unit:
704 713 unit = ' ' + unit
705 714 if item:
706 715 item = ' ' + item
707 716
708 717 if total:
709 718 pct = 100.0 * pos / total
710 719 self.debug('%s:%s %s/%s%s (%4.2f%%)\n'
711 720 % (topic, item, pos, total, unit, pct))
712 721 else:
713 722 self.debug('%s:%s %s%s\n' % (topic, item, pos, unit))
714 723
715 724 def log(self, service, message):
716 725 '''hook for logging facility extensions
717 726
718 727 service should be a readily-identifiable subsystem, which will
719 728 allow filtering.
720 729 message should be a newline-terminated string to log.
721 730 '''
722 731 pass
723 732
724 733 def label(self, msg, label):
725 734 '''style msg based on supplied label
726 735
727 736 Like ui.write(), this just returns msg unchanged, but extensions
728 737 and GUI tools can override it to allow styling output without
729 738 writing it.
730 739
731 740 ui.write(s, 'label') is equivalent to
732 741 ui.write(ui.label(s, 'label')).
733 742 '''
734 743 return msg
General Comments 0
You need to be logged in to leave comments. Login now