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