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