##// END OF EJS Templates
progress: obtain stderr from ui...
Yuya Nishihara -
r30324:cb1ea3cc default
parent child Browse files
Show More
@@ -1,256 +1,255
1 # progress.py progress bars related code
1 # progress.py progress bars related code
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 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import sys
11 import threading
10 import threading
12 import time
11 import time
13
12
14 from .i18n import _
13 from .i18n import _
15 from . import encoding
14 from . import encoding
16
15
17 def spacejoin(*args):
16 def spacejoin(*args):
18 return ' '.join(s for s in args if s)
17 return ' '.join(s for s in args if s)
19
18
20 def shouldprint(ui):
19 def shouldprint(ui):
21 return not (ui.quiet or ui.plain('progress')) and (
20 return not (ui.quiet or ui.plain('progress')) and (
22 ui._isatty(sys.stderr) or ui.configbool('progress', 'assume-tty'))
21 ui._isatty(ui.ferr) or ui.configbool('progress', 'assume-tty'))
23
22
24 def fmtremaining(seconds):
23 def fmtremaining(seconds):
25 """format a number of remaining seconds in human readable way
24 """format a number of remaining seconds in human readable way
26
25
27 This will properly display seconds, minutes, hours, days if needed"""
26 This will properly display seconds, minutes, hours, days if needed"""
28 if seconds < 60:
27 if seconds < 60:
29 # i18n: format XX seconds as "XXs"
28 # i18n: format XX seconds as "XXs"
30 return _("%02ds") % (seconds)
29 return _("%02ds") % (seconds)
31 minutes = seconds // 60
30 minutes = seconds // 60
32 if minutes < 60:
31 if minutes < 60:
33 seconds -= minutes * 60
32 seconds -= minutes * 60
34 # i18n: format X minutes and YY seconds as "XmYYs"
33 # i18n: format X minutes and YY seconds as "XmYYs"
35 return _("%dm%02ds") % (minutes, seconds)
34 return _("%dm%02ds") % (minutes, seconds)
36 # we're going to ignore seconds in this case
35 # we're going to ignore seconds in this case
37 minutes += 1
36 minutes += 1
38 hours = minutes // 60
37 hours = minutes // 60
39 minutes -= hours * 60
38 minutes -= hours * 60
40 if hours < 30:
39 if hours < 30:
41 # i18n: format X hours and YY minutes as "XhYYm"
40 # i18n: format X hours and YY minutes as "XhYYm"
42 return _("%dh%02dm") % (hours, minutes)
41 return _("%dh%02dm") % (hours, minutes)
43 # we're going to ignore minutes in this case
42 # we're going to ignore minutes in this case
44 hours += 1
43 hours += 1
45 days = hours // 24
44 days = hours // 24
46 hours -= days * 24
45 hours -= days * 24
47 if days < 15:
46 if days < 15:
48 # i18n: format X days and YY hours as "XdYYh"
47 # i18n: format X days and YY hours as "XdYYh"
49 return _("%dd%02dh") % (days, hours)
48 return _("%dd%02dh") % (days, hours)
50 # we're going to ignore hours in this case
49 # we're going to ignore hours in this case
51 days += 1
50 days += 1
52 weeks = days // 7
51 weeks = days // 7
53 days -= weeks * 7
52 days -= weeks * 7
54 if weeks < 55:
53 if weeks < 55:
55 # i18n: format X weeks and YY days as "XwYYd"
54 # i18n: format X weeks and YY days as "XwYYd"
56 return _("%dw%02dd") % (weeks, days)
55 return _("%dw%02dd") % (weeks, days)
57 # we're going to ignore days and treat a year as 52 weeks
56 # we're going to ignore days and treat a year as 52 weeks
58 weeks += 1
57 weeks += 1
59 years = weeks // 52
58 years = weeks // 52
60 weeks -= years * 52
59 weeks -= years * 52
61 # i18n: format X years and YY weeks as "XyYYw"
60 # i18n: format X years and YY weeks as "XyYYw"
62 return _("%dy%02dw") % (years, weeks)
61 return _("%dy%02dw") % (years, weeks)
63
62
64 class progbar(object):
63 class progbar(object):
65 def __init__(self, ui):
64 def __init__(self, ui):
66 self.ui = ui
65 self.ui = ui
67 self._refreshlock = threading.Lock()
66 self._refreshlock = threading.Lock()
68 self.resetstate()
67 self.resetstate()
69
68
70 def resetstate(self):
69 def resetstate(self):
71 self.topics = []
70 self.topics = []
72 self.topicstates = {}
71 self.topicstates = {}
73 self.starttimes = {}
72 self.starttimes = {}
74 self.startvals = {}
73 self.startvals = {}
75 self.printed = False
74 self.printed = False
76 self.lastprint = time.time() + float(self.ui.config(
75 self.lastprint = time.time() + float(self.ui.config(
77 'progress', 'delay', default=3))
76 'progress', 'delay', default=3))
78 self.curtopic = None
77 self.curtopic = None
79 self.lasttopic = None
78 self.lasttopic = None
80 self.indetcount = 0
79 self.indetcount = 0
81 self.refresh = float(self.ui.config(
80 self.refresh = float(self.ui.config(
82 'progress', 'refresh', default=0.1))
81 'progress', 'refresh', default=0.1))
83 self.changedelay = max(3 * self.refresh,
82 self.changedelay = max(3 * self.refresh,
84 float(self.ui.config(
83 float(self.ui.config(
85 'progress', 'changedelay', default=1)))
84 'progress', 'changedelay', default=1)))
86 self.order = self.ui.configlist(
85 self.order = self.ui.configlist(
87 'progress', 'format',
86 'progress', 'format',
88 default=['topic', 'bar', 'number', 'estimate'])
87 default=['topic', 'bar', 'number', 'estimate'])
89
88
90 def show(self, now, topic, pos, item, unit, total):
89 def show(self, now, topic, pos, item, unit, total):
91 if not shouldprint(self.ui):
90 if not shouldprint(self.ui):
92 return
91 return
93 termwidth = self.width()
92 termwidth = self.width()
94 self.printed = True
93 self.printed = True
95 head = ''
94 head = ''
96 needprogress = False
95 needprogress = False
97 tail = ''
96 tail = ''
98 for indicator in self.order:
97 for indicator in self.order:
99 add = ''
98 add = ''
100 if indicator == 'topic':
99 if indicator == 'topic':
101 add = topic
100 add = topic
102 elif indicator == 'number':
101 elif indicator == 'number':
103 if total:
102 if total:
104 add = ('% ' + str(len(str(total))) +
103 add = ('% ' + str(len(str(total))) +
105 's/%s') % (pos, total)
104 's/%s') % (pos, total)
106 else:
105 else:
107 add = str(pos)
106 add = str(pos)
108 elif indicator.startswith('item') and item:
107 elif indicator.startswith('item') and item:
109 slice = 'end'
108 slice = 'end'
110 if '-' in indicator:
109 if '-' in indicator:
111 wid = int(indicator.split('-')[1])
110 wid = int(indicator.split('-')[1])
112 elif '+' in indicator:
111 elif '+' in indicator:
113 slice = 'beginning'
112 slice = 'beginning'
114 wid = int(indicator.split('+')[1])
113 wid = int(indicator.split('+')[1])
115 else:
114 else:
116 wid = 20
115 wid = 20
117 if slice == 'end':
116 if slice == 'end':
118 add = encoding.trim(item, wid, leftside=True)
117 add = encoding.trim(item, wid, leftside=True)
119 else:
118 else:
120 add = encoding.trim(item, wid)
119 add = encoding.trim(item, wid)
121 add += (wid - encoding.colwidth(add)) * ' '
120 add += (wid - encoding.colwidth(add)) * ' '
122 elif indicator == 'bar':
121 elif indicator == 'bar':
123 add = ''
122 add = ''
124 needprogress = True
123 needprogress = True
125 elif indicator == 'unit' and unit:
124 elif indicator == 'unit' and unit:
126 add = unit
125 add = unit
127 elif indicator == 'estimate':
126 elif indicator == 'estimate':
128 add = self.estimate(topic, pos, total, now)
127 add = self.estimate(topic, pos, total, now)
129 elif indicator == 'speed':
128 elif indicator == 'speed':
130 add = self.speed(topic, pos, unit, now)
129 add = self.speed(topic, pos, unit, now)
131 if not needprogress:
130 if not needprogress:
132 head = spacejoin(head, add)
131 head = spacejoin(head, add)
133 else:
132 else:
134 tail = spacejoin(tail, add)
133 tail = spacejoin(tail, add)
135 if needprogress:
134 if needprogress:
136 used = 0
135 used = 0
137 if head:
136 if head:
138 used += encoding.colwidth(head) + 1
137 used += encoding.colwidth(head) + 1
139 if tail:
138 if tail:
140 used += encoding.colwidth(tail) + 1
139 used += encoding.colwidth(tail) + 1
141 progwidth = termwidth - used - 3
140 progwidth = termwidth - used - 3
142 if total and pos <= total:
141 if total and pos <= total:
143 amt = pos * progwidth // total
142 amt = pos * progwidth // total
144 bar = '=' * (amt - 1)
143 bar = '=' * (amt - 1)
145 if amt > 0:
144 if amt > 0:
146 bar += '>'
145 bar += '>'
147 bar += ' ' * (progwidth - amt)
146 bar += ' ' * (progwidth - amt)
148 else:
147 else:
149 progwidth -= 3
148 progwidth -= 3
150 self.indetcount += 1
149 self.indetcount += 1
151 # mod the count by twice the width so we can make the
150 # mod the count by twice the width so we can make the
152 # cursor bounce between the right and left sides
151 # cursor bounce between the right and left sides
153 amt = self.indetcount % (2 * progwidth)
152 amt = self.indetcount % (2 * progwidth)
154 amt -= progwidth
153 amt -= progwidth
155 bar = (' ' * int(progwidth - abs(amt)) + '<=>' +
154 bar = (' ' * int(progwidth - abs(amt)) + '<=>' +
156 ' ' * int(abs(amt)))
155 ' ' * int(abs(amt)))
157 prog = ''.join(('[', bar , ']'))
156 prog = ''.join(('[', bar , ']'))
158 out = spacejoin(head, prog, tail)
157 out = spacejoin(head, prog, tail)
159 else:
158 else:
160 out = spacejoin(head, tail)
159 out = spacejoin(head, tail)
161 sys.stderr.write('\r' + encoding.trim(out, termwidth))
160 self.ui.ferr.write('\r' + encoding.trim(out, termwidth))
162 self.lasttopic = topic
161 self.lasttopic = topic
163 sys.stderr.flush()
162 self.ui.ferr.flush()
164
163
165 def clear(self):
164 def clear(self):
166 if not self.printed or not self.lastprint or not shouldprint(self.ui):
165 if not self.printed or not self.lastprint or not shouldprint(self.ui):
167 return
166 return
168 sys.stderr.write('\r%s\r' % (' ' * self.width()))
167 self.ui.ferr.write('\r%s\r' % (' ' * self.width()))
169 if self.printed:
168 if self.printed:
170 # force immediate re-paint of progress bar
169 # force immediate re-paint of progress bar
171 self.lastprint = 0
170 self.lastprint = 0
172
171
173 def complete(self):
172 def complete(self):
174 if not shouldprint(self.ui):
173 if not shouldprint(self.ui):
175 return
174 return
176 if self.ui.configbool('progress', 'clear-complete', default=True):
175 if self.ui.configbool('progress', 'clear-complete', default=True):
177 self.clear()
176 self.clear()
178 else:
177 else:
179 sys.stderr.write('\n')
178 self.ui.ferr.write('\n')
180 sys.stderr.flush()
179 self.ui.ferr.flush()
181
180
182 def width(self):
181 def width(self):
183 tw = self.ui.termwidth()
182 tw = self.ui.termwidth()
184 return min(int(self.ui.config('progress', 'width', default=tw)), tw)
183 return min(int(self.ui.config('progress', 'width', default=tw)), tw)
185
184
186 def estimate(self, topic, pos, total, now):
185 def estimate(self, topic, pos, total, now):
187 if total is None:
186 if total is None:
188 return ''
187 return ''
189 initialpos = self.startvals[topic]
188 initialpos = self.startvals[topic]
190 target = total - initialpos
189 target = total - initialpos
191 delta = pos - initialpos
190 delta = pos - initialpos
192 if delta > 0:
191 if delta > 0:
193 elapsed = now - self.starttimes[topic]
192 elapsed = now - self.starttimes[topic]
194 # experimental config: progress.estimate
193 # experimental config: progress.estimate
195 if elapsed > float(
194 if elapsed > float(
196 self.ui.config('progress', 'estimate', default=2)):
195 self.ui.config('progress', 'estimate', default=2)):
197 seconds = (elapsed * (target - delta)) // delta + 1
196 seconds = (elapsed * (target - delta)) // delta + 1
198 return fmtremaining(seconds)
197 return fmtremaining(seconds)
199 return ''
198 return ''
200
199
201 def speed(self, topic, pos, unit, now):
200 def speed(self, topic, pos, unit, now):
202 initialpos = self.startvals[topic]
201 initialpos = self.startvals[topic]
203 delta = pos - initialpos
202 delta = pos - initialpos
204 elapsed = now - self.starttimes[topic]
203 elapsed = now - self.starttimes[topic]
205 if elapsed > float(
204 if elapsed > float(
206 self.ui.config('progress', 'estimate', default=2)):
205 self.ui.config('progress', 'estimate', default=2)):
207 return _('%d %s/sec') % (delta / elapsed, unit)
206 return _('%d %s/sec') % (delta / elapsed, unit)
208 return ''
207 return ''
209
208
210 def _oktoprint(self, now):
209 def _oktoprint(self, now):
211 '''Check if conditions are met to print - e.g. changedelay elapsed'''
210 '''Check if conditions are met to print - e.g. changedelay elapsed'''
212 if (self.lasttopic is None # first time we printed
211 if (self.lasttopic is None # first time we printed
213 # not a topic change
212 # not a topic change
214 or self.curtopic == self.lasttopic
213 or self.curtopic == self.lasttopic
215 # it's been long enough we should print anyway
214 # it's been long enough we should print anyway
216 or now - self.lastprint >= self.changedelay):
215 or now - self.lastprint >= self.changedelay):
217 return True
216 return True
218 else:
217 else:
219 return False
218 return False
220
219
221 def progress(self, topic, pos, item='', unit='', total=None):
220 def progress(self, topic, pos, item='', unit='', total=None):
222 now = time.time()
221 now = time.time()
223 self._refreshlock.acquire()
222 self._refreshlock.acquire()
224 try:
223 try:
225 if pos is None:
224 if pos is None:
226 self.starttimes.pop(topic, None)
225 self.starttimes.pop(topic, None)
227 self.startvals.pop(topic, None)
226 self.startvals.pop(topic, None)
228 self.topicstates.pop(topic, None)
227 self.topicstates.pop(topic, None)
229 # reset the progress bar if this is the outermost topic
228 # reset the progress bar if this is the outermost topic
230 if self.topics and self.topics[0] == topic and self.printed:
229 if self.topics and self.topics[0] == topic and self.printed:
231 self.complete()
230 self.complete()
232 self.resetstate()
231 self.resetstate()
233 # truncate the list of topics assuming all topics within
232 # truncate the list of topics assuming all topics within
234 # this one are also closed
233 # this one are also closed
235 if topic in self.topics:
234 if topic in self.topics:
236 self.topics = self.topics[:self.topics.index(topic)]
235 self.topics = self.topics[:self.topics.index(topic)]
237 # reset the last topic to the one we just unwound to,
236 # reset the last topic to the one we just unwound to,
238 # so that higher-level topics will be stickier than
237 # so that higher-level topics will be stickier than
239 # lower-level topics
238 # lower-level topics
240 if self.topics:
239 if self.topics:
241 self.lasttopic = self.topics[-1]
240 self.lasttopic = self.topics[-1]
242 else:
241 else:
243 self.lasttopic = None
242 self.lasttopic = None
244 else:
243 else:
245 if topic not in self.topics:
244 if topic not in self.topics:
246 self.starttimes[topic] = now
245 self.starttimes[topic] = now
247 self.startvals[topic] = pos
246 self.startvals[topic] = pos
248 self.topics.append(topic)
247 self.topics.append(topic)
249 self.topicstates[topic] = pos, item, unit, total
248 self.topicstates[topic] = pos, item, unit, total
250 self.curtopic = topic
249 self.curtopic = topic
251 if now - self.lastprint >= self.refresh and self.topics:
250 if now - self.lastprint >= self.refresh and self.topics:
252 if self._oktoprint(now):
251 if self._oktoprint(now):
253 self.lastprint = now
252 self.lastprint = now
254 self.show(now, topic, *self.topicstates[topic])
253 self.show(now, topic, *self.topicstates[topic])
255 finally:
254 finally:
256 self._refreshlock.release()
255 self._refreshlock.release()
General Comments 0
You need to be logged in to leave comments. Login now