##// END OF EJS Templates
progress: Add estimated time remaining for long tasks...
timeless -
r13131:c9ae7e09 default
parent child Browse files
Show More
@@ -1,214 +1,241 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 program is free software; you can redistribute it and/or modify it
5 # This program is free software; you can redistribute it and/or modify it
6 # under the terms of the GNU General Public License as published by the
6 # under the terms of the GNU General Public License as published by the
7 # Free Software Foundation; either version 2 of the License, or (at your
7 # Free Software Foundation; either version 2 of the License, or (at your
8 # option) any later version.
8 # option) any later version.
9 #
9 #
10 # This program is distributed in the hope that it will be useful, but
10 # This program is distributed in the hope that it will be useful, but
11 # WITHOUT ANY WARRANTY; without even the implied warranty of
11 # WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
13 # Public License for more details.
13 # Public License for more details.
14 #
14 #
15 # You should have received a copy of the GNU General Public License along
15 # You should have received a copy of the GNU General Public License along
16 # with this program; if not, write to the Free Software Foundation, Inc.,
16 # with this program; if not, write to the Free Software Foundation, Inc.,
17 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18
18
19 """show progress bars for some actions
19 """show progress bars for some actions
20
20
21 This extension uses the progress information logged by hg commands
21 This extension uses the progress information logged by hg commands
22 to draw progress bars that are as informative as possible. Some progress
22 to draw progress bars that are as informative as possible. Some progress
23 bars only offer indeterminate information, while others have a definite
23 bars only offer indeterminate information, while others have a definite
24 end point.
24 end point.
25
25
26 The following settings are available::
26 The following settings are available::
27
27
28 [progress]
28 [progress]
29 delay = 3 # number of seconds (float) before showing the progress bar
29 delay = 3 # number of seconds (float) before showing the progress bar
30 refresh = 0.1 # time in seconds between refreshes of the progress bar
30 refresh = 0.1 # time in seconds between refreshes of the progress bar
31 format = topic bar number # format of the progress bar
31 format = topic bar number # format of the progress bar
32 width = <none> # if set, the maximum width of the progress information
32 width = <none> # if set, the maximum width of the progress information
33 # (that is, min(width, term width) will be used)
33 # (that is, min(width, term width) will be used)
34 clear-complete = True # clear the progress bar after it's done
34 clear-complete = True # clear the progress bar after it's done
35 disable = False # if true, don't show a progress bar
35 disable = False # if true, don't show a progress bar
36 assume-tty = False # if true, ALWAYS show a progress bar, unless
36 assume-tty = False # if true, ALWAYS show a progress bar, unless
37 # disable is given
37 # disable is given
38
38
39 Valid entries for the format field are topic, bar, number, unit, and
39 Valid entries for the format field are topic, bar, number, unit, and
40 item. item defaults to the last 20 characters of the item, but this
40 item. item defaults to the last 20 characters of the item, but this
41 can be changed by adding either ``-<num>`` which would take the last
41 can be changed by adding either ``-<num>`` which would take the last
42 num characters, or ``+<num>`` for the first num characters.
42 num characters, or ``+<num>`` for the first num characters.
43 """
43 """
44
44
45 import sys
45 import sys
46 import time
46 import time
47
47
48 from mercurial.i18n import _
48 from mercurial import util
49 from mercurial import util
49
50
50 def spacejoin(*args):
51 def spacejoin(*args):
51 return ' '.join(s for s in args if s)
52 return ' '.join(s for s in args if s)
52
53
53 def shouldprint(ui):
54 def shouldprint(ui):
54 return (getattr(sys.stderr, 'isatty', None) and
55 return (getattr(sys.stderr, 'isatty', None) and
55 (sys.stderr.isatty() or ui.configbool('progress', 'assume-tty')))
56 (sys.stderr.isatty() or ui.configbool('progress', 'assume-tty')))
56
57
57 class progbar(object):
58 class progbar(object):
58 def __init__(self, ui):
59 def __init__(self, ui):
59 self.ui = ui
60 self.ui = ui
60 self.resetstate()
61 self.resetstate()
61
62
62 def resetstate(self):
63 def resetstate(self):
63 self.topics = []
64 self.topics = []
64 self.topicstates = {}
65 self.topicstates = {}
66 self.starttimes = {}
67 self.startvals = {}
65 self.printed = False
68 self.printed = False
66 self.lastprint = time.time() + float(self.ui.config(
69 self.lastprint = time.time() + float(self.ui.config(
67 'progress', 'delay', default=3))
70 'progress', 'delay', default=3))
68 self.indetcount = 0
71 self.indetcount = 0
69 self.refresh = float(self.ui.config(
72 self.refresh = float(self.ui.config(
70 'progress', 'refresh', default=0.1))
73 'progress', 'refresh', default=0.1))
71 self.order = self.ui.configlist(
74 self.order = self.ui.configlist(
72 'progress', 'format',
75 'progress', 'format',
73 default=['topic', 'bar', 'number'])
76 default=['topic', 'bar', 'number'])
74
77
75 def show(self, topic, pos, item, unit, total):
78 def show(self, now, topic, pos, item, unit, total):
76 if not shouldprint(self.ui):
79 if not shouldprint(self.ui):
77 return
80 return
78 termwidth = self.width()
81 termwidth = self.width()
79 self.printed = True
82 self.printed = True
80 head = ''
83 head = ''
81 needprogress = False
84 needprogress = False
82 tail = ''
85 tail = ''
83 for indicator in self.order:
86 for indicator in self.order:
84 add = ''
87 add = ''
85 if indicator == 'topic':
88 if indicator == 'topic':
86 add = topic
89 add = topic
87 elif indicator == 'number':
90 elif indicator == 'number':
88 if total:
91 if total:
89 add = ('% ' + str(len(str(total))) +
92 add = ('% ' + str(len(str(total))) +
90 's/%s') % (pos, total)
93 's/%s') % (pos, total)
91 else:
94 else:
92 add = str(pos)
95 add = str(pos)
93 elif indicator.startswith('item') and item:
96 elif indicator.startswith('item') and item:
94 slice = 'end'
97 slice = 'end'
95 if '-' in indicator:
98 if '-' in indicator:
96 wid = int(indicator.split('-')[1])
99 wid = int(indicator.split('-')[1])
97 elif '+' in indicator:
100 elif '+' in indicator:
98 slice = 'beginning'
101 slice = 'beginning'
99 wid = int(indicator.split('+')[1])
102 wid = int(indicator.split('+')[1])
100 else:
103 else:
101 wid = 20
104 wid = 20
102 if slice == 'end':
105 if slice == 'end':
103 add = item[-wid:]
106 add = item[-wid:]
104 else:
107 else:
105 add = item[:wid]
108 add = item[:wid]
106 add += (wid - len(add)) * ' '
109 add += (wid - len(add)) * ' '
107 elif indicator == 'bar':
110 elif indicator == 'bar':
108 add = ''
111 add = ''
109 needprogress = True
112 needprogress = True
110 elif indicator == 'unit' and unit:
113 elif indicator == 'unit' and unit:
111 add = unit
114 add = unit
112 if not needprogress:
115 if not needprogress:
113 head = spacejoin(head, add)
116 head = spacejoin(head, add)
114 else:
117 else:
115 tail = spacejoin(add, tail)
118 tail = spacejoin(add, tail)
116 if needprogress:
119 if needprogress:
117 used = 0
120 used = 0
118 if head:
121 if head:
119 used += len(head) + 1
122 used += len(head) + 1
120 if tail:
123 if tail:
121 used += len(tail) + 1
124 used += len(tail) + 1
122 progwidth = termwidth - used - 3
125 progwidth = termwidth - used - 3
123 if total and pos <= total:
126 if total and pos <= total:
127 initial = self.startvals[topic]
128 target = total - initial
129 delta = pos - initial
130 if delta > 0:
131 elapsed = now - self.starttimes[topic]
132 if elapsed > float(
133 self.ui.config('progress', 'estimate', default=2)):
134 seconds = (elapsed * (target - delta)) // delta + 1
135 minutes = seconds // 60
136 if minutes < 10:
137 seconds -= minutes * 60
138 remaining = _("%dm%02ds") % (minutes, seconds)
139 else:
140 # we're going to ignore seconds in this case
141 minutes += 1
142 hours = minutes // 60
143 minutes -= hours * 60
144 remaining = _("%dh%02dm") % (hours, minutes)
145 progwidth -= len(remaining) + 1
146 tail = spacejoin(tail, remaining)
124 amt = pos * progwidth // total
147 amt = pos * progwidth // total
125 bar = '=' * (amt - 1)
148 bar = '=' * (amt - 1)
126 if amt > 0:
149 if amt > 0:
127 bar += '>'
150 bar += '>'
128 bar += ' ' * (progwidth - amt)
151 bar += ' ' * (progwidth - amt)
129 else:
152 else:
130 progwidth -= 3
153 progwidth -= 3
131 self.indetcount += 1
154 self.indetcount += 1
132 # mod the count by twice the width so we can make the
155 # mod the count by twice the width so we can make the
133 # cursor bounce between the right and left sides
156 # cursor bounce between the right and left sides
134 amt = self.indetcount % (2 * progwidth)
157 amt = self.indetcount % (2 * progwidth)
135 amt -= progwidth
158 amt -= progwidth
136 bar = (' ' * int(progwidth - abs(amt)) + '<=>' +
159 bar = (' ' * int(progwidth - abs(amt)) + '<=>' +
137 ' ' * int(abs(amt)))
160 ' ' * int(abs(amt)))
138 prog = ''.join(('[', bar , ']'))
161 prog = ''.join(('[', bar , ']'))
139 out = spacejoin(head, prog, tail)
162 out = spacejoin(head, prog, tail)
140 else:
163 else:
141 out = spacejoin(head, tail)
164 out = spacejoin(head, tail)
142 sys.stderr.write('\r' + out[:termwidth])
165 sys.stderr.write('\r' + out[:termwidth])
143 sys.stderr.flush()
166 sys.stderr.flush()
144
167
145 def clear(self):
168 def clear(self):
146 if not shouldprint(self.ui):
169 if not shouldprint(self.ui):
147 return
170 return
148 sys.stderr.write('\r%s\r' % (' ' * self.width()))
171 sys.stderr.write('\r%s\r' % (' ' * self.width()))
149
172
150 def complete(self):
173 def complete(self):
151 if not shouldprint(self.ui):
174 if not shouldprint(self.ui):
152 return
175 return
153 if self.ui.configbool('progress', 'clear-complete', default=True):
176 if self.ui.configbool('progress', 'clear-complete', default=True):
154 self.clear()
177 self.clear()
155 else:
178 else:
156 sys.stderr.write('\n')
179 sys.stderr.write('\n')
157 sys.stderr.flush()
180 sys.stderr.flush()
158
181
159 def width(self):
182 def width(self):
160 tw = self.ui.termwidth()
183 tw = self.ui.termwidth()
161 return min(int(self.ui.config('progress', 'width', default=tw)), tw)
184 return min(int(self.ui.config('progress', 'width', default=tw)), tw)
162
185
163 def progress(self, topic, pos, item='', unit='', total=None):
186 def progress(self, topic, pos, item='', unit='', total=None):
187 now = time.time()
164 if pos is None:
188 if pos is None:
189 self.starttimes.pop(topic, None)
190 self.startvals.pop(topic, None)
165 self.topicstates.pop(topic, None)
191 self.topicstates.pop(topic, None)
166 # reset the progress bar if this is the outermost topic
192 # reset the progress bar if this is the outermost topic
167 if self.topics and self.topics[0] == topic and self.printed:
193 if self.topics and self.topics[0] == topic and self.printed:
168 self.complete()
194 self.complete()
169 self.resetstate()
195 self.resetstate()
170 # truncate the list of topics assuming all topics within
196 # truncate the list of topics assuming all topics within
171 # this one are also closed
197 # this one are also closed
172 if topic in self.topics:
198 if topic in self.topics:
173 self.topics = self.topics[:self.topics.index(topic)]
199 self.topics = self.topics[:self.topics.index(topic)]
174 else:
200 else:
175 if topic not in self.topics:
201 if topic not in self.topics:
202 self.starttimes[topic] = now
203 self.startvals[topic] = pos
176 self.topics.append(topic)
204 self.topics.append(topic)
177 now = time.time()
178 self.topicstates[topic] = pos, item, unit, total
205 self.topicstates[topic] = pos, item, unit, total
179 if now - self.lastprint >= self.refresh and self.topics:
206 if now - self.lastprint >= self.refresh and self.topics:
180 self.lastprint = now
207 self.lastprint = now
181 current = self.topics[-1]
208 current = self.topics[-1]
182 self.show(current, *self.topicstates[current])
209 self.show(now, topic, *self.topicstates[topic])
183
210
184 def uisetup(ui):
211 def uisetup(ui):
185 class progressui(ui.__class__):
212 class progressui(ui.__class__):
186 _progbar = None
213 _progbar = None
187
214
188 def progress(self, *args, **opts):
215 def progress(self, *args, **opts):
189 self._progbar.progress(*args, **opts)
216 self._progbar.progress(*args, **opts)
190 return super(progressui, self).progress(*args, **opts)
217 return super(progressui, self).progress(*args, **opts)
191
218
192 def write(self, *args, **opts):
219 def write(self, *args, **opts):
193 if self._progbar.printed:
220 if self._progbar.printed:
194 self._progbar.clear()
221 self._progbar.clear()
195 return super(progressui, self).write(*args, **opts)
222 return super(progressui, self).write(*args, **opts)
196
223
197 def write_err(self, *args, **opts):
224 def write_err(self, *args, **opts):
198 if self._progbar.printed:
225 if self._progbar.printed:
199 self._progbar.clear()
226 self._progbar.clear()
200 return super(progressui, self).write_err(*args, **opts)
227 return super(progressui, self).write_err(*args, **opts)
201
228
202 # Apps that derive a class from ui.ui() can use
229 # Apps that derive a class from ui.ui() can use
203 # setconfig('progress', 'disable', 'True') to disable this extension
230 # setconfig('progress', 'disable', 'True') to disable this extension
204 if ui.configbool('progress', 'disable'):
231 if ui.configbool('progress', 'disable'):
205 return
232 return
206 if shouldprint(ui) and not ui.debugflag and not ui.quiet:
233 if shouldprint(ui) and not ui.debugflag and not ui.quiet:
207 ui.__class__ = progressui
234 ui.__class__ = progressui
208 # we instantiate one globally shared progress bar to avoid
235 # we instantiate one globally shared progress bar to avoid
209 # competing progress bars when multiple UI objects get created
236 # competing progress bars when multiple UI objects get created
210 if not progressui._progbar:
237 if not progressui._progbar:
211 progressui._progbar = progbar(ui)
238 progressui._progbar = progbar(ui)
212
239
213 def reposetup(ui, repo):
240 def reposetup(ui, repo):
214 uisetup(repo.ui)
241 uisetup(repo.ui)
General Comments 0
You need to be logged in to leave comments. Login now