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