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