diff options
Diffstat (limited to 'bitbake/lib/progressbar/progressbar.py')
-rw-r--r-- | bitbake/lib/progressbar/progressbar.py | 307 |
1 files changed, 307 insertions, 0 deletions
diff --git a/bitbake/lib/progressbar/progressbar.py b/bitbake/lib/progressbar/progressbar.py new file mode 100644 index 0000000000..0b9dcf763e --- /dev/null +++ b/bitbake/lib/progressbar/progressbar.py | |||
@@ -0,0 +1,307 @@ | |||
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 | """Main ProgressBar class.""" | ||
21 | |||
22 | from __future__ import division | ||
23 | |||
24 | import math | ||
25 | import os | ||
26 | import signal | ||
27 | import sys | ||
28 | import time | ||
29 | |||
30 | try: | ||
31 | from fcntl import ioctl | ||
32 | from array import array | ||
33 | import termios | ||
34 | except ImportError: | ||
35 | pass | ||
36 | |||
37 | from .compat import * # for: any, next | ||
38 | from . import widgets | ||
39 | |||
40 | |||
41 | class UnknownLength: pass | ||
42 | |||
43 | |||
44 | class ProgressBar(object): | ||
45 | """The ProgressBar class which updates and prints the bar. | ||
46 | |||
47 | A common way of using it is like: | ||
48 | >>> pbar = ProgressBar().start() | ||
49 | >>> for i in range(100): | ||
50 | ... # do something | ||
51 | ... pbar.update(i+1) | ||
52 | ... | ||
53 | >>> pbar.finish() | ||
54 | |||
55 | You can also use a ProgressBar as an iterator: | ||
56 | >>> progress = ProgressBar() | ||
57 | >>> for i in progress(some_iterable): | ||
58 | ... # do something | ||
59 | ... | ||
60 | |||
61 | Since the progress bar is incredibly customizable you can specify | ||
62 | different widgets of any type in any order. You can even write your own | ||
63 | widgets! However, since there are already a good number of widgets you | ||
64 | should probably play around with them before moving on to create your own | ||
65 | widgets. | ||
66 | |||
67 | The term_width parameter represents the current terminal width. If the | ||
68 | parameter is set to an integer then the progress bar will use that, | ||
69 | otherwise it will attempt to determine the terminal width falling back to | ||
70 | 80 columns if the width cannot be determined. | ||
71 | |||
72 | When implementing a widget's update method you are passed a reference to | ||
73 | the current progress bar. As a result, you have access to the | ||
74 | ProgressBar's methods and attributes. Although there is nothing preventing | ||
75 | you from changing the ProgressBar you should treat it as read only. | ||
76 | |||
77 | Useful methods and attributes include (Public API): | ||
78 | - currval: current progress (0 <= currval <= maxval) | ||
79 | - maxval: maximum (and final) value | ||
80 | - finished: True if the bar has finished (reached 100%) | ||
81 | - start_time: the time when start() method of ProgressBar was called | ||
82 | - seconds_elapsed: seconds elapsed since start_time and last call to | ||
83 | update | ||
84 | - percentage(): progress in percent [0..100] | ||
85 | """ | ||
86 | |||
87 | __slots__ = ('currval', 'fd', 'finished', 'last_update_time', | ||
88 | 'left_justify', 'maxval', 'next_update', 'num_intervals', | ||
89 | 'poll', 'seconds_elapsed', 'signal_set', 'start_time', | ||
90 | 'term_width', 'update_interval', 'widgets', '_time_sensitive', | ||
91 | '__iterable') | ||
92 | |||
93 | _DEFAULT_MAXVAL = 100 | ||
94 | _DEFAULT_TERMSIZE = 80 | ||
95 | _DEFAULT_WIDGETS = [widgets.Percentage(), ' ', widgets.Bar()] | ||
96 | |||
97 | def __init__(self, maxval=None, widgets=None, term_width=None, poll=1, | ||
98 | left_justify=True, fd=sys.stderr): | ||
99 | """Initializes a progress bar with sane defaults.""" | ||
100 | |||
101 | # Don't share a reference with any other progress bars | ||
102 | if widgets is None: | ||
103 | widgets = list(self._DEFAULT_WIDGETS) | ||
104 | |||
105 | self.maxval = maxval | ||
106 | self.widgets = widgets | ||
107 | self.fd = fd | ||
108 | self.left_justify = left_justify | ||
109 | |||
110 | self.signal_set = False | ||
111 | if term_width is not None: | ||
112 | self.term_width = term_width | ||
113 | else: | ||
114 | try: | ||
115 | self._handle_resize(None, None) | ||
116 | signal.signal(signal.SIGWINCH, self._handle_resize) | ||
117 | self.signal_set = True | ||
118 | except (SystemExit, KeyboardInterrupt): raise | ||
119 | except Exception as e: | ||
120 | print("DEBUG 5 %s" % e) | ||
121 | self.term_width = self._env_size() | ||
122 | |||
123 | self.__iterable = None | ||
124 | self._update_widgets() | ||
125 | self.currval = 0 | ||
126 | self.finished = False | ||
127 | self.last_update_time = None | ||
128 | self.poll = poll | ||
129 | self.seconds_elapsed = 0 | ||
130 | self.start_time = None | ||
131 | self.update_interval = 1 | ||
132 | self.next_update = 0 | ||
133 | |||
134 | |||
135 | def __call__(self, iterable): | ||
136 | """Use a ProgressBar to iterate through an iterable.""" | ||
137 | |||
138 | try: | ||
139 | self.maxval = len(iterable) | ||
140 | except: | ||
141 | if self.maxval is None: | ||
142 | self.maxval = UnknownLength | ||
143 | |||
144 | self.__iterable = iter(iterable) | ||
145 | return self | ||
146 | |||
147 | |||
148 | def __iter__(self): | ||
149 | return self | ||
150 | |||
151 | |||
152 | def __next__(self): | ||
153 | try: | ||
154 | value = next(self.__iterable) | ||
155 | if self.start_time is None: | ||
156 | self.start() | ||
157 | else: | ||
158 | self.update(self.currval + 1) | ||
159 | return value | ||
160 | except StopIteration: | ||
161 | if self.start_time is None: | ||
162 | self.start() | ||
163 | self.finish() | ||
164 | raise | ||
165 | |||
166 | |||
167 | # Create an alias so that Python 2.x won't complain about not being | ||
168 | # an iterator. | ||
169 | next = __next__ | ||
170 | |||
171 | |||
172 | def _env_size(self): | ||
173 | """Tries to find the term_width from the environment.""" | ||
174 | |||
175 | return int(os.environ.get('COLUMNS', self._DEFAULT_TERMSIZE)) - 1 | ||
176 | |||
177 | |||
178 | def _handle_resize(self, signum=None, frame=None): | ||
179 | """Tries to catch resize signals sent from the terminal.""" | ||
180 | |||
181 | h, w = array('h', ioctl(self.fd, termios.TIOCGWINSZ, '\0' * 8))[:2] | ||
182 | self.term_width = w | ||
183 | |||
184 | |||
185 | def percentage(self): | ||
186 | """Returns the progress as a percentage.""" | ||
187 | if self.currval >= self.maxval: | ||
188 | return 100.0 | ||
189 | return (self.currval * 100.0 / self.maxval) if self.maxval else 100.00 | ||
190 | |||
191 | percent = property(percentage) | ||
192 | |||
193 | |||
194 | def _format_widgets(self): | ||
195 | result = [] | ||
196 | expanding = [] | ||
197 | width = self.term_width | ||
198 | |||
199 | for index, widget in enumerate(self.widgets): | ||
200 | if isinstance(widget, widgets.WidgetHFill): | ||
201 | result.append(widget) | ||
202 | expanding.insert(0, index) | ||
203 | else: | ||
204 | widget = widgets.format_updatable(widget, self) | ||
205 | result.append(widget) | ||
206 | width -= len(widget) | ||
207 | |||
208 | count = len(expanding) | ||
209 | while count: | ||
210 | portion = max(int(math.ceil(width * 1. / count)), 0) | ||
211 | index = expanding.pop() | ||
212 | count -= 1 | ||
213 | |||
214 | widget = result[index].update(self, portion) | ||
215 | width -= len(widget) | ||
216 | result[index] = widget | ||
217 | |||
218 | return result | ||
219 | |||
220 | |||
221 | def _format_line(self): | ||
222 | """Joins the widgets and justifies the line.""" | ||
223 | |||
224 | widgets = ''.join(self._format_widgets()) | ||
225 | |||
226 | if self.left_justify: return widgets.ljust(self.term_width) | ||
227 | else: return widgets.rjust(self.term_width) | ||
228 | |||
229 | |||
230 | def _need_update(self): | ||
231 | """Returns whether the ProgressBar should redraw the line.""" | ||
232 | if self.currval >= self.next_update or self.finished: return True | ||
233 | |||
234 | delta = time.time() - self.last_update_time | ||
235 | return self._time_sensitive and delta > self.poll | ||
236 | |||
237 | |||
238 | def _update_widgets(self): | ||
239 | """Checks all widgets for the time sensitive bit.""" | ||
240 | |||
241 | self._time_sensitive = any(getattr(w, 'TIME_SENSITIVE', False) | ||
242 | for w in self.widgets) | ||
243 | |||
244 | |||
245 | def update(self, value=None): | ||
246 | """Updates the ProgressBar to a new value.""" | ||
247 | |||
248 | if value is not None and value is not UnknownLength: | ||
249 | if (self.maxval is not UnknownLength | ||
250 | and not 0 <= value <= self.maxval): | ||
251 | |||
252 | raise ValueError('Value out of range') | ||
253 | |||
254 | self.currval = value | ||
255 | |||
256 | |||
257 | if not self._need_update(): return | ||
258 | if self.start_time is None: | ||
259 | raise RuntimeError('You must call "start" before calling "update"') | ||
260 | |||
261 | now = time.time() | ||
262 | self.seconds_elapsed = now - self.start_time | ||
263 | self.next_update = self.currval + self.update_interval | ||
264 | self.fd.write(self._format_line() + '\r') | ||
265 | self.fd.flush() | ||
266 | self.last_update_time = now | ||
267 | |||
268 | |||
269 | def start(self): | ||
270 | """Starts measuring time, and prints the bar at 0%. | ||
271 | |||
272 | It returns self so you can use it like this: | ||
273 | >>> pbar = ProgressBar().start() | ||
274 | >>> for i in range(100): | ||
275 | ... # do something | ||
276 | ... pbar.update(i+1) | ||
277 | ... | ||
278 | >>> pbar.finish() | ||
279 | """ | ||
280 | |||
281 | if self.maxval is None: | ||
282 | self.maxval = self._DEFAULT_MAXVAL | ||
283 | |||
284 | self.num_intervals = max(100, self.term_width) | ||
285 | self.next_update = 0 | ||
286 | |||
287 | if self.maxval is not UnknownLength: | ||
288 | if self.maxval < 0: raise ValueError('Value out of range') | ||
289 | self.update_interval = self.maxval / self.num_intervals | ||
290 | |||
291 | |||
292 | self.start_time = self.last_update_time = time.time() | ||
293 | self.update(0) | ||
294 | |||
295 | return self | ||
296 | |||
297 | |||
298 | def finish(self): | ||
299 | """Puts the ProgressBar bar in the finished state.""" | ||
300 | |||
301 | if self.finished: | ||
302 | return | ||
303 | self.finished = True | ||
304 | self.update(self.maxval) | ||
305 | self.fd.write('\n') | ||
306 | if self.signal_set: | ||
307 | signal.signal(signal.SIGWINCH, signal.SIG_DFL) | ||