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