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