##// END OF EJS Templates
progress: add try/finally to make the diffs for the next commit more readable...
Solomon Matthews -
r23905:8b5b963b default
parent child Browse files
Show More
@@ -1,305 +1,308
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 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 """show progress bars for some actions
9 9
10 10 This extension uses the progress information logged by hg commands
11 11 to draw progress bars that are as informative as possible. Some progress
12 12 bars only offer indeterminate information, while others have a definite
13 13 end point.
14 14
15 15 The following settings are available::
16 16
17 17 [progress]
18 18 delay = 3 # number of seconds (float) before showing the progress bar
19 19 changedelay = 1 # changedelay: minimum delay before showing a new topic.
20 20 # If set to less than 3 * refresh, that value will
21 21 # be used instead.
22 22 refresh = 0.1 # time in seconds between refreshes of the progress bar
23 23 format = topic bar number estimate # format of the progress bar
24 24 width = <none> # if set, the maximum width of the progress information
25 25 # (that is, min(width, term width) will be used)
26 26 clear-complete = True # clear the progress bar after it's done
27 27 disable = False # if true, don't show a progress bar
28 28 assume-tty = False # if true, ALWAYS show a progress bar, unless
29 29 # disable is given
30 30
31 31 Valid entries for the format field are topic, bar, number, unit,
32 32 estimate, speed, and item. item defaults to the last 20 characters of
33 33 the item, but this can be changed by adding either ``-<num>`` which
34 34 would take the last num characters, or ``+<num>`` for the first num
35 35 characters.
36 36 """
37 37
38 38 import sys
39 39 import time
40 40
41 41 from mercurial.i18n import _
42 42 testedwith = 'internal'
43 43
44 44 from mercurial import encoding
45 45
46 46 def spacejoin(*args):
47 47 return ' '.join(s for s in args if s)
48 48
49 49 def shouldprint(ui):
50 50 return not ui.plain() and (ui._isatty(sys.stderr) or
51 51 ui.configbool('progress', 'assume-tty'))
52 52
53 53 def fmtremaining(seconds):
54 54 if seconds < 60:
55 55 # i18n: format XX seconds as "XXs"
56 56 return _("%02ds") % (seconds)
57 57 minutes = seconds // 60
58 58 if minutes < 60:
59 59 seconds -= minutes * 60
60 60 # i18n: format X minutes and YY seconds as "XmYYs"
61 61 return _("%dm%02ds") % (minutes, seconds)
62 62 # we're going to ignore seconds in this case
63 63 minutes += 1
64 64 hours = minutes // 60
65 65 minutes -= hours * 60
66 66 if hours < 30:
67 67 # i18n: format X hours and YY minutes as "XhYYm"
68 68 return _("%dh%02dm") % (hours, minutes)
69 69 # we're going to ignore minutes in this case
70 70 hours += 1
71 71 days = hours // 24
72 72 hours -= days * 24
73 73 if days < 15:
74 74 # i18n: format X days and YY hours as "XdYYh"
75 75 return _("%dd%02dh") % (days, hours)
76 76 # we're going to ignore hours in this case
77 77 days += 1
78 78 weeks = days // 7
79 79 days -= weeks * 7
80 80 if weeks < 55:
81 81 # i18n: format X weeks and YY days as "XwYYd"
82 82 return _("%dw%02dd") % (weeks, days)
83 83 # we're going to ignore days and treat a year as 52 weeks
84 84 weeks += 1
85 85 years = weeks // 52
86 86 weeks -= years * 52
87 87 # i18n: format X years and YY weeks as "XyYYw"
88 88 return _("%dy%02dw") % (years, weeks)
89 89
90 90 class progbar(object):
91 91 def __init__(self, ui):
92 92 self.ui = ui
93 93 self.resetstate()
94 94
95 95 def resetstate(self):
96 96 self.topics = []
97 97 self.topicstates = {}
98 98 self.starttimes = {}
99 99 self.startvals = {}
100 100 self.printed = False
101 101 self.lastprint = time.time() + float(self.ui.config(
102 102 'progress', 'delay', default=3))
103 103 self.lasttopic = None
104 104 self.indetcount = 0
105 105 self.refresh = float(self.ui.config(
106 106 'progress', 'refresh', default=0.1))
107 107 self.changedelay = max(3 * self.refresh,
108 108 float(self.ui.config(
109 109 'progress', 'changedelay', default=1)))
110 110 self.order = self.ui.configlist(
111 111 'progress', 'format',
112 112 default=['topic', 'bar', 'number', 'estimate'])
113 113
114 114 def show(self, now, topic, pos, item, unit, total):
115 115 if not shouldprint(self.ui):
116 116 return
117 117 termwidth = self.width()
118 118 self.printed = True
119 119 head = ''
120 120 needprogress = False
121 121 tail = ''
122 122 for indicator in self.order:
123 123 add = ''
124 124 if indicator == 'topic':
125 125 add = topic
126 126 elif indicator == 'number':
127 127 if total:
128 128 add = ('% ' + str(len(str(total))) +
129 129 's/%s') % (pos, total)
130 130 else:
131 131 add = str(pos)
132 132 elif indicator.startswith('item') and item:
133 133 slice = 'end'
134 134 if '-' in indicator:
135 135 wid = int(indicator.split('-')[1])
136 136 elif '+' in indicator:
137 137 slice = 'beginning'
138 138 wid = int(indicator.split('+')[1])
139 139 else:
140 140 wid = 20
141 141 if slice == 'end':
142 142 add = encoding.trim(item, wid, leftside=True)
143 143 else:
144 144 add = encoding.trim(item, wid)
145 145 add += (wid - encoding.colwidth(add)) * ' '
146 146 elif indicator == 'bar':
147 147 add = ''
148 148 needprogress = True
149 149 elif indicator == 'unit' and unit:
150 150 add = unit
151 151 elif indicator == 'estimate':
152 152 add = self.estimate(topic, pos, total, now)
153 153 elif indicator == 'speed':
154 154 add = self.speed(topic, pos, unit, now)
155 155 if not needprogress:
156 156 head = spacejoin(head, add)
157 157 else:
158 158 tail = spacejoin(tail, add)
159 159 if needprogress:
160 160 used = 0
161 161 if head:
162 162 used += encoding.colwidth(head) + 1
163 163 if tail:
164 164 used += encoding.colwidth(tail) + 1
165 165 progwidth = termwidth - used - 3
166 166 if total and pos <= total:
167 167 amt = pos * progwidth // total
168 168 bar = '=' * (amt - 1)
169 169 if amt > 0:
170 170 bar += '>'
171 171 bar += ' ' * (progwidth - amt)
172 172 else:
173 173 progwidth -= 3
174 174 self.indetcount += 1
175 175 # mod the count by twice the width so we can make the
176 176 # cursor bounce between the right and left sides
177 177 amt = self.indetcount % (2 * progwidth)
178 178 amt -= progwidth
179 179 bar = (' ' * int(progwidth - abs(amt)) + '<=>' +
180 180 ' ' * int(abs(amt)))
181 181 prog = ''.join(('[', bar , ']'))
182 182 out = spacejoin(head, prog, tail)
183 183 else:
184 184 out = spacejoin(head, tail)
185 185 sys.stderr.write('\r' + encoding.trim(out, termwidth))
186 186 self.lasttopic = topic
187 187 sys.stderr.flush()
188 188
189 189 def clear(self):
190 190 if not shouldprint(self.ui):
191 191 return
192 192 sys.stderr.write('\r%s\r' % (' ' * self.width()))
193 193
194 194 def complete(self):
195 195 if not shouldprint(self.ui):
196 196 return
197 197 if self.ui.configbool('progress', 'clear-complete', default=True):
198 198 self.clear()
199 199 else:
200 200 sys.stderr.write('\n')
201 201 sys.stderr.flush()
202 202
203 203 def width(self):
204 204 tw = self.ui.termwidth()
205 205 return min(int(self.ui.config('progress', 'width', default=tw)), tw)
206 206
207 207 def estimate(self, topic, pos, total, now):
208 208 if total is None:
209 209 return ''
210 210 initialpos = self.startvals[topic]
211 211 target = total - initialpos
212 212 delta = pos - initialpos
213 213 if delta > 0:
214 214 elapsed = now - self.starttimes[topic]
215 215 if elapsed > float(
216 216 self.ui.config('progress', 'estimate', default=2)):
217 217 seconds = (elapsed * (target - delta)) // delta + 1
218 218 return fmtremaining(seconds)
219 219 return ''
220 220
221 221 def speed(self, topic, pos, unit, now):
222 222 initialpos = self.startvals[topic]
223 223 delta = pos - initialpos
224 224 elapsed = now - self.starttimes[topic]
225 225 if elapsed > float(
226 226 self.ui.config('progress', 'estimate', default=2)):
227 227 return _('%d %s/sec') % (delta / elapsed, unit)
228 228 return ''
229 229
230 230 def progress(self, topic, pos, item='', unit='', total=None):
231 231 now = time.time()
232 if pos is None:
233 self.starttimes.pop(topic, None)
234 self.startvals.pop(topic, None)
235 self.topicstates.pop(topic, None)
236 # reset the progress bar if this is the outermost topic
237 if self.topics and self.topics[0] == topic and self.printed:
238 self.complete()
239 self.resetstate()
240 # truncate the list of topics assuming all topics within
241 # this one are also closed
242 if topic in self.topics:
243 self.topics = self.topics[:self.topics.index(topic)]
244 # reset the last topic to the one we just unwound to,
245 # so that higher-level topics will be stickier than
246 # lower-level topics
247 if self.topics:
248 self.lasttopic = self.topics[-1]
249 else:
250 self.lasttopic = None
251 else:
252 if topic not in self.topics:
253 self.starttimes[topic] = now
254 self.startvals[topic] = pos
255 self.topics.append(topic)
256 self.topicstates[topic] = pos, item, unit, total
257 if now - self.lastprint >= self.refresh and self.topics:
258 if (self.lasttopic is None # first time we printed
259 # not a topic change
260 or topic == self.lasttopic
261 # it's been long enough we should print anyway
262 or now - self.lastprint >= self.changedelay):
263 self.lastprint = now
264 self.show(now, topic, *self.topicstates[topic])
232 try:
233 if pos is None:
234 self.starttimes.pop(topic, None)
235 self.startvals.pop(topic, None)
236 self.topicstates.pop(topic, None)
237 # reset the progress bar if this is the outermost topic
238 if self.topics and self.topics[0] == topic and self.printed:
239 self.complete()
240 self.resetstate()
241 # truncate the list of topics assuming all topics within
242 # this one are also closed
243 if topic in self.topics:
244 self.topics = self.topics[:self.topics.index(topic)]
245 # reset the last topic to the one we just unwound to,
246 # so that higher-level topics will be stickier than
247 # lower-level topics
248 if self.topics:
249 self.lasttopic = self.topics[-1]
250 else:
251 self.lasttopic = None
252 else:
253 if topic not in self.topics:
254 self.starttimes[topic] = now
255 self.startvals[topic] = pos
256 self.topics.append(topic)
257 self.topicstates[topic] = pos, item, unit, total
258 if now - self.lastprint >= self.refresh and self.topics:
259 if (self.lasttopic is None # first time we printed
260 # not a topic change
261 or topic == self.lasttopic
262 # it's been long enough we should print anyway
263 or now - self.lastprint >= self.changedelay):
264 self.lastprint = now
265 self.show(now, topic, *self.topicstates[topic])
266 finally:
267 pass
265 268
266 269 _singleton = None
267 270
268 271 def uisetup(ui):
269 272 global _singleton
270 273 class progressui(ui.__class__):
271 274 _progbar = None
272 275
273 276 def _quiet(self):
274 277 return self.debugflag or self.quiet
275 278
276 279 def progress(self, *args, **opts):
277 280 if not self._quiet():
278 281 self._progbar.progress(*args, **opts)
279 282 return super(progressui, self).progress(*args, **opts)
280 283
281 284 def write(self, *args, **opts):
282 285 if not self._quiet() and self._progbar.printed:
283 286 self._progbar.clear()
284 287 return super(progressui, self).write(*args, **opts)
285 288
286 289 def write_err(self, *args, **opts):
287 290 if not self._quiet() and self._progbar.printed:
288 291 self._progbar.clear()
289 292 return super(progressui, self).write_err(*args, **opts)
290 293
291 294 # Apps that derive a class from ui.ui() can use
292 295 # setconfig('progress', 'disable', 'True') to disable this extension
293 296 if ui.configbool('progress', 'disable'):
294 297 return
295 298 if shouldprint(ui) and not ui.debugflag and not ui.quiet:
296 299 ui.__class__ = progressui
297 300 # we instantiate one globally shared progress bar to avoid
298 301 # competing progress bars when multiple UI objects get created
299 302 if not progressui._progbar:
300 303 if _singleton is None:
301 304 _singleton = progbar(ui)
302 305 progressui._progbar = _singleton
303 306
304 307 def reposetup(ui, repo):
305 308 uisetup(repo.ui)
General Comments 0
You need to be logged in to leave comments. Login now