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