diff options
Diffstat (limited to 'bitbake/lib/progressbar/widgets.py')
-rw-r--r-- | bitbake/lib/progressbar/widgets.py | 355 |
1 files changed, 355 insertions, 0 deletions
diff --git a/bitbake/lib/progressbar/widgets.py b/bitbake/lib/progressbar/widgets.py new file mode 100644 index 0000000000..6434ad5591 --- /dev/null +++ b/bitbake/lib/progressbar/widgets.py | |||
@@ -0,0 +1,355 @@ | |||
1 | # -*- coding: utf-8 -*- | ||
2 | # | ||
3 | # progressbar - Text progress bar library for Python. | ||
4 | # Copyright (c) 2005 Nilton Volpato | ||
5 | # | ||
6 | # This library is free software; you can redistribute it and/or | ||
7 | # modify it under the terms of the GNU Lesser General Public | ||
8 | # License as published by the Free Software Foundation; either | ||
9 | # version 2.1 of the License, or (at your option) any later version. | ||
10 | # | ||
11 | # This library is distributed in the hope that it will be useful, | ||
12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
14 | # Lesser General Public License for more details. | ||
15 | # | ||
16 | # You should have received a copy of the GNU Lesser General Public | ||
17 | # License along with this library; if not, write to the Free Software | ||
18 | # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA | ||
19 | |||
20 | """Default ProgressBar widgets.""" | ||
21 | |||
22 | from __future__ import division | ||
23 | |||
24 | import datetime | ||
25 | import math | ||
26 | |||
27 | try: | ||
28 | from abc import ABCMeta, abstractmethod | ||
29 | except ImportError: | ||
30 | AbstractWidget = object | ||
31 | abstractmethod = lambda fn: fn | ||
32 | else: | ||
33 | AbstractWidget = ABCMeta('AbstractWidget', (object,), {}) | ||
34 | |||
35 | |||
36 | def format_updatable(updatable, pbar): | ||
37 | if hasattr(updatable, 'update'): return updatable.update(pbar) | ||
38 | else: return updatable | ||
39 | |||
40 | |||
41 | class Widget(AbstractWidget): | ||
42 | """The base class for all widgets. | ||
43 | |||
44 | The ProgressBar will call the widget's update value when the widget should | ||
45 | be updated. The widget's size may change between calls, but the widget may | ||
46 | display incorrectly if the size changes drastically and repeatedly. | ||
47 | |||
48 | The boolean TIME_SENSITIVE informs the ProgressBar that it should be | ||
49 | updated more often because it is time sensitive. | ||
50 | """ | ||
51 | |||
52 | TIME_SENSITIVE = False | ||
53 | __slots__ = () | ||
54 | |||
55 | @abstractmethod | ||
56 | def update(self, pbar): | ||
57 | """Updates the widget. | ||
58 | |||
59 | pbar - a reference to the calling ProgressBar | ||
60 | """ | ||
61 | |||
62 | |||
63 | class WidgetHFill(Widget): | ||
64 | """The base class for all variable width widgets. | ||
65 | |||
66 | This widget is much like the \\hfill command in TeX, it will expand to | ||
67 | fill the line. You can use more than one in the same line, and they will | ||
68 | all have the same width, and together will fill the line. | ||
69 | """ | ||
70 | |||
71 | @abstractmethod | ||
72 | def update(self, pbar, width): | ||
73 | """Updates the widget providing the total width the widget must fill. | ||
74 | |||
75 | pbar - a reference to the calling ProgressBar | ||
76 | width - The total width the widget must fill | ||
77 | """ | ||
78 | |||
79 | |||
80 | class Timer(Widget): | ||
81 | """Widget which displays the elapsed seconds.""" | ||
82 | |||
83 | __slots__ = ('format_string',) | ||
84 | TIME_SENSITIVE = True | ||
85 | |||
86 | def __init__(self, format='Elapsed Time: %s'): | ||
87 | self.format_string = format | ||
88 | |||
89 | @staticmethod | ||
90 | def format_time(seconds): | ||
91 | """Formats time as the string "HH:MM:SS".""" | ||
92 | |||
93 | return str(datetime.timedelta(seconds=int(seconds))) | ||
94 | |||
95 | |||
96 | def update(self, pbar): | ||
97 | """Updates the widget to show the elapsed time.""" | ||
98 | |||
99 | return self.format_string % self.format_time(pbar.seconds_elapsed) | ||
100 | |||
101 | |||
102 | class ETA(Timer): | ||
103 | """Widget which attempts to estimate the time of arrival.""" | ||
104 | |||
105 | TIME_SENSITIVE = True | ||
106 | |||
107 | def update(self, pbar): | ||
108 | """Updates the widget to show the ETA or total time when finished.""" | ||
109 | |||
110 | if pbar.currval == 0: | ||
111 | return 'ETA: --:--:--' | ||
112 | elif pbar.finished: | ||
113 | return 'Time: %s' % self.format_time(pbar.seconds_elapsed) | ||
114 | else: | ||
115 | elapsed = pbar.seconds_elapsed | ||
116 | eta = elapsed * pbar.maxval / pbar.currval - elapsed | ||
117 | return 'ETA: %s' % self.format_time(eta) | ||
118 | |||
119 | |||
120 | class AdaptiveETA(Timer): | ||
121 | """Widget which attempts to estimate the time of arrival. | ||
122 | |||
123 | Uses a weighted average of two estimates: | ||
124 | 1) ETA based on the total progress and time elapsed so far | ||
125 | 2) ETA based on the progress as per the last 10 update reports | ||
126 | |||
127 | The weight depends on the current progress so that to begin with the | ||
128 | total progress is used and at the end only the most recent progress is | ||
129 | used. | ||
130 | """ | ||
131 | |||
132 | TIME_SENSITIVE = True | ||
133 | NUM_SAMPLES = 10 | ||
134 | |||
135 | def _update_samples(self, currval, elapsed): | ||
136 | sample = (currval, elapsed) | ||
137 | if not hasattr(self, 'samples'): | ||
138 | self.samples = [sample] * (self.NUM_SAMPLES + 1) | ||
139 | else: | ||
140 | self.samples.append(sample) | ||
141 | return self.samples.pop(0) | ||
142 | |||
143 | def _eta(self, maxval, currval, elapsed): | ||
144 | return elapsed * maxval / float(currval) - elapsed | ||
145 | |||
146 | def update(self, pbar): | ||
147 | """Updates the widget to show the ETA or total time when finished.""" | ||
148 | if pbar.currval == 0: | ||
149 | return 'ETA: --:--:--' | ||
150 | elif pbar.finished: | ||
151 | return 'Time: %s' % self.format_time(pbar.seconds_elapsed) | ||
152 | else: | ||
153 | elapsed = pbar.seconds_elapsed | ||
154 | currval1, elapsed1 = self._update_samples(pbar.currval, elapsed) | ||
155 | eta = self._eta(pbar.maxval, pbar.currval, elapsed) | ||
156 | if pbar.currval > currval1: | ||
157 | etasamp = self._eta(pbar.maxval - currval1, | ||
158 | pbar.currval - currval1, | ||
159 | elapsed - elapsed1) | ||
160 | weight = (pbar.currval / float(pbar.maxval)) ** 0.5 | ||
161 | eta = (1 - weight) * eta + weight * etasamp | ||
162 | return 'ETA: %s' % self.format_time(eta) | ||
163 | |||
164 | |||
165 | class FileTransferSpeed(Widget): | ||
166 | """Widget for showing the transfer speed (useful for file transfers).""" | ||
167 | |||
168 | FORMAT = '%6.2f %s%s/s' | ||
169 | PREFIXES = ' kMGTPEZY' | ||
170 | __slots__ = ('unit',) | ||
171 | |||
172 | def __init__(self, unit='B'): | ||
173 | self.unit = unit | ||
174 | |||
175 | def update(self, pbar): | ||
176 | """Updates the widget with the current SI prefixed speed.""" | ||
177 | |||
178 | if pbar.seconds_elapsed < 2e-6 or pbar.currval < 2e-6: # =~ 0 | ||
179 | scaled = power = 0 | ||
180 | else: | ||
181 | speed = pbar.currval / pbar.seconds_elapsed | ||
182 | power = int(math.log(speed, 1000)) | ||
183 | scaled = speed / 1000.**power | ||
184 | |||
185 | return self.FORMAT % (scaled, self.PREFIXES[power], self.unit) | ||
186 | |||
187 | |||
188 | class AnimatedMarker(Widget): | ||
189 | """An animated marker for the progress bar which defaults to appear as if | ||
190 | it were rotating. | ||
191 | """ | ||
192 | |||
193 | __slots__ = ('markers', 'curmark') | ||
194 | |||
195 | def __init__(self, markers='|/-\\'): | ||
196 | self.markers = markers | ||
197 | self.curmark = -1 | ||
198 | |||
199 | def update(self, pbar): | ||
200 | """Updates the widget to show the next marker or the first marker when | ||
201 | finished""" | ||
202 | |||
203 | if pbar.finished: return self.markers[0] | ||
204 | |||
205 | self.curmark = (self.curmark + 1) % len(self.markers) | ||
206 | return self.markers[self.curmark] | ||
207 | |||
208 | # Alias for backwards compatibility | ||
209 | RotatingMarker = AnimatedMarker | ||
210 | |||
211 | |||
212 | class Counter(Widget): | ||
213 | """Displays the current count.""" | ||
214 | |||
215 | __slots__ = ('format_string',) | ||
216 | |||
217 | def __init__(self, format='%d'): | ||
218 | self.format_string = format | ||
219 | |||
220 | def update(self, pbar): | ||
221 | return self.format_string % pbar.currval | ||
222 | |||
223 | |||
224 | class Percentage(Widget): | ||
225 | """Displays the current percentage as a number with a percent sign.""" | ||
226 | |||
227 | def update(self, pbar): | ||
228 | return '%3d%%' % pbar.percentage() | ||
229 | |||
230 | |||
231 | class FormatLabel(Timer): | ||
232 | """Displays a formatted label.""" | ||
233 | |||
234 | mapping = { | ||
235 | 'elapsed': ('seconds_elapsed', Timer.format_time), | ||
236 | 'finished': ('finished', None), | ||
237 | 'last_update': ('last_update_time', None), | ||
238 | 'max': ('maxval', None), | ||
239 | 'seconds': ('seconds_elapsed', None), | ||
240 | 'start': ('start_time', None), | ||
241 | 'value': ('currval', None) | ||
242 | } | ||
243 | |||
244 | __slots__ = ('format_string',) | ||
245 | def __init__(self, format): | ||
246 | self.format_string = format | ||
247 | |||
248 | def update(self, pbar): | ||
249 | context = {} | ||
250 | for name, (key, transform) in self.mapping.items(): | ||
251 | try: | ||
252 | value = getattr(pbar, key) | ||
253 | |||
254 | if transform is None: | ||
255 | context[name] = value | ||
256 | else: | ||
257 | context[name] = transform(value) | ||
258 | except: pass | ||
259 | |||
260 | return self.format_string % context | ||
261 | |||
262 | |||
263 | class SimpleProgress(Widget): | ||
264 | """Returns progress as a count of the total (e.g.: "5 of 47").""" | ||
265 | |||
266 | __slots__ = ('sep',) | ||
267 | |||
268 | def __init__(self, sep=' of '): | ||
269 | self.sep = sep | ||
270 | |||
271 | def update(self, pbar): | ||
272 | return '%d%s%d' % (pbar.currval, self.sep, pbar.maxval) | ||
273 | |||
274 | |||
275 | class Bar(WidgetHFill): | ||
276 | """A progress bar which stretches to fill the line.""" | ||
277 | |||
278 | __slots__ = ('marker', 'left', 'right', 'fill', 'fill_left') | ||
279 | |||
280 | def __init__(self, marker='#', left='|', right='|', fill=' ', | ||
281 | fill_left=True): | ||
282 | """Creates a customizable progress bar. | ||
283 | |||
284 | marker - string or updatable object to use as a marker | ||
285 | left - string or updatable object to use as a left border | ||
286 | right - string or updatable object to use as a right border | ||
287 | fill - character to use for the empty part of the progress bar | ||
288 | fill_left - whether to fill from the left or the right | ||
289 | """ | ||
290 | self.marker = marker | ||
291 | self.left = left | ||
292 | self.right = right | ||
293 | self.fill = fill | ||
294 | self.fill_left = fill_left | ||
295 | |||
296 | |||
297 | def update(self, pbar, width): | ||
298 | """Updates the progress bar and its subcomponents.""" | ||
299 | |||
300 | left, marked, right = (format_updatable(i, pbar) for i in | ||
301 | (self.left, self.marker, self.right)) | ||
302 | |||
303 | width -= len(left) + len(right) | ||
304 | # Marked must *always* have length of 1 | ||
305 | if pbar.maxval: | ||
306 | marked *= int(pbar.currval / pbar.maxval * width) | ||
307 | else: | ||
308 | marked = '' | ||
309 | |||
310 | if self.fill_left: | ||
311 | return '%s%s%s' % (left, marked.ljust(width, self.fill), right) | ||
312 | else: | ||
313 | return '%s%s%s' % (left, marked.rjust(width, self.fill), right) | ||
314 | |||
315 | |||
316 | class ReverseBar(Bar): | ||
317 | """A bar which has a marker which bounces from side to side.""" | ||
318 | |||
319 | def __init__(self, marker='#', left='|', right='|', fill=' ', | ||
320 | fill_left=False): | ||
321 | """Creates a customizable progress bar. | ||
322 | |||
323 | marker - string or updatable object to use as a marker | ||
324 | left - string or updatable object to use as a left border | ||
325 | right - string or updatable object to use as a right border | ||
326 | fill - character to use for the empty part of the progress bar | ||
327 | fill_left - whether to fill from the left or the right | ||
328 | """ | ||
329 | self.marker = marker | ||
330 | self.left = left | ||
331 | self.right = right | ||
332 | self.fill = fill | ||
333 | self.fill_left = fill_left | ||
334 | |||
335 | |||
336 | class BouncingBar(Bar): | ||
337 | def update(self, pbar, width): | ||
338 | """Updates the progress bar and its subcomponents.""" | ||
339 | |||
340 | left, marker, right = (format_updatable(i, pbar) for i in | ||
341 | (self.left, self.marker, self.right)) | ||
342 | |||
343 | width -= len(left) + len(right) | ||
344 | |||
345 | if pbar.finished: return '%s%s%s' % (left, width * marker, right) | ||
346 | |||
347 | position = int(pbar.currval % (width * 2 - 1)) | ||
348 | if position > width: position = width * 2 - position | ||
349 | lpad = self.fill * (position - 1) | ||
350 | rpad = self.fill * (width - len(marker) - len(lpad)) | ||
351 | |||
352 | # Swap if we want to bounce the other way | ||
353 | if not self.fill_left: rpad, lpad = lpad, rpad | ||
354 | |||
355 | return '%s%s%s%s%s' % (left, lpad, marker, rpad, right) | ||