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