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