diff options
author | Tom Zanussi <tom.zanussi@linux.intel.com> | 2013-08-24 15:31:34 +0000 |
---|---|---|
committer | Richard Purdie <richard.purdie@linuxfoundation.org> | 2013-10-01 22:56:03 +0100 |
commit | 9fc88f96d40b17c90bac53b90045a87b2d2cff84 (patch) | |
tree | 63010e5aabf895697655baf89bd668d6752b3f97 /scripts/lib/mic/3rdparty/pykickstart/urlgrabber/progress.py | |
parent | 53a1d9a788fd9f970af980da2ab975cca60685c4 (diff) | |
download | poky-9fc88f96d40b17c90bac53b90045a87b2d2cff84.tar.gz |
wic: Add mic w/pykickstart
This is the starting point for the implemention described in [YOCTO
3847] which came to the conclusion that it would make sense to use
kickstart syntax to implement image creation in OpenEmbedded. I
subsequently realized that there was an existing tool that already
implemented image creation using kickstart syntax, the Tizen/Meego mic
tool. As such, it made sense to use that as a starting point - this
commit essentially just copies the relevant Python code from the MIC
tool to the scripts/lib dir, where it can be accessed by the
previously created wic tool.
Most of this will be removed or renamed by later commits, since we're
initially focusing on partitioning only. Care should be taken so that
we can easily add back any additional functionality should we decide
later to expand the tool, though (we may also want to contribute our
local changes to the mic tool to the Tizen project if it makes sense,
and therefore should avoid gratuitous changes to the original code if
possible).
Added the /mic subdir from Tizen mic repo as a starting point:
git clone git://review.tizen.org/tools/mic.git
For reference, the top commit:
commit 20164175ddc234a17b8a12c33d04b012347b1530
Author: Gui Chen <gui.chen@intel.com>
Date: Sun Jun 30 22:32:16 2013 -0400
bump up to 0.19.2
Also added the /plugins subdir, moved to under the /mic subdir (to
match the default plugin_dir location in mic.conf.in, which was
renamed to yocto-image.conf (moved and renamed by later patches) and
put into /scripts.
(From OE-Core rev: 31f0360f1fd4ebc9dfcaed42d1c50d2448b4632e)
Signed-off-by: Tom Zanussi <tom.zanussi@linux.intel.com>
Signed-off-by: Saul Wold <sgw@linux.intel.com>
Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
Diffstat (limited to 'scripts/lib/mic/3rdparty/pykickstart/urlgrabber/progress.py')
-rw-r--r-- | scripts/lib/mic/3rdparty/pykickstart/urlgrabber/progress.py | 530 |
1 files changed, 530 insertions, 0 deletions
diff --git a/scripts/lib/mic/3rdparty/pykickstart/urlgrabber/progress.py b/scripts/lib/mic/3rdparty/pykickstart/urlgrabber/progress.py new file mode 100644 index 0000000000..02db524e76 --- /dev/null +++ b/scripts/lib/mic/3rdparty/pykickstart/urlgrabber/progress.py | |||
@@ -0,0 +1,530 @@ | |||
1 | # This library is free software; you can redistribute it and/or | ||
2 | # modify it under the terms of the GNU Lesser General Public | ||
3 | # License as published by the Free Software Foundation; either | ||
4 | # version 2.1 of the License, or (at your option) any later version. | ||
5 | # | ||
6 | # This library is distributed in the hope that it will be useful, | ||
7 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
8 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
9 | # Lesser General Public License for more details. | ||
10 | # | ||
11 | # You should have received a copy of the GNU Lesser General Public | ||
12 | # License along with this library; if not, write to the | ||
13 | # Free Software Foundation, Inc., | ||
14 | # 59 Temple Place, Suite 330, | ||
15 | # Boston, MA 02111-1307 USA | ||
16 | |||
17 | # This file is part of urlgrabber, a high-level cross-protocol url-grabber | ||
18 | # Copyright 2002-2004 Michael D. Stenner, Ryan Tomayko | ||
19 | |||
20 | # $Id: progress.py,v 1.7 2005/08/19 21:59:07 mstenner Exp $ | ||
21 | |||
22 | import sys | ||
23 | import time | ||
24 | import math | ||
25 | import thread | ||
26 | |||
27 | class BaseMeter: | ||
28 | def __init__(self): | ||
29 | self.update_period = 0.3 # seconds | ||
30 | |||
31 | self.filename = None | ||
32 | self.url = None | ||
33 | self.basename = None | ||
34 | self.text = None | ||
35 | self.size = None | ||
36 | self.start_time = None | ||
37 | self.last_amount_read = 0 | ||
38 | self.last_update_time = None | ||
39 | self.re = RateEstimator() | ||
40 | |||
41 | def start(self, filename=None, url=None, basename=None, | ||
42 | size=None, now=None, text=None): | ||
43 | self.filename = filename | ||
44 | self.url = url | ||
45 | self.basename = basename | ||
46 | self.text = text | ||
47 | |||
48 | #size = None ######### TESTING | ||
49 | self.size = size | ||
50 | if not size is None: self.fsize = format_number(size) + 'B' | ||
51 | |||
52 | if now is None: now = time.time() | ||
53 | self.start_time = now | ||
54 | self.re.start(size, now) | ||
55 | self.last_amount_read = 0 | ||
56 | self.last_update_time = now | ||
57 | self._do_start(now) | ||
58 | |||
59 | def _do_start(self, now=None): | ||
60 | pass | ||
61 | |||
62 | def update(self, amount_read, now=None): | ||
63 | # for a real gui, you probably want to override and put a call | ||
64 | # to your mainloop iteration function here | ||
65 | if now is None: now = time.time() | ||
66 | if (now >= self.last_update_time + self.update_period) or \ | ||
67 | not self.last_update_time: | ||
68 | self.re.update(amount_read, now) | ||
69 | self.last_amount_read = amount_read | ||
70 | self.last_update_time = now | ||
71 | self._do_update(amount_read, now) | ||
72 | |||
73 | def _do_update(self, amount_read, now=None): | ||
74 | pass | ||
75 | |||
76 | def end(self, amount_read, now=None): | ||
77 | if now is None: now = time.time() | ||
78 | self.re.update(amount_read, now) | ||
79 | self.last_amount_read = amount_read | ||
80 | self.last_update_time = now | ||
81 | self._do_end(amount_read, now) | ||
82 | |||
83 | def _do_end(self, amount_read, now=None): | ||
84 | pass | ||
85 | |||
86 | class TextMeter(BaseMeter): | ||
87 | def __init__(self, fo=sys.stderr): | ||
88 | BaseMeter.__init__(self) | ||
89 | self.fo = fo | ||
90 | |||
91 | def _do_update(self, amount_read, now=None): | ||
92 | etime = self.re.elapsed_time() | ||
93 | fetime = format_time(etime) | ||
94 | fread = format_number(amount_read) | ||
95 | #self.size = None | ||
96 | if self.text is not None: | ||
97 | text = self.text | ||
98 | else: | ||
99 | text = self.basename | ||
100 | if self.size is None: | ||
101 | out = '\r%-60.60s %5sB %s ' % \ | ||
102 | (text, fread, fetime) | ||
103 | else: | ||
104 | rtime = self.re.remaining_time() | ||
105 | frtime = format_time(rtime) | ||
106 | frac = self.re.fraction_read() | ||
107 | bar = '='*int(25 * frac) | ||
108 | |||
109 | out = '\r%-25.25s %3i%% |%-25.25s| %5sB %8s ETA ' % \ | ||
110 | (text, frac*100, bar, fread, frtime) | ||
111 | |||
112 | self.fo.write(out) | ||
113 | self.fo.flush() | ||
114 | |||
115 | def _do_end(self, amount_read, now=None): | ||
116 | total_time = format_time(self.re.elapsed_time()) | ||
117 | total_size = format_number(amount_read) | ||
118 | if self.text is not None: | ||
119 | text = self.text | ||
120 | else: | ||
121 | text = self.basename | ||
122 | if self.size is None: | ||
123 | out = '\r%-60.60s %5sB %s ' % \ | ||
124 | (text, total_size, total_time) | ||
125 | else: | ||
126 | bar = '='*25 | ||
127 | out = '\r%-25.25s %3i%% |%-25.25s| %5sB %8s ' % \ | ||
128 | (text, 100, bar, total_size, total_time) | ||
129 | self.fo.write(out + '\n') | ||
130 | self.fo.flush() | ||
131 | |||
132 | text_progress_meter = TextMeter | ||
133 | |||
134 | class MultiFileHelper(BaseMeter): | ||
135 | def __init__(self, master): | ||
136 | BaseMeter.__init__(self) | ||
137 | self.master = master | ||
138 | |||
139 | def _do_start(self, now): | ||
140 | self.master.start_meter(self, now) | ||
141 | |||
142 | def _do_update(self, amount_read, now): | ||
143 | # elapsed time since last update | ||
144 | self.master.update_meter(self, now) | ||
145 | |||
146 | def _do_end(self, amount_read, now): | ||
147 | self.ftotal_time = format_time(now - self.start_time) | ||
148 | self.ftotal_size = format_number(self.last_amount_read) | ||
149 | self.master.end_meter(self, now) | ||
150 | |||
151 | def failure(self, message, now=None): | ||
152 | self.master.failure_meter(self, message, now) | ||
153 | |||
154 | def message(self, message): | ||
155 | self.master.message_meter(self, message) | ||
156 | |||
157 | class MultiFileMeter: | ||
158 | helperclass = MultiFileHelper | ||
159 | def __init__(self): | ||
160 | self.meters = [] | ||
161 | self.in_progress_meters = [] | ||
162 | self._lock = thread.allocate_lock() | ||
163 | self.update_period = 0.3 # seconds | ||
164 | |||
165 | self.numfiles = None | ||
166 | self.finished_files = 0 | ||
167 | self.failed_files = 0 | ||
168 | self.open_files = 0 | ||
169 | self.total_size = None | ||
170 | self.failed_size = 0 | ||
171 | self.start_time = None | ||
172 | self.finished_file_size = 0 | ||
173 | self.last_update_time = None | ||
174 | self.re = RateEstimator() | ||
175 | |||
176 | def start(self, numfiles=None, total_size=None, now=None): | ||
177 | if now is None: now = time.time() | ||
178 | self.numfiles = numfiles | ||
179 | self.finished_files = 0 | ||
180 | self.failed_files = 0 | ||
181 | self.open_files = 0 | ||
182 | self.total_size = total_size | ||
183 | self.failed_size = 0 | ||
184 | self.start_time = now | ||
185 | self.finished_file_size = 0 | ||
186 | self.last_update_time = now | ||
187 | self.re.start(total_size, now) | ||
188 | self._do_start(now) | ||
189 | |||
190 | def _do_start(self, now): | ||
191 | pass | ||
192 | |||
193 | def end(self, now=None): | ||
194 | if now is None: now = time.time() | ||
195 | self._do_end(now) | ||
196 | |||
197 | def _do_end(self, now): | ||
198 | pass | ||
199 | |||
200 | def lock(self): self._lock.acquire() | ||
201 | def unlock(self): self._lock.release() | ||
202 | |||
203 | ########################################################### | ||
204 | # child meter creation and destruction | ||
205 | def newMeter(self): | ||
206 | newmeter = self.helperclass(self) | ||
207 | self.meters.append(newmeter) | ||
208 | return newmeter | ||
209 | |||
210 | def removeMeter(self, meter): | ||
211 | self.meters.remove(meter) | ||
212 | |||
213 | ########################################################### | ||
214 | # child functions - these should only be called by helpers | ||
215 | def start_meter(self, meter, now): | ||
216 | if not meter in self.meters: | ||
217 | raise ValueError('attempt to use orphaned meter') | ||
218 | self._lock.acquire() | ||
219 | try: | ||
220 | if not meter in self.in_progress_meters: | ||
221 | self.in_progress_meters.append(meter) | ||
222 | self.open_files += 1 | ||
223 | finally: | ||
224 | self._lock.release() | ||
225 | self._do_start_meter(meter, now) | ||
226 | |||
227 | def _do_start_meter(self, meter, now): | ||
228 | pass | ||
229 | |||
230 | def update_meter(self, meter, now): | ||
231 | if not meter in self.meters: | ||
232 | raise ValueError('attempt to use orphaned meter') | ||
233 | if (now >= self.last_update_time + self.update_period) or \ | ||
234 | not self.last_update_time: | ||
235 | self.re.update(self._amount_read(), now) | ||
236 | self.last_update_time = now | ||
237 | self._do_update_meter(meter, now) | ||
238 | |||
239 | def _do_update_meter(self, meter, now): | ||
240 | pass | ||
241 | |||
242 | def end_meter(self, meter, now): | ||
243 | if not meter in self.meters: | ||
244 | raise ValueError('attempt to use orphaned meter') | ||
245 | self._lock.acquire() | ||
246 | try: | ||
247 | try: self.in_progress_meters.remove(meter) | ||
248 | except ValueError: pass | ||
249 | self.open_files -= 1 | ||
250 | self.finished_files += 1 | ||
251 | self.finished_file_size += meter.last_amount_read | ||
252 | finally: | ||
253 | self._lock.release() | ||
254 | self._do_end_meter(meter, now) | ||
255 | |||
256 | def _do_end_meter(self, meter, now): | ||
257 | pass | ||
258 | |||
259 | def failure_meter(self, meter, message, now): | ||
260 | if not meter in self.meters: | ||
261 | raise ValueError('attempt to use orphaned meter') | ||
262 | self._lock.acquire() | ||
263 | try: | ||
264 | try: self.in_progress_meters.remove(meter) | ||
265 | except ValueError: pass | ||
266 | self.open_files -= 1 | ||
267 | self.failed_files += 1 | ||
268 | if meter.size and self.failed_size is not None: | ||
269 | self.failed_size += meter.size | ||
270 | else: | ||
271 | self.failed_size = None | ||
272 | finally: | ||
273 | self._lock.release() | ||
274 | self._do_failure_meter(meter, message, now) | ||
275 | |||
276 | def _do_failure_meter(self, meter, message, now): | ||
277 | pass | ||
278 | |||
279 | def message_meter(self, meter, message): | ||
280 | pass | ||
281 | |||
282 | ######################################################## | ||
283 | # internal functions | ||
284 | def _amount_read(self): | ||
285 | tot = self.finished_file_size | ||
286 | for m in self.in_progress_meters: | ||
287 | tot += m.last_amount_read | ||
288 | return tot | ||
289 | |||
290 | |||
291 | class TextMultiFileMeter(MultiFileMeter): | ||
292 | def __init__(self, fo=sys.stderr): | ||
293 | self.fo = fo | ||
294 | MultiFileMeter.__init__(self) | ||
295 | |||
296 | # files: ###/### ###% data: ######/###### ###% time: ##:##:##/##:##:## | ||
297 | def _do_update_meter(self, meter, now): | ||
298 | self._lock.acquire() | ||
299 | try: | ||
300 | format = "files: %3i/%-3i %3i%% data: %6.6s/%-6.6s %3i%% " \ | ||
301 | "time: %8.8s/%8.8s" | ||
302 | df = self.finished_files | ||
303 | tf = self.numfiles or 1 | ||
304 | pf = 100 * float(df)/tf + 0.49 | ||
305 | dd = self.re.last_amount_read | ||
306 | td = self.total_size | ||
307 | pd = 100 * (self.re.fraction_read() or 0) + 0.49 | ||
308 | dt = self.re.elapsed_time() | ||
309 | rt = self.re.remaining_time() | ||
310 | if rt is None: tt = None | ||
311 | else: tt = dt + rt | ||
312 | |||
313 | fdd = format_number(dd) + 'B' | ||
314 | ftd = format_number(td) + 'B' | ||
315 | fdt = format_time(dt, 1) | ||
316 | ftt = format_time(tt, 1) | ||
317 | |||
318 | out = '%-79.79s' % (format % (df, tf, pf, fdd, ftd, pd, fdt, ftt)) | ||
319 | self.fo.write('\r' + out) | ||
320 | self.fo.flush() | ||
321 | finally: | ||
322 | self._lock.release() | ||
323 | |||
324 | def _do_end_meter(self, meter, now): | ||
325 | self._lock.acquire() | ||
326 | try: | ||
327 | format = "%-30.30s %6.6s %8.8s %9.9s" | ||
328 | fn = meter.basename | ||
329 | size = meter.last_amount_read | ||
330 | fsize = format_number(size) + 'B' | ||
331 | et = meter.re.elapsed_time() | ||
332 | fet = format_time(et, 1) | ||
333 | frate = format_number(size / et) + 'B/s' | ||
334 | |||
335 | out = '%-79.79s' % (format % (fn, fsize, fet, frate)) | ||
336 | self.fo.write('\r' + out + '\n') | ||
337 | finally: | ||
338 | self._lock.release() | ||
339 | self._do_update_meter(meter, now) | ||
340 | |||
341 | def _do_failure_meter(self, meter, message, now): | ||
342 | self._lock.acquire() | ||
343 | try: | ||
344 | format = "%-30.30s %6.6s %s" | ||
345 | fn = meter.basename | ||
346 | if type(message) in (type(''), type(u'')): | ||
347 | message = message.splitlines() | ||
348 | if not message: message = [''] | ||
349 | out = '%-79s' % (format % (fn, 'FAILED', message[0] or '')) | ||
350 | self.fo.write('\r' + out + '\n') | ||
351 | for m in message[1:]: self.fo.write(' ' + m + '\n') | ||
352 | self._lock.release() | ||
353 | finally: | ||
354 | self._do_update_meter(meter, now) | ||
355 | |||
356 | def message_meter(self, meter, message): | ||
357 | self._lock.acquire() | ||
358 | try: | ||
359 | pass | ||
360 | finally: | ||
361 | self._lock.release() | ||
362 | |||
363 | def _do_end(self, now): | ||
364 | self._do_update_meter(None, now) | ||
365 | self._lock.acquire() | ||
366 | try: | ||
367 | self.fo.write('\n') | ||
368 | self.fo.flush() | ||
369 | finally: | ||
370 | self._lock.release() | ||
371 | |||
372 | ###################################################################### | ||
373 | # support classes and functions | ||
374 | |||
375 | class RateEstimator: | ||
376 | def __init__(self, timescale=5.0): | ||
377 | self.timescale = timescale | ||
378 | |||
379 | def start(self, total=None, now=None): | ||
380 | if now is None: now = time.time() | ||
381 | self.total = total | ||
382 | self.start_time = now | ||
383 | self.last_update_time = now | ||
384 | self.last_amount_read = 0 | ||
385 | self.ave_rate = None | ||
386 | |||
387 | def update(self, amount_read, now=None): | ||
388 | if now is None: now = time.time() | ||
389 | if amount_read == 0: | ||
390 | # if we just started this file, all bets are off | ||
391 | self.last_update_time = now | ||
392 | self.last_amount_read = 0 | ||
393 | self.ave_rate = None | ||
394 | return | ||
395 | |||
396 | #print 'times', now, self.last_update_time | ||
397 | time_diff = now - self.last_update_time | ||
398 | read_diff = amount_read - self.last_amount_read | ||
399 | self.last_update_time = now | ||
400 | self.last_amount_read = amount_read | ||
401 | self.ave_rate = self._temporal_rolling_ave(\ | ||
402 | time_diff, read_diff, self.ave_rate, self.timescale) | ||
403 | #print 'results', time_diff, read_diff, self.ave_rate | ||
404 | |||
405 | ##################################################################### | ||
406 | # result methods | ||
407 | def average_rate(self): | ||
408 | "get the average transfer rate (in bytes/second)" | ||
409 | return self.ave_rate | ||
410 | |||
411 | def elapsed_time(self): | ||
412 | "the time between the start of the transfer and the most recent update" | ||
413 | return self.last_update_time - self.start_time | ||
414 | |||
415 | def remaining_time(self): | ||
416 | "estimated time remaining" | ||
417 | if not self.ave_rate or not self.total: return None | ||
418 | return (self.total - self.last_amount_read) / self.ave_rate | ||
419 | |||
420 | def fraction_read(self): | ||
421 | """the fraction of the data that has been read | ||
422 | (can be None for unknown transfer size)""" | ||
423 | if self.total is None: return None | ||
424 | elif self.total == 0: return 1.0 | ||
425 | else: return float(self.last_amount_read)/self.total | ||
426 | |||
427 | ######################################################################### | ||
428 | # support methods | ||
429 | def _temporal_rolling_ave(self, time_diff, read_diff, last_ave, timescale): | ||
430 | """a temporal rolling average performs smooth averaging even when | ||
431 | updates come at irregular intervals. This is performed by scaling | ||
432 | the "epsilon" according to the time since the last update. | ||
433 | Specifically, epsilon = time_diff / timescale | ||
434 | |||
435 | As a general rule, the average will take on a completely new value | ||
436 | after 'timescale' seconds.""" | ||
437 | epsilon = time_diff / timescale | ||
438 | if epsilon > 1: epsilon = 1.0 | ||
439 | return self._rolling_ave(time_diff, read_diff, last_ave, epsilon) | ||
440 | |||
441 | def _rolling_ave(self, time_diff, read_diff, last_ave, epsilon): | ||
442 | """perform a "rolling average" iteration | ||
443 | a rolling average "folds" new data into an existing average with | ||
444 | some weight, epsilon. epsilon must be between 0.0 and 1.0 (inclusive) | ||
445 | a value of 0.0 means only the old value (initial value) counts, | ||
446 | and a value of 1.0 means only the newest value is considered.""" | ||
447 | |||
448 | try: | ||
449 | recent_rate = read_diff / time_diff | ||
450 | except ZeroDivisionError: | ||
451 | recent_rate = None | ||
452 | if last_ave is None: return recent_rate | ||
453 | elif recent_rate is None: return last_ave | ||
454 | |||
455 | # at this point, both last_ave and recent_rate are numbers | ||
456 | return epsilon * recent_rate + (1 - epsilon) * last_ave | ||
457 | |||
458 | def _round_remaining_time(self, rt, start_time=15.0): | ||
459 | """round the remaining time, depending on its size | ||
460 | If rt is between n*start_time and (n+1)*start_time round downward | ||
461 | to the nearest multiple of n (for any counting number n). | ||
462 | If rt < start_time, round down to the nearest 1. | ||
463 | For example (for start_time = 15.0): | ||
464 | 2.7 -> 2.0 | ||
465 | 25.2 -> 25.0 | ||
466 | 26.4 -> 26.0 | ||
467 | 35.3 -> 34.0 | ||
468 | 63.6 -> 60.0 | ||
469 | """ | ||
470 | |||
471 | if rt < 0: return 0.0 | ||
472 | shift = int(math.log(rt/start_time)/math.log(2)) | ||
473 | rt = int(rt) | ||
474 | if shift <= 0: return rt | ||
475 | return float(int(rt) >> shift << shift) | ||
476 | |||
477 | |||
478 | def format_time(seconds, use_hours=0): | ||
479 | if seconds is None or seconds < 0: | ||
480 | if use_hours: return '--:--:--' | ||
481 | else: return '--:--' | ||
482 | else: | ||
483 | seconds = int(seconds) | ||
484 | minutes = seconds / 60 | ||
485 | seconds = seconds % 60 | ||
486 | if use_hours: | ||
487 | hours = minutes / 60 | ||
488 | minutes = minutes % 60 | ||
489 | return '%02i:%02i:%02i' % (hours, minutes, seconds) | ||
490 | else: | ||
491 | return '%02i:%02i' % (minutes, seconds) | ||
492 | |||
493 | def format_number(number, SI=0, space=' '): | ||
494 | """Turn numbers into human-readable metric-like numbers""" | ||
495 | symbols = ['', # (none) | ||
496 | 'k', # kilo | ||
497 | 'M', # mega | ||
498 | 'G', # giga | ||
499 | 'T', # tera | ||
500 | 'P', # peta | ||
501 | 'E', # exa | ||
502 | 'Z', # zetta | ||
503 | 'Y'] # yotta | ||
504 | |||
505 | if SI: step = 1000.0 | ||
506 | else: step = 1024.0 | ||
507 | |||
508 | thresh = 999 | ||
509 | depth = 0 | ||
510 | max_depth = len(symbols) - 1 | ||
511 | |||
512 | # we want numbers between 0 and thresh, but don't exceed the length | ||
513 | # of our list. In that event, the formatting will be screwed up, | ||
514 | # but it'll still show the right number. | ||
515 | while number > thresh and depth < max_depth: | ||
516 | depth = depth + 1 | ||
517 | number = number / step | ||
518 | |||
519 | if type(number) == type(1) or type(number) == type(1L): | ||
520 | # it's an int or a long, which means it didn't get divided, | ||
521 | # which means it's already short enough | ||
522 | format = '%i%s%s' | ||
523 | elif number < 9.95: | ||
524 | # must use 9.95 for proper sizing. For example, 9.99 will be | ||
525 | # rounded to 10.0 with the .1f format string (which is too long) | ||
526 | format = '%.1f%s%s' | ||
527 | else: | ||
528 | format = '%.0f%s%s' | ||
529 | |||
530 | return(format % (float(number or 0), space, symbols[depth])) | ||