##// END OF EJS Templates
remove unused imports
timeless -
r14139:4e5a36ee default
parent child Browse files
Show More
@@ -1,482 +1,482
1 1 # color.py color output for the status and qseries commands
2 2 #
3 3 # Copyright (C) 2007 Kevin Christen <kevin.christen@gmail.com>
4 4 #
5 5 # This program is free software; you can redistribute it and/or modify it
6 6 # under the terms of the GNU General Public License as published by the
7 7 # Free Software Foundation; either version 2 of the License, or (at your
8 8 # option) any later version.
9 9 #
10 10 # This program is distributed in the hope that it will be useful, but
11 11 # WITHOUT ANY WARRANTY; without even the implied warranty of
12 12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
13 13 # Public License for more details.
14 14 #
15 15 # You should have received a copy of the GNU General Public License along
16 16 # with this program; if not, write to the Free Software Foundation, Inc.,
17 17 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 18
19 19 '''colorize output from some commands
20 20
21 21 This extension modifies the status and resolve commands to add color
22 22 to their output to reflect file status, the qseries command to add
23 23 color to reflect patch status (applied, unapplied, missing), and to
24 24 diff-related commands to highlight additions, removals, diff headers,
25 25 and trailing whitespace.
26 26
27 27 Other effects in addition to color, like bold and underlined text, are
28 28 also available. By default, the terminfo database is used to find the
29 29 terminal codes used to change color and effect. If terminfo is not
30 30 available, then effects are rendered with the ECMA-48 SGR control
31 31 function (aka ANSI escape codes).
32 32
33 33 Default effects may be overridden from your configuration file::
34 34
35 35 [color]
36 36 status.modified = blue bold underline red_background
37 37 status.added = green bold
38 38 status.removed = red bold blue_background
39 39 status.deleted = cyan bold underline
40 40 status.unknown = magenta bold underline
41 41 status.ignored = black bold
42 42
43 43 # 'none' turns off all effects
44 44 status.clean = none
45 45 status.copied = none
46 46
47 47 qseries.applied = blue bold underline
48 48 qseries.unapplied = black bold
49 49 qseries.missing = red bold
50 50
51 51 diff.diffline = bold
52 52 diff.extended = cyan bold
53 53 diff.file_a = red bold
54 54 diff.file_b = green bold
55 55 diff.hunk = magenta
56 56 diff.deleted = red
57 57 diff.inserted = green
58 58 diff.changed = white
59 59 diff.trailingwhitespace = bold red_background
60 60
61 61 resolve.unresolved = red bold
62 62 resolve.resolved = green bold
63 63
64 64 bookmarks.current = green
65 65
66 66 branches.active = none
67 67 branches.closed = black bold
68 68 branches.current = green
69 69 branches.inactive = none
70 70
71 71 The available effects in terminfo mode are 'blink', 'bold', 'dim',
72 72 'inverse', 'invisible', 'italic', 'standout', and 'underline'; in
73 73 ECMA-48 mode, the options are 'bold', 'inverse', 'italic', and
74 74 'underline'. How each is rendered depends on the terminal emulator.
75 75 Some may not be available for a given terminal type, and will be
76 76 silently ignored.
77 77
78 78 Because there are only eight standard colors, this module allows you
79 79 to define color names for other color slots which might be available
80 80 for your terminal type, assuming terminfo mode. For instance::
81 81
82 82 color.brightblue = 12
83 83 color.pink = 207
84 84 color.orange = 202
85 85
86 86 to set 'brightblue' to color slot 12 (useful for 16 color terminals
87 87 that have brighter colors defined in the upper eight) and, 'pink' and
88 88 'orange' to colors in 256-color xterm's default color cube. These
89 89 defined colors may then be used as any of the pre-defined eight,
90 90 including appending '_background' to set the background to that color.
91 91
92 92 The color extension will try to detect whether to use terminfo, ANSI
93 93 codes or Win32 console APIs, unless it is made explicit; e.g.::
94 94
95 95 [color]
96 96 mode = ansi
97 97
98 98 Any value other than 'ansi', 'win32', 'terminfo', or 'auto' will
99 99 disable color.
100 100
101 101 '''
102 102
103 import os, sys
103 import os
104 104
105 105 from mercurial import commands, dispatch, extensions, ui as uimod, util
106 106 from mercurial.i18n import _
107 107
108 108 # start and stop parameters for effects
109 109 _effects = {'none': 0, 'black': 30, 'red': 31, 'green': 32, 'yellow': 33,
110 110 'blue': 34, 'magenta': 35, 'cyan': 36, 'white': 37, 'bold': 1,
111 111 'italic': 3, 'underline': 4, 'inverse': 7,
112 112 'black_background': 40, 'red_background': 41,
113 113 'green_background': 42, 'yellow_background': 43,
114 114 'blue_background': 44, 'purple_background': 45,
115 115 'cyan_background': 46, 'white_background': 47}
116 116
117 117 def _terminfosetup(ui):
118 118 '''Initialize terminfo data and the terminal if we're in terminfo mode.'''
119 119
120 120 global _terminfo_params
121 121 # If we failed to load curses, we go ahead and return.
122 122 if not _terminfo_params:
123 123 return
124 124 # Otherwise, see what the config file says.
125 125 mode = ui.config('color', 'mode', 'auto')
126 126 if mode not in ('auto', 'terminfo'):
127 127 return
128 128
129 129 _terminfo_params.update((key[6:], (False, int(val)))
130 130 for key, val in ui.configitems('color')
131 131 if key.startswith('color.'))
132 132
133 133 try:
134 134 curses.setupterm()
135 135 except curses.error, e:
136 136 _terminfo_params = {}
137 137 return
138 138
139 139 for key, (b, e) in _terminfo_params.items():
140 140 if not b:
141 141 continue
142 142 if not curses.tigetstr(e):
143 143 # Most terminals don't support dim, invis, etc, so don't be
144 144 # noisy and use ui.debug().
145 145 ui.debug("no terminfo entry for %s\n" % e)
146 146 del _terminfo_params[key]
147 147 if not curses.tigetstr('setaf') or not curses.tigetstr('setab'):
148 148 ui.warn(_("no terminfo entry for setab/setaf: reverting to "
149 149 "ECMA-48 color\n"))
150 150 _terminfo_params = {}
151 151
152 152 try:
153 153 import curses
154 154 # Mapping from effect name to terminfo attribute name or color number.
155 155 # This will also force-load the curses module.
156 156 _terminfo_params = {'none': (True, 'sgr0'),
157 157 'standout': (True, 'smso'),
158 158 'underline': (True, 'smul'),
159 159 'reverse': (True, 'rev'),
160 160 'inverse': (True, 'rev'),
161 161 'blink': (True, 'blink'),
162 162 'dim': (True, 'dim'),
163 163 'bold': (True, 'bold'),
164 164 'invisible': (True, 'invis'),
165 165 'italic': (True, 'sitm'),
166 166 'black': (False, curses.COLOR_BLACK),
167 167 'red': (False, curses.COLOR_RED),
168 168 'green': (False, curses.COLOR_GREEN),
169 169 'yellow': (False, curses.COLOR_YELLOW),
170 170 'blue': (False, curses.COLOR_BLUE),
171 171 'magenta': (False, curses.COLOR_MAGENTA),
172 172 'cyan': (False, curses.COLOR_CYAN),
173 173 'white': (False, curses.COLOR_WHITE)}
174 174 except ImportError:
175 175 _terminfo_params = False
176 176
177 177 _styles = {'grep.match': 'red bold',
178 178 'bookmarks.current': 'green',
179 179 'branches.active': 'none',
180 180 'branches.closed': 'black bold',
181 181 'branches.current': 'green',
182 182 'branches.inactive': 'none',
183 183 'diff.changed': 'white',
184 184 'diff.deleted': 'red',
185 185 'diff.diffline': 'bold',
186 186 'diff.extended': 'cyan bold',
187 187 'diff.file_a': 'red bold',
188 188 'diff.file_b': 'green bold',
189 189 'diff.hunk': 'magenta',
190 190 'diff.inserted': 'green',
191 191 'diff.trailingwhitespace': 'bold red_background',
192 192 'diffstat.deleted': 'red',
193 193 'diffstat.inserted': 'green',
194 194 'ui.prompt': 'yellow',
195 195 'log.changeset': 'yellow',
196 196 'resolve.resolved': 'green bold',
197 197 'resolve.unresolved': 'red bold',
198 198 'status.added': 'green bold',
199 199 'status.clean': 'none',
200 200 'status.copied': 'none',
201 201 'status.deleted': 'cyan bold underline',
202 202 'status.ignored': 'black bold',
203 203 'status.modified': 'blue bold',
204 204 'status.removed': 'red bold',
205 205 'status.unknown': 'magenta bold underline'}
206 206
207 207
208 208 def _effect_str(effect):
209 209 '''Helper function for render_effects().'''
210 210
211 211 bg = False
212 212 if effect.endswith('_background'):
213 213 bg = True
214 214 effect = effect[:-11]
215 215 attr, val = _terminfo_params[effect]
216 216 if attr:
217 217 return curses.tigetstr(val)
218 218 elif bg:
219 219 return curses.tparm(curses.tigetstr('setab'), val)
220 220 else:
221 221 return curses.tparm(curses.tigetstr('setaf'), val)
222 222
223 223 def render_effects(text, effects):
224 224 'Wrap text in commands to turn on each effect.'
225 225 if not text:
226 226 return text
227 227 if not _terminfo_params:
228 228 start = [str(_effects[e]) for e in ['none'] + effects.split()]
229 229 start = '\033[' + ';'.join(start) + 'm'
230 230 stop = '\033[' + str(_effects['none']) + 'm'
231 231 else:
232 232 start = ''.join(_effect_str(effect)
233 233 for effect in ['none'] + effects.split())
234 234 stop = _effect_str('none')
235 235 return ''.join([start, text, stop])
236 236
237 237 def extstyles():
238 238 for name, ext in extensions.extensions():
239 239 _styles.update(getattr(ext, 'colortable', {}))
240 240
241 241 def configstyles(ui):
242 242 for status, cfgeffects in ui.configitems('color'):
243 243 if '.' not in status or status.startswith('color.'):
244 244 continue
245 245 cfgeffects = ui.configlist('color', status)
246 246 if cfgeffects:
247 247 good = []
248 248 for e in cfgeffects:
249 249 if not _terminfo_params and e in _effects:
250 250 good.append(e)
251 251 elif e in _terminfo_params or e[:-11] in _terminfo_params:
252 252 good.append(e)
253 253 else:
254 254 ui.warn(_("ignoring unknown color/effect %r "
255 255 "(configured in color.%s)\n")
256 256 % (e, status))
257 257 _styles[status] = ' '.join(good)
258 258
259 259 class colorui(uimod.ui):
260 260 def popbuffer(self, labeled=False):
261 261 if labeled:
262 262 return ''.join(self.label(a, label) for a, label
263 263 in self._buffers.pop())
264 264 return ''.join(a for a, label in self._buffers.pop())
265 265
266 266 _colormode = 'ansi'
267 267 def write(self, *args, **opts):
268 268 label = opts.get('label', '')
269 269 if self._buffers:
270 270 self._buffers[-1].extend([(str(a), label) for a in args])
271 271 elif self._colormode == 'win32':
272 272 for a in args:
273 273 win32print(a, super(colorui, self).write, **opts)
274 274 else:
275 275 return super(colorui, self).write(
276 276 *[self.label(str(a), label) for a in args], **opts)
277 277
278 278 def write_err(self, *args, **opts):
279 279 label = opts.get('label', '')
280 280 if self._colormode == 'win32':
281 281 for a in args:
282 282 win32print(a, super(colorui, self).write_err, **opts)
283 283 else:
284 284 return super(colorui, self).write_err(
285 285 *[self.label(str(a), label) for a in args], **opts)
286 286
287 287 def label(self, msg, label):
288 288 effects = []
289 289 for l in label.split():
290 290 s = _styles.get(l, '')
291 291 if s:
292 292 effects.append(s)
293 293 effects = ''.join(effects)
294 294 if effects:
295 295 return '\n'.join([render_effects(s, effects)
296 296 for s in msg.split('\n')])
297 297 return msg
298 298
299 299
300 300 def uisetup(ui):
301 301 global _terminfo_params
302 302 if ui.plain():
303 303 return
304 304
305 305 formatted = (os.environ.get('TERM') != 'dumb' and ui.formatted())
306 306 mode = ui.config('color', 'mode', 'auto')
307 307 if mode == 'auto':
308 308 if os.name == 'nt' and 'TERM' not in os.environ:
309 309 # looks line a cmd.exe console, use win32 API or nothing
310 310 mode = w32effects and 'win32' or 'none'
311 311 else:
312 312 if not formatted:
313 313 _terminfo_params = False
314 314 else:
315 315 _terminfosetup(ui)
316 316 if not _terminfo_params:
317 317 mode = 'ansi'
318 318 else:
319 319 mode = 'terminfo'
320 320 if mode == 'win32':
321 321 if w32effects is None:
322 322 # only warn if color.mode is explicitly set to win32
323 323 ui.warn(_('warning: failed to set color mode to %s\n') % mode)
324 324 return
325 325 _effects.update(w32effects)
326 326 elif mode == 'ansi':
327 327 _terminfo_params = {}
328 328 elif mode == 'terminfo':
329 329 _terminfosetup(ui)
330 330 else:
331 331 return
332 332 def colorcmd(orig, ui_, opts, cmd, cmdfunc):
333 333 coloropt = opts['color']
334 334 auto = coloropt == 'auto'
335 335 always = util.parsebool(coloropt)
336 336 if (always or
337 337 (always is None and auto and formatted)):
338 338 colorui._colormode = mode
339 339 colorui.__bases__ = (ui_.__class__,)
340 340 ui_.__class__ = colorui
341 341 extstyles()
342 342 configstyles(ui_)
343 343 return orig(ui_, opts, cmd, cmdfunc)
344 344 extensions.wrapfunction(dispatch, '_runcommand', colorcmd)
345 345
346 346 def extsetup(ui):
347 347 commands.globalopts.append(
348 348 ('', 'color', 'auto',
349 349 # i18n: 'always', 'auto', and 'never' are keywords and should
350 350 # not be translated
351 351 _("when to colorize (boolean, always, auto, or never)"),
352 352 _('TYPE')))
353 353
354 354 if os.name != 'nt':
355 355 w32effects = None
356 356 else:
357 357 import re, ctypes
358 358
359 359 _kernel32 = ctypes.windll.kernel32
360 360
361 361 _WORD = ctypes.c_ushort
362 362
363 363 _INVALID_HANDLE_VALUE = -1
364 364
365 365 class _COORD(ctypes.Structure):
366 366 _fields_ = [('X', ctypes.c_short),
367 367 ('Y', ctypes.c_short)]
368 368
369 369 class _SMALL_RECT(ctypes.Structure):
370 370 _fields_ = [('Left', ctypes.c_short),
371 371 ('Top', ctypes.c_short),
372 372 ('Right', ctypes.c_short),
373 373 ('Bottom', ctypes.c_short)]
374 374
375 375 class _CONSOLE_SCREEN_BUFFER_INFO(ctypes.Structure):
376 376 _fields_ = [('dwSize', _COORD),
377 377 ('dwCursorPosition', _COORD),
378 378 ('wAttributes', _WORD),
379 379 ('srWindow', _SMALL_RECT),
380 380 ('dwMaximumWindowSize', _COORD)]
381 381
382 382 _STD_OUTPUT_HANDLE = 0xfffffff5L # (DWORD)-11
383 383 _STD_ERROR_HANDLE = 0xfffffff4L # (DWORD)-12
384 384
385 385 _FOREGROUND_BLUE = 0x0001
386 386 _FOREGROUND_GREEN = 0x0002
387 387 _FOREGROUND_RED = 0x0004
388 388 _FOREGROUND_INTENSITY = 0x0008
389 389
390 390 _BACKGROUND_BLUE = 0x0010
391 391 _BACKGROUND_GREEN = 0x0020
392 392 _BACKGROUND_RED = 0x0040
393 393 _BACKGROUND_INTENSITY = 0x0080
394 394
395 395 _COMMON_LVB_REVERSE_VIDEO = 0x4000
396 396 _COMMON_LVB_UNDERSCORE = 0x8000
397 397
398 398 # http://msdn.microsoft.com/en-us/library/ms682088%28VS.85%29.aspx
399 399 w32effects = {
400 400 'none': -1,
401 401 'black': 0,
402 402 'red': _FOREGROUND_RED,
403 403 'green': _FOREGROUND_GREEN,
404 404 'yellow': _FOREGROUND_RED | _FOREGROUND_GREEN,
405 405 'blue': _FOREGROUND_BLUE,
406 406 'magenta': _FOREGROUND_BLUE | _FOREGROUND_RED,
407 407 'cyan': _FOREGROUND_BLUE | _FOREGROUND_GREEN,
408 408 'white': _FOREGROUND_RED | _FOREGROUND_GREEN | _FOREGROUND_BLUE,
409 409 'bold': _FOREGROUND_INTENSITY,
410 410 'black_background': 0x100, # unused value > 0x0f
411 411 'red_background': _BACKGROUND_RED,
412 412 'green_background': _BACKGROUND_GREEN,
413 413 'yellow_background': _BACKGROUND_RED | _BACKGROUND_GREEN,
414 414 'blue_background': _BACKGROUND_BLUE,
415 415 'purple_background': _BACKGROUND_BLUE | _BACKGROUND_RED,
416 416 'cyan_background': _BACKGROUND_BLUE | _BACKGROUND_GREEN,
417 417 'white_background': (_BACKGROUND_RED | _BACKGROUND_GREEN |
418 418 _BACKGROUND_BLUE),
419 419 'bold_background': _BACKGROUND_INTENSITY,
420 420 'underline': _COMMON_LVB_UNDERSCORE, # double-byte charsets only
421 421 'inverse': _COMMON_LVB_REVERSE_VIDEO, # double-byte charsets only
422 422 }
423 423
424 424 passthrough = set([_FOREGROUND_INTENSITY,
425 425 _BACKGROUND_INTENSITY,
426 426 _COMMON_LVB_UNDERSCORE,
427 427 _COMMON_LVB_REVERSE_VIDEO])
428 428
429 429 stdout = _kernel32.GetStdHandle(
430 430 _STD_OUTPUT_HANDLE) # don't close the handle returned
431 431 if stdout is None or stdout == _INVALID_HANDLE_VALUE:
432 432 w32effects = None
433 433 else:
434 434 csbi = _CONSOLE_SCREEN_BUFFER_INFO()
435 435 if not _kernel32.GetConsoleScreenBufferInfo(
436 436 stdout, ctypes.byref(csbi)):
437 437 # stdout may not support GetConsoleScreenBufferInfo()
438 438 # when called from subprocess or redirected
439 439 w32effects = None
440 440 else:
441 441 origattr = csbi.wAttributes
442 442 ansire = re.compile('\033\[([^m]*)m([^\033]*)(.*)',
443 443 re.MULTILINE | re.DOTALL)
444 444
445 445 def win32print(text, orig, **opts):
446 446 label = opts.get('label', '')
447 447 attr = origattr
448 448
449 449 def mapcolor(val, attr):
450 450 if val == -1:
451 451 return origattr
452 452 elif val in passthrough:
453 453 return attr | val
454 454 elif val > 0x0f:
455 455 return (val & 0x70) | (attr & 0x8f)
456 456 else:
457 457 return (val & 0x07) | (attr & 0xf8)
458 458
459 459 # determine console attributes based on labels
460 460 for l in label.split():
461 461 style = _styles.get(l, '')
462 462 for effect in style.split():
463 463 attr = mapcolor(w32effects[effect], attr)
464 464
465 465 # hack to ensure regexp finds data
466 466 if not text.startswith('\033['):
467 467 text = '\033[m' + text
468 468
469 469 # Look for ANSI-like codes embedded in text
470 470 m = re.match(ansire, text)
471 471
472 472 try:
473 473 while m:
474 474 for sattr in m.group(1).split(';'):
475 475 if sattr:
476 476 attr = mapcolor(int(sattr), attr)
477 477 _kernel32.SetConsoleTextAttribute(stdout, attr)
478 478 orig(m.group(2), **opts)
479 479 m = re.match(ansire, m.group(3))
480 480 finally:
481 481 # Explicity reset original attributes
482 482 _kernel32.SetConsoleTextAttribute(stdout, origattr)
@@ -1,200 +1,200
1 1 # darcs.py - darcs support for the convert extension
2 2 #
3 3 # Copyright 2007-2009 Matt Mackall <mpm@selenic.com> and others
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 common import NoRepo, checktool, commandline, commit, converter_source
9 9 from mercurial.i18n import _
10 from mercurial import encoding, util
10 from mercurial import util
11 11 import os, shutil, tempfile, re
12 12
13 13 # The naming drift of ElementTree is fun!
14 14
15 15 try:
16 16 from xml.etree.cElementTree import ElementTree, XMLParser
17 17 except ImportError:
18 18 try:
19 19 from xml.etree.ElementTree import ElementTree, XMLParser
20 20 except ImportError:
21 21 try:
22 22 from elementtree.cElementTree import ElementTree, XMLParser
23 23 except ImportError:
24 24 try:
25 25 from elementtree.ElementTree import ElementTree, XMLParser
26 26 except ImportError:
27 27 ElementTree = None
28 28
29 29 class darcs_source(converter_source, commandline):
30 30 def __init__(self, ui, path, rev=None):
31 31 converter_source.__init__(self, ui, path, rev=rev)
32 32 commandline.__init__(self, ui, 'darcs')
33 33
34 34 # check for _darcs, ElementTree so that we can easily skip
35 35 # test-convert-darcs if ElementTree is not around
36 36 if not os.path.exists(os.path.join(path, '_darcs')):
37 37 raise NoRepo(_("%s does not look like a darcs repository") % path)
38 38
39 39 checktool('darcs')
40 40 version = self.run0('--version').splitlines()[0].strip()
41 41 if version < '2.1':
42 42 raise util.Abort(_('darcs version 2.1 or newer needed (found %r)') %
43 43 version)
44 44
45 45 if ElementTree is None:
46 46 raise util.Abort(_("Python ElementTree module is not available"))
47 47
48 48 self.path = os.path.realpath(path)
49 49
50 50 self.lastrev = None
51 51 self.changes = {}
52 52 self.parents = {}
53 53 self.tags = {}
54 54
55 55 # Check darcs repository format
56 56 format = self.format()
57 57 if format:
58 58 if format in ('darcs-1.0', 'hashed'):
59 59 raise NoRepo(_("%s repository format is unsupported, "
60 60 "please upgrade") % format)
61 61 else:
62 62 self.ui.warn(_('failed to detect repository format!'))
63 63
64 64 def before(self):
65 65 self.tmppath = tempfile.mkdtemp(
66 66 prefix='convert-' + os.path.basename(self.path) + '-')
67 67 output, status = self.run('init', repodir=self.tmppath)
68 68 self.checkexit(status)
69 69
70 70 tree = self.xml('changes', xml_output=True, summary=True,
71 71 repodir=self.path)
72 72 tagname = None
73 73 child = None
74 74 for elt in tree.findall('patch'):
75 75 node = elt.get('hash')
76 76 name = elt.findtext('name', '')
77 77 if name.startswith('TAG '):
78 78 tagname = name[4:].strip()
79 79 elif tagname is not None:
80 80 self.tags[tagname] = node
81 81 tagname = None
82 82 self.changes[node] = elt
83 83 self.parents[child] = [node]
84 84 child = node
85 85 self.parents[child] = []
86 86
87 87 def after(self):
88 88 self.ui.debug('cleaning up %s\n' % self.tmppath)
89 89 shutil.rmtree(self.tmppath, ignore_errors=True)
90 90
91 91 def recode(self, s, encoding=None):
92 92 if isinstance(s, unicode):
93 93 # XMLParser returns unicode objects for anything it can't
94 94 # encode into ASCII. We convert them back to str to get
95 95 # recode's normal conversion behavior.
96 96 s = s.encode('latin-1')
97 97 return super(darcs_source, self).recode(s, encoding)
98 98
99 99 def xml(self, cmd, **kwargs):
100 100 # NOTE: darcs is currently encoding agnostic and will print
101 101 # patch metadata byte-for-byte, even in the XML changelog.
102 102 etree = ElementTree()
103 103 # While we are decoding the XML as latin-1 to be as liberal as
104 104 # possible, etree will still raise an exception if any
105 105 # non-printable characters are in the XML changelog.
106 106 parser = XMLParser(encoding='latin-1')
107 107 fp = self._run(cmd, **kwargs)
108 108 etree.parse(fp, parser=parser)
109 109 self.checkexit(fp.close())
110 110 return etree.getroot()
111 111
112 112 def format(self):
113 113 output, status = self.run('show', 'repo', no_files=True,
114 114 repodir=self.path)
115 115 self.checkexit(status)
116 116 m = re.search(r'^\s*Format:\s*(.*)$', output, re.MULTILINE)
117 117 if not m:
118 118 return None
119 119 return ','.join(sorted(f.strip() for f in m.group(1).split(',')))
120 120
121 121 def manifest(self):
122 122 man = []
123 123 output, status = self.run('show', 'files', no_directories=True,
124 124 repodir=self.tmppath)
125 125 self.checkexit(status)
126 126 for line in output.split('\n'):
127 127 path = line[2:]
128 128 if path:
129 129 man.append(path)
130 130 return man
131 131
132 132 def getheads(self):
133 133 return self.parents[None]
134 134
135 135 def getcommit(self, rev):
136 136 elt = self.changes[rev]
137 137 date = util.strdate(elt.get('local_date'), '%a %b %d %H:%M:%S %Z %Y')
138 138 desc = elt.findtext('name') + '\n' + elt.findtext('comment', '')
139 139 # etree can return unicode objects for name, comment, and author,
140 140 # so recode() is used to ensure str objects are emitted.
141 141 return commit(author=self.recode(elt.get('author')),
142 142 date=util.datestr(date),
143 143 desc=self.recode(desc).strip(),
144 144 parents=self.parents[rev])
145 145
146 146 def pull(self, rev):
147 147 output, status = self.run('pull', self.path, all=True,
148 148 match='hash %s' % rev,
149 149 no_test=True, no_posthook=True,
150 150 external_merge='/bin/false',
151 151 repodir=self.tmppath)
152 152 if status:
153 153 if output.find('We have conflicts in') == -1:
154 154 self.checkexit(status, output)
155 155 output, status = self.run('revert', all=True, repodir=self.tmppath)
156 156 self.checkexit(status, output)
157 157
158 158 def getchanges(self, rev):
159 159 copies = {}
160 160 changes = []
161 161 man = None
162 162 for elt in self.changes[rev].find('summary').getchildren():
163 163 if elt.tag in ('add_directory', 'remove_directory'):
164 164 continue
165 165 if elt.tag == 'move':
166 166 if man is None:
167 167 man = self.manifest()
168 168 source, dest = elt.get('from'), elt.get('to')
169 169 if source in man:
170 170 # File move
171 171 changes.append((source, rev))
172 172 changes.append((dest, rev))
173 173 copies[dest] = source
174 174 else:
175 175 # Directory move, deduce file moves from manifest
176 176 source = source + '/'
177 177 for f in man:
178 178 if not f.startswith(source):
179 179 continue
180 180 fdest = dest + '/' + f[len(source):]
181 181 changes.append((f, rev))
182 182 changes.append((fdest, rev))
183 183 copies[fdest] = f
184 184 else:
185 185 changes.append((elt.text.strip(), rev))
186 186 self.pull(rev)
187 187 self.lastrev = rev
188 188 return sorted(changes), copies
189 189
190 190 def getfile(self, name, rev):
191 191 if rev != self.lastrev:
192 192 raise util.Abort(_('internal calling inconsistency'))
193 193 path = os.path.join(self.tmppath, name)
194 194 data = open(path, 'rb').read()
195 195 mode = os.lstat(path).st_mode
196 196 mode = (mode & 0111) and 'x' or ''
197 197 return data, mode
198 198
199 199 def gettags(self):
200 200 return self.tags
@@ -1,399 +1,399
1 1 # ASCII graph log extension for Mercurial
2 2 #
3 3 # Copyright 2007 Joel Rosdahl <joel@rosdahl.net>
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 '''command to view revision graphs from a shell
9 9
10 10 This extension adds a --graph option to the incoming, outgoing and log
11 11 commands. When this options is given, an ASCII representation of the
12 12 revision graph is also shown.
13 13 '''
14 14
15 15 from mercurial.cmdutil import revrange, show_changeset
16 16 from mercurial.commands import templateopts
17 17 from mercurial.i18n import _
18 18 from mercurial.node import nullrev
19 19 from mercurial import cmdutil, commands, extensions
20 from mercurial import hg, scmutil, util, graphmod
20 from mercurial import hg, util, graphmod
21 21
22 22 ASCIIDATA = 'ASC'
23 23
24 24 def asciiedges(type, char, lines, seen, rev, parents):
25 25 """adds edge info to changelog DAG walk suitable for ascii()"""
26 26 if rev not in seen:
27 27 seen.append(rev)
28 28 nodeidx = seen.index(rev)
29 29
30 30 knownparents = []
31 31 newparents = []
32 32 for parent in parents:
33 33 if parent in seen:
34 34 knownparents.append(parent)
35 35 else:
36 36 newparents.append(parent)
37 37
38 38 ncols = len(seen)
39 39 nextseen = seen[:]
40 40 nextseen[nodeidx:nodeidx + 1] = newparents
41 41 edges = [(nodeidx, nextseen.index(p)) for p in knownparents]
42 42
43 43 while len(newparents) > 2:
44 44 # ascii() only knows how to add or remove a single column between two
45 45 # calls. Nodes with more than two parents break this constraint so we
46 46 # introduce intermediate expansion lines to grow the active node list
47 47 # slowly.
48 48 edges.append((nodeidx, nodeidx))
49 49 edges.append((nodeidx, nodeidx + 1))
50 50 nmorecols = 1
51 51 yield (type, char, lines, (nodeidx, edges, ncols, nmorecols))
52 52 char = '\\'
53 53 lines = []
54 54 nodeidx += 1
55 55 ncols += 1
56 56 edges = []
57 57 del newparents[0]
58 58
59 59 if len(newparents) > 0:
60 60 edges.append((nodeidx, nodeidx))
61 61 if len(newparents) > 1:
62 62 edges.append((nodeidx, nodeidx + 1))
63 63 nmorecols = len(nextseen) - ncols
64 64 seen[:] = nextseen
65 65 yield (type, char, lines, (nodeidx, edges, ncols, nmorecols))
66 66
67 67 def fix_long_right_edges(edges):
68 68 for (i, (start, end)) in enumerate(edges):
69 69 if end > start:
70 70 edges[i] = (start, end + 1)
71 71
72 72 def get_nodeline_edges_tail(
73 73 node_index, p_node_index, n_columns, n_columns_diff, p_diff, fix_tail):
74 74 if fix_tail and n_columns_diff == p_diff and n_columns_diff != 0:
75 75 # Still going in the same non-vertical direction.
76 76 if n_columns_diff == -1:
77 77 start = max(node_index + 1, p_node_index)
78 78 tail = ["|", " "] * (start - node_index - 1)
79 79 tail.extend(["/", " "] * (n_columns - start))
80 80 return tail
81 81 else:
82 82 return ["\\", " "] * (n_columns - node_index - 1)
83 83 else:
84 84 return ["|", " "] * (n_columns - node_index - 1)
85 85
86 86 def draw_edges(edges, nodeline, interline):
87 87 for (start, end) in edges:
88 88 if start == end + 1:
89 89 interline[2 * end + 1] = "/"
90 90 elif start == end - 1:
91 91 interline[2 * start + 1] = "\\"
92 92 elif start == end:
93 93 interline[2 * start] = "|"
94 94 else:
95 95 nodeline[2 * end] = "+"
96 96 if start > end:
97 97 (start, end) = (end, start)
98 98 for i in range(2 * start + 1, 2 * end):
99 99 if nodeline[i] != "+":
100 100 nodeline[i] = "-"
101 101
102 102 def get_padding_line(ni, n_columns, edges):
103 103 line = []
104 104 line.extend(["|", " "] * ni)
105 105 if (ni, ni - 1) in edges or (ni, ni) in edges:
106 106 # (ni, ni - 1) (ni, ni)
107 107 # | | | | | | | |
108 108 # +---o | | o---+
109 109 # | | c | | c | |
110 110 # | |/ / | |/ /
111 111 # | | | | | |
112 112 c = "|"
113 113 else:
114 114 c = " "
115 115 line.extend([c, " "])
116 116 line.extend(["|", " "] * (n_columns - ni - 1))
117 117 return line
118 118
119 119 def asciistate():
120 120 """returns the initial value for the "state" argument to ascii()"""
121 121 return [0, 0]
122 122
123 123 def ascii(ui, state, type, char, text, coldata):
124 124 """prints an ASCII graph of the DAG
125 125
126 126 takes the following arguments (one call per node in the graph):
127 127
128 128 - ui to write to
129 129 - Somewhere to keep the needed state in (init to asciistate())
130 130 - Column of the current node in the set of ongoing edges.
131 131 - Type indicator of node data == ASCIIDATA.
132 132 - Payload: (char, lines):
133 133 - Character to use as node's symbol.
134 134 - List of lines to display as the node's text.
135 135 - Edges; a list of (col, next_col) indicating the edges between
136 136 the current node and its parents.
137 137 - Number of columns (ongoing edges) in the current revision.
138 138 - The difference between the number of columns (ongoing edges)
139 139 in the next revision and the number of columns (ongoing edges)
140 140 in the current revision. That is: -1 means one column removed;
141 141 0 means no columns added or removed; 1 means one column added.
142 142 """
143 143
144 144 idx, edges, ncols, coldiff = coldata
145 145 assert -2 < coldiff < 2
146 146 if coldiff == -1:
147 147 # Transform
148 148 #
149 149 # | | | | | |
150 150 # o | | into o---+
151 151 # |X / |/ /
152 152 # | | | |
153 153 fix_long_right_edges(edges)
154 154
155 155 # add_padding_line says whether to rewrite
156 156 #
157 157 # | | | | | | | |
158 158 # | o---+ into | o---+
159 159 # | / / | | | # <--- padding line
160 160 # o | | | / /
161 161 # o | |
162 162 add_padding_line = (len(text) > 2 and coldiff == -1 and
163 163 [x for (x, y) in edges if x + 1 < y])
164 164
165 165 # fix_nodeline_tail says whether to rewrite
166 166 #
167 167 # | | o | | | | o | |
168 168 # | | |/ / | | |/ /
169 169 # | o | | into | o / / # <--- fixed nodeline tail
170 170 # | |/ / | |/ /
171 171 # o | | o | |
172 172 fix_nodeline_tail = len(text) <= 2 and not add_padding_line
173 173
174 174 # nodeline is the line containing the node character (typically o)
175 175 nodeline = ["|", " "] * idx
176 176 nodeline.extend([char, " "])
177 177
178 178 nodeline.extend(
179 179 get_nodeline_edges_tail(idx, state[1], ncols, coldiff,
180 180 state[0], fix_nodeline_tail))
181 181
182 182 # shift_interline is the line containing the non-vertical
183 183 # edges between this entry and the next
184 184 shift_interline = ["|", " "] * idx
185 185 if coldiff == -1:
186 186 n_spaces = 1
187 187 edge_ch = "/"
188 188 elif coldiff == 0:
189 189 n_spaces = 2
190 190 edge_ch = "|"
191 191 else:
192 192 n_spaces = 3
193 193 edge_ch = "\\"
194 194 shift_interline.extend(n_spaces * [" "])
195 195 shift_interline.extend([edge_ch, " "] * (ncols - idx - 1))
196 196
197 197 # draw edges from the current node to its parents
198 198 draw_edges(edges, nodeline, shift_interline)
199 199
200 200 # lines is the list of all graph lines to print
201 201 lines = [nodeline]
202 202 if add_padding_line:
203 203 lines.append(get_padding_line(idx, ncols, edges))
204 204 lines.append(shift_interline)
205 205
206 206 # make sure that there are as many graph lines as there are
207 207 # log strings
208 208 while len(text) < len(lines):
209 209 text.append("")
210 210 if len(lines) < len(text):
211 211 extra_interline = ["|", " "] * (ncols + coldiff)
212 212 while len(lines) < len(text):
213 213 lines.append(extra_interline)
214 214
215 215 # print lines
216 216 indentation_level = max(ncols, ncols + coldiff)
217 217 for (line, logstr) in zip(lines, text):
218 218 ln = "%-*s %s" % (2 * indentation_level, "".join(line), logstr)
219 219 ui.write(ln.rstrip() + '\n')
220 220
221 221 # ... and start over
222 222 state[0] = coldiff
223 223 state[1] = idx
224 224
225 225 def get_revs(repo, rev_opt):
226 226 if rev_opt:
227 227 revs = revrange(repo, rev_opt)
228 228 if len(revs) == 0:
229 229 return (nullrev, nullrev)
230 230 return (max(revs), min(revs))
231 231 else:
232 232 return (len(repo) - 1, 0)
233 233
234 234 def check_unsupported_flags(pats, opts):
235 235 for op in ["follow_first", "copies", "newest_first"]:
236 236 if op in opts and opts[op]:
237 237 raise util.Abort(_("-G/--graph option is incompatible with --%s")
238 238 % op.replace("_", "-"))
239 239 if pats and opts.get('follow'):
240 240 raise util.Abort(_("-G/--graph option is incompatible with --follow "
241 241 "with file argument"))
242 242
243 243 def revset(pats, opts):
244 244 """Return revset str built of revisions, log options and file patterns.
245 245 """
246 246 opt2revset = {
247 247 'follow': (0, 'follow()'),
248 248 'no_merges': (0, 'not merge()'),
249 249 'only_merges': (0, 'merge()'),
250 250 'removed': (0, 'removes("*")'),
251 251 'date': (1, 'date($)'),
252 252 'branch': (2, 'branch($)'),
253 253 'exclude': (2, 'not file($)'),
254 254 'include': (2, 'file($)'),
255 255 'keyword': (2, 'keyword($)'),
256 256 'only_branch': (2, 'branch($)'),
257 257 'prune': (2, 'not ($ or ancestors($))'),
258 258 'user': (2, 'user($)'),
259 259 }
260 260 optrevset = []
261 261 revset = []
262 262 for op, val in opts.iteritems():
263 263 if not val:
264 264 continue
265 265 if op == 'rev':
266 266 # Already a revset
267 267 revset.extend(val)
268 268 if op not in opt2revset:
269 269 continue
270 270 arity, revop = opt2revset[op]
271 271 revop = revop.replace('$', '%(val)r')
272 272 if arity == 0:
273 273 optrevset.append(revop)
274 274 elif arity == 1:
275 275 optrevset.append(revop % {'val': val})
276 276 else:
277 277 for f in val:
278 278 optrevset.append(revop % {'val': f})
279 279
280 280 for path in pats:
281 281 optrevset.append('file(%r)' % path)
282 282
283 283 if revset or optrevset:
284 284 if revset:
285 285 revset = ['(' + ' or '.join(revset) + ')']
286 286 if optrevset:
287 287 revset.append('(' + ' and '.join(optrevset) + ')')
288 288 revset = ' and '.join(revset)
289 289 else:
290 290 revset = 'all()'
291 291 return revset
292 292
293 293 def generate(ui, dag, displayer, showparents, edgefn):
294 294 seen, state = [], asciistate()
295 295 for rev, type, ctx, parents in dag:
296 296 char = ctx.node() in showparents and '@' or 'o'
297 297 displayer.show(ctx)
298 298 lines = displayer.hunk.pop(rev).split('\n')[:-1]
299 299 displayer.flush(rev)
300 300 edges = edgefn(type, char, lines, seen, rev, parents)
301 301 for type, char, lines, coldata in edges:
302 302 ascii(ui, state, type, char, lines, coldata)
303 303 displayer.close()
304 304
305 305 def graphlog(ui, repo, *pats, **opts):
306 306 """show revision history alongside an ASCII revision graph
307 307
308 308 Print a revision history alongside a revision graph drawn with
309 309 ASCII characters.
310 310
311 311 Nodes printed as an @ character are parents of the working
312 312 directory.
313 313 """
314 314
315 315 check_unsupported_flags(pats, opts)
316 316
317 317 revs = sorted(revrange(repo, [revset(pats, opts)]), reverse=1)
318 318 limit = cmdutil.loglimit(opts)
319 319 if limit is not None:
320 320 revs = revs[:limit]
321 321 revdag = graphmod.dagwalker(repo, revs)
322 322
323 323 displayer = show_changeset(ui, repo, opts, buffered=True)
324 324 showparents = [ctx.node() for ctx in repo[None].parents()]
325 325 generate(ui, revdag, displayer, showparents, asciiedges)
326 326
327 327 def graphrevs(repo, nodes, opts):
328 328 limit = cmdutil.loglimit(opts)
329 329 nodes.reverse()
330 330 if limit is not None:
331 331 nodes = nodes[:limit]
332 332 return graphmod.nodes(repo, nodes)
333 333
334 334 def goutgoing(ui, repo, dest=None, **opts):
335 335 """show the outgoing changesets alongside an ASCII revision graph
336 336
337 337 Print the outgoing changesets alongside a revision graph drawn with
338 338 ASCII characters.
339 339
340 340 Nodes printed as an @ character are parents of the working
341 341 directory.
342 342 """
343 343
344 344 check_unsupported_flags([], opts)
345 345 o = hg._outgoing(ui, repo, dest, opts)
346 346 if o is None:
347 347 return
348 348
349 349 revdag = graphrevs(repo, o, opts)
350 350 displayer = show_changeset(ui, repo, opts, buffered=True)
351 351 showparents = [ctx.node() for ctx in repo[None].parents()]
352 352 generate(ui, revdag, displayer, showparents, asciiedges)
353 353
354 354 def gincoming(ui, repo, source="default", **opts):
355 355 """show the incoming changesets alongside an ASCII revision graph
356 356
357 357 Print the incoming changesets alongside a revision graph drawn with
358 358 ASCII characters.
359 359
360 360 Nodes printed as an @ character are parents of the working
361 361 directory.
362 362 """
363 363 def subreporecurse():
364 364 return 1
365 365
366 366 check_unsupported_flags([], opts)
367 367 def display(other, chlist, displayer):
368 368 revdag = graphrevs(other, chlist, opts)
369 369 showparents = [ctx.node() for ctx in repo[None].parents()]
370 370 generate(ui, revdag, displayer, showparents, asciiedges)
371 371
372 372 hg._incoming(display, subreporecurse, ui, repo, source, opts, buffered=True)
373 373
374 374 def uisetup(ui):
375 375 '''Initialize the extension.'''
376 376 _wrapcmd(ui, 'log', commands.table, graphlog)
377 377 _wrapcmd(ui, 'incoming', commands.table, gincoming)
378 378 _wrapcmd(ui, 'outgoing', commands.table, goutgoing)
379 379
380 380 def _wrapcmd(ui, cmd, table, wrapfn):
381 381 '''wrap the command'''
382 382 def graph(orig, *args, **kwargs):
383 383 if kwargs['graph']:
384 384 return wrapfn(*args, **kwargs)
385 385 return orig(*args, **kwargs)
386 386 entry = extensions.wrapcommand(table, cmd, graph)
387 387 entry[1].append(('G', 'graph', None, _("show the revision DAG")))
388 388
389 389 cmdtable = {
390 390 "glog":
391 391 (graphlog,
392 392 [('l', 'limit', '',
393 393 _('limit number of changes displayed'), _('NUM')),
394 394 ('p', 'patch', False, _('show patch')),
395 395 ('r', 'rev', [],
396 396 _('show the specified revision or range'), _('REV')),
397 397 ] + templateopts,
398 398 _('hg glog [OPTION]... [FILE]')),
399 399 }
@@ -1,275 +1,274
1 1 # progress.py show progress bars for some actions
2 2 #
3 3 # Copyright (C) 2010 Augie Fackler <durin42@gmail.com>
4 4 #
5 5 # This program is free software; you can redistribute it and/or modify it
6 6 # under the terms of the GNU General Public License as published by the
7 7 # Free Software Foundation; either version 2 of the License, or (at your
8 8 # option) any later version.
9 9 #
10 10 # This program is distributed in the hope that it will be useful, but
11 11 # WITHOUT ANY WARRANTY; without even the implied warranty of
12 12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
13 13 # Public License for more details.
14 14 #
15 15 # You should have received a copy of the GNU General Public License along
16 16 # with this program; if not, write to the Free Software Foundation, Inc.,
17 17 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 18
19 19 """show progress bars for some actions
20 20
21 21 This extension uses the progress information logged by hg commands
22 22 to draw progress bars that are as informative as possible. Some progress
23 23 bars only offer indeterminate information, while others have a definite
24 24 end point.
25 25
26 26 The following settings are available::
27 27
28 28 [progress]
29 29 delay = 3 # number of seconds (float) before showing the progress bar
30 30 refresh = 0.1 # time in seconds between refreshes of the progress bar
31 31 format = topic bar number estimate # format of the progress bar
32 32 width = <none> # if set, the maximum width of the progress information
33 33 # (that is, min(width, term width) will be used)
34 34 clear-complete = True # clear the progress bar after it's done
35 35 disable = False # if true, don't show a progress bar
36 36 assume-tty = False # if true, ALWAYS show a progress bar, unless
37 37 # disable is given
38 38
39 39 Valid entries for the format field are topic, bar, number, unit,
40 40 estimate, and item. item defaults to the last 20 characters of the
41 41 item, but this can be changed by adding either ``-<num>`` which would
42 42 take the last num characters, or ``+<num>`` for the first num
43 43 characters.
44 44 """
45 45
46 46 import sys
47 47 import time
48 48
49 49 from mercurial.i18n import _
50 from mercurial import util
51 50
52 51 def spacejoin(*args):
53 52 return ' '.join(s for s in args if s)
54 53
55 54 def shouldprint(ui):
56 55 return (getattr(sys.stderr, 'isatty', None) and
57 56 (sys.stderr.isatty() or ui.configbool('progress', 'assume-tty')))
58 57
59 58 def fmtremaining(seconds):
60 59 if seconds < 60:
61 60 # i18n: format XX seconds as "XXs"
62 61 return _("%02ds") % (seconds)
63 62 minutes = seconds // 60
64 63 if minutes < 60:
65 64 seconds -= minutes * 60
66 65 # i18n: format X minutes and YY seconds as "XmYYs"
67 66 return _("%dm%02ds") % (minutes, seconds)
68 67 # we're going to ignore seconds in this case
69 68 minutes += 1
70 69 hours = minutes // 60
71 70 minutes -= hours * 60
72 71 if hours < 30:
73 72 # i18n: format X hours and YY minutes as "XhYYm"
74 73 return _("%dh%02dm") % (hours, minutes)
75 74 # we're going to ignore minutes in this case
76 75 hours += 1
77 76 days = hours // 24
78 77 hours -= days * 24
79 78 if days < 15:
80 79 # i18n: format X days and YY hours as "XdYYh"
81 80 return _("%dd%02dh") % (days, hours)
82 81 # we're going to ignore hours in this case
83 82 days += 1
84 83 weeks = days // 7
85 84 days -= weeks * 7
86 85 if weeks < 55:
87 86 # i18n: format X weeks and YY days as "XwYYd"
88 87 return _("%dw%02dd") % (weeks, days)
89 88 # we're going to ignore days and treat a year as 52 weeks
90 89 weeks += 1
91 90 years = weeks // 52
92 91 weeks -= years * 52
93 92 # i18n: format X years and YY weeks as "XyYYw"
94 93 return _("%dy%02dw") % (years, weeks)
95 94
96 95 class progbar(object):
97 96 def __init__(self, ui):
98 97 self.ui = ui
99 98 self.resetstate()
100 99
101 100 def resetstate(self):
102 101 self.topics = []
103 102 self.topicstates = {}
104 103 self.starttimes = {}
105 104 self.startvals = {}
106 105 self.printed = False
107 106 self.lastprint = time.time() + float(self.ui.config(
108 107 'progress', 'delay', default=3))
109 108 self.indetcount = 0
110 109 self.refresh = float(self.ui.config(
111 110 'progress', 'refresh', default=0.1))
112 111 self.order = self.ui.configlist(
113 112 'progress', 'format',
114 113 default=['topic', 'bar', 'number', 'estimate'])
115 114
116 115 def show(self, now, topic, pos, item, unit, total):
117 116 if not shouldprint(self.ui):
118 117 return
119 118 termwidth = self.width()
120 119 self.printed = True
121 120 head = ''
122 121 needprogress = False
123 122 tail = ''
124 123 for indicator in self.order:
125 124 add = ''
126 125 if indicator == 'topic':
127 126 add = topic
128 127 elif indicator == 'number':
129 128 if total:
130 129 add = ('% ' + str(len(str(total))) +
131 130 's/%s') % (pos, total)
132 131 else:
133 132 add = str(pos)
134 133 elif indicator.startswith('item') and item:
135 134 slice = 'end'
136 135 if '-' in indicator:
137 136 wid = int(indicator.split('-')[1])
138 137 elif '+' in indicator:
139 138 slice = 'beginning'
140 139 wid = int(indicator.split('+')[1])
141 140 else:
142 141 wid = 20
143 142 if slice == 'end':
144 143 add = item[-wid:]
145 144 else:
146 145 add = item[:wid]
147 146 add += (wid - len(add)) * ' '
148 147 elif indicator == 'bar':
149 148 add = ''
150 149 needprogress = True
151 150 elif indicator == 'unit' and unit:
152 151 add = unit
153 152 elif indicator == 'estimate':
154 153 add = self.estimate(topic, pos, total, now)
155 154 if not needprogress:
156 155 head = spacejoin(head, add)
157 156 else:
158 157 tail = spacejoin(tail, add)
159 158 if needprogress:
160 159 used = 0
161 160 if head:
162 161 used += len(head) + 1
163 162 if tail:
164 163 used += len(tail) + 1
165 164 progwidth = termwidth - used - 3
166 165 if total and pos <= total:
167 166 amt = pos * progwidth // total
168 167 bar = '=' * (amt - 1)
169 168 if amt > 0:
170 169 bar += '>'
171 170 bar += ' ' * (progwidth - amt)
172 171 else:
173 172 progwidth -= 3
174 173 self.indetcount += 1
175 174 # mod the count by twice the width so we can make the
176 175 # cursor bounce between the right and left sides
177 176 amt = self.indetcount % (2 * progwidth)
178 177 amt -= progwidth
179 178 bar = (' ' * int(progwidth - abs(amt)) + '<=>' +
180 179 ' ' * int(abs(amt)))
181 180 prog = ''.join(('[', bar , ']'))
182 181 out = spacejoin(head, prog, tail)
183 182 else:
184 183 out = spacejoin(head, tail)
185 184 sys.stderr.write('\r' + out[:termwidth])
186 185 sys.stderr.flush()
187 186
188 187 def clear(self):
189 188 if not shouldprint(self.ui):
190 189 return
191 190 sys.stderr.write('\r%s\r' % (' ' * self.width()))
192 191
193 192 def complete(self):
194 193 if not shouldprint(self.ui):
195 194 return
196 195 if self.ui.configbool('progress', 'clear-complete', default=True):
197 196 self.clear()
198 197 else:
199 198 sys.stderr.write('\n')
200 199 sys.stderr.flush()
201 200
202 201 def width(self):
203 202 tw = self.ui.termwidth()
204 203 return min(int(self.ui.config('progress', 'width', default=tw)), tw)
205 204
206 205 def estimate(self, topic, pos, total, now):
207 206 if total is None:
208 207 return ''
209 208 initialpos = self.startvals[topic]
210 209 target = total - initialpos
211 210 delta = pos - initialpos
212 211 if delta > 0:
213 212 elapsed = now - self.starttimes[topic]
214 213 if elapsed > float(
215 214 self.ui.config('progress', 'estimate', default=2)):
216 215 seconds = (elapsed * (target - delta)) // delta + 1
217 216 return fmtremaining(seconds)
218 217 return ''
219 218
220 219 def progress(self, topic, pos, item='', unit='', total=None):
221 220 now = time.time()
222 221 if pos is None:
223 222 self.starttimes.pop(topic, None)
224 223 self.startvals.pop(topic, None)
225 224 self.topicstates.pop(topic, None)
226 225 # reset the progress bar if this is the outermost topic
227 226 if self.topics and self.topics[0] == topic and self.printed:
228 227 self.complete()
229 228 self.resetstate()
230 229 # truncate the list of topics assuming all topics within
231 230 # this one are also closed
232 231 if topic in self.topics:
233 232 self.topics = self.topics[:self.topics.index(topic)]
234 233 else:
235 234 if topic not in self.topics:
236 235 self.starttimes[topic] = now
237 236 self.startvals[topic] = pos
238 237 self.topics.append(topic)
239 238 self.topicstates[topic] = pos, item, unit, total
240 239 if now - self.lastprint >= self.refresh and self.topics:
241 240 self.lastprint = now
242 241 current = self.topics[-1]
243 242 self.show(now, topic, *self.topicstates[topic])
244 243
245 244 def uisetup(ui):
246 245 class progressui(ui.__class__):
247 246 _progbar = None
248 247
249 248 def progress(self, *args, **opts):
250 249 self._progbar.progress(*args, **opts)
251 250 return super(progressui, self).progress(*args, **opts)
252 251
253 252 def write(self, *args, **opts):
254 253 if self._progbar.printed:
255 254 self._progbar.clear()
256 255 return super(progressui, self).write(*args, **opts)
257 256
258 257 def write_err(self, *args, **opts):
259 258 if self._progbar.printed:
260 259 self._progbar.clear()
261 260 return super(progressui, self).write_err(*args, **opts)
262 261
263 262 # Apps that derive a class from ui.ui() can use
264 263 # setconfig('progress', 'disable', 'True') to disable this extension
265 264 if ui.configbool('progress', 'disable'):
266 265 return
267 266 if shouldprint(ui) and not ui.debugflag and not ui.quiet:
268 267 ui.__class__ = progressui
269 268 # we instantiate one globally shared progress bar to avoid
270 269 # competing progress bars when multiple UI objects get created
271 270 if not progressui._progbar:
272 271 progressui._progbar = progbar(ui)
273 272
274 273 def reposetup(ui, repo):
275 274 uisetup(repo.ui)
General Comments 0
You need to be logged in to leave comments. Login now