##// END OF EJS Templates
progress: move update check into helper method
Solomon Matthews -
r23907:63c7783a default
parent child Browse files
Show More
@@ -1,310 +1,317 b''
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.curtopic = None
103 self.curtopic = None
104 self.lasttopic = None
104 self.lasttopic = None
105 self.indetcount = 0
105 self.indetcount = 0
106 self.refresh = float(self.ui.config(
106 self.refresh = float(self.ui.config(
107 'progress', 'refresh', default=0.1))
107 'progress', 'refresh', default=0.1))
108 self.changedelay = max(3 * self.refresh,
108 self.changedelay = max(3 * self.refresh,
109 float(self.ui.config(
109 float(self.ui.config(
110 'progress', 'changedelay', default=1)))
110 'progress', 'changedelay', default=1)))
111 self.order = self.ui.configlist(
111 self.order = self.ui.configlist(
112 'progress', 'format',
112 'progress', 'format',
113 default=['topic', 'bar', 'number', 'estimate'])
113 default=['topic', 'bar', 'number', 'estimate'])
114
114
115 def show(self, now, topic, pos, item, unit, total):
115 def show(self, now, topic, pos, item, unit, total):
116 if not shouldprint(self.ui):
116 if not shouldprint(self.ui):
117 return
117 return
118 termwidth = self.width()
118 termwidth = self.width()
119 self.printed = True
119 self.printed = True
120 head = ''
120 head = ''
121 needprogress = False
121 needprogress = False
122 tail = ''
122 tail = ''
123 for indicator in self.order:
123 for indicator in self.order:
124 add = ''
124 add = ''
125 if indicator == 'topic':
125 if indicator == 'topic':
126 add = topic
126 add = topic
127 elif indicator == 'number':
127 elif indicator == 'number':
128 if total:
128 if total:
129 add = ('% ' + str(len(str(total))) +
129 add = ('% ' + str(len(str(total))) +
130 's/%s') % (pos, total)
130 's/%s') % (pos, total)
131 else:
131 else:
132 add = str(pos)
132 add = str(pos)
133 elif indicator.startswith('item') and item:
133 elif indicator.startswith('item') and item:
134 slice = 'end'
134 slice = 'end'
135 if '-' in indicator:
135 if '-' in indicator:
136 wid = int(indicator.split('-')[1])
136 wid = int(indicator.split('-')[1])
137 elif '+' in indicator:
137 elif '+' in indicator:
138 slice = 'beginning'
138 slice = 'beginning'
139 wid = int(indicator.split('+')[1])
139 wid = int(indicator.split('+')[1])
140 else:
140 else:
141 wid = 20
141 wid = 20
142 if slice == 'end':
142 if slice == 'end':
143 add = encoding.trim(item, wid, leftside=True)
143 add = encoding.trim(item, wid, leftside=True)
144 else:
144 else:
145 add = encoding.trim(item, wid)
145 add = encoding.trim(item, wid)
146 add += (wid - encoding.colwidth(add)) * ' '
146 add += (wid - encoding.colwidth(add)) * ' '
147 elif indicator == 'bar':
147 elif indicator == 'bar':
148 add = ''
148 add = ''
149 needprogress = True
149 needprogress = True
150 elif indicator == 'unit' and unit:
150 elif indicator == 'unit' and unit:
151 add = unit
151 add = unit
152 elif indicator == 'estimate':
152 elif indicator == 'estimate':
153 add = self.estimate(topic, pos, total, now)
153 add = self.estimate(topic, pos, total, now)
154 elif indicator == 'speed':
154 elif indicator == 'speed':
155 add = self.speed(topic, pos, unit, now)
155 add = self.speed(topic, pos, unit, now)
156 if not needprogress:
156 if not needprogress:
157 head = spacejoin(head, add)
157 head = spacejoin(head, add)
158 else:
158 else:
159 tail = spacejoin(tail, add)
159 tail = spacejoin(tail, add)
160 if needprogress:
160 if needprogress:
161 used = 0
161 used = 0
162 if head:
162 if head:
163 used += encoding.colwidth(head) + 1
163 used += encoding.colwidth(head) + 1
164 if tail:
164 if tail:
165 used += encoding.colwidth(tail) + 1
165 used += encoding.colwidth(tail) + 1
166 progwidth = termwidth - used - 3
166 progwidth = termwidth - used - 3
167 if total and pos <= total:
167 if total and pos <= total:
168 amt = pos * progwidth // total
168 amt = pos * progwidth // total
169 bar = '=' * (amt - 1)
169 bar = '=' * (amt - 1)
170 if amt > 0:
170 if amt > 0:
171 bar += '>'
171 bar += '>'
172 bar += ' ' * (progwidth - amt)
172 bar += ' ' * (progwidth - amt)
173 else:
173 else:
174 progwidth -= 3
174 progwidth -= 3
175 self.indetcount += 1
175 self.indetcount += 1
176 # mod the count by twice the width so we can make the
176 # mod the count by twice the width so we can make the
177 # cursor bounce between the right and left sides
177 # cursor bounce between the right and left sides
178 amt = self.indetcount % (2 * progwidth)
178 amt = self.indetcount % (2 * progwidth)
179 amt -= progwidth
179 amt -= progwidth
180 bar = (' ' * int(progwidth - abs(amt)) + '<=>' +
180 bar = (' ' * int(progwidth - abs(amt)) + '<=>' +
181 ' ' * int(abs(amt)))
181 ' ' * int(abs(amt)))
182 prog = ''.join(('[', bar , ']'))
182 prog = ''.join(('[', bar , ']'))
183 out = spacejoin(head, prog, tail)
183 out = spacejoin(head, prog, tail)
184 else:
184 else:
185 out = spacejoin(head, tail)
185 out = spacejoin(head, tail)
186 sys.stderr.write('\r' + encoding.trim(out, termwidth))
186 sys.stderr.write('\r' + encoding.trim(out, termwidth))
187 self.lasttopic = topic
187 self.lasttopic = topic
188 sys.stderr.flush()
188 sys.stderr.flush()
189
189
190 def clear(self):
190 def clear(self):
191 if not shouldprint(self.ui):
191 if not shouldprint(self.ui):
192 return
192 return
193 sys.stderr.write('\r%s\r' % (' ' * self.width()))
193 sys.stderr.write('\r%s\r' % (' ' * self.width()))
194
194
195 def complete(self):
195 def complete(self):
196 if not shouldprint(self.ui):
196 if not shouldprint(self.ui):
197 return
197 return
198 if self.ui.configbool('progress', 'clear-complete', default=True):
198 if self.ui.configbool('progress', 'clear-complete', default=True):
199 self.clear()
199 self.clear()
200 else:
200 else:
201 sys.stderr.write('\n')
201 sys.stderr.write('\n')
202 sys.stderr.flush()
202 sys.stderr.flush()
203
203
204 def width(self):
204 def width(self):
205 tw = self.ui.termwidth()
205 tw = self.ui.termwidth()
206 return min(int(self.ui.config('progress', 'width', default=tw)), tw)
206 return min(int(self.ui.config('progress', 'width', default=tw)), tw)
207
207
208 def estimate(self, topic, pos, total, now):
208 def estimate(self, topic, pos, total, now):
209 if total is None:
209 if total is None:
210 return ''
210 return ''
211 initialpos = self.startvals[topic]
211 initialpos = self.startvals[topic]
212 target = total - initialpos
212 target = total - initialpos
213 delta = pos - initialpos
213 delta = pos - initialpos
214 if delta > 0:
214 if delta > 0:
215 elapsed = now - self.starttimes[topic]
215 elapsed = now - self.starttimes[topic]
216 if elapsed > float(
216 if elapsed > float(
217 self.ui.config('progress', 'estimate', default=2)):
217 self.ui.config('progress', 'estimate', default=2)):
218 seconds = (elapsed * (target - delta)) // delta + 1
218 seconds = (elapsed * (target - delta)) // delta + 1
219 return fmtremaining(seconds)
219 return fmtremaining(seconds)
220 return ''
220 return ''
221
221
222 def speed(self, topic, pos, unit, now):
222 def speed(self, topic, pos, unit, now):
223 initialpos = self.startvals[topic]
223 initialpos = self.startvals[topic]
224 delta = pos - initialpos
224 delta = pos - initialpos
225 elapsed = now - self.starttimes[topic]
225 elapsed = now - self.starttimes[topic]
226 if elapsed > float(
226 if elapsed > float(
227 self.ui.config('progress', 'estimate', default=2)):
227 self.ui.config('progress', 'estimate', default=2)):
228 return _('%d %s/sec') % (delta / elapsed, unit)
228 return _('%d %s/sec') % (delta / elapsed, unit)
229 return ''
229 return ''
230
230
231 def _oktoprint(self, now):
232 '''Check if conditions are met to print - e.g. changedelay elapsed'''
233 if (self.lasttopic is None # first time we printed
234 # not a topic change
235 or self.curtopic == self.lasttopic
236 # it's been long enough we should print anyway
237 or now - self.lastprint >= self.changedelay):
238 return True
239 else:
240 return False
241
231 def progress(self, topic, pos, item='', unit='', total=None):
242 def progress(self, topic, pos, item='', unit='', total=None):
232 now = time.time()
243 now = time.time()
233 try:
244 try:
234 if pos is None:
245 if pos is None:
235 self.starttimes.pop(topic, None)
246 self.starttimes.pop(topic, None)
236 self.startvals.pop(topic, None)
247 self.startvals.pop(topic, None)
237 self.topicstates.pop(topic, None)
248 self.topicstates.pop(topic, None)
238 # reset the progress bar if this is the outermost topic
249 # reset the progress bar if this is the outermost topic
239 if self.topics and self.topics[0] == topic and self.printed:
250 if self.topics and self.topics[0] == topic and self.printed:
240 self.complete()
251 self.complete()
241 self.resetstate()
252 self.resetstate()
242 # truncate the list of topics assuming all topics within
253 # truncate the list of topics assuming all topics within
243 # this one are also closed
254 # this one are also closed
244 if topic in self.topics:
255 if topic in self.topics:
245 self.topics = self.topics[:self.topics.index(topic)]
256 self.topics = self.topics[:self.topics.index(topic)]
246 # reset the last topic to the one we just unwound to,
257 # reset the last topic to the one we just unwound to,
247 # so that higher-level topics will be stickier than
258 # so that higher-level topics will be stickier than
248 # lower-level topics
259 # lower-level topics
249 if self.topics:
260 if self.topics:
250 self.lasttopic = self.topics[-1]
261 self.lasttopic = self.topics[-1]
251 else:
262 else:
252 self.lasttopic = None
263 self.lasttopic = None
253 else:
264 else:
254 if topic not in self.topics:
265 if topic not in self.topics:
255 self.starttimes[topic] = now
266 self.starttimes[topic] = now
256 self.startvals[topic] = pos
267 self.startvals[topic] = pos
257 self.topics.append(topic)
268 self.topics.append(topic)
258 self.topicstates[topic] = pos, item, unit, total
269 self.topicstates[topic] = pos, item, unit, total
259 self.curtopic = topic
270 self.curtopic = topic
260 if now - self.lastprint >= self.refresh and self.topics:
271 if now - self.lastprint >= self.refresh and self.topics:
261 if (self.lasttopic is None # first time we printed
272 if self._oktoprint(now):
262 # not a topic change
263 or topic == self.lasttopic
264 # it's been long enough we should print anyway
265 or now - self.lastprint >= self.changedelay):
266 self.lastprint = now
273 self.lastprint = now
267 self.show(now, topic, *self.topicstates[topic])
274 self.show(now, topic, *self.topicstates[topic])
268 finally:
275 finally:
269 pass
276 pass
270
277
271 _singleton = None
278 _singleton = None
272
279
273 def uisetup(ui):
280 def uisetup(ui):
274 global _singleton
281 global _singleton
275 class progressui(ui.__class__):
282 class progressui(ui.__class__):
276 _progbar = None
283 _progbar = None
277
284
278 def _quiet(self):
285 def _quiet(self):
279 return self.debugflag or self.quiet
286 return self.debugflag or self.quiet
280
287
281 def progress(self, *args, **opts):
288 def progress(self, *args, **opts):
282 if not self._quiet():
289 if not self._quiet():
283 self._progbar.progress(*args, **opts)
290 self._progbar.progress(*args, **opts)
284 return super(progressui, self).progress(*args, **opts)
291 return super(progressui, self).progress(*args, **opts)
285
292
286 def write(self, *args, **opts):
293 def write(self, *args, **opts):
287 if not self._quiet() and self._progbar.printed:
294 if not self._quiet() and self._progbar.printed:
288 self._progbar.clear()
295 self._progbar.clear()
289 return super(progressui, self).write(*args, **opts)
296 return super(progressui, self).write(*args, **opts)
290
297
291 def write_err(self, *args, **opts):
298 def write_err(self, *args, **opts):
292 if not self._quiet() and self._progbar.printed:
299 if not self._quiet() and self._progbar.printed:
293 self._progbar.clear()
300 self._progbar.clear()
294 return super(progressui, self).write_err(*args, **opts)
301 return super(progressui, self).write_err(*args, **opts)
295
302
296 # Apps that derive a class from ui.ui() can use
303 # Apps that derive a class from ui.ui() can use
297 # setconfig('progress', 'disable', 'True') to disable this extension
304 # setconfig('progress', 'disable', 'True') to disable this extension
298 if ui.configbool('progress', 'disable'):
305 if ui.configbool('progress', 'disable'):
299 return
306 return
300 if shouldprint(ui) and not ui.debugflag and not ui.quiet:
307 if shouldprint(ui) and not ui.debugflag and not ui.quiet:
301 ui.__class__ = progressui
308 ui.__class__ = progressui
302 # we instantiate one globally shared progress bar to avoid
309 # we instantiate one globally shared progress bar to avoid
303 # competing progress bars when multiple UI objects get created
310 # competing progress bars when multiple UI objects get created
304 if not progressui._progbar:
311 if not progressui._progbar:
305 if _singleton is None:
312 if _singleton is None:
306 _singleton = progbar(ui)
313 _singleton = progbar(ui)
307 progressui._progbar = _singleton
314 progressui._progbar = _singleton
308
315
309 def reposetup(ui, repo):
316 def reposetup(ui, repo):
310 uisetup(repo.ui)
317 uisetup(repo.ui)
General Comments 0
You need to be logged in to leave comments. Login now