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