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