##// END OF EJS Templates
progress: fix adding format elements after the progress bar...
Augie Fackler -
r13146:43575c67 default
parent child Browse files
Show More
@@ -1,248 +1,248 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.i18n import _
49 from mercurial import util
49 from mercurial import util
50
50
51 def spacejoin(*args):
51 def spacejoin(*args):
52 return ' '.join(s for s in args if s)
52 return ' '.join(s for s in args if s)
53
53
54 def shouldprint(ui):
54 def shouldprint(ui):
55 return (getattr(sys.stderr, 'isatty', None) and
55 return (getattr(sys.stderr, 'isatty', None) and
56 (sys.stderr.isatty() or ui.configbool('progress', 'assume-tty')))
56 (sys.stderr.isatty() or ui.configbool('progress', 'assume-tty')))
57
57
58 def fmtremaining(seconds):
58 def fmtremaining(seconds):
59 if seconds < 60:
59 if seconds < 60:
60 # i18n: format XX seconds as "XXs"
60 # i18n: format XX seconds as "XXs"
61 return _("%02ds") % (seconds)
61 return _("%02ds") % (seconds)
62 minutes = seconds // 60
62 minutes = seconds // 60
63 if minutes < 60:
63 if minutes < 60:
64 seconds -= minutes * 60
64 seconds -= minutes * 60
65 # i18n: format X minutes and YY seconds as "XmYYs"
65 # i18n: format X minutes and YY seconds as "XmYYs"
66 return _("%dm%02ds") % (minutes, seconds)
66 return _("%dm%02ds") % (minutes, seconds)
67 # we're going to ignore seconds in this case
67 # we're going to ignore seconds in this case
68 minutes += 1
68 minutes += 1
69 hours = minutes // 60
69 hours = minutes // 60
70 minutes -= hours * 60
70 minutes -= hours * 60
71 # i18n: format X hours and YY minutes as "XhYYm"
71 # i18n: format X hours and YY minutes as "XhYYm"
72 return _("%dh%02dm") % (hours, minutes)
72 return _("%dh%02dm") % (hours, minutes)
73
73
74 class progbar(object):
74 class progbar(object):
75 def __init__(self, ui):
75 def __init__(self, ui):
76 self.ui = ui
76 self.ui = ui
77 self.resetstate()
77 self.resetstate()
78
78
79 def resetstate(self):
79 def resetstate(self):
80 self.topics = []
80 self.topics = []
81 self.topicstates = {}
81 self.topicstates = {}
82 self.starttimes = {}
82 self.starttimes = {}
83 self.startvals = {}
83 self.startvals = {}
84 self.printed = False
84 self.printed = False
85 self.lastprint = time.time() + float(self.ui.config(
85 self.lastprint = time.time() + float(self.ui.config(
86 'progress', 'delay', default=3))
86 'progress', 'delay', default=3))
87 self.indetcount = 0
87 self.indetcount = 0
88 self.refresh = float(self.ui.config(
88 self.refresh = float(self.ui.config(
89 'progress', 'refresh', default=0.1))
89 'progress', 'refresh', default=0.1))
90 self.order = self.ui.configlist(
90 self.order = self.ui.configlist(
91 'progress', 'format',
91 'progress', 'format',
92 default=['topic', 'bar', 'number'])
92 default=['topic', 'bar', 'number'])
93
93
94 def show(self, now, topic, pos, item, unit, total):
94 def show(self, now, topic, pos, item, unit, total):
95 if not shouldprint(self.ui):
95 if not shouldprint(self.ui):
96 return
96 return
97 termwidth = self.width()
97 termwidth = self.width()
98 self.printed = True
98 self.printed = True
99 head = ''
99 head = ''
100 needprogress = False
100 needprogress = False
101 tail = ''
101 tail = ''
102 for indicator in self.order:
102 for indicator in self.order:
103 add = ''
103 add = ''
104 if indicator == 'topic':
104 if indicator == 'topic':
105 add = topic
105 add = topic
106 elif indicator == 'number':
106 elif indicator == 'number':
107 if total:
107 if total:
108 add = ('% ' + str(len(str(total))) +
108 add = ('% ' + str(len(str(total))) +
109 's/%s') % (pos, total)
109 's/%s') % (pos, total)
110 else:
110 else:
111 add = str(pos)
111 add = str(pos)
112 elif indicator.startswith('item') and item:
112 elif indicator.startswith('item') and item:
113 slice = 'end'
113 slice = 'end'
114 if '-' in indicator:
114 if '-' in indicator:
115 wid = int(indicator.split('-')[1])
115 wid = int(indicator.split('-')[1])
116 elif '+' in indicator:
116 elif '+' in indicator:
117 slice = 'beginning'
117 slice = 'beginning'
118 wid = int(indicator.split('+')[1])
118 wid = int(indicator.split('+')[1])
119 else:
119 else:
120 wid = 20
120 wid = 20
121 if slice == 'end':
121 if slice == 'end':
122 add = item[-wid:]
122 add = item[-wid:]
123 else:
123 else:
124 add = item[:wid]
124 add = item[:wid]
125 add += (wid - len(add)) * ' '
125 add += (wid - len(add)) * ' '
126 elif indicator == 'bar':
126 elif indicator == 'bar':
127 add = ''
127 add = ''
128 needprogress = True
128 needprogress = True
129 elif indicator == 'unit' and unit:
129 elif indicator == 'unit' and unit:
130 add = unit
130 add = unit
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(add, tail)
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 += len(head) + 1
138 used += len(head) + 1
139 if tail:
139 if tail:
140 used += len(tail) + 1
140 used += len(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 initial = self.startvals[topic]
143 initial = self.startvals[topic]
144 target = total - initial
144 target = total - initial
145 delta = pos - initial
145 delta = pos - initial
146 if delta > 0:
146 if delta > 0:
147 elapsed = now - self.starttimes[topic]
147 elapsed = now - self.starttimes[topic]
148 if elapsed > float(
148 if elapsed > float(
149 self.ui.config('progress', 'estimate', default=2)):
149 self.ui.config('progress', 'estimate', default=2)):
150 seconds = (elapsed * (target - delta)) // delta + 1
150 seconds = (elapsed * (target - delta)) // delta + 1
151 remaining = fmtremaining(seconds)
151 remaining = fmtremaining(seconds)
152 progwidth -= len(remaining) + 1
152 progwidth -= len(remaining) + 1
153 tail = spacejoin(tail, remaining)
153 tail = spacejoin(tail, remaining)
154 amt = pos * progwidth // total
154 amt = pos * progwidth // total
155 bar = '=' * (amt - 1)
155 bar = '=' * (amt - 1)
156 if amt > 0:
156 if amt > 0:
157 bar += '>'
157 bar += '>'
158 bar += ' ' * (progwidth - amt)
158 bar += ' ' * (progwidth - amt)
159 else:
159 else:
160 progwidth -= 3
160 progwidth -= 3
161 self.indetcount += 1
161 self.indetcount += 1
162 # mod the count by twice the width so we can make the
162 # mod the count by twice the width so we can make the
163 # cursor bounce between the right and left sides
163 # cursor bounce between the right and left sides
164 amt = self.indetcount % (2 * progwidth)
164 amt = self.indetcount % (2 * progwidth)
165 amt -= progwidth
165 amt -= progwidth
166 bar = (' ' * int(progwidth - abs(amt)) + '<=>' +
166 bar = (' ' * int(progwidth - abs(amt)) + '<=>' +
167 ' ' * int(abs(amt)))
167 ' ' * int(abs(amt)))
168 prog = ''.join(('[', bar , ']'))
168 prog = ''.join(('[', bar , ']'))
169 out = spacejoin(head, prog, tail)
169 out = spacejoin(head, prog, tail)
170 else:
170 else:
171 out = spacejoin(head, tail)
171 out = spacejoin(head, tail)
172 sys.stderr.write('\r' + out[:termwidth])
172 sys.stderr.write('\r' + out[:termwidth])
173 sys.stderr.flush()
173 sys.stderr.flush()
174
174
175 def clear(self):
175 def clear(self):
176 if not shouldprint(self.ui):
176 if not shouldprint(self.ui):
177 return
177 return
178 sys.stderr.write('\r%s\r' % (' ' * self.width()))
178 sys.stderr.write('\r%s\r' % (' ' * self.width()))
179
179
180 def complete(self):
180 def complete(self):
181 if not shouldprint(self.ui):
181 if not shouldprint(self.ui):
182 return
182 return
183 if self.ui.configbool('progress', 'clear-complete', default=True):
183 if self.ui.configbool('progress', 'clear-complete', default=True):
184 self.clear()
184 self.clear()
185 else:
185 else:
186 sys.stderr.write('\n')
186 sys.stderr.write('\n')
187 sys.stderr.flush()
187 sys.stderr.flush()
188
188
189 def width(self):
189 def width(self):
190 tw = self.ui.termwidth()
190 tw = self.ui.termwidth()
191 return min(int(self.ui.config('progress', 'width', default=tw)), tw)
191 return min(int(self.ui.config('progress', 'width', default=tw)), tw)
192
192
193 def progress(self, topic, pos, item='', unit='', total=None):
193 def progress(self, topic, pos, item='', unit='', total=None):
194 now = time.time()
194 now = time.time()
195 if pos is None:
195 if pos is None:
196 self.starttimes.pop(topic, None)
196 self.starttimes.pop(topic, None)
197 self.startvals.pop(topic, None)
197 self.startvals.pop(topic, None)
198 self.topicstates.pop(topic, None)
198 self.topicstates.pop(topic, None)
199 # reset the progress bar if this is the outermost topic
199 # reset the progress bar if this is the outermost topic
200 if self.topics and self.topics[0] == topic and self.printed:
200 if self.topics and self.topics[0] == topic and self.printed:
201 self.complete()
201 self.complete()
202 self.resetstate()
202 self.resetstate()
203 # truncate the list of topics assuming all topics within
203 # truncate the list of topics assuming all topics within
204 # this one are also closed
204 # this one are also closed
205 if topic in self.topics:
205 if topic in self.topics:
206 self.topics = self.topics[:self.topics.index(topic)]
206 self.topics = self.topics[:self.topics.index(topic)]
207 else:
207 else:
208 if topic not in self.topics:
208 if topic not in self.topics:
209 self.starttimes[topic] = now
209 self.starttimes[topic] = now
210 self.startvals[topic] = pos
210 self.startvals[topic] = pos
211 self.topics.append(topic)
211 self.topics.append(topic)
212 self.topicstates[topic] = pos, item, unit, total
212 self.topicstates[topic] = pos, item, unit, total
213 if now - self.lastprint >= self.refresh and self.topics:
213 if now - self.lastprint >= self.refresh and self.topics:
214 self.lastprint = now
214 self.lastprint = now
215 current = self.topics[-1]
215 current = self.topics[-1]
216 self.show(now, topic, *self.topicstates[topic])
216 self.show(now, topic, *self.topicstates[topic])
217
217
218 def uisetup(ui):
218 def uisetup(ui):
219 class progressui(ui.__class__):
219 class progressui(ui.__class__):
220 _progbar = None
220 _progbar = None
221
221
222 def progress(self, *args, **opts):
222 def progress(self, *args, **opts):
223 self._progbar.progress(*args, **opts)
223 self._progbar.progress(*args, **opts)
224 return super(progressui, self).progress(*args, **opts)
224 return super(progressui, self).progress(*args, **opts)
225
225
226 def write(self, *args, **opts):
226 def write(self, *args, **opts):
227 if self._progbar.printed:
227 if self._progbar.printed:
228 self._progbar.clear()
228 self._progbar.clear()
229 return super(progressui, self).write(*args, **opts)
229 return super(progressui, self).write(*args, **opts)
230
230
231 def write_err(self, *args, **opts):
231 def write_err(self, *args, **opts):
232 if self._progbar.printed:
232 if self._progbar.printed:
233 self._progbar.clear()
233 self._progbar.clear()
234 return super(progressui, self).write_err(*args, **opts)
234 return super(progressui, self).write_err(*args, **opts)
235
235
236 # Apps that derive a class from ui.ui() can use
236 # Apps that derive a class from ui.ui() can use
237 # setconfig('progress', 'disable', 'True') to disable this extension
237 # setconfig('progress', 'disable', 'True') to disable this extension
238 if ui.configbool('progress', 'disable'):
238 if ui.configbool('progress', 'disable'):
239 return
239 return
240 if shouldprint(ui) and not ui.debugflag and not ui.quiet:
240 if shouldprint(ui) and not ui.debugflag and not ui.quiet:
241 ui.__class__ = progressui
241 ui.__class__ = progressui
242 # we instantiate one globally shared progress bar to avoid
242 # we instantiate one globally shared progress bar to avoid
243 # competing progress bars when multiple UI objects get created
243 # competing progress bars when multiple UI objects get created
244 if not progressui._progbar:
244 if not progressui._progbar:
245 progressui._progbar = progbar(ui)
245 progressui._progbar = progbar(ui)
246
246
247 def reposetup(ui, repo):
247 def reposetup(ui, repo):
248 uisetup(repo.ui)
248 uisetup(repo.ui)
General Comments 0
You need to be logged in to leave comments. Login now