##// END OF EJS Templates
progress: Use the same GPL boilerplate as most hg files
Augie Fackler -
r15772:83a14075 default
parent child Browse files
Show More
@@ -1,306 +1,295 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 software may be used and distributed according to the terms of the
6 # under the terms of the GNU General Public License as published by the
6 # GNU General Public License version 2 or any later version.
7 # Free Software Foundation; either version 2 of the License, or (at your
8 # option) any later version.
9 #
10 # This program is distributed in the hope that it will be useful, but
11 # WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
13 # Public License for more details.
14 #
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.,
17 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18
7
19 """show progress bars for some actions
8 """show progress bars for some actions
20
9
21 This extension uses the progress information logged by hg commands
10 This extension uses the progress information logged by hg commands
22 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
23 bars only offer indeterminate information, while others have a definite
12 bars only offer indeterminate information, while others have a definite
24 end point.
13 end point.
25
14
26 The following settings are available::
15 The following settings are available::
27
16
28 [progress]
17 [progress]
29 delay = 3 # number of seconds (float) before showing the progress bar
18 delay = 3 # number of seconds (float) before showing the progress bar
30 changedelay = 1 # changedelay: minimum delay before showing a new topic.
19 changedelay = 1 # changedelay: minimum delay before showing a new topic.
31 # If set to less than 3 * refresh, that value will
20 # If set to less than 3 * refresh, that value will
32 # be used instead.
21 # be used instead.
33 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
34 format = topic bar number estimate # format of the progress bar
23 format = topic bar number estimate # format of the progress bar
35 width = <none> # if set, the maximum width of the progress information
24 width = <none> # if set, the maximum width of the progress information
36 # (that is, min(width, term width) will be used)
25 # (that is, min(width, term width) will be used)
37 clear-complete = True # clear the progress bar after it's done
26 clear-complete = True # clear the progress bar after it's done
38 disable = False # if true, don't show a progress bar
27 disable = False # if true, don't show a progress bar
39 assume-tty = False # if true, ALWAYS show a progress bar, unless
28 assume-tty = False # if true, ALWAYS show a progress bar, unless
40 # disable is given
29 # disable is given
41
30
42 Valid entries for the format field are topic, bar, number, unit,
31 Valid entries for the format field are topic, bar, number, unit,
43 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
44 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
45 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
46 characters.
35 characters.
47 """
36 """
48
37
49 import sys
38 import sys
50 import time
39 import time
51
40
52 from mercurial import util
41 from mercurial import util
53 from mercurial.i18n import _
42 from mercurial.i18n import _
54
43
55 def spacejoin(*args):
44 def spacejoin(*args):
56 return ' '.join(s for s in args if s)
45 return ' '.join(s for s in args if s)
57
46
58 def shouldprint(ui):
47 def shouldprint(ui):
59 return util.isatty(sys.stderr) or ui.configbool('progress', 'assume-tty')
48 return util.isatty(sys.stderr) or ui.configbool('progress', 'assume-tty')
60
49
61 def fmtremaining(seconds):
50 def fmtremaining(seconds):
62 if seconds < 60:
51 if seconds < 60:
63 # i18n: format XX seconds as "XXs"
52 # i18n: format XX seconds as "XXs"
64 return _("%02ds") % (seconds)
53 return _("%02ds") % (seconds)
65 minutes = seconds // 60
54 minutes = seconds // 60
66 if minutes < 60:
55 if minutes < 60:
67 seconds -= minutes * 60
56 seconds -= minutes * 60
68 # i18n: format X minutes and YY seconds as "XmYYs"
57 # i18n: format X minutes and YY seconds as "XmYYs"
69 return _("%dm%02ds") % (minutes, seconds)
58 return _("%dm%02ds") % (minutes, seconds)
70 # we're going to ignore seconds in this case
59 # we're going to ignore seconds in this case
71 minutes += 1
60 minutes += 1
72 hours = minutes // 60
61 hours = minutes // 60
73 minutes -= hours * 60
62 minutes -= hours * 60
74 if hours < 30:
63 if hours < 30:
75 # i18n: format X hours and YY minutes as "XhYYm"
64 # i18n: format X hours and YY minutes as "XhYYm"
76 return _("%dh%02dm") % (hours, minutes)
65 return _("%dh%02dm") % (hours, minutes)
77 # we're going to ignore minutes in this case
66 # we're going to ignore minutes in this case
78 hours += 1
67 hours += 1
79 days = hours // 24
68 days = hours // 24
80 hours -= days * 24
69 hours -= days * 24
81 if days < 15:
70 if days < 15:
82 # i18n: format X days and YY hours as "XdYYh"
71 # i18n: format X days and YY hours as "XdYYh"
83 return _("%dd%02dh") % (days, hours)
72 return _("%dd%02dh") % (days, hours)
84 # we're going to ignore hours in this case
73 # we're going to ignore hours in this case
85 days += 1
74 days += 1
86 weeks = days // 7
75 weeks = days // 7
87 days -= weeks * 7
76 days -= weeks * 7
88 if weeks < 55:
77 if weeks < 55:
89 # i18n: format X weeks and YY days as "XwYYd"
78 # i18n: format X weeks and YY days as "XwYYd"
90 return _("%dw%02dd") % (weeks, days)
79 return _("%dw%02dd") % (weeks, days)
91 # we're going to ignore days and treat a year as 52 weeks
80 # we're going to ignore days and treat a year as 52 weeks
92 weeks += 1
81 weeks += 1
93 years = weeks // 52
82 years = weeks // 52
94 weeks -= years * 52
83 weeks -= years * 52
95 # i18n: format X years and YY weeks as "XyYYw"
84 # i18n: format X years and YY weeks as "XyYYw"
96 return _("%dy%02dw") % (years, weeks)
85 return _("%dy%02dw") % (years, weeks)
97
86
98 class progbar(object):
87 class progbar(object):
99 def __init__(self, ui):
88 def __init__(self, ui):
100 self.ui = ui
89 self.ui = ui
101 self.resetstate()
90 self.resetstate()
102
91
103 def resetstate(self):
92 def resetstate(self):
104 self.topics = []
93 self.topics = []
105 self.topicstates = {}
94 self.topicstates = {}
106 self.starttimes = {}
95 self.starttimes = {}
107 self.startvals = {}
96 self.startvals = {}
108 self.printed = False
97 self.printed = False
109 self.lastprint = time.time() + float(self.ui.config(
98 self.lastprint = time.time() + float(self.ui.config(
110 'progress', 'delay', default=3))
99 'progress', 'delay', default=3))
111 self.lasttopic = None
100 self.lasttopic = None
112 self.indetcount = 0
101 self.indetcount = 0
113 self.refresh = float(self.ui.config(
102 self.refresh = float(self.ui.config(
114 'progress', 'refresh', default=0.1))
103 'progress', 'refresh', default=0.1))
115 self.changedelay = max(3 * self.refresh,
104 self.changedelay = max(3 * self.refresh,
116 float(self.ui.config(
105 float(self.ui.config(
117 'progress', 'changedelay', default=1)))
106 'progress', 'changedelay', default=1)))
118 self.order = self.ui.configlist(
107 self.order = self.ui.configlist(
119 'progress', 'format',
108 'progress', 'format',
120 default=['topic', 'bar', 'number', 'estimate'])
109 default=['topic', 'bar', 'number', 'estimate'])
121
110
122 def show(self, now, topic, pos, item, unit, total):
111 def show(self, now, topic, pos, item, unit, total):
123 if not shouldprint(self.ui):
112 if not shouldprint(self.ui):
124 return
113 return
125 termwidth = self.width()
114 termwidth = self.width()
126 self.printed = True
115 self.printed = True
127 head = ''
116 head = ''
128 needprogress = False
117 needprogress = False
129 tail = ''
118 tail = ''
130 for indicator in self.order:
119 for indicator in self.order:
131 add = ''
120 add = ''
132 if indicator == 'topic':
121 if indicator == 'topic':
133 add = topic
122 add = topic
134 elif indicator == 'number':
123 elif indicator == 'number':
135 if total:
124 if total:
136 add = ('% ' + str(len(str(total))) +
125 add = ('% ' + str(len(str(total))) +
137 's/%s') % (pos, total)
126 's/%s') % (pos, total)
138 else:
127 else:
139 add = str(pos)
128 add = str(pos)
140 elif indicator.startswith('item') and item:
129 elif indicator.startswith('item') and item:
141 slice = 'end'
130 slice = 'end'
142 if '-' in indicator:
131 if '-' in indicator:
143 wid = int(indicator.split('-')[1])
132 wid = int(indicator.split('-')[1])
144 elif '+' in indicator:
133 elif '+' in indicator:
145 slice = 'beginning'
134 slice = 'beginning'
146 wid = int(indicator.split('+')[1])
135 wid = int(indicator.split('+')[1])
147 else:
136 else:
148 wid = 20
137 wid = 20
149 if slice == 'end':
138 if slice == 'end':
150 add = item[-wid:]
139 add = item[-wid:]
151 else:
140 else:
152 add = item[:wid]
141 add = item[:wid]
153 add += (wid - len(add)) * ' '
142 add += (wid - len(add)) * ' '
154 elif indicator == 'bar':
143 elif indicator == 'bar':
155 add = ''
144 add = ''
156 needprogress = True
145 needprogress = True
157 elif indicator == 'unit' and unit:
146 elif indicator == 'unit' and unit:
158 add = unit
147 add = unit
159 elif indicator == 'estimate':
148 elif indicator == 'estimate':
160 add = self.estimate(topic, pos, total, now)
149 add = self.estimate(topic, pos, total, now)
161 elif indicator == 'speed':
150 elif indicator == 'speed':
162 add = self.speed(topic, pos, unit, now)
151 add = self.speed(topic, pos, unit, now)
163 if not needprogress:
152 if not needprogress:
164 head = spacejoin(head, add)
153 head = spacejoin(head, add)
165 else:
154 else:
166 tail = spacejoin(tail, add)
155 tail = spacejoin(tail, add)
167 if needprogress:
156 if needprogress:
168 used = 0
157 used = 0
169 if head:
158 if head:
170 used += len(head) + 1
159 used += len(head) + 1
171 if tail:
160 if tail:
172 used += len(tail) + 1
161 used += len(tail) + 1
173 progwidth = termwidth - used - 3
162 progwidth = termwidth - used - 3
174 if total and pos <= total:
163 if total and pos <= total:
175 amt = pos * progwidth // total
164 amt = pos * progwidth // total
176 bar = '=' * (amt - 1)
165 bar = '=' * (amt - 1)
177 if amt > 0:
166 if amt > 0:
178 bar += '>'
167 bar += '>'
179 bar += ' ' * (progwidth - amt)
168 bar += ' ' * (progwidth - amt)
180 else:
169 else:
181 progwidth -= 3
170 progwidth -= 3
182 self.indetcount += 1
171 self.indetcount += 1
183 # mod the count by twice the width so we can make the
172 # mod the count by twice the width so we can make the
184 # cursor bounce between the right and left sides
173 # cursor bounce between the right and left sides
185 amt = self.indetcount % (2 * progwidth)
174 amt = self.indetcount % (2 * progwidth)
186 amt -= progwidth
175 amt -= progwidth
187 bar = (' ' * int(progwidth - abs(amt)) + '<=>' +
176 bar = (' ' * int(progwidth - abs(amt)) + '<=>' +
188 ' ' * int(abs(amt)))
177 ' ' * int(abs(amt)))
189 prog = ''.join(('[', bar , ']'))
178 prog = ''.join(('[', bar , ']'))
190 out = spacejoin(head, prog, tail)
179 out = spacejoin(head, prog, tail)
191 else:
180 else:
192 out = spacejoin(head, tail)
181 out = spacejoin(head, tail)
193 sys.stderr.write('\r' + out[:termwidth])
182 sys.stderr.write('\r' + out[:termwidth])
194 self.lasttopic = topic
183 self.lasttopic = topic
195 sys.stderr.flush()
184 sys.stderr.flush()
196
185
197 def clear(self):
186 def clear(self):
198 if not shouldprint(self.ui):
187 if not shouldprint(self.ui):
199 return
188 return
200 sys.stderr.write('\r%s\r' % (' ' * self.width()))
189 sys.stderr.write('\r%s\r' % (' ' * self.width()))
201
190
202 def complete(self):
191 def complete(self):
203 if not shouldprint(self.ui):
192 if not shouldprint(self.ui):
204 return
193 return
205 if self.ui.configbool('progress', 'clear-complete', default=True):
194 if self.ui.configbool('progress', 'clear-complete', default=True):
206 self.clear()
195 self.clear()
207 else:
196 else:
208 sys.stderr.write('\n')
197 sys.stderr.write('\n')
209 sys.stderr.flush()
198 sys.stderr.flush()
210
199
211 def width(self):
200 def width(self):
212 tw = self.ui.termwidth()
201 tw = self.ui.termwidth()
213 return min(int(self.ui.config('progress', 'width', default=tw)), tw)
202 return min(int(self.ui.config('progress', 'width', default=tw)), tw)
214
203
215 def estimate(self, topic, pos, total, now):
204 def estimate(self, topic, pos, total, now):
216 if total is None:
205 if total is None:
217 return ''
206 return ''
218 initialpos = self.startvals[topic]
207 initialpos = self.startvals[topic]
219 target = total - initialpos
208 target = total - initialpos
220 delta = pos - initialpos
209 delta = pos - initialpos
221 if delta > 0:
210 if delta > 0:
222 elapsed = now - self.starttimes[topic]
211 elapsed = now - self.starttimes[topic]
223 if elapsed > float(
212 if elapsed > float(
224 self.ui.config('progress', 'estimate', default=2)):
213 self.ui.config('progress', 'estimate', default=2)):
225 seconds = (elapsed * (target - delta)) // delta + 1
214 seconds = (elapsed * (target - delta)) // delta + 1
226 return fmtremaining(seconds)
215 return fmtremaining(seconds)
227 return ''
216 return ''
228
217
229 def speed(self, topic, pos, unit, now):
218 def speed(self, topic, pos, unit, now):
230 initialpos = self.startvals[topic]
219 initialpos = self.startvals[topic]
231 delta = pos - initialpos
220 delta = pos - initialpos
232 elapsed = now - self.starttimes[topic]
221 elapsed = now - self.starttimes[topic]
233 if elapsed > float(
222 if elapsed > float(
234 self.ui.config('progress', 'estimate', default=2)):
223 self.ui.config('progress', 'estimate', default=2)):
235 return _('%d %s/sec') % (delta / elapsed, unit)
224 return _('%d %s/sec') % (delta / elapsed, unit)
236 return ''
225 return ''
237
226
238 def progress(self, topic, pos, item='', unit='', total=None):
227 def progress(self, topic, pos, item='', unit='', total=None):
239 now = time.time()
228 now = time.time()
240 if pos is None:
229 if pos is None:
241 self.starttimes.pop(topic, None)
230 self.starttimes.pop(topic, None)
242 self.startvals.pop(topic, None)
231 self.startvals.pop(topic, None)
243 self.topicstates.pop(topic, None)
232 self.topicstates.pop(topic, None)
244 # reset the progress bar if this is the outermost topic
233 # reset the progress bar if this is the outermost topic
245 if self.topics and self.topics[0] == topic and self.printed:
234 if self.topics and self.topics[0] == topic and self.printed:
246 self.complete()
235 self.complete()
247 self.resetstate()
236 self.resetstate()
248 # truncate the list of topics assuming all topics within
237 # truncate the list of topics assuming all topics within
249 # this one are also closed
238 # this one are also closed
250 if topic in self.topics:
239 if topic in self.topics:
251 self.topics = self.topics[:self.topics.index(topic)]
240 self.topics = self.topics[:self.topics.index(topic)]
252 else:
241 else:
253 if topic not in self.topics:
242 if topic not in self.topics:
254 self.starttimes[topic] = now
243 self.starttimes[topic] = now
255 self.startvals[topic] = pos
244 self.startvals[topic] = pos
256 self.topics.append(topic)
245 self.topics.append(topic)
257 self.topicstates[topic] = pos, item, unit, total
246 self.topicstates[topic] = pos, item, unit, total
258 if now - self.lastprint >= self.refresh and self.topics:
247 if now - self.lastprint >= self.refresh and self.topics:
259 if (self.lasttopic is None # first time we printed
248 if (self.lasttopic is None # first time we printed
260 # not a topic change
249 # not a topic change
261 or topic == self.lasttopic
250 or topic == self.lasttopic
262 # it's been long enough we should print anyway
251 # it's been long enough we should print anyway
263 or now - self.lastprint >= self.changedelay):
252 or now - self.lastprint >= self.changedelay):
264 self.lastprint = now
253 self.lastprint = now
265 self.show(now, topic, *self.topicstates[topic])
254 self.show(now, topic, *self.topicstates[topic])
266
255
267 _singleton = None
256 _singleton = None
268
257
269 def uisetup(ui):
258 def uisetup(ui):
270 global _singleton
259 global _singleton
271 class progressui(ui.__class__):
260 class progressui(ui.__class__):
272 _progbar = None
261 _progbar = None
273
262
274 def _quiet(self):
263 def _quiet(self):
275 return self.debugflag or self.quiet
264 return self.debugflag or self.quiet
276
265
277 def progress(self, *args, **opts):
266 def progress(self, *args, **opts):
278 if not self._quiet():
267 if not self._quiet():
279 self._progbar.progress(*args, **opts)
268 self._progbar.progress(*args, **opts)
280 return super(progressui, self).progress(*args, **opts)
269 return super(progressui, self).progress(*args, **opts)
281
270
282 def write(self, *args, **opts):
271 def write(self, *args, **opts):
283 if not self._quiet() and self._progbar.printed:
272 if not self._quiet() and self._progbar.printed:
284 self._progbar.clear()
273 self._progbar.clear()
285 return super(progressui, self).write(*args, **opts)
274 return super(progressui, self).write(*args, **opts)
286
275
287 def write_err(self, *args, **opts):
276 def write_err(self, *args, **opts):
288 if not self._quiet() and self._progbar.printed:
277 if not self._quiet() and self._progbar.printed:
289 self._progbar.clear()
278 self._progbar.clear()
290 return super(progressui, self).write_err(*args, **opts)
279 return super(progressui, self).write_err(*args, **opts)
291
280
292 # Apps that derive a class from ui.ui() can use
281 # Apps that derive a class from ui.ui() can use
293 # setconfig('progress', 'disable', 'True') to disable this extension
282 # setconfig('progress', 'disable', 'True') to disable this extension
294 if ui.configbool('progress', 'disable'):
283 if ui.configbool('progress', 'disable'):
295 return
284 return
296 if shouldprint(ui) and not ui.debugflag and not ui.quiet:
285 if shouldprint(ui) and not ui.debugflag and not ui.quiet:
297 ui.__class__ = progressui
286 ui.__class__ = progressui
298 # we instantiate one globally shared progress bar to avoid
287 # we instantiate one globally shared progress bar to avoid
299 # competing progress bars when multiple UI objects get created
288 # competing progress bars when multiple UI objects get created
300 if not progressui._progbar:
289 if not progressui._progbar:
301 if _singleton is None:
290 if _singleton is None:
302 _singleton = progbar(ui)
291 _singleton = progbar(ui)
303 progressui._progbar = _singleton
292 progressui._progbar = _singleton
304
293
305 def reposetup(ui, repo):
294 def reposetup(ui, repo):
306 uisetup(repo.ui)
295 uisetup(repo.ui)
General Comments 0
You need to be logged in to leave comments. Login now