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