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