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