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