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