##// END OF EJS Templates
progress: force a repaint of a printed progress bar after a clear()...
Augie Fackler -
r26407:72bccc1f default
parent child Browse files
Show More
@@ -1,253 +1,256
1 # progress.py progress bars related code
1 # progress.py progress bars related code
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 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import sys
10 import sys
11 import threading
11 import threading
12 import time
12 import time
13
13
14 from .i18n import _
14 from .i18n import _
15 from . import encoding
15 from . import encoding
16
16
17 def spacejoin(*args):
17 def spacejoin(*args):
18 return ' '.join(s for s in args if s)
18 return ' '.join(s for s in args if s)
19
19
20 def shouldprint(ui):
20 def shouldprint(ui):
21 return not (ui.quiet or ui.plain()) and (
21 return not (ui.quiet or ui.plain()) and (
22 ui._isatty(sys.stderr) or ui.configbool('progress', 'assume-tty'))
22 ui._isatty(sys.stderr) or ui.configbool('progress', 'assume-tty'))
23
23
24 def fmtremaining(seconds):
24 def fmtremaining(seconds):
25 """format a number of remaining seconds in humain readable way
25 """format a number of remaining seconds in humain readable way
26
26
27 This will properly display seconds, minutes, hours, days if needed"""
27 This will properly display seconds, minutes, hours, days if needed"""
28 if seconds < 60:
28 if seconds < 60:
29 # i18n: format XX seconds as "XXs"
29 # i18n: format XX seconds as "XXs"
30 return _("%02ds") % (seconds)
30 return _("%02ds") % (seconds)
31 minutes = seconds // 60
31 minutes = seconds // 60
32 if minutes < 60:
32 if minutes < 60:
33 seconds -= minutes * 60
33 seconds -= minutes * 60
34 # i18n: format X minutes and YY seconds as "XmYYs"
34 # i18n: format X minutes and YY seconds as "XmYYs"
35 return _("%dm%02ds") % (minutes, seconds)
35 return _("%dm%02ds") % (minutes, seconds)
36 # we're going to ignore seconds in this case
36 # we're going to ignore seconds in this case
37 minutes += 1
37 minutes += 1
38 hours = minutes // 60
38 hours = minutes // 60
39 minutes -= hours * 60
39 minutes -= hours * 60
40 if hours < 30:
40 if hours < 30:
41 # i18n: format X hours and YY minutes as "XhYYm"
41 # i18n: format X hours and YY minutes as "XhYYm"
42 return _("%dh%02dm") % (hours, minutes)
42 return _("%dh%02dm") % (hours, minutes)
43 # we're going to ignore minutes in this case
43 # we're going to ignore minutes in this case
44 hours += 1
44 hours += 1
45 days = hours // 24
45 days = hours // 24
46 hours -= days * 24
46 hours -= days * 24
47 if days < 15:
47 if days < 15:
48 # i18n: format X days and YY hours as "XdYYh"
48 # i18n: format X days and YY hours as "XdYYh"
49 return _("%dd%02dh") % (days, hours)
49 return _("%dd%02dh") % (days, hours)
50 # we're going to ignore hours in this case
50 # we're going to ignore hours in this case
51 days += 1
51 days += 1
52 weeks = days // 7
52 weeks = days // 7
53 days -= weeks * 7
53 days -= weeks * 7
54 if weeks < 55:
54 if weeks < 55:
55 # i18n: format X weeks and YY days as "XwYYd"
55 # i18n: format X weeks and YY days as "XwYYd"
56 return _("%dw%02dd") % (weeks, days)
56 return _("%dw%02dd") % (weeks, days)
57 # we're going to ignore days and treat a year as 52 weeks
57 # we're going to ignore days and treat a year as 52 weeks
58 weeks += 1
58 weeks += 1
59 years = weeks // 52
59 years = weeks // 52
60 weeks -= years * 52
60 weeks -= years * 52
61 # i18n: format X years and YY weeks as "XyYYw"
61 # i18n: format X years and YY weeks as "XyYYw"
62 return _("%dy%02dw") % (years, weeks)
62 return _("%dy%02dw") % (years, weeks)
63
63
64 class progbar(object):
64 class progbar(object):
65 def __init__(self, ui):
65 def __init__(self, ui):
66 self.ui = ui
66 self.ui = ui
67 self._refreshlock = threading.Lock()
67 self._refreshlock = threading.Lock()
68 self.resetstate()
68 self.resetstate()
69
69
70 def resetstate(self):
70 def resetstate(self):
71 self.topics = []
71 self.topics = []
72 self.topicstates = {}
72 self.topicstates = {}
73 self.starttimes = {}
73 self.starttimes = {}
74 self.startvals = {}
74 self.startvals = {}
75 self.printed = False
75 self.printed = False
76 self.lastprint = time.time() + float(self.ui.config(
76 self.lastprint = time.time() + float(self.ui.config(
77 'progress', 'delay', default=3))
77 'progress', 'delay', default=3))
78 self.curtopic = None
78 self.curtopic = None
79 self.lasttopic = None
79 self.lasttopic = None
80 self.indetcount = 0
80 self.indetcount = 0
81 self.refresh = float(self.ui.config(
81 self.refresh = float(self.ui.config(
82 'progress', 'refresh', default=0.1))
82 'progress', 'refresh', default=0.1))
83 self.changedelay = max(3 * self.refresh,
83 self.changedelay = max(3 * self.refresh,
84 float(self.ui.config(
84 float(self.ui.config(
85 'progress', 'changedelay', default=1)))
85 'progress', 'changedelay', default=1)))
86 self.order = self.ui.configlist(
86 self.order = self.ui.configlist(
87 'progress', 'format',
87 'progress', 'format',
88 default=['topic', 'bar', 'number', 'estimate'])
88 default=['topic', 'bar', 'number', 'estimate'])
89
89
90 def show(self, now, topic, pos, item, unit, total):
90 def show(self, now, topic, pos, item, unit, total):
91 if not shouldprint(self.ui):
91 if not shouldprint(self.ui):
92 return
92 return
93 termwidth = self.width()
93 termwidth = self.width()
94 self.printed = True
94 self.printed = True
95 head = ''
95 head = ''
96 needprogress = False
96 needprogress = False
97 tail = ''
97 tail = ''
98 for indicator in self.order:
98 for indicator in self.order:
99 add = ''
99 add = ''
100 if indicator == 'topic':
100 if indicator == 'topic':
101 add = topic
101 add = topic
102 elif indicator == 'number':
102 elif indicator == 'number':
103 if total:
103 if total:
104 add = ('% ' + str(len(str(total))) +
104 add = ('% ' + str(len(str(total))) +
105 's/%s') % (pos, total)
105 's/%s') % (pos, total)
106 else:
106 else:
107 add = str(pos)
107 add = str(pos)
108 elif indicator.startswith('item') and item:
108 elif indicator.startswith('item') and item:
109 slice = 'end'
109 slice = 'end'
110 if '-' in indicator:
110 if '-' in indicator:
111 wid = int(indicator.split('-')[1])
111 wid = int(indicator.split('-')[1])
112 elif '+' in indicator:
112 elif '+' in indicator:
113 slice = 'beginning'
113 slice = 'beginning'
114 wid = int(indicator.split('+')[1])
114 wid = int(indicator.split('+')[1])
115 else:
115 else:
116 wid = 20
116 wid = 20
117 if slice == 'end':
117 if slice == 'end':
118 add = encoding.trim(item, wid, leftside=True)
118 add = encoding.trim(item, wid, leftside=True)
119 else:
119 else:
120 add = encoding.trim(item, wid)
120 add = encoding.trim(item, wid)
121 add += (wid - encoding.colwidth(add)) * ' '
121 add += (wid - encoding.colwidth(add)) * ' '
122 elif indicator == 'bar':
122 elif indicator == 'bar':
123 add = ''
123 add = ''
124 needprogress = True
124 needprogress = True
125 elif indicator == 'unit' and unit:
125 elif indicator == 'unit' and unit:
126 add = unit
126 add = unit
127 elif indicator == 'estimate':
127 elif indicator == 'estimate':
128 add = self.estimate(topic, pos, total, now)
128 add = self.estimate(topic, pos, total, now)
129 elif indicator == 'speed':
129 elif indicator == 'speed':
130 add = self.speed(topic, pos, unit, now)
130 add = self.speed(topic, pos, unit, now)
131 if not needprogress:
131 if not needprogress:
132 head = spacejoin(head, add)
132 head = spacejoin(head, add)
133 else:
133 else:
134 tail = spacejoin(tail, add)
134 tail = spacejoin(tail, add)
135 if needprogress:
135 if needprogress:
136 used = 0
136 used = 0
137 if head:
137 if head:
138 used += encoding.colwidth(head) + 1
138 used += encoding.colwidth(head) + 1
139 if tail:
139 if tail:
140 used += encoding.colwidth(tail) + 1
140 used += encoding.colwidth(tail) + 1
141 progwidth = termwidth - used - 3
141 progwidth = termwidth - used - 3
142 if total and pos <= total:
142 if total and pos <= total:
143 amt = pos * progwidth // total
143 amt = pos * progwidth // total
144 bar = '=' * (amt - 1)
144 bar = '=' * (amt - 1)
145 if amt > 0:
145 if amt > 0:
146 bar += '>'
146 bar += '>'
147 bar += ' ' * (progwidth - amt)
147 bar += ' ' * (progwidth - amt)
148 else:
148 else:
149 progwidth -= 3
149 progwidth -= 3
150 self.indetcount += 1
150 self.indetcount += 1
151 # mod the count by twice the width so we can make the
151 # mod the count by twice the width so we can make the
152 # cursor bounce between the right and left sides
152 # cursor bounce between the right and left sides
153 amt = self.indetcount % (2 * progwidth)
153 amt = self.indetcount % (2 * progwidth)
154 amt -= progwidth
154 amt -= progwidth
155 bar = (' ' * int(progwidth - abs(amt)) + '<=>' +
155 bar = (' ' * int(progwidth - abs(amt)) + '<=>' +
156 ' ' * int(abs(amt)))
156 ' ' * int(abs(amt)))
157 prog = ''.join(('[', bar , ']'))
157 prog = ''.join(('[', bar , ']'))
158 out = spacejoin(head, prog, tail)
158 out = spacejoin(head, prog, tail)
159 else:
159 else:
160 out = spacejoin(head, tail)
160 out = spacejoin(head, tail)
161 sys.stderr.write('\r' + encoding.trim(out, termwidth))
161 sys.stderr.write('\r' + encoding.trim(out, termwidth))
162 self.lasttopic = topic
162 self.lasttopic = topic
163 sys.stderr.flush()
163 sys.stderr.flush()
164
164
165 def clear(self):
165 def clear(self):
166 if not shouldprint(self.ui):
166 if not shouldprint(self.ui):
167 return
167 return
168 sys.stderr.write('\r%s\r' % (' ' * self.width()))
168 sys.stderr.write('\r%s\r' % (' ' * self.width()))
169 if self.printed:
170 # force immediate re-paint of progress bar
171 self.lastprint = 0
169
172
170 def complete(self):
173 def complete(self):
171 if not shouldprint(self.ui):
174 if not shouldprint(self.ui):
172 return
175 return
173 if self.ui.configbool('progress', 'clear-complete', default=True):
176 if self.ui.configbool('progress', 'clear-complete', default=True):
174 self.clear()
177 self.clear()
175 else:
178 else:
176 sys.stderr.write('\n')
179 sys.stderr.write('\n')
177 sys.stderr.flush()
180 sys.stderr.flush()
178
181
179 def width(self):
182 def width(self):
180 tw = self.ui.termwidth()
183 tw = self.ui.termwidth()
181 return min(int(self.ui.config('progress', 'width', default=tw)), tw)
184 return min(int(self.ui.config('progress', 'width', default=tw)), tw)
182
185
183 def estimate(self, topic, pos, total, now):
186 def estimate(self, topic, pos, total, now):
184 if total is None:
187 if total is None:
185 return ''
188 return ''
186 initialpos = self.startvals[topic]
189 initialpos = self.startvals[topic]
187 target = total - initialpos
190 target = total - initialpos
188 delta = pos - initialpos
191 delta = pos - initialpos
189 if delta > 0:
192 if delta > 0:
190 elapsed = now - self.starttimes[topic]
193 elapsed = now - self.starttimes[topic]
191 # experimental config: progress.estimate
194 # experimental config: progress.estimate
192 if elapsed > float(
195 if elapsed > float(
193 self.ui.config('progress', 'estimate', default=2)):
196 self.ui.config('progress', 'estimate', default=2)):
194 seconds = (elapsed * (target - delta)) // delta + 1
197 seconds = (elapsed * (target - delta)) // delta + 1
195 return fmtremaining(seconds)
198 return fmtremaining(seconds)
196 return ''
199 return ''
197
200
198 def speed(self, topic, pos, unit, now):
201 def speed(self, topic, pos, unit, now):
199 initialpos = self.startvals[topic]
202 initialpos = self.startvals[topic]
200 delta = pos - initialpos
203 delta = pos - initialpos
201 elapsed = now - self.starttimes[topic]
204 elapsed = now - self.starttimes[topic]
202 if elapsed > float(
205 if elapsed > float(
203 self.ui.config('progress', 'estimate', default=2)):
206 self.ui.config('progress', 'estimate', default=2)):
204 return _('%d %s/sec') % (delta / elapsed, unit)
207 return _('%d %s/sec') % (delta / elapsed, unit)
205 return ''
208 return ''
206
209
207 def _oktoprint(self, now):
210 def _oktoprint(self, now):
208 '''Check if conditions are met to print - e.g. changedelay elapsed'''
211 '''Check if conditions are met to print - e.g. changedelay elapsed'''
209 if (self.lasttopic is None # first time we printed
212 if (self.lasttopic is None # first time we printed
210 # not a topic change
213 # not a topic change
211 or self.curtopic == self.lasttopic
214 or self.curtopic == self.lasttopic
212 # it's been long enough we should print anyway
215 # it's been long enough we should print anyway
213 or now - self.lastprint >= self.changedelay):
216 or now - self.lastprint >= self.changedelay):
214 return True
217 return True
215 else:
218 else:
216 return False
219 return False
217
220
218 def progress(self, topic, pos, item='', unit='', total=None):
221 def progress(self, topic, pos, item='', unit='', total=None):
219 now = time.time()
222 now = time.time()
220 self._refreshlock.acquire()
223 self._refreshlock.acquire()
221 try:
224 try:
222 if pos is None:
225 if pos is None:
223 self.starttimes.pop(topic, None)
226 self.starttimes.pop(topic, None)
224 self.startvals.pop(topic, None)
227 self.startvals.pop(topic, None)
225 self.topicstates.pop(topic, None)
228 self.topicstates.pop(topic, None)
226 # reset the progress bar if this is the outermost topic
229 # reset the progress bar if this is the outermost topic
227 if self.topics and self.topics[0] == topic and self.printed:
230 if self.topics and self.topics[0] == topic and self.printed:
228 self.complete()
231 self.complete()
229 self.resetstate()
232 self.resetstate()
230 # truncate the list of topics assuming all topics within
233 # truncate the list of topics assuming all topics within
231 # this one are also closed
234 # this one are also closed
232 if topic in self.topics:
235 if topic in self.topics:
233 self.topics = self.topics[:self.topics.index(topic)]
236 self.topics = self.topics[:self.topics.index(topic)]
234 # reset the last topic to the one we just unwound to,
237 # reset the last topic to the one we just unwound to,
235 # so that higher-level topics will be stickier than
238 # so that higher-level topics will be stickier than
236 # lower-level topics
239 # lower-level topics
237 if self.topics:
240 if self.topics:
238 self.lasttopic = self.topics[-1]
241 self.lasttopic = self.topics[-1]
239 else:
242 else:
240 self.lasttopic = None
243 self.lasttopic = None
241 else:
244 else:
242 if topic not in self.topics:
245 if topic not in self.topics:
243 self.starttimes[topic] = now
246 self.starttimes[topic] = now
244 self.startvals[topic] = pos
247 self.startvals[topic] = pos
245 self.topics.append(topic)
248 self.topics.append(topic)
246 self.topicstates[topic] = pos, item, unit, total
249 self.topicstates[topic] = pos, item, unit, total
247 self.curtopic = topic
250 self.curtopic = topic
248 if now - self.lastprint >= self.refresh and self.topics:
251 if now - self.lastprint >= self.refresh and self.topics:
249 if self._oktoprint(now):
252 if self._oktoprint(now):
250 self.lastprint = now
253 self.lastprint = now
251 self.show(now, topic, *self.topicstates[topic])
254 self.show(now, topic, *self.topicstates[topic])
252 finally:
255 finally:
253 self._refreshlock.release()
256 self._refreshlock.release()
General Comments 0
You need to be logged in to leave comments. Login now