##// END OF EJS Templates
ui: add "extractchoices()" to share the logic to extract choices from prompt
FUJIWARA Katsunori -
r20265:e5803150 default
parent child Browse files
Show More
@@ -1,815 +1,827 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 (see "hg help config")'))
453 453 if "\n" in user:
454 454 raise util.Abort(_("username %s contains a newline\n") % repr(user))
455 455 return user
456 456
457 457 def shortuser(self, user):
458 458 """Return a short representation of a user name or email address."""
459 459 if not self.verbose:
460 460 user = util.shortuser(user)
461 461 return user
462 462
463 463 def expandpath(self, loc, default=None):
464 464 """Return repository location relative to cwd or from [paths]"""
465 465 if util.hasscheme(loc) or os.path.isdir(os.path.join(loc, '.hg')):
466 466 return loc
467 467
468 468 path = self.config('paths', loc)
469 469 if not path and default is not None:
470 470 path = self.config('paths', default)
471 471 return path or loc
472 472
473 473 def pushbuffer(self):
474 474 self._buffers.append([])
475 475
476 476 def popbuffer(self, labeled=False):
477 477 '''pop the last buffer and return the buffered output
478 478
479 479 If labeled is True, any labels associated with buffered
480 480 output will be handled. By default, this has no effect
481 481 on the output returned, but extensions and GUI tools may
482 482 handle this argument and returned styled output. If output
483 483 is being buffered so it can be captured and parsed or
484 484 processed, labeled should not be set to True.
485 485 '''
486 486 return "".join(self._buffers.pop())
487 487
488 488 def write(self, *args, **opts):
489 489 '''write args to output
490 490
491 491 By default, this method simply writes to the buffer or stdout,
492 492 but extensions or GUI tools may override this method,
493 493 write_err(), popbuffer(), and label() to style output from
494 494 various parts of hg.
495 495
496 496 An optional keyword argument, "label", can be passed in.
497 497 This should be a string containing label names separated by
498 498 space. Label names take the form of "topic.type". For example,
499 499 ui.debug() issues a label of "ui.debug".
500 500
501 501 When labeling output for a specific command, a label of
502 502 "cmdname.type" is recommended. For example, status issues
503 503 a label of "status.modified" for modified files.
504 504 '''
505 505 if self._buffers:
506 506 self._buffers[-1].extend([str(a) for a in args])
507 507 else:
508 508 for a in args:
509 509 self.fout.write(str(a))
510 510
511 511 def write_err(self, *args, **opts):
512 512 try:
513 513 if not getattr(self.fout, 'closed', False):
514 514 self.fout.flush()
515 515 for a in args:
516 516 self.ferr.write(str(a))
517 517 # stderr may be buffered under win32 when redirected to files,
518 518 # including stdout.
519 519 if not getattr(self.ferr, 'closed', False):
520 520 self.ferr.flush()
521 521 except IOError, inst:
522 522 if inst.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
523 523 raise
524 524
525 525 def flush(self):
526 526 try: self.fout.flush()
527 527 except (IOError, ValueError): pass
528 528 try: self.ferr.flush()
529 529 except (IOError, ValueError): pass
530 530
531 531 def _isatty(self, fh):
532 532 if self.configbool('ui', 'nontty', False):
533 533 return False
534 534 return util.isatty(fh)
535 535
536 536 def interactive(self):
537 537 '''is interactive input allowed?
538 538
539 539 An interactive session is a session where input can be reasonably read
540 540 from `sys.stdin'. If this function returns false, any attempt to read
541 541 from stdin should fail with an error, unless a sensible default has been
542 542 specified.
543 543
544 544 Interactiveness is triggered by the value of the `ui.interactive'
545 545 configuration variable or - if it is unset - when `sys.stdin' points
546 546 to a terminal device.
547 547
548 548 This function refers to input only; for output, see `ui.formatted()'.
549 549 '''
550 550 i = self.configbool("ui", "interactive", None)
551 551 if i is None:
552 552 # some environments replace stdin without implementing isatty
553 553 # usually those are non-interactive
554 554 return self._isatty(self.fin)
555 555
556 556 return i
557 557
558 558 def termwidth(self):
559 559 '''how wide is the terminal in columns?
560 560 '''
561 561 if 'COLUMNS' in os.environ:
562 562 try:
563 563 return int(os.environ['COLUMNS'])
564 564 except ValueError:
565 565 pass
566 566 return util.termwidth()
567 567
568 568 def formatted(self):
569 569 '''should formatted output be used?
570 570
571 571 It is often desirable to format the output to suite the output medium.
572 572 Examples of this are truncating long lines or colorizing messages.
573 573 However, this is not often not desirable when piping output into other
574 574 utilities, e.g. `grep'.
575 575
576 576 Formatted output is triggered by the value of the `ui.formatted'
577 577 configuration variable or - if it is unset - when `sys.stdout' points
578 578 to a terminal device. Please note that `ui.formatted' should be
579 579 considered an implementation detail; it is not intended for use outside
580 580 Mercurial or its extensions.
581 581
582 582 This function refers to output only; for input, see `ui.interactive()'.
583 583 This function always returns false when in plain mode, see `ui.plain()'.
584 584 '''
585 585 if self.plain():
586 586 return False
587 587
588 588 i = self.configbool("ui", "formatted", None)
589 589 if i is None:
590 590 # some environments replace stdout without implementing isatty
591 591 # usually those are non-interactive
592 592 return self._isatty(self.fout)
593 593
594 594 return i
595 595
596 596 def _readline(self, prompt=''):
597 597 if self._isatty(self.fin):
598 598 try:
599 599 # magically add command line editing support, where
600 600 # available
601 601 import readline
602 602 # force demandimport to really load the module
603 603 readline.read_history_file
604 604 # windows sometimes raises something other than ImportError
605 605 except Exception:
606 606 pass
607 607
608 608 # call write() so output goes through subclassed implementation
609 609 # e.g. color extension on Windows
610 610 self.write(prompt)
611 611
612 612 # instead of trying to emulate raw_input, swap (self.fin,
613 613 # self.fout) with (sys.stdin, sys.stdout)
614 614 oldin = sys.stdin
615 615 oldout = sys.stdout
616 616 sys.stdin = self.fin
617 617 sys.stdout = self.fout
618 618 line = raw_input(' ')
619 619 sys.stdin = oldin
620 620 sys.stdout = oldout
621 621
622 622 # When stdin is in binary mode on Windows, it can cause
623 623 # raw_input() to emit an extra trailing carriage return
624 624 if os.linesep == '\r\n' and line and line[-1] == '\r':
625 625 line = line[:-1]
626 626 return line
627 627
628 628 def prompt(self, msg, default="y"):
629 629 """Prompt user with msg, read response.
630 630 If ui is not interactive, the default is returned.
631 631 """
632 632 if not self.interactive():
633 633 self.write(msg, ' ', default, "\n")
634 634 return default
635 635 try:
636 636 r = self._readline(self.label(msg, 'ui.prompt'))
637 637 if not r:
638 638 return default
639 639 return r
640 640 except EOFError:
641 641 raise util.Abort(_('response expected'))
642 642
643 @staticmethod
644 def extractchoices(prompt):
645 """Extract prompt message and list of choices from specified prompt.
646
647 This returns tuple "(message, choices)", and "choices" is the
648 list of tuple "(response character, text without &)".
649 """
650 parts = prompt.split('$$')
651 msg = parts[0].rstrip(' ')
652 choices = [p.strip(' ') for p in parts[1:]]
653 return (msg,
654 [(s[s.index('&') + 1].lower(), s.replace('&', '', 1))
655 for s in choices])
656
643 657 def promptchoice(self, prompt, default=0):
644 658 """Prompt user with a message, read response, and ensure it matches
645 659 one of the provided choices. The prompt is formatted as follows:
646 660
647 661 "would you like fries with that (Yn)? $$ &Yes $$ &No"
648 662
649 663 The index of the choice is returned. Responses are case
650 664 insensitive. If ui is not interactive, the default is
651 665 returned.
652 666 """
653 667
654 parts = prompt.split('$$')
655 msg = parts[0].rstrip(' ')
656 choices = [p.strip(' ') for p in parts[1:]]
657 resps = [s[s.index('&') + 1].lower() for s in choices]
668 msg, choices = self.extractchoices(prompt)
669 resps = [r for r, t in choices]
658 670 while True:
659 671 r = self.prompt(msg, resps[default])
660 672 if r.lower() in resps:
661 673 return resps.index(r.lower())
662 674 self.write(_("unrecognized response\n"))
663 675
664 676 def getpass(self, prompt=None, default=None):
665 677 if not self.interactive():
666 678 return default
667 679 try:
668 680 self.write_err(self.label(prompt or _('password: '), 'ui.prompt'))
669 681 return getpass.getpass('')
670 682 except EOFError:
671 683 raise util.Abort(_('response expected'))
672 684 def status(self, *msg, **opts):
673 685 '''write status message to output (if ui.quiet is False)
674 686
675 687 This adds an output label of "ui.status".
676 688 '''
677 689 if not self.quiet:
678 690 opts['label'] = opts.get('label', '') + ' ui.status'
679 691 self.write(*msg, **opts)
680 692 def warn(self, *msg, **opts):
681 693 '''write warning message to output (stderr)
682 694
683 695 This adds an output label of "ui.warning".
684 696 '''
685 697 opts['label'] = opts.get('label', '') + ' ui.warning'
686 698 self.write_err(*msg, **opts)
687 699 def note(self, *msg, **opts):
688 700 '''write note to output (if ui.verbose is True)
689 701
690 702 This adds an output label of "ui.note".
691 703 '''
692 704 if self.verbose:
693 705 opts['label'] = opts.get('label', '') + ' ui.note'
694 706 self.write(*msg, **opts)
695 707 def debug(self, *msg, **opts):
696 708 '''write debug message to output (if ui.debugflag is True)
697 709
698 710 This adds an output label of "ui.debug".
699 711 '''
700 712 if self.debugflag:
701 713 opts['label'] = opts.get('label', '') + ' ui.debug'
702 714 self.write(*msg, **opts)
703 715 def edit(self, text, user):
704 716 (fd, name) = tempfile.mkstemp(prefix="hg-editor-", suffix=".txt",
705 717 text=True)
706 718 try:
707 719 f = os.fdopen(fd, "w")
708 720 f.write(text)
709 721 f.close()
710 722
711 723 editor = self.geteditor()
712 724
713 725 util.system("%s \"%s\"" % (editor, name),
714 726 environ={'HGUSER': user},
715 727 onerr=util.Abort, errprefix=_("edit failed"),
716 728 out=self.fout)
717 729
718 730 f = open(name)
719 731 t = f.read()
720 732 f.close()
721 733 finally:
722 734 os.unlink(name)
723 735
724 736 return t
725 737
726 738 def traceback(self, exc=None, force=False):
727 739 '''print exception traceback if traceback printing enabled or forced.
728 740 only to call in exception handler. returns true if traceback
729 741 printed.'''
730 742 if self.tracebackflag or force:
731 743 if exc is None:
732 744 exc = sys.exc_info()
733 745 cause = getattr(exc[1], 'cause', None)
734 746
735 747 if cause is not None:
736 748 causetb = traceback.format_tb(cause[2])
737 749 exctb = traceback.format_tb(exc[2])
738 750 exconly = traceback.format_exception_only(cause[0], cause[1])
739 751
740 752 # exclude frame where 'exc' was chained and rethrown from exctb
741 753 self.write_err('Traceback (most recent call last):\n',
742 754 ''.join(exctb[:-1]),
743 755 ''.join(causetb),
744 756 ''.join(exconly))
745 757 else:
746 758 traceback.print_exception(exc[0], exc[1], exc[2],
747 759 file=self.ferr)
748 760 return self.tracebackflag or force
749 761
750 762 def geteditor(self):
751 763 '''return editor to use'''
752 764 if sys.platform == 'plan9':
753 765 # vi is the MIPS instruction simulator on Plan 9. We
754 766 # instead default to E to plumb commit messages to
755 767 # avoid confusion.
756 768 editor = 'E'
757 769 else:
758 770 editor = 'vi'
759 771 return (os.environ.get("HGEDITOR") or
760 772 self.config("ui", "editor") or
761 773 os.environ.get("VISUAL") or
762 774 os.environ.get("EDITOR", editor))
763 775
764 776 def progress(self, topic, pos, item="", unit="", total=None):
765 777 '''show a progress message
766 778
767 779 With stock hg, this is simply a debug message that is hidden
768 780 by default, but with extensions or GUI tools it may be
769 781 visible. 'topic' is the current operation, 'item' is a
770 782 non-numeric marker of the current position (i.e. the currently
771 783 in-process file), 'pos' is the current numeric position (i.e.
772 784 revision, bytes, etc.), unit is a corresponding unit label,
773 785 and total is the highest expected pos.
774 786
775 787 Multiple nested topics may be active at a time.
776 788
777 789 All topics should be marked closed by setting pos to None at
778 790 termination.
779 791 '''
780 792
781 793 if pos is None or not self.debugflag:
782 794 return
783 795
784 796 if unit:
785 797 unit = ' ' + unit
786 798 if item:
787 799 item = ' ' + item
788 800
789 801 if total:
790 802 pct = 100.0 * pos / total
791 803 self.debug('%s:%s %s/%s%s (%4.2f%%)\n'
792 804 % (topic, item, pos, total, unit, pct))
793 805 else:
794 806 self.debug('%s:%s %s%s\n' % (topic, item, pos, unit))
795 807
796 808 def log(self, service, *msg, **opts):
797 809 '''hook for logging facility extensions
798 810
799 811 service should be a readily-identifiable subsystem, which will
800 812 allow filtering.
801 813 message should be a newline-terminated string to log.
802 814 '''
803 815 pass
804 816
805 817 def label(self, msg, label):
806 818 '''style msg based on supplied label
807 819
808 820 Like ui.write(), this just returns msg unchanged, but extensions
809 821 and GUI tools can override it to allow styling output without
810 822 writing it.
811 823
812 824 ui.write(s, 'label') is equivalent to
813 825 ui.write(ui.label(s, 'label')).
814 826 '''
815 827 return msg
General Comments 0
You need to be logged in to leave comments. Login now