##// END OF EJS Templates
ui: allow capture of subprocess output...
Pierre-Yves David -
r24848:2f888218 stable
parent child Browse files
Show More
@@ -1,683 +1,683 b''
1 1 # color.py color output for Mercurial commands
2 2 #
3 3 # Copyright (C) 2007 Kevin Christen <kevin.christen@gmail.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 '''colorize output from some commands
9 9
10 10 The color extension colorizes output from several Mercurial commands.
11 11 For example, the diff command shows additions in green and deletions
12 12 in red, while the status command shows modified files in magenta. Many
13 13 other commands have analogous colors. It is possible to customize
14 14 these colors.
15 15
16 16 Effects
17 17 -------
18 18
19 19 Other effects in addition to color, like bold and underlined text, are
20 20 also available. By default, the terminfo database is used to find the
21 21 terminal codes used to change color and effect. If terminfo is not
22 22 available, then effects are rendered with the ECMA-48 SGR control
23 23 function (aka ANSI escape codes).
24 24
25 25 The available effects in terminfo mode are 'blink', 'bold', 'dim',
26 26 'inverse', 'invisible', 'italic', 'standout', and 'underline'; in
27 27 ECMA-48 mode, the options are 'bold', 'inverse', 'italic', and
28 28 'underline'. How each is rendered depends on the terminal emulator.
29 29 Some may not be available for a given terminal type, and will be
30 30 silently ignored.
31 31
32 32 Labels
33 33 ------
34 34
35 35 Text receives color effects depending on the labels that it has. Many
36 36 default Mercurial commands emit labelled text. You can also define
37 37 your own labels in templates using the label function, see :hg:`help
38 38 templates`. A single portion of text may have more than one label. In
39 39 that case, effects given to the last label will override any other
40 40 effects. This includes the special "none" effect, which nullifies
41 41 other effects.
42 42
43 43 Labels are normally invisible. In order to see these labels and their
44 44 position in the text, use the global --color=debug option. The same
45 45 anchor text may be associated to multiple labels, e.g.
46 46
47 47 [log.changeset changeset.secret|changeset: 22611:6f0a53c8f587]
48 48
49 49 The following are the default effects for some default labels. Default
50 50 effects may be overridden from your configuration file::
51 51
52 52 [color]
53 53 status.modified = blue bold underline red_background
54 54 status.added = green bold
55 55 status.removed = red bold blue_background
56 56 status.deleted = cyan bold underline
57 57 status.unknown = magenta bold underline
58 58 status.ignored = black bold
59 59
60 60 # 'none' turns off all effects
61 61 status.clean = none
62 62 status.copied = none
63 63
64 64 qseries.applied = blue bold underline
65 65 qseries.unapplied = black bold
66 66 qseries.missing = red bold
67 67
68 68 diff.diffline = bold
69 69 diff.extended = cyan bold
70 70 diff.file_a = red bold
71 71 diff.file_b = green bold
72 72 diff.hunk = magenta
73 73 diff.deleted = red
74 74 diff.inserted = green
75 75 diff.changed = white
76 76 diff.tab =
77 77 diff.trailingwhitespace = bold red_background
78 78
79 79 # Blank so it inherits the style of the surrounding label
80 80 changeset.public =
81 81 changeset.draft =
82 82 changeset.secret =
83 83
84 84 resolve.unresolved = red bold
85 85 resolve.resolved = green bold
86 86
87 87 bookmarks.current = green
88 88
89 89 branches.active = none
90 90 branches.closed = black bold
91 91 branches.current = green
92 92 branches.inactive = none
93 93
94 94 tags.normal = green
95 95 tags.local = black bold
96 96
97 97 rebase.rebased = blue
98 98 rebase.remaining = red bold
99 99
100 100 shelve.age = cyan
101 101 shelve.newest = green bold
102 102 shelve.name = blue bold
103 103
104 104 histedit.remaining = red bold
105 105
106 106 Custom colors
107 107 -------------
108 108
109 109 Because there are only eight standard colors, this module allows you
110 110 to define color names for other color slots which might be available
111 111 for your terminal type, assuming terminfo mode. For instance::
112 112
113 113 color.brightblue = 12
114 114 color.pink = 207
115 115 color.orange = 202
116 116
117 117 to set 'brightblue' to color slot 12 (useful for 16 color terminals
118 118 that have brighter colors defined in the upper eight) and, 'pink' and
119 119 'orange' to colors in 256-color xterm's default color cube. These
120 120 defined colors may then be used as any of the pre-defined eight,
121 121 including appending '_background' to set the background to that color.
122 122
123 123 Modes
124 124 -----
125 125
126 126 By default, the color extension will use ANSI mode (or win32 mode on
127 127 Windows) if it detects a terminal. To override auto mode (to enable
128 128 terminfo mode, for example), set the following configuration option::
129 129
130 130 [color]
131 131 mode = terminfo
132 132
133 133 Any value other than 'ansi', 'win32', 'terminfo', or 'auto' will
134 134 disable color.
135 135
136 136 Note that on some systems, terminfo mode may cause problems when using
137 137 color with the pager extension and less -R. less with the -R option
138 138 will only display ECMA-48 color codes, and terminfo mode may sometimes
139 139 emit codes that less doesn't understand. You can work around this by
140 140 either using ansi mode (or auto mode), or by using less -r (which will
141 141 pass through all terminal control codes, not just color control
142 142 codes).
143 143
144 144 On some systems (such as MSYS in Windows), the terminal may support
145 145 a different color mode than the pager (activated via the "pager"
146 146 extension). It is possible to define separate modes depending on whether
147 147 the pager is active::
148 148
149 149 [color]
150 150 mode = auto
151 151 pagermode = ansi
152 152
153 153 If ``pagermode`` is not defined, the ``mode`` will be used.
154 154 '''
155 155
156 156 import os
157 157
158 158 from mercurial import cmdutil, commands, dispatch, extensions, subrepo, util
159 159 from mercurial import ui as uimod
160 160 from mercurial import templater, error
161 161 from mercurial.i18n import _
162 162
163 163 cmdtable = {}
164 164 command = cmdutil.command(cmdtable)
165 165 testedwith = 'internal'
166 166
167 167 # start and stop parameters for effects
168 168 _effects = {'none': 0, 'black': 30, 'red': 31, 'green': 32, 'yellow': 33,
169 169 'blue': 34, 'magenta': 35, 'cyan': 36, 'white': 37, 'bold': 1,
170 170 'italic': 3, 'underline': 4, 'inverse': 7, 'dim': 2,
171 171 'black_background': 40, 'red_background': 41,
172 172 'green_background': 42, 'yellow_background': 43,
173 173 'blue_background': 44, 'purple_background': 45,
174 174 'cyan_background': 46, 'white_background': 47}
175 175
176 176 def _terminfosetup(ui, mode):
177 177 '''Initialize terminfo data and the terminal if we're in terminfo mode.'''
178 178
179 179 global _terminfo_params
180 180 # If we failed to load curses, we go ahead and return.
181 181 if not _terminfo_params:
182 182 return
183 183 # Otherwise, see what the config file says.
184 184 if mode not in ('auto', 'terminfo'):
185 185 return
186 186
187 187 _terminfo_params.update((key[6:], (False, int(val)))
188 188 for key, val in ui.configitems('color')
189 189 if key.startswith('color.'))
190 190
191 191 try:
192 192 curses.setupterm()
193 193 except curses.error, e:
194 194 _terminfo_params = {}
195 195 return
196 196
197 197 for key, (b, e) in _terminfo_params.items():
198 198 if not b:
199 199 continue
200 200 if not curses.tigetstr(e):
201 201 # Most terminals don't support dim, invis, etc, so don't be
202 202 # noisy and use ui.debug().
203 203 ui.debug("no terminfo entry for %s\n" % e)
204 204 del _terminfo_params[key]
205 205 if not curses.tigetstr('setaf') or not curses.tigetstr('setab'):
206 206 # Only warn about missing terminfo entries if we explicitly asked for
207 207 # terminfo mode.
208 208 if mode == "terminfo":
209 209 ui.warn(_("no terminfo entry for setab/setaf: reverting to "
210 210 "ECMA-48 color\n"))
211 211 _terminfo_params = {}
212 212
213 213 def _modesetup(ui, coloropt):
214 214 global _terminfo_params
215 215
216 216 if coloropt == 'debug':
217 217 return 'debug'
218 218
219 219 auto = (coloropt == 'auto')
220 220 always = not auto and util.parsebool(coloropt)
221 221 if not always and not auto:
222 222 return None
223 223
224 224 formatted = always or (os.environ.get('TERM') != 'dumb' and ui.formatted())
225 225
226 226 mode = ui.config('color', 'mode', 'auto')
227 227
228 228 # If pager is active, color.pagermode overrides color.mode.
229 229 if getattr(ui, 'pageractive', False):
230 230 mode = ui.config('color', 'pagermode', mode)
231 231
232 232 realmode = mode
233 233 if mode == 'auto':
234 234 if os.name == 'nt':
235 235 term = os.environ.get('TERM')
236 236 # TERM won't be defined in a vanilla cmd.exe environment.
237 237
238 238 # UNIX-like environments on Windows such as Cygwin and MSYS will
239 239 # set TERM. They appear to make a best effort attempt at setting it
240 240 # to something appropriate. However, not all environments with TERM
241 241 # defined support ANSI. Since "ansi" could result in terminal
242 242 # gibberish, we error on the side of selecting "win32". However, if
243 243 # w32effects is not defined, we almost certainly don't support
244 244 # "win32", so don't even try.
245 245 if (term and 'xterm' in term) or not w32effects:
246 246 realmode = 'ansi'
247 247 else:
248 248 realmode = 'win32'
249 249 else:
250 250 realmode = 'ansi'
251 251
252 252 def modewarn():
253 253 # only warn if color.mode was explicitly set and we're in
254 254 # an interactive terminal
255 255 if mode == realmode and ui.interactive():
256 256 ui.warn(_('warning: failed to set color mode to %s\n') % mode)
257 257
258 258 if realmode == 'win32':
259 259 _terminfo_params = {}
260 260 if not w32effects:
261 261 modewarn()
262 262 return None
263 263 _effects.update(w32effects)
264 264 elif realmode == 'ansi':
265 265 _terminfo_params = {}
266 266 elif realmode == 'terminfo':
267 267 _terminfosetup(ui, mode)
268 268 if not _terminfo_params:
269 269 ## FIXME Shouldn't we return None in this case too?
270 270 modewarn()
271 271 realmode = 'ansi'
272 272 else:
273 273 return None
274 274
275 275 if always or (auto and formatted):
276 276 return realmode
277 277 return None
278 278
279 279 try:
280 280 import curses
281 281 # Mapping from effect name to terminfo attribute name or color number.
282 282 # This will also force-load the curses module.
283 283 _terminfo_params = {'none': (True, 'sgr0'),
284 284 'standout': (True, 'smso'),
285 285 'underline': (True, 'smul'),
286 286 'reverse': (True, 'rev'),
287 287 'inverse': (True, 'rev'),
288 288 'blink': (True, 'blink'),
289 289 'dim': (True, 'dim'),
290 290 'bold': (True, 'bold'),
291 291 'invisible': (True, 'invis'),
292 292 'italic': (True, 'sitm'),
293 293 'black': (False, curses.COLOR_BLACK),
294 294 'red': (False, curses.COLOR_RED),
295 295 'green': (False, curses.COLOR_GREEN),
296 296 'yellow': (False, curses.COLOR_YELLOW),
297 297 'blue': (False, curses.COLOR_BLUE),
298 298 'magenta': (False, curses.COLOR_MAGENTA),
299 299 'cyan': (False, curses.COLOR_CYAN),
300 300 'white': (False, curses.COLOR_WHITE)}
301 301 except ImportError:
302 302 _terminfo_params = {}
303 303
304 304 _styles = {'grep.match': 'red bold',
305 305 'grep.linenumber': 'green',
306 306 'grep.rev': 'green',
307 307 'grep.change': 'green',
308 308 'grep.sep': 'cyan',
309 309 'grep.filename': 'magenta',
310 310 'grep.user': 'magenta',
311 311 'grep.date': 'magenta',
312 312 'bookmarks.current': 'green',
313 313 'branches.active': 'none',
314 314 'branches.closed': 'black bold',
315 315 'branches.current': 'green',
316 316 'branches.inactive': 'none',
317 317 'diff.changed': 'white',
318 318 'diff.deleted': 'red',
319 319 'diff.diffline': 'bold',
320 320 'diff.extended': 'cyan bold',
321 321 'diff.file_a': 'red bold',
322 322 'diff.file_b': 'green bold',
323 323 'diff.hunk': 'magenta',
324 324 'diff.inserted': 'green',
325 325 'diff.tab': '',
326 326 'diff.trailingwhitespace': 'bold red_background',
327 327 'changeset.public' : '',
328 328 'changeset.draft' : '',
329 329 'changeset.secret' : '',
330 330 'diffstat.deleted': 'red',
331 331 'diffstat.inserted': 'green',
332 332 'histedit.remaining': 'red bold',
333 333 'ui.prompt': 'yellow',
334 334 'log.changeset': 'yellow',
335 335 'patchbomb.finalsummary': '',
336 336 'patchbomb.from': 'magenta',
337 337 'patchbomb.to': 'cyan',
338 338 'patchbomb.subject': 'green',
339 339 'patchbomb.diffstats': '',
340 340 'rebase.rebased': 'blue',
341 341 'rebase.remaining': 'red bold',
342 342 'resolve.resolved': 'green bold',
343 343 'resolve.unresolved': 'red bold',
344 344 'shelve.age': 'cyan',
345 345 'shelve.newest': 'green bold',
346 346 'shelve.name': 'blue bold',
347 347 'status.added': 'green bold',
348 348 'status.clean': 'none',
349 349 'status.copied': 'none',
350 350 'status.deleted': 'cyan bold underline',
351 351 'status.ignored': 'black bold',
352 352 'status.modified': 'blue bold',
353 353 'status.removed': 'red bold',
354 354 'status.unknown': 'magenta bold underline',
355 355 'tags.normal': 'green',
356 356 'tags.local': 'black bold'}
357 357
358 358
359 359 def _effect_str(effect):
360 360 '''Helper function for render_effects().'''
361 361
362 362 bg = False
363 363 if effect.endswith('_background'):
364 364 bg = True
365 365 effect = effect[:-11]
366 366 attr, val = _terminfo_params[effect]
367 367 if attr:
368 368 return curses.tigetstr(val)
369 369 elif bg:
370 370 return curses.tparm(curses.tigetstr('setab'), val)
371 371 else:
372 372 return curses.tparm(curses.tigetstr('setaf'), val)
373 373
374 374 def render_effects(text, effects):
375 375 'Wrap text in commands to turn on each effect.'
376 376 if not text:
377 377 return text
378 378 if not _terminfo_params:
379 379 start = [str(_effects[e]) for e in ['none'] + effects.split()]
380 380 start = '\033[' + ';'.join(start) + 'm'
381 381 stop = '\033[' + str(_effects['none']) + 'm'
382 382 else:
383 383 start = ''.join(_effect_str(effect)
384 384 for effect in ['none'] + effects.split())
385 385 stop = _effect_str('none')
386 386 return ''.join([start, text, stop])
387 387
388 388 def extstyles():
389 389 for name, ext in extensions.extensions():
390 390 _styles.update(getattr(ext, 'colortable', {}))
391 391
392 392 def valideffect(effect):
393 393 'Determine if the effect is valid or not.'
394 394 good = False
395 395 if not _terminfo_params and effect in _effects:
396 396 good = True
397 397 elif effect in _terminfo_params or effect[:-11] in _terminfo_params:
398 398 good = True
399 399 return good
400 400
401 401 def configstyles(ui):
402 402 for status, cfgeffects in ui.configitems('color'):
403 403 if '.' not in status or status.startswith('color.'):
404 404 continue
405 405 cfgeffects = ui.configlist('color', status)
406 406 if cfgeffects:
407 407 good = []
408 408 for e in cfgeffects:
409 409 if valideffect(e):
410 410 good.append(e)
411 411 else:
412 412 ui.warn(_("ignoring unknown color/effect %r "
413 413 "(configured in color.%s)\n")
414 414 % (e, status))
415 415 _styles[status] = ' '.join(good)
416 416
417 417 class colorui(uimod.ui):
418 418 def popbuffer(self, labeled=False):
419 419 if self._colormode is None:
420 420 return super(colorui, self).popbuffer(labeled)
421 421
422 422 self._bufferstates.pop()
423 423 if labeled:
424 424 return ''.join(self.label(a, label) for a, label
425 425 in self._buffers.pop())
426 426 return ''.join(a for a, label in self._buffers.pop())
427 427
428 428 _colormode = 'ansi'
429 429 def write(self, *args, **opts):
430 430 if self._colormode is None:
431 431 return super(colorui, self).write(*args, **opts)
432 432
433 433 label = opts.get('label', '')
434 434 if self._buffers:
435 435 self._buffers[-1].extend([(str(a), label) for a in args])
436 436 elif self._colormode == 'win32':
437 437 for a in args:
438 438 win32print(a, super(colorui, self).write, **opts)
439 439 else:
440 440 return super(colorui, self).write(
441 441 *[self.label(str(a), label) for a in args], **opts)
442 442
443 443 def write_err(self, *args, **opts):
444 444 if self._colormode is None:
445 445 return super(colorui, self).write_err(*args, **opts)
446 446
447 447 label = opts.get('label', '')
448 if self._bufferstates and self._bufferstates[-1]:
448 if self._bufferstates and self._bufferstates[-1][0]:
449 449 return self.write(*args, **opts)
450 450 if self._colormode == 'win32':
451 451 for a in args:
452 452 win32print(a, super(colorui, self).write_err, **opts)
453 453 else:
454 454 return super(colorui, self).write_err(
455 455 *[self.label(str(a), label) for a in args], **opts)
456 456
457 457 def showlabel(self, msg, label):
458 458 if label and msg:
459 459 if msg[-1] == '\n':
460 460 return "[%s|%s]\n" % (label, msg[:-1])
461 461 else:
462 462 return "[%s|%s]" % (label, msg)
463 463 else:
464 464 return msg
465 465
466 466 def label(self, msg, label):
467 467 if self._colormode is None:
468 468 return super(colorui, self).label(msg, label)
469 469
470 470 if self._colormode == 'debug':
471 471 return self.showlabel(msg, label)
472 472
473 473 effects = []
474 474 for l in label.split():
475 475 s = _styles.get(l, '')
476 476 if s:
477 477 effects.append(s)
478 478 elif valideffect(l):
479 479 effects.append(l)
480 480 effects = ' '.join(effects)
481 481 if effects:
482 482 return '\n'.join([render_effects(s, effects)
483 483 for s in msg.split('\n')])
484 484 return msg
485 485
486 486 def templatelabel(context, mapping, args):
487 487 if len(args) != 2:
488 488 # i18n: "label" is a keyword
489 489 raise error.ParseError(_("label expects two arguments"))
490 490
491 491 # add known effects to the mapping so symbols like 'red', 'bold',
492 492 # etc. don't need to be quoted
493 493 mapping.update(dict([(k, k) for k in _effects]))
494 494
495 495 thing = templater._evalifliteral(args[1], context, mapping)
496 496
497 497 # apparently, repo could be a string that is the favicon?
498 498 repo = mapping.get('repo', '')
499 499 if isinstance(repo, str):
500 500 return thing
501 501
502 502 label = templater._evalifliteral(args[0], context, mapping)
503 503
504 504 thing = templater.stringify(thing)
505 505 label = templater.stringify(label)
506 506
507 507 return repo.ui.label(thing, label)
508 508
509 509 def uisetup(ui):
510 510 if ui.plain():
511 511 return
512 512 if not isinstance(ui, colorui):
513 513 colorui.__bases__ = (ui.__class__,)
514 514 ui.__class__ = colorui
515 515 def colorcmd(orig, ui_, opts, cmd, cmdfunc):
516 516 mode = _modesetup(ui_, opts['color'])
517 517 colorui._colormode = mode
518 518 if mode and mode != 'debug':
519 519 extstyles()
520 520 configstyles(ui_)
521 521 return orig(ui_, opts, cmd, cmdfunc)
522 522 def colorgit(orig, gitsub, commands, env=None, stream=False, cwd=None):
523 523 if gitsub.ui._colormode and len(commands) and commands[0] == "diff":
524 524 # insert the argument in the front,
525 525 # the end of git diff arguments is used for paths
526 526 commands.insert(1, '--color')
527 527 return orig(gitsub, commands, env, stream, cwd)
528 528 extensions.wrapfunction(dispatch, '_runcommand', colorcmd)
529 529 extensions.wrapfunction(subrepo.gitsubrepo, '_gitnodir', colorgit)
530 530 templater.funcs['label'] = templatelabel
531 531
532 532 def extsetup(ui):
533 533 commands.globalopts.append(
534 534 ('', 'color', 'auto',
535 535 # i18n: 'always', 'auto', 'never', and 'debug' are keywords
536 536 # and should not be translated
537 537 _("when to colorize (boolean, always, auto, never, or debug)"),
538 538 _('TYPE')))
539 539
540 540 @command('debugcolor', [], 'hg debugcolor')
541 541 def debugcolor(ui, repo, **opts):
542 542 global _styles
543 543 _styles = {}
544 544 for effect in _effects.keys():
545 545 _styles[effect] = effect
546 546 ui.write(('color mode: %s\n') % ui._colormode)
547 547 ui.write(_('available colors:\n'))
548 548 for label, colors in _styles.items():
549 549 ui.write(('%s\n') % colors, label=label)
550 550
551 551 if os.name != 'nt':
552 552 w32effects = None
553 553 else:
554 554 import re, ctypes
555 555
556 556 _kernel32 = ctypes.windll.kernel32
557 557
558 558 _WORD = ctypes.c_ushort
559 559
560 560 _INVALID_HANDLE_VALUE = -1
561 561
562 562 class _COORD(ctypes.Structure):
563 563 _fields_ = [('X', ctypes.c_short),
564 564 ('Y', ctypes.c_short)]
565 565
566 566 class _SMALL_RECT(ctypes.Structure):
567 567 _fields_ = [('Left', ctypes.c_short),
568 568 ('Top', ctypes.c_short),
569 569 ('Right', ctypes.c_short),
570 570 ('Bottom', ctypes.c_short)]
571 571
572 572 class _CONSOLE_SCREEN_BUFFER_INFO(ctypes.Structure):
573 573 _fields_ = [('dwSize', _COORD),
574 574 ('dwCursorPosition', _COORD),
575 575 ('wAttributes', _WORD),
576 576 ('srWindow', _SMALL_RECT),
577 577 ('dwMaximumWindowSize', _COORD)]
578 578
579 579 _STD_OUTPUT_HANDLE = 0xfffffff5L # (DWORD)-11
580 580 _STD_ERROR_HANDLE = 0xfffffff4L # (DWORD)-12
581 581
582 582 _FOREGROUND_BLUE = 0x0001
583 583 _FOREGROUND_GREEN = 0x0002
584 584 _FOREGROUND_RED = 0x0004
585 585 _FOREGROUND_INTENSITY = 0x0008
586 586
587 587 _BACKGROUND_BLUE = 0x0010
588 588 _BACKGROUND_GREEN = 0x0020
589 589 _BACKGROUND_RED = 0x0040
590 590 _BACKGROUND_INTENSITY = 0x0080
591 591
592 592 _COMMON_LVB_REVERSE_VIDEO = 0x4000
593 593 _COMMON_LVB_UNDERSCORE = 0x8000
594 594
595 595 # http://msdn.microsoft.com/en-us/library/ms682088%28VS.85%29.aspx
596 596 w32effects = {
597 597 'none': -1,
598 598 'black': 0,
599 599 'red': _FOREGROUND_RED,
600 600 'green': _FOREGROUND_GREEN,
601 601 'yellow': _FOREGROUND_RED | _FOREGROUND_GREEN,
602 602 'blue': _FOREGROUND_BLUE,
603 603 'magenta': _FOREGROUND_BLUE | _FOREGROUND_RED,
604 604 'cyan': _FOREGROUND_BLUE | _FOREGROUND_GREEN,
605 605 'white': _FOREGROUND_RED | _FOREGROUND_GREEN | _FOREGROUND_BLUE,
606 606 'bold': _FOREGROUND_INTENSITY,
607 607 'black_background': 0x100, # unused value > 0x0f
608 608 'red_background': _BACKGROUND_RED,
609 609 'green_background': _BACKGROUND_GREEN,
610 610 'yellow_background': _BACKGROUND_RED | _BACKGROUND_GREEN,
611 611 'blue_background': _BACKGROUND_BLUE,
612 612 'purple_background': _BACKGROUND_BLUE | _BACKGROUND_RED,
613 613 'cyan_background': _BACKGROUND_BLUE | _BACKGROUND_GREEN,
614 614 'white_background': (_BACKGROUND_RED | _BACKGROUND_GREEN |
615 615 _BACKGROUND_BLUE),
616 616 'bold_background': _BACKGROUND_INTENSITY,
617 617 'underline': _COMMON_LVB_UNDERSCORE, # double-byte charsets only
618 618 'inverse': _COMMON_LVB_REVERSE_VIDEO, # double-byte charsets only
619 619 }
620 620
621 621 passthrough = set([_FOREGROUND_INTENSITY,
622 622 _BACKGROUND_INTENSITY,
623 623 _COMMON_LVB_UNDERSCORE,
624 624 _COMMON_LVB_REVERSE_VIDEO])
625 625
626 626 stdout = _kernel32.GetStdHandle(
627 627 _STD_OUTPUT_HANDLE) # don't close the handle returned
628 628 if stdout is None or stdout == _INVALID_HANDLE_VALUE:
629 629 w32effects = None
630 630 else:
631 631 csbi = _CONSOLE_SCREEN_BUFFER_INFO()
632 632 if not _kernel32.GetConsoleScreenBufferInfo(
633 633 stdout, ctypes.byref(csbi)):
634 634 # stdout may not support GetConsoleScreenBufferInfo()
635 635 # when called from subprocess or redirected
636 636 w32effects = None
637 637 else:
638 638 origattr = csbi.wAttributes
639 639 ansire = re.compile('\033\[([^m]*)m([^\033]*)(.*)',
640 640 re.MULTILINE | re.DOTALL)
641 641
642 642 def win32print(text, orig, **opts):
643 643 label = opts.get('label', '')
644 644 attr = origattr
645 645
646 646 def mapcolor(val, attr):
647 647 if val == -1:
648 648 return origattr
649 649 elif val in passthrough:
650 650 return attr | val
651 651 elif val > 0x0f:
652 652 return (val & 0x70) | (attr & 0x8f)
653 653 else:
654 654 return (val & 0x07) | (attr & 0xf8)
655 655
656 656 # determine console attributes based on labels
657 657 for l in label.split():
658 658 style = _styles.get(l, '')
659 659 for effect in style.split():
660 660 try:
661 661 attr = mapcolor(w32effects[effect], attr)
662 662 except KeyError:
663 663 # w32effects could not have certain attributes so we skip
664 664 # them if not found
665 665 pass
666 666 # hack to ensure regexp finds data
667 667 if not text.startswith('\033['):
668 668 text = '\033[m' + text
669 669
670 670 # Look for ANSI-like codes embedded in text
671 671 m = re.match(ansire, text)
672 672
673 673 try:
674 674 while m:
675 675 for sattr in m.group(1).split(';'):
676 676 if sattr:
677 677 attr = mapcolor(int(sattr), attr)
678 678 _kernel32.SetConsoleTextAttribute(stdout, attr)
679 679 orig(m.group(2), **opts)
680 680 m = re.match(ansire, m.group(3))
681 681 finally:
682 682 # Explicitly reset original attributes
683 683 _kernel32.SetConsoleTextAttribute(stdout, origattr)
@@ -1,974 +1,981 b''
1 1 # ui.py - user interface bits for mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from i18n import _
9 9 import errno, getpass, os, socket, sys, tempfile, traceback
10 10 import config, scmutil, util, error, formatter
11 11 from node import hex
12 12
13 13 samplehgrcs = {
14 14 'user':
15 15 """# example user config (see "hg help config" for more info)
16 16 [ui]
17 17 # name and email, e.g.
18 18 # username = Jane Doe <jdoe@example.com>
19 19 username =
20 20
21 21 [extensions]
22 22 # uncomment these lines to enable some popular extensions
23 23 # (see "hg help extensions" for more info)
24 24 #
25 25 # pager =
26 26 # progress =
27 27 # color =""",
28 28
29 29 'cloned':
30 30 """# example repository config (see "hg help config" for more info)
31 31 [paths]
32 32 default = %s
33 33
34 34 # path aliases to other clones of this repo in URLs or filesystem paths
35 35 # (see "hg help config.paths" for more info)
36 36 #
37 37 # default-push = ssh://jdoe@example.net/hg/jdoes-fork
38 38 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
39 39 # my-clone = /home/jdoe/jdoes-clone
40 40
41 41 [ui]
42 42 # name and email (local to this repository, optional), e.g.
43 43 # username = Jane Doe <jdoe@example.com>
44 44 """,
45 45
46 46 'local':
47 47 """# example repository config (see "hg help config" for more info)
48 48 [paths]
49 49 # path aliases to other clones of this repo in URLs or filesystem paths
50 50 # (see "hg help config.paths" for more info)
51 51 #
52 52 # default = http://example.com/hg/example-repo
53 53 # default-push = ssh://jdoe@example.net/hg/jdoes-fork
54 54 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
55 55 # my-clone = /home/jdoe/jdoes-clone
56 56
57 57 [ui]
58 58 # name and email (local to this repository, optional), e.g.
59 59 # username = Jane Doe <jdoe@example.com>
60 60 """,
61 61
62 62 'global':
63 63 """# example system-wide hg config (see "hg help config" for more info)
64 64
65 65 [extensions]
66 66 # uncomment these lines to enable some popular extensions
67 67 # (see "hg help extensions" for more info)
68 68 #
69 69 # blackbox =
70 70 # progress =
71 71 # color =
72 72 # pager =""",
73 73 }
74 74
75 75 class ui(object):
76 76 def __init__(self, src=None):
77 77 # _buffers: used for temporary capture of output
78 78 self._buffers = []
79 # _bufferstates: Should the temporary capture includes stderr
79 # _bufferstates:
80 # should the temporary capture include stderr and subprocess output
80 81 self._bufferstates = []
81 82 self.quiet = self.verbose = self.debugflag = self.tracebackflag = False
82 83 self._reportuntrusted = True
83 84 self._ocfg = config.config() # overlay
84 85 self._tcfg = config.config() # trusted
85 86 self._ucfg = config.config() # untrusted
86 87 self._trustusers = set()
87 88 self._trustgroups = set()
88 89 self.callhooks = True
89 90
90 91 if src:
91 92 self.fout = src.fout
92 93 self.ferr = src.ferr
93 94 self.fin = src.fin
94 95
95 96 self._tcfg = src._tcfg.copy()
96 97 self._ucfg = src._ucfg.copy()
97 98 self._ocfg = src._ocfg.copy()
98 99 self._trustusers = src._trustusers.copy()
99 100 self._trustgroups = src._trustgroups.copy()
100 101 self.environ = src.environ
101 102 self.callhooks = src.callhooks
102 103 self.fixconfig()
103 104 else:
104 105 self.fout = sys.stdout
105 106 self.ferr = sys.stderr
106 107 self.fin = sys.stdin
107 108
108 109 # shared read-only environment
109 110 self.environ = os.environ
110 111 # we always trust global config files
111 112 for f in scmutil.rcpath():
112 113 self.readconfig(f, trust=True)
113 114
114 115 def copy(self):
115 116 return self.__class__(self)
116 117
117 118 def formatter(self, topic, opts):
118 119 return formatter.formatter(self, topic, opts)
119 120
120 121 def _trusted(self, fp, f):
121 122 st = util.fstat(fp)
122 123 if util.isowner(st):
123 124 return True
124 125
125 126 tusers, tgroups = self._trustusers, self._trustgroups
126 127 if '*' in tusers or '*' in tgroups:
127 128 return True
128 129
129 130 user = util.username(st.st_uid)
130 131 group = util.groupname(st.st_gid)
131 132 if user in tusers or group in tgroups or user == util.username():
132 133 return True
133 134
134 135 if self._reportuntrusted:
135 136 self.warn(_('not trusting file %s from untrusted '
136 137 'user %s, group %s\n') % (f, user, group))
137 138 return False
138 139
139 140 def readconfig(self, filename, root=None, trust=False,
140 141 sections=None, remap=None):
141 142 try:
142 143 fp = open(filename)
143 144 except IOError:
144 145 if not sections: # ignore unless we were looking for something
145 146 return
146 147 raise
147 148
148 149 cfg = config.config()
149 150 trusted = sections or trust or self._trusted(fp, filename)
150 151
151 152 try:
152 153 cfg.read(filename, fp, sections=sections, remap=remap)
153 154 fp.close()
154 155 except error.ConfigError, inst:
155 156 if trusted:
156 157 raise
157 158 self.warn(_("ignored: %s\n") % str(inst))
158 159
159 160 if self.plain():
160 161 for k in ('debug', 'fallbackencoding', 'quiet', 'slash',
161 162 'logtemplate', 'statuscopies', 'style',
162 163 'traceback', 'verbose'):
163 164 if k in cfg['ui']:
164 165 del cfg['ui'][k]
165 166 for k, v in cfg.items('defaults'):
166 167 del cfg['defaults'][k]
167 168 # Don't remove aliases from the configuration if in the exceptionlist
168 169 if self.plain('alias'):
169 170 for k, v in cfg.items('alias'):
170 171 del cfg['alias'][k]
171 172
172 173 if trusted:
173 174 self._tcfg.update(cfg)
174 175 self._tcfg.update(self._ocfg)
175 176 self._ucfg.update(cfg)
176 177 self._ucfg.update(self._ocfg)
177 178
178 179 if root is None:
179 180 root = os.path.expanduser('~')
180 181 self.fixconfig(root=root)
181 182
182 183 def fixconfig(self, root=None, section=None):
183 184 if section in (None, 'paths'):
184 185 # expand vars and ~
185 186 # translate paths relative to root (or home) into absolute paths
186 187 root = root or os.getcwd()
187 188 for c in self._tcfg, self._ucfg, self._ocfg:
188 189 for n, p in c.items('paths'):
189 190 if not p:
190 191 continue
191 192 if '%%' in p:
192 193 self.warn(_("(deprecated '%%' in path %s=%s from %s)\n")
193 194 % (n, p, self.configsource('paths', n)))
194 195 p = p.replace('%%', '%')
195 196 p = util.expandpath(p)
196 197 if not util.hasscheme(p) and not os.path.isabs(p):
197 198 p = os.path.normpath(os.path.join(root, p))
198 199 c.set("paths", n, p)
199 200
200 201 if section in (None, 'ui'):
201 202 # update ui options
202 203 self.debugflag = self.configbool('ui', 'debug')
203 204 self.verbose = self.debugflag or self.configbool('ui', 'verbose')
204 205 self.quiet = not self.debugflag and self.configbool('ui', 'quiet')
205 206 if self.verbose and self.quiet:
206 207 self.quiet = self.verbose = False
207 208 self._reportuntrusted = self.debugflag or self.configbool("ui",
208 209 "report_untrusted", True)
209 210 self.tracebackflag = self.configbool('ui', 'traceback', False)
210 211
211 212 if section in (None, 'trusted'):
212 213 # update trust information
213 214 self._trustusers.update(self.configlist('trusted', 'users'))
214 215 self._trustgroups.update(self.configlist('trusted', 'groups'))
215 216
216 217 def backupconfig(self, section, item):
217 218 return (self._ocfg.backup(section, item),
218 219 self._tcfg.backup(section, item),
219 220 self._ucfg.backup(section, item),)
220 221 def restoreconfig(self, data):
221 222 self._ocfg.restore(data[0])
222 223 self._tcfg.restore(data[1])
223 224 self._ucfg.restore(data[2])
224 225
225 226 def setconfig(self, section, name, value, source=''):
226 227 for cfg in (self._ocfg, self._tcfg, self._ucfg):
227 228 cfg.set(section, name, value, source)
228 229 self.fixconfig(section=section)
229 230
230 231 def _data(self, untrusted):
231 232 return untrusted and self._ucfg or self._tcfg
232 233
233 234 def configsource(self, section, name, untrusted=False):
234 235 return self._data(untrusted).source(section, name) or 'none'
235 236
236 237 def config(self, section, name, default=None, untrusted=False):
237 238 if isinstance(name, list):
238 239 alternates = name
239 240 else:
240 241 alternates = [name]
241 242
242 243 for n in alternates:
243 244 value = self._data(untrusted).get(section, n, None)
244 245 if value is not None:
245 246 name = n
246 247 break
247 248 else:
248 249 value = default
249 250
250 251 if self.debugflag and not untrusted and self._reportuntrusted:
251 252 for n in alternates:
252 253 uvalue = self._ucfg.get(section, n)
253 254 if uvalue is not None and uvalue != value:
254 255 self.debug("ignoring untrusted configuration option "
255 256 "%s.%s = %s\n" % (section, n, uvalue))
256 257 return value
257 258
258 259 def configpath(self, section, name, default=None, untrusted=False):
259 260 'get a path config item, expanded relative to repo root or config file'
260 261 v = self.config(section, name, default, untrusted)
261 262 if v is None:
262 263 return None
263 264 if not os.path.isabs(v) or "://" not in v:
264 265 src = self.configsource(section, name, untrusted)
265 266 if ':' in src:
266 267 base = os.path.dirname(src.rsplit(':')[0])
267 268 v = os.path.join(base, os.path.expanduser(v))
268 269 return v
269 270
270 271 def configbool(self, section, name, default=False, untrusted=False):
271 272 """parse a configuration element as a boolean
272 273
273 274 >>> u = ui(); s = 'foo'
274 275 >>> u.setconfig(s, 'true', 'yes')
275 276 >>> u.configbool(s, 'true')
276 277 True
277 278 >>> u.setconfig(s, 'false', 'no')
278 279 >>> u.configbool(s, 'false')
279 280 False
280 281 >>> u.configbool(s, 'unknown')
281 282 False
282 283 >>> u.configbool(s, 'unknown', True)
283 284 True
284 285 >>> u.setconfig(s, 'invalid', 'somevalue')
285 286 >>> u.configbool(s, 'invalid')
286 287 Traceback (most recent call last):
287 288 ...
288 289 ConfigError: foo.invalid is not a boolean ('somevalue')
289 290 """
290 291
291 292 v = self.config(section, name, None, untrusted)
292 293 if v is None:
293 294 return default
294 295 if isinstance(v, bool):
295 296 return v
296 297 b = util.parsebool(v)
297 298 if b is None:
298 299 raise error.ConfigError(_("%s.%s is not a boolean ('%s')")
299 300 % (section, name, v))
300 301 return b
301 302
302 303 def configint(self, section, name, default=None, untrusted=False):
303 304 """parse a configuration element as an integer
304 305
305 306 >>> u = ui(); s = 'foo'
306 307 >>> u.setconfig(s, 'int1', '42')
307 308 >>> u.configint(s, 'int1')
308 309 42
309 310 >>> u.setconfig(s, 'int2', '-42')
310 311 >>> u.configint(s, 'int2')
311 312 -42
312 313 >>> u.configint(s, 'unknown', 7)
313 314 7
314 315 >>> u.setconfig(s, 'invalid', 'somevalue')
315 316 >>> u.configint(s, 'invalid')
316 317 Traceback (most recent call last):
317 318 ...
318 319 ConfigError: foo.invalid is not an integer ('somevalue')
319 320 """
320 321
321 322 v = self.config(section, name, None, untrusted)
322 323 if v is None:
323 324 return default
324 325 try:
325 326 return int(v)
326 327 except ValueError:
327 328 raise error.ConfigError(_("%s.%s is not an integer ('%s')")
328 329 % (section, name, v))
329 330
330 331 def configbytes(self, section, name, default=0, untrusted=False):
331 332 """parse a configuration element as a quantity in bytes
332 333
333 334 Units can be specified as b (bytes), k or kb (kilobytes), m or
334 335 mb (megabytes), g or gb (gigabytes).
335 336
336 337 >>> u = ui(); s = 'foo'
337 338 >>> u.setconfig(s, 'val1', '42')
338 339 >>> u.configbytes(s, 'val1')
339 340 42
340 341 >>> u.setconfig(s, 'val2', '42.5 kb')
341 342 >>> u.configbytes(s, 'val2')
342 343 43520
343 344 >>> u.configbytes(s, 'unknown', '7 MB')
344 345 7340032
345 346 >>> u.setconfig(s, 'invalid', 'somevalue')
346 347 >>> u.configbytes(s, 'invalid')
347 348 Traceback (most recent call last):
348 349 ...
349 350 ConfigError: foo.invalid is not a byte quantity ('somevalue')
350 351 """
351 352
352 353 value = self.config(section, name)
353 354 if value is None:
354 355 if not isinstance(default, str):
355 356 return default
356 357 value = default
357 358 try:
358 359 return util.sizetoint(value)
359 360 except error.ParseError:
360 361 raise error.ConfigError(_("%s.%s is not a byte quantity ('%s')")
361 362 % (section, name, value))
362 363
363 364 def configlist(self, section, name, default=None, untrusted=False):
364 365 """parse a configuration element as a list of comma/space separated
365 366 strings
366 367
367 368 >>> u = ui(); s = 'foo'
368 369 >>> u.setconfig(s, 'list1', 'this,is "a small" ,test')
369 370 >>> u.configlist(s, 'list1')
370 371 ['this', 'is', 'a small', 'test']
371 372 """
372 373
373 374 def _parse_plain(parts, s, offset):
374 375 whitespace = False
375 376 while offset < len(s) and (s[offset].isspace() or s[offset] == ','):
376 377 whitespace = True
377 378 offset += 1
378 379 if offset >= len(s):
379 380 return None, parts, offset
380 381 if whitespace:
381 382 parts.append('')
382 383 if s[offset] == '"' and not parts[-1]:
383 384 return _parse_quote, parts, offset + 1
384 385 elif s[offset] == '"' and parts[-1][-1] == '\\':
385 386 parts[-1] = parts[-1][:-1] + s[offset]
386 387 return _parse_plain, parts, offset + 1
387 388 parts[-1] += s[offset]
388 389 return _parse_plain, parts, offset + 1
389 390
390 391 def _parse_quote(parts, s, offset):
391 392 if offset < len(s) and s[offset] == '"': # ""
392 393 parts.append('')
393 394 offset += 1
394 395 while offset < len(s) and (s[offset].isspace() or
395 396 s[offset] == ','):
396 397 offset += 1
397 398 return _parse_plain, parts, offset
398 399
399 400 while offset < len(s) and s[offset] != '"':
400 401 if (s[offset] == '\\' and offset + 1 < len(s)
401 402 and s[offset + 1] == '"'):
402 403 offset += 1
403 404 parts[-1] += '"'
404 405 else:
405 406 parts[-1] += s[offset]
406 407 offset += 1
407 408
408 409 if offset >= len(s):
409 410 real_parts = _configlist(parts[-1])
410 411 if not real_parts:
411 412 parts[-1] = '"'
412 413 else:
413 414 real_parts[0] = '"' + real_parts[0]
414 415 parts = parts[:-1]
415 416 parts.extend(real_parts)
416 417 return None, parts, offset
417 418
418 419 offset += 1
419 420 while offset < len(s) and s[offset] in [' ', ',']:
420 421 offset += 1
421 422
422 423 if offset < len(s):
423 424 if offset + 1 == len(s) and s[offset] == '"':
424 425 parts[-1] += '"'
425 426 offset += 1
426 427 else:
427 428 parts.append('')
428 429 else:
429 430 return None, parts, offset
430 431
431 432 return _parse_plain, parts, offset
432 433
433 434 def _configlist(s):
434 435 s = s.rstrip(' ,')
435 436 if not s:
436 437 return []
437 438 parser, parts, offset = _parse_plain, [''], 0
438 439 while parser:
439 440 parser, parts, offset = parser(parts, s, offset)
440 441 return parts
441 442
442 443 result = self.config(section, name, untrusted=untrusted)
443 444 if result is None:
444 445 result = default or []
445 446 if isinstance(result, basestring):
446 447 result = _configlist(result.lstrip(' ,\n'))
447 448 if result is None:
448 449 result = default or []
449 450 return result
450 451
451 452 def has_section(self, section, untrusted=False):
452 453 '''tell whether section exists in config.'''
453 454 return section in self._data(untrusted)
454 455
455 456 def configitems(self, section, untrusted=False):
456 457 items = self._data(untrusted).items(section)
457 458 if self.debugflag and not untrusted and self._reportuntrusted:
458 459 for k, v in self._ucfg.items(section):
459 460 if self._tcfg.get(section, k) != v:
460 461 self.debug("ignoring untrusted configuration option "
461 462 "%s.%s = %s\n" % (section, k, v))
462 463 return items
463 464
464 465 def walkconfig(self, untrusted=False):
465 466 cfg = self._data(untrusted)
466 467 for section in cfg.sections():
467 468 for name, value in self.configitems(section, untrusted):
468 469 yield section, name, value
469 470
470 471 def plain(self, feature=None):
471 472 '''is plain mode active?
472 473
473 474 Plain mode means that all configuration variables which affect
474 475 the behavior and output of Mercurial should be
475 476 ignored. Additionally, the output should be stable,
476 477 reproducible and suitable for use in scripts or applications.
477 478
478 479 The only way to trigger plain mode is by setting either the
479 480 `HGPLAIN' or `HGPLAINEXCEPT' environment variables.
480 481
481 482 The return value can either be
482 483 - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT
483 484 - True otherwise
484 485 '''
485 486 if 'HGPLAIN' not in os.environ and 'HGPLAINEXCEPT' not in os.environ:
486 487 return False
487 488 exceptions = os.environ.get('HGPLAINEXCEPT', '').strip().split(',')
488 489 if feature and exceptions:
489 490 return feature not in exceptions
490 491 return True
491 492
492 493 def username(self):
493 494 """Return default username to be used in commits.
494 495
495 496 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
496 497 and stop searching if one of these is set.
497 498 If not found and ui.askusername is True, ask the user, else use
498 499 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
499 500 """
500 501 user = os.environ.get("HGUSER")
501 502 if user is None:
502 503 user = self.config("ui", ["username", "user"])
503 504 if user is not None:
504 505 user = os.path.expandvars(user)
505 506 if user is None:
506 507 user = os.environ.get("EMAIL")
507 508 if user is None and self.configbool("ui", "askusername"):
508 509 user = self.prompt(_("enter a commit username:"), default=None)
509 510 if user is None and not self.interactive():
510 511 try:
511 512 user = '%s@%s' % (util.getuser(), socket.getfqdn())
512 513 self.warn(_("no username found, using '%s' instead\n") % user)
513 514 except KeyError:
514 515 pass
515 516 if not user:
516 517 raise util.Abort(_('no username supplied'),
517 518 hint=_('use "hg config --edit" '
518 519 'to set your username'))
519 520 if "\n" in user:
520 521 raise util.Abort(_("username %s contains a newline\n") % repr(user))
521 522 return user
522 523
523 524 def shortuser(self, user):
524 525 """Return a short representation of a user name or email address."""
525 526 if not self.verbose:
526 527 user = util.shortuser(user)
527 528 return user
528 529
529 530 def expandpath(self, loc, default=None):
530 531 """Return repository location relative to cwd or from [paths]"""
531 532 if util.hasscheme(loc) or os.path.isdir(os.path.join(loc, '.hg')):
532 533 return loc
533 534
534 535 p = self.paths.getpath(loc, default=default)
535 536 if p:
536 537 return p.loc
537 538 return loc
538 539
539 540 @util.propertycache
540 541 def paths(self):
541 542 return paths(self)
542 543
543 def pushbuffer(self, error=False):
544 def pushbuffer(self, error=False, subproc=False):
544 545 """install a buffer to capture standard output of the ui object
545 546
546 If error is True, the error output will be captured too."""
547 If error is True, the error output will be captured too.
548
549 If subproc is True, output from subprocesses (typically hooks) will be
550 captured too."""
547 551 self._buffers.append([])
548 self._bufferstates.append(error)
552 self._bufferstates.append((error, subproc))
549 553
550 554 def popbuffer(self, labeled=False):
551 555 '''pop the last buffer and return the buffered output
552 556
553 557 If labeled is True, any labels associated with buffered
554 558 output will be handled. By default, this has no effect
555 559 on the output returned, but extensions and GUI tools may
556 560 handle this argument and returned styled output. If output
557 561 is being buffered so it can be captured and parsed or
558 562 processed, labeled should not be set to True.
559 563 '''
560 564 self._bufferstates.pop()
561 565 return "".join(self._buffers.pop())
562 566
563 567 def write(self, *args, **opts):
564 568 '''write args to output
565 569
566 570 By default, this method simply writes to the buffer or stdout,
567 571 but extensions or GUI tools may override this method,
568 572 write_err(), popbuffer(), and label() to style output from
569 573 various parts of hg.
570 574
571 575 An optional keyword argument, "label", can be passed in.
572 576 This should be a string containing label names separated by
573 577 space. Label names take the form of "topic.type". For example,
574 578 ui.debug() issues a label of "ui.debug".
575 579
576 580 When labeling output for a specific command, a label of
577 581 "cmdname.type" is recommended. For example, status issues
578 582 a label of "status.modified" for modified files.
579 583 '''
580 584 if self._buffers:
581 585 self._buffers[-1].extend([str(a) for a in args])
582 586 else:
583 587 for a in args:
584 588 self.fout.write(str(a))
585 589
586 590 def write_err(self, *args, **opts):
587 591 try:
588 if self._bufferstates and self._bufferstates[-1]:
592 if self._bufferstates and self._bufferstates[-1][0]:
589 593 return self.write(*args, **opts)
590 594 if not getattr(self.fout, 'closed', False):
591 595 self.fout.flush()
592 596 for a in args:
593 597 self.ferr.write(str(a))
594 598 # stderr may be buffered under win32 when redirected to files,
595 599 # including stdout.
596 600 if not getattr(self.ferr, 'closed', False):
597 601 self.ferr.flush()
598 602 except IOError, inst:
599 603 if inst.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
600 604 raise
601 605
602 606 def flush(self):
603 607 try: self.fout.flush()
604 608 except (IOError, ValueError): pass
605 609 try: self.ferr.flush()
606 610 except (IOError, ValueError): pass
607 611
608 612 def _isatty(self, fh):
609 613 if self.configbool('ui', 'nontty', False):
610 614 return False
611 615 return util.isatty(fh)
612 616
613 617 def interactive(self):
614 618 '''is interactive input allowed?
615 619
616 620 An interactive session is a session where input can be reasonably read
617 621 from `sys.stdin'. If this function returns false, any attempt to read
618 622 from stdin should fail with an error, unless a sensible default has been
619 623 specified.
620 624
621 625 Interactiveness is triggered by the value of the `ui.interactive'
622 626 configuration variable or - if it is unset - when `sys.stdin' points
623 627 to a terminal device.
624 628
625 629 This function refers to input only; for output, see `ui.formatted()'.
626 630 '''
627 631 i = self.configbool("ui", "interactive", None)
628 632 if i is None:
629 633 # some environments replace stdin without implementing isatty
630 634 # usually those are non-interactive
631 635 return self._isatty(self.fin)
632 636
633 637 return i
634 638
635 639 def termwidth(self):
636 640 '''how wide is the terminal in columns?
637 641 '''
638 642 if 'COLUMNS' in os.environ:
639 643 try:
640 644 return int(os.environ['COLUMNS'])
641 645 except ValueError:
642 646 pass
643 647 return util.termwidth()
644 648
645 649 def formatted(self):
646 650 '''should formatted output be used?
647 651
648 652 It is often desirable to format the output to suite the output medium.
649 653 Examples of this are truncating long lines or colorizing messages.
650 654 However, this is not often not desirable when piping output into other
651 655 utilities, e.g. `grep'.
652 656
653 657 Formatted output is triggered by the value of the `ui.formatted'
654 658 configuration variable or - if it is unset - when `sys.stdout' points
655 659 to a terminal device. Please note that `ui.formatted' should be
656 660 considered an implementation detail; it is not intended for use outside
657 661 Mercurial or its extensions.
658 662
659 663 This function refers to output only; for input, see `ui.interactive()'.
660 664 This function always returns false when in plain mode, see `ui.plain()'.
661 665 '''
662 666 if self.plain():
663 667 return False
664 668
665 669 i = self.configbool("ui", "formatted", None)
666 670 if i is None:
667 671 # some environments replace stdout without implementing isatty
668 672 # usually those are non-interactive
669 673 return self._isatty(self.fout)
670 674
671 675 return i
672 676
673 677 def _readline(self, prompt=''):
674 678 if self._isatty(self.fin):
675 679 try:
676 680 # magically add command line editing support, where
677 681 # available
678 682 import readline
679 683 # force demandimport to really load the module
680 684 readline.read_history_file
681 685 # windows sometimes raises something other than ImportError
682 686 except Exception:
683 687 pass
684 688
685 689 # call write() so output goes through subclassed implementation
686 690 # e.g. color extension on Windows
687 691 self.write(prompt)
688 692
689 693 # instead of trying to emulate raw_input, swap (self.fin,
690 694 # self.fout) with (sys.stdin, sys.stdout)
691 695 oldin = sys.stdin
692 696 oldout = sys.stdout
693 697 sys.stdin = self.fin
694 698 sys.stdout = self.fout
695 699 # prompt ' ' must exist; otherwise readline may delete entire line
696 700 # - http://bugs.python.org/issue12833
697 701 line = raw_input(' ')
698 702 sys.stdin = oldin
699 703 sys.stdout = oldout
700 704
701 705 # When stdin is in binary mode on Windows, it can cause
702 706 # raw_input() to emit an extra trailing carriage return
703 707 if os.linesep == '\r\n' and line and line[-1] == '\r':
704 708 line = line[:-1]
705 709 return line
706 710
707 711 def prompt(self, msg, default="y"):
708 712 """Prompt user with msg, read response.
709 713 If ui is not interactive, the default is returned.
710 714 """
711 715 if not self.interactive():
712 716 self.write(msg, ' ', default, "\n")
713 717 return default
714 718 try:
715 719 r = self._readline(self.label(msg, 'ui.prompt'))
716 720 if not r:
717 721 r = default
718 722 if self.configbool('ui', 'promptecho'):
719 723 self.write(r, "\n")
720 724 return r
721 725 except EOFError:
722 726 raise util.Abort(_('response expected'))
723 727
724 728 @staticmethod
725 729 def extractchoices(prompt):
726 730 """Extract prompt message and list of choices from specified prompt.
727 731
728 732 This returns tuple "(message, choices)", and "choices" is the
729 733 list of tuple "(response character, text without &)".
730 734 """
731 735 parts = prompt.split('$$')
732 736 msg = parts[0].rstrip(' ')
733 737 choices = [p.strip(' ') for p in parts[1:]]
734 738 return (msg,
735 739 [(s[s.index('&') + 1].lower(), s.replace('&', '', 1))
736 740 for s in choices])
737 741
738 742 def promptchoice(self, prompt, default=0):
739 743 """Prompt user with a message, read response, and ensure it matches
740 744 one of the provided choices. The prompt is formatted as follows:
741 745
742 746 "would you like fries with that (Yn)? $$ &Yes $$ &No"
743 747
744 748 The index of the choice is returned. Responses are case
745 749 insensitive. If ui is not interactive, the default is
746 750 returned.
747 751 """
748 752
749 753 msg, choices = self.extractchoices(prompt)
750 754 resps = [r for r, t in choices]
751 755 while True:
752 756 r = self.prompt(msg, resps[default])
753 757 if r.lower() in resps:
754 758 return resps.index(r.lower())
755 759 self.write(_("unrecognized response\n"))
756 760
757 761 def getpass(self, prompt=None, default=None):
758 762 if not self.interactive():
759 763 return default
760 764 try:
761 765 self.write_err(self.label(prompt or _('password: '), 'ui.prompt'))
762 766 # disable getpass() only if explicitly specified. it's still valid
763 767 # to interact with tty even if fin is not a tty.
764 768 if self.configbool('ui', 'nontty'):
765 769 return self.fin.readline().rstrip('\n')
766 770 else:
767 771 return getpass.getpass('')
768 772 except EOFError:
769 773 raise util.Abort(_('response expected'))
770 774 def status(self, *msg, **opts):
771 775 '''write status message to output (if ui.quiet is False)
772 776
773 777 This adds an output label of "ui.status".
774 778 '''
775 779 if not self.quiet:
776 780 opts['label'] = opts.get('label', '') + ' ui.status'
777 781 self.write(*msg, **opts)
778 782 def warn(self, *msg, **opts):
779 783 '''write warning message to output (stderr)
780 784
781 785 This adds an output label of "ui.warning".
782 786 '''
783 787 opts['label'] = opts.get('label', '') + ' ui.warning'
784 788 self.write_err(*msg, **opts)
785 789 def note(self, *msg, **opts):
786 790 '''write note to output (if ui.verbose is True)
787 791
788 792 This adds an output label of "ui.note".
789 793 '''
790 794 if self.verbose:
791 795 opts['label'] = opts.get('label', '') + ' ui.note'
792 796 self.write(*msg, **opts)
793 797 def debug(self, *msg, **opts):
794 798 '''write debug message to output (if ui.debugflag is True)
795 799
796 800 This adds an output label of "ui.debug".
797 801 '''
798 802 if self.debugflag:
799 803 opts['label'] = opts.get('label', '') + ' ui.debug'
800 804 self.write(*msg, **opts)
801 805 def edit(self, text, user, extra={}, editform=None):
802 806 (fd, name) = tempfile.mkstemp(prefix="hg-editor-", suffix=".txt",
803 807 text=True)
804 808 try:
805 809 f = os.fdopen(fd, "w")
806 810 f.write(text)
807 811 f.close()
808 812
809 813 environ = {'HGUSER': user}
810 814 if 'transplant_source' in extra:
811 815 environ.update({'HGREVISION': hex(extra['transplant_source'])})
812 816 for label in ('intermediate-source', 'source', 'rebase_source'):
813 817 if label in extra:
814 818 environ.update({'HGREVISION': extra[label]})
815 819 break
816 820 if editform:
817 821 environ.update({'HGEDITFORM': editform})
818 822
819 823 editor = self.geteditor()
820 824
821 825 self.system("%s \"%s\"" % (editor, name),
822 826 environ=environ,
823 827 onerr=util.Abort, errprefix=_("edit failed"))
824 828
825 829 f = open(name)
826 830 t = f.read()
827 831 f.close()
828 832 finally:
829 833 os.unlink(name)
830 834
831 835 return t
832 836
833 837 def system(self, cmd, environ={}, cwd=None, onerr=None, errprefix=None):
834 838 '''execute shell command with appropriate output stream. command
835 839 output will be redirected if fout is not stdout.
836 840 '''
841 out = self.fout
842 if util.any(s[1] for s in self._bufferstates):
843 out = self
837 844 return util.system(cmd, environ=environ, cwd=cwd, onerr=onerr,
838 errprefix=errprefix, out=self.fout)
845 errprefix=errprefix, out=out)
839 846
840 847 def traceback(self, exc=None, force=False):
841 848 '''print exception traceback if traceback printing enabled or forced.
842 849 only to call in exception handler. returns true if traceback
843 850 printed.'''
844 851 if self.tracebackflag or force:
845 852 if exc is None:
846 853 exc = sys.exc_info()
847 854 cause = getattr(exc[1], 'cause', None)
848 855
849 856 if cause is not None:
850 857 causetb = traceback.format_tb(cause[2])
851 858 exctb = traceback.format_tb(exc[2])
852 859 exconly = traceback.format_exception_only(cause[0], cause[1])
853 860
854 861 # exclude frame where 'exc' was chained and rethrown from exctb
855 862 self.write_err('Traceback (most recent call last):\n',
856 863 ''.join(exctb[:-1]),
857 864 ''.join(causetb),
858 865 ''.join(exconly))
859 866 else:
860 867 traceback.print_exception(exc[0], exc[1], exc[2],
861 868 file=self.ferr)
862 869 return self.tracebackflag or force
863 870
864 871 def geteditor(self):
865 872 '''return editor to use'''
866 873 if sys.platform == 'plan9':
867 874 # vi is the MIPS instruction simulator on Plan 9. We
868 875 # instead default to E to plumb commit messages to
869 876 # avoid confusion.
870 877 editor = 'E'
871 878 else:
872 879 editor = 'vi'
873 880 return (os.environ.get("HGEDITOR") or
874 881 self.config("ui", "editor") or
875 882 os.environ.get("VISUAL") or
876 883 os.environ.get("EDITOR", editor))
877 884
878 885 def progress(self, topic, pos, item="", unit="", total=None):
879 886 '''show a progress message
880 887
881 888 With stock hg, this is simply a debug message that is hidden
882 889 by default, but with extensions or GUI tools it may be
883 890 visible. 'topic' is the current operation, 'item' is a
884 891 non-numeric marker of the current position (i.e. the currently
885 892 in-process file), 'pos' is the current numeric position (i.e.
886 893 revision, bytes, etc.), unit is a corresponding unit label,
887 894 and total is the highest expected pos.
888 895
889 896 Multiple nested topics may be active at a time.
890 897
891 898 All topics should be marked closed by setting pos to None at
892 899 termination.
893 900 '''
894 901
895 902 if pos is None or not self.debugflag:
896 903 return
897 904
898 905 if unit:
899 906 unit = ' ' + unit
900 907 if item:
901 908 item = ' ' + item
902 909
903 910 if total:
904 911 pct = 100.0 * pos / total
905 912 self.debug('%s:%s %s/%s%s (%4.2f%%)\n'
906 913 % (topic, item, pos, total, unit, pct))
907 914 else:
908 915 self.debug('%s:%s %s%s\n' % (topic, item, pos, unit))
909 916
910 917 def log(self, service, *msg, **opts):
911 918 '''hook for logging facility extensions
912 919
913 920 service should be a readily-identifiable subsystem, which will
914 921 allow filtering.
915 922 message should be a newline-terminated string to log.
916 923 '''
917 924 pass
918 925
919 926 def label(self, msg, label):
920 927 '''style msg based on supplied label
921 928
922 929 Like ui.write(), this just returns msg unchanged, but extensions
923 930 and GUI tools can override it to allow styling output without
924 931 writing it.
925 932
926 933 ui.write(s, 'label') is equivalent to
927 934 ui.write(ui.label(s, 'label')).
928 935 '''
929 936 return msg
930 937
931 938 class paths(dict):
932 939 """Represents a collection of paths and their configs.
933 940
934 941 Data is initially derived from ui instances and the config files they have
935 942 loaded.
936 943 """
937 944 def __init__(self, ui):
938 945 dict.__init__(self)
939 946
940 947 for name, loc in ui.configitems('paths'):
941 948 # No location is the same as not existing.
942 949 if not loc:
943 950 continue
944 951 self[name] = path(name, rawloc=loc)
945 952
946 953 def getpath(self, name, default=None):
947 954 """Return a ``path`` for the specified name, falling back to a default.
948 955
949 956 Returns the first of ``name`` or ``default`` that is present, or None
950 957 if neither is present.
951 958 """
952 959 try:
953 960 return self[name]
954 961 except KeyError:
955 962 if default is not None:
956 963 try:
957 964 return self[default]
958 965 except KeyError:
959 966 pass
960 967
961 968 return None
962 969
963 970 class path(object):
964 971 """Represents an individual path and its configuration."""
965 972
966 973 def __init__(self, name, rawloc=None):
967 974 """Construct a path from its config options.
968 975
969 976 ``name`` is the symbolic name of the path.
970 977 ``rawloc`` is the raw location, as defined in the config.
971 978 """
972 979 self.name = name
973 980 # We'll do more intelligent things with rawloc in the future.
974 981 self.loc = rawloc
General Comments 0
You need to be logged in to leave comments. Login now