##// END OF EJS Templates
ui: add 'force' parameter to traceback() to override the current print setting...
Matt Harbison -
r18966:5572f688 default
parent child Browse files
Show More
@@ -1,773 +1,773 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 configlist(self, section, name, default=None, untrusted=False):
266 266 """parse a configuration element as a list of comma/space separated
267 267 strings
268 268
269 269 >>> u = ui(); s = 'foo'
270 270 >>> u.setconfig(s, 'list1', 'this,is "a small" ,test')
271 271 >>> u.configlist(s, 'list1')
272 272 ['this', 'is', 'a small', 'test']
273 273 """
274 274
275 275 def _parse_plain(parts, s, offset):
276 276 whitespace = False
277 277 while offset < len(s) and (s[offset].isspace() or s[offset] == ','):
278 278 whitespace = True
279 279 offset += 1
280 280 if offset >= len(s):
281 281 return None, parts, offset
282 282 if whitespace:
283 283 parts.append('')
284 284 if s[offset] == '"' and not parts[-1]:
285 285 return _parse_quote, parts, offset + 1
286 286 elif s[offset] == '"' and parts[-1][-1] == '\\':
287 287 parts[-1] = parts[-1][:-1] + s[offset]
288 288 return _parse_plain, parts, offset + 1
289 289 parts[-1] += s[offset]
290 290 return _parse_plain, parts, offset + 1
291 291
292 292 def _parse_quote(parts, s, offset):
293 293 if offset < len(s) and s[offset] == '"': # ""
294 294 parts.append('')
295 295 offset += 1
296 296 while offset < len(s) and (s[offset].isspace() or
297 297 s[offset] == ','):
298 298 offset += 1
299 299 return _parse_plain, parts, offset
300 300
301 301 while offset < len(s) and s[offset] != '"':
302 302 if (s[offset] == '\\' and offset + 1 < len(s)
303 303 and s[offset + 1] == '"'):
304 304 offset += 1
305 305 parts[-1] += '"'
306 306 else:
307 307 parts[-1] += s[offset]
308 308 offset += 1
309 309
310 310 if offset >= len(s):
311 311 real_parts = _configlist(parts[-1])
312 312 if not real_parts:
313 313 parts[-1] = '"'
314 314 else:
315 315 real_parts[0] = '"' + real_parts[0]
316 316 parts = parts[:-1]
317 317 parts.extend(real_parts)
318 318 return None, parts, offset
319 319
320 320 offset += 1
321 321 while offset < len(s) and s[offset] in [' ', ',']:
322 322 offset += 1
323 323
324 324 if offset < len(s):
325 325 if offset + 1 == len(s) and s[offset] == '"':
326 326 parts[-1] += '"'
327 327 offset += 1
328 328 else:
329 329 parts.append('')
330 330 else:
331 331 return None, parts, offset
332 332
333 333 return _parse_plain, parts, offset
334 334
335 335 def _configlist(s):
336 336 s = s.rstrip(' ,')
337 337 if not s:
338 338 return []
339 339 parser, parts, offset = _parse_plain, [''], 0
340 340 while parser:
341 341 parser, parts, offset = parser(parts, s, offset)
342 342 return parts
343 343
344 344 result = self.config(section, name, untrusted=untrusted)
345 345 if result is None:
346 346 result = default or []
347 347 if isinstance(result, basestring):
348 348 result = _configlist(result.lstrip(' ,\n'))
349 349 if result is None:
350 350 result = default or []
351 351 return result
352 352
353 353 def has_section(self, section, untrusted=False):
354 354 '''tell whether section exists in config.'''
355 355 return section in self._data(untrusted)
356 356
357 357 def configitems(self, section, untrusted=False):
358 358 items = self._data(untrusted).items(section)
359 359 if self.debugflag and not untrusted and self._reportuntrusted:
360 360 for k, v in self._ucfg.items(section):
361 361 if self._tcfg.get(section, k) != v:
362 362 self.debug("ignoring untrusted configuration option "
363 363 "%s.%s = %s\n" % (section, k, v))
364 364 return items
365 365
366 366 def walkconfig(self, untrusted=False):
367 367 cfg = self._data(untrusted)
368 368 for section in cfg.sections():
369 369 for name, value in self.configitems(section, untrusted):
370 370 yield section, name, value
371 371
372 372 def plain(self, feature=None):
373 373 '''is plain mode active?
374 374
375 375 Plain mode means that all configuration variables which affect
376 376 the behavior and output of Mercurial should be
377 377 ignored. Additionally, the output should be stable,
378 378 reproducible and suitable for use in scripts or applications.
379 379
380 380 The only way to trigger plain mode is by setting either the
381 381 `HGPLAIN' or `HGPLAINEXCEPT' environment variables.
382 382
383 383 The return value can either be
384 384 - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT
385 385 - True otherwise
386 386 '''
387 387 if 'HGPLAIN' not in os.environ and 'HGPLAINEXCEPT' not in os.environ:
388 388 return False
389 389 exceptions = os.environ.get('HGPLAINEXCEPT', '').strip().split(',')
390 390 if feature and exceptions:
391 391 return feature not in exceptions
392 392 return True
393 393
394 394 def username(self):
395 395 """Return default username to be used in commits.
396 396
397 397 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
398 398 and stop searching if one of these is set.
399 399 If not found and ui.askusername is True, ask the user, else use
400 400 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
401 401 """
402 402 user = os.environ.get("HGUSER")
403 403 if user is None:
404 404 user = self.config("ui", "username")
405 405 if user is not None:
406 406 user = os.path.expandvars(user)
407 407 if user is None:
408 408 user = os.environ.get("EMAIL")
409 409 if user is None and self.configbool("ui", "askusername"):
410 410 user = self.prompt(_("enter a commit username:"), default=None)
411 411 if user is None and not self.interactive():
412 412 try:
413 413 user = '%s@%s' % (util.getuser(), socket.getfqdn())
414 414 self.warn(_("no username found, using '%s' instead\n") % user)
415 415 except KeyError:
416 416 pass
417 417 if not user:
418 418 raise util.Abort(_('no username supplied (see "hg help config")'))
419 419 if "\n" in user:
420 420 raise util.Abort(_("username %s contains a newline\n") % repr(user))
421 421 return user
422 422
423 423 def shortuser(self, user):
424 424 """Return a short representation of a user name or email address."""
425 425 if not self.verbose:
426 426 user = util.shortuser(user)
427 427 return user
428 428
429 429 def expandpath(self, loc, default=None):
430 430 """Return repository location relative to cwd or from [paths]"""
431 431 if util.hasscheme(loc) or os.path.isdir(os.path.join(loc, '.hg')):
432 432 return loc
433 433
434 434 path = self.config('paths', loc)
435 435 if not path and default is not None:
436 436 path = self.config('paths', default)
437 437 return path or loc
438 438
439 439 def pushbuffer(self):
440 440 self._buffers.append([])
441 441
442 442 def popbuffer(self, labeled=False):
443 443 '''pop the last buffer and return the buffered output
444 444
445 445 If labeled is True, any labels associated with buffered
446 446 output will be handled. By default, this has no effect
447 447 on the output returned, but extensions and GUI tools may
448 448 handle this argument and returned styled output. If output
449 449 is being buffered so it can be captured and parsed or
450 450 processed, labeled should not be set to True.
451 451 '''
452 452 return "".join(self._buffers.pop())
453 453
454 454 def write(self, *args, **opts):
455 455 '''write args to output
456 456
457 457 By default, this method simply writes to the buffer or stdout,
458 458 but extensions or GUI tools may override this method,
459 459 write_err(), popbuffer(), and label() to style output from
460 460 various parts of hg.
461 461
462 462 An optional keyword argument, "label", can be passed in.
463 463 This should be a string containing label names separated by
464 464 space. Label names take the form of "topic.type". For example,
465 465 ui.debug() issues a label of "ui.debug".
466 466
467 467 When labeling output for a specific command, a label of
468 468 "cmdname.type" is recommended. For example, status issues
469 469 a label of "status.modified" for modified files.
470 470 '''
471 471 if self._buffers:
472 472 self._buffers[-1].extend([str(a) for a in args])
473 473 else:
474 474 for a in args:
475 475 self.fout.write(str(a))
476 476
477 477 def write_err(self, *args, **opts):
478 478 try:
479 479 if not getattr(self.fout, 'closed', False):
480 480 self.fout.flush()
481 481 for a in args:
482 482 self.ferr.write(str(a))
483 483 # stderr may be buffered under win32 when redirected to files,
484 484 # including stdout.
485 485 if not getattr(self.ferr, 'closed', False):
486 486 self.ferr.flush()
487 487 except IOError, inst:
488 488 if inst.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
489 489 raise
490 490
491 491 def flush(self):
492 492 try: self.fout.flush()
493 493 except (IOError, ValueError): pass
494 494 try: self.ferr.flush()
495 495 except (IOError, ValueError): pass
496 496
497 497 def _isatty(self, fh):
498 498 if self.configbool('ui', 'nontty', False):
499 499 return False
500 500 return util.isatty(fh)
501 501
502 502 def interactive(self):
503 503 '''is interactive input allowed?
504 504
505 505 An interactive session is a session where input can be reasonably read
506 506 from `sys.stdin'. If this function returns false, any attempt to read
507 507 from stdin should fail with an error, unless a sensible default has been
508 508 specified.
509 509
510 510 Interactiveness is triggered by the value of the `ui.interactive'
511 511 configuration variable or - if it is unset - when `sys.stdin' points
512 512 to a terminal device.
513 513
514 514 This function refers to input only; for output, see `ui.formatted()'.
515 515 '''
516 516 i = self.configbool("ui", "interactive", None)
517 517 if i is None:
518 518 # some environments replace stdin without implementing isatty
519 519 # usually those are non-interactive
520 520 return self._isatty(self.fin)
521 521
522 522 return i
523 523
524 524 def termwidth(self):
525 525 '''how wide is the terminal in columns?
526 526 '''
527 527 if 'COLUMNS' in os.environ:
528 528 try:
529 529 return int(os.environ['COLUMNS'])
530 530 except ValueError:
531 531 pass
532 532 return util.termwidth()
533 533
534 534 def formatted(self):
535 535 '''should formatted output be used?
536 536
537 537 It is often desirable to format the output to suite the output medium.
538 538 Examples of this are truncating long lines or colorizing messages.
539 539 However, this is not often not desirable when piping output into other
540 540 utilities, e.g. `grep'.
541 541
542 542 Formatted output is triggered by the value of the `ui.formatted'
543 543 configuration variable or - if it is unset - when `sys.stdout' points
544 544 to a terminal device. Please note that `ui.formatted' should be
545 545 considered an implementation detail; it is not intended for use outside
546 546 Mercurial or its extensions.
547 547
548 548 This function refers to output only; for input, see `ui.interactive()'.
549 549 This function always returns false when in plain mode, see `ui.plain()'.
550 550 '''
551 551 if self.plain():
552 552 return False
553 553
554 554 i = self.configbool("ui", "formatted", None)
555 555 if i is None:
556 556 # some environments replace stdout without implementing isatty
557 557 # usually those are non-interactive
558 558 return self._isatty(self.fout)
559 559
560 560 return i
561 561
562 562 def _readline(self, prompt=''):
563 563 if self._isatty(self.fin):
564 564 try:
565 565 # magically add command line editing support, where
566 566 # available
567 567 import readline
568 568 # force demandimport to really load the module
569 569 readline.read_history_file
570 570 # windows sometimes raises something other than ImportError
571 571 except Exception:
572 572 pass
573 573
574 574 # call write() so output goes through subclassed implementation
575 575 # e.g. color extension on Windows
576 576 self.write(prompt)
577 577
578 578 # instead of trying to emulate raw_input, swap (self.fin,
579 579 # self.fout) with (sys.stdin, sys.stdout)
580 580 oldin = sys.stdin
581 581 oldout = sys.stdout
582 582 sys.stdin = self.fin
583 583 sys.stdout = self.fout
584 584 line = raw_input(' ')
585 585 sys.stdin = oldin
586 586 sys.stdout = oldout
587 587
588 588 # When stdin is in binary mode on Windows, it can cause
589 589 # raw_input() to emit an extra trailing carriage return
590 590 if os.linesep == '\r\n' and line and line[-1] == '\r':
591 591 line = line[:-1]
592 592 return line
593 593
594 594 def prompt(self, msg, default="y"):
595 595 """Prompt user with msg, read response.
596 596 If ui is not interactive, the default is returned.
597 597 """
598 598 if not self.interactive():
599 599 self.write(msg, ' ', default, "\n")
600 600 return default
601 601 try:
602 602 r = self._readline(self.label(msg, 'ui.prompt'))
603 603 if not r:
604 604 return default
605 605 return r
606 606 except EOFError:
607 607 raise util.Abort(_('response expected'))
608 608
609 609 def promptchoice(self, msg, choices, default=0):
610 610 """Prompt user with msg, read response, and ensure it matches
611 611 one of the provided choices. The index of the choice is returned.
612 612 choices is a sequence of acceptable responses with the format:
613 613 ('&None', 'E&xec', 'Sym&link') Responses are case insensitive.
614 614 If ui is not interactive, the default is returned.
615 615 """
616 616 resps = [s[s.index('&') + 1].lower() for s in choices]
617 617 while True:
618 618 r = self.prompt(msg, resps[default])
619 619 if r.lower() in resps:
620 620 return resps.index(r.lower())
621 621 self.write(_("unrecognized response\n"))
622 622
623 623 def getpass(self, prompt=None, default=None):
624 624 if not self.interactive():
625 625 return default
626 626 try:
627 627 return getpass.getpass(prompt or _('password: '))
628 628 except EOFError:
629 629 raise util.Abort(_('response expected'))
630 630 def status(self, *msg, **opts):
631 631 '''write status message to output (if ui.quiet is False)
632 632
633 633 This adds an output label of "ui.status".
634 634 '''
635 635 if not self.quiet:
636 636 opts['label'] = opts.get('label', '') + ' ui.status'
637 637 self.write(*msg, **opts)
638 638 def warn(self, *msg, **opts):
639 639 '''write warning message to output (stderr)
640 640
641 641 This adds an output label of "ui.warning".
642 642 '''
643 643 opts['label'] = opts.get('label', '') + ' ui.warning'
644 644 self.write_err(*msg, **opts)
645 645 def note(self, *msg, **opts):
646 646 '''write note to output (if ui.verbose is True)
647 647
648 648 This adds an output label of "ui.note".
649 649 '''
650 650 if self.verbose:
651 651 opts['label'] = opts.get('label', '') + ' ui.note'
652 652 self.write(*msg, **opts)
653 653 def debug(self, *msg, **opts):
654 654 '''write debug message to output (if ui.debugflag is True)
655 655
656 656 This adds an output label of "ui.debug".
657 657 '''
658 658 if self.debugflag:
659 659 opts['label'] = opts.get('label', '') + ' ui.debug'
660 660 self.write(*msg, **opts)
661 661 def edit(self, text, user):
662 662 (fd, name) = tempfile.mkstemp(prefix="hg-editor-", suffix=".txt",
663 663 text=True)
664 664 try:
665 665 f = os.fdopen(fd, "w")
666 666 f.write(text)
667 667 f.close()
668 668
669 669 editor = self.geteditor()
670 670
671 671 util.system("%s \"%s\"" % (editor, name),
672 672 environ={'HGUSER': user},
673 673 onerr=util.Abort, errprefix=_("edit failed"),
674 674 out=self.fout)
675 675
676 676 f = open(name)
677 677 t = f.read()
678 678 f.close()
679 679 finally:
680 680 os.unlink(name)
681 681
682 682 return t
683 683
684 def traceback(self, exc=None):
685 '''print exception traceback if traceback printing enabled.
684 def traceback(self, exc=None, force=False):
685 '''print exception traceback if traceback printing enabled or forced.
686 686 only to call in exception handler. returns true if traceback
687 687 printed.'''
688 if self.tracebackflag:
688 if self.tracebackflag or force:
689 689 if exc is None:
690 690 exc = sys.exc_info()
691 691 cause = getattr(exc[1], 'cause', None)
692 692
693 693 if cause is not None:
694 694 causetb = traceback.format_tb(cause[2])
695 695 exctb = traceback.format_tb(exc[2])
696 696 exconly = traceback.format_exception_only(cause[0], cause[1])
697 697
698 698 # exclude frame where 'exc' was chained and rethrown from exctb
699 699 self.write_err('Traceback (most recent call last):\n',
700 700 ''.join(exctb[:-1]),
701 701 ''.join(causetb),
702 702 ''.join(exconly))
703 703 else:
704 704 traceback.print_exception(exc[0], exc[1], exc[2],
705 705 file=self.ferr)
706 return self.tracebackflag
706 return self.tracebackflag or force
707 707
708 708 def geteditor(self):
709 709 '''return editor to use'''
710 710 if sys.platform == 'plan9':
711 711 # vi is the MIPS instruction simulator on Plan 9. We
712 712 # instead default to E to plumb commit messages to
713 713 # avoid confusion.
714 714 editor = 'E'
715 715 else:
716 716 editor = 'vi'
717 717 return (os.environ.get("HGEDITOR") or
718 718 self.config("ui", "editor") or
719 719 os.environ.get("VISUAL") or
720 720 os.environ.get("EDITOR", editor))
721 721
722 722 def progress(self, topic, pos, item="", unit="", total=None):
723 723 '''show a progress message
724 724
725 725 With stock hg, this is simply a debug message that is hidden
726 726 by default, but with extensions or GUI tools it may be
727 727 visible. 'topic' is the current operation, 'item' is a
728 728 non-numeric marker of the current position (i.e. the currently
729 729 in-process file), 'pos' is the current numeric position (i.e.
730 730 revision, bytes, etc.), unit is a corresponding unit label,
731 731 and total is the highest expected pos.
732 732
733 733 Multiple nested topics may be active at a time.
734 734
735 735 All topics should be marked closed by setting pos to None at
736 736 termination.
737 737 '''
738 738
739 739 if pos is None or not self.debugflag:
740 740 return
741 741
742 742 if unit:
743 743 unit = ' ' + unit
744 744 if item:
745 745 item = ' ' + item
746 746
747 747 if total:
748 748 pct = 100.0 * pos / total
749 749 self.debug('%s:%s %s/%s%s (%4.2f%%)\n'
750 750 % (topic, item, pos, total, unit, pct))
751 751 else:
752 752 self.debug('%s:%s %s%s\n' % (topic, item, pos, unit))
753 753
754 754 def log(self, service, *msg, **opts):
755 755 '''hook for logging facility extensions
756 756
757 757 service should be a readily-identifiable subsystem, which will
758 758 allow filtering.
759 759 message should be a newline-terminated string to log.
760 760 '''
761 761 pass
762 762
763 763 def label(self, msg, label):
764 764 '''style msg based on supplied label
765 765
766 766 Like ui.write(), this just returns msg unchanged, but extensions
767 767 and GUI tools can override it to allow styling output without
768 768 writing it.
769 769
770 770 ui.write(s, 'label') is equivalent to
771 771 ui.write(ui.label(s, 'label')).
772 772 '''
773 773 return msg
General Comments 0
You need to be logged in to leave comments. Login now