From: David Cantrell <dcantrell@xxxxxxxxxx> Created a new class called Size under the storage module. It can represent sizes where we have traditionally used megabytes. The size is stored as bytes in the object, but can convert to any other type unit supported by the class. The class also has the humanReadable() method, which will output the size in a human readable format to a specified number of decimal places, or fewer if that makes sense in the given situation. The initialization method also accepts a number of bytes or a string specification for the size, such as "32 GB" or "640 kb". --- pyanaconda/storage/size.py | 230 ++++++++++++++++++++++++++++++++++++++++++++ 1 files changed, 230 insertions(+), 0 deletions(-) create mode 100644 pyanaconda/storage/size.py diff --git a/pyanaconda/storage/size.py b/pyanaconda/storage/size.py new file mode 100644 index 0000000..7a4977c --- /dev/null +++ b/pyanaconda/storage/size.py @@ -0,0 +1,230 @@ +# size.py +# Python module to represent storage sizes +# +# Copyright (C) 2010 Red Hat, Inc. +# +# This copyrighted material is made available to anyone wishing to use, +# modify, copy, or redistribute it subject to the terms and conditions of +# the GNU General Public License v.2, or (at your option) any later version. +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY expressed or implied, including the implied warranties of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +# Public License for more details. You should have received a copy of the +# GNU General Public License along with this program; if not, write to the +# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301, USA. Any Red Hat trademarks that are incorporated in the +# source code or documentation are not subject to the GNU General Public +# License and may only be used or replicated with the express permission of +# Red Hat, Inc. +# +# Red Hat Author(s): David Cantrell <dcantrell@xxxxxxxxxx> + +import decimal + +from errors import * + +class Size: + """ Common class to represent storage device and filesystem sizes. + Can handle parsing strings such as 45MB or 6.7GB to initialize + itself, or can be initialized with a numerical size in bytes. + Also generates human readable strings to a specified number of + decimal places. + """ + + # Decimal prefixes for different size increments, along with the name + # and accepted abbreviation for the prefix. These prefixes are all + # for 'bytes'. + _decimalPrefix = [(1000, "kilo", "k"), + (1000**2, "mega", "M"), + (1000**3, "giga", "G"), + (1000**4, "tera", "T"), + (1000**5, "peta", "P"), + (1000**6, "exa", "E"), + (1000**7, "zetta", "Z"), + (1000**8, "yotta", "Y")] + + # Binary prefixes for the different size increments. Same structure + # as the above list. + _binaryPrefix = [(1024, "kibi", "Ki"), + (1024**2, "mebi", "Mi"), + (1024**3, "gibi", "Gi"), + (1024**4, "tebi", None), + (1024**5, "pebi", None), + (1024**6, "ebi", None), + (1024**7, "zebi", None), + (1024**8, "yobi", None)] + + _prefixes = _decimalPrefix + _binaryPrefix + + def __init__(self, bytes=None, spec=None): + """ Initialize a new Size object. Must pass either bytes or spec, + but not both. The bytes parameter is a numerical value for + the size this object represents, in bytes. The spec parameter + is a string specification of the size using any of the size + specifiers in the _decimalPrefix or _binaryPrefix lists combined + with a 'b' or 'B'. For example, to specify 640 kilobytes, you + could pass any of these parameter: + + spec="640kb" + spec="640 kb" + spec="640KB" + spec="640 KB" + spec="640 kilobytes" + + If you want to use spec to pass a bytes value, you can use the + letter 'b' or 'B' or simply leave the specifier off and bytes + will be assumed. + """ + self._bytes = None + + if bytes and spec: + raise SizeParamsError("only specify one parameter") + + if bytes: + if type(bytes).__name__ in ["int", "long"] and bytes > 0: + self._bytes = long(bytes) + else: + raise SizePositiveError("bytes= param must be >0") + elif spec: + self._bytes = self._parseSpec(spec) + + if self._bytes is None: + raise SizeFailedError("failed to create new Size object") + + def _makeSpecs(self, prefix, abbr): + """ Internal method used to generate a list of specifiers. """ + specs = [] + + if prefix: + specs.append("%sbytes" % prefix.lower()) + + if abbr: + specs.append("%sb" % abbr.lower()) + + return specs + + def _parseSpec(self, spec): + """ Parse string representation of size. """ + if not spec: + return None + + # first check for strings like "47 MB" or "1.21 GB" + fields = spec.split() + if len(fields) == 2: + # assume value for the first field and specifier for the second + size = long(fields[0]) + specifier = fields[1].lower() + + if size < 1: + raise SizePositiveError("spec= param must be >0") + + if specifier in ['b', 'bytes']: + return size + + for val, prefix, abbr in self._prefixes: + check = self._makeSpecs(prefix, abbr) + + if specifier in check: + return size * val + elif len(fields) > 2 or len(fields) < 1: + # too much data in the string + return None + else: + # no space between value and specifier + try: + size = long(spec) + + if size < 1: + raise SizePositiveError("spec= param must be >0") + + if str(size) == spec: + return size + except ValueError: + pass + + for val, prefix, abbr in self._prefixes: + check = self._makeSpecs(prefix, abbr) + match = filter(lambda x: spec.lower().endswith(x), check) + + if match != [] and len(match) == 1: + size = long(spec[:len(match[0])].strip()) + + if size < 1: + raise SizePositiveError("spec= param must be >0") + + return size * val + + return None + + def _trimEnd(self, val): + """ Internal method to trim trailing zeros. """ + while val != '' and val.endswith('0'): + val = val[:-1] + + if val.endswith('.'): + val = val[:-1] + + return val + + @property + def bytes(self): + return self._bytes + + def convertTo(self, spec="b"): + """ Return the size in the units indicated by the specifier. The + specifier can be prefixes from the _decimalPrefix and + _binaryPrefix lists combined with 'b' or 'B' for abbreviations) + or 'bytes' (for prefixes like kilo or mega). The size is + returned as a long. Keep in mind you won't get an exact + value as this method forces conversion to the specified unit, + rounding as necessary so the caller gets a whole value. + """ + spec = spec.lower() + + if spec in ['b', 'bytes']: + return self._bytes + + for val, prefix, abbr in self._prefixes: + check = self._makeSpecs(prefix, abbr) + + if spec in check: + ret = decimal.Decimal(self._bytes) / decimal.Decimal(val) + return long(ret.quantize(decimal.Decimal('1.'), + rounding=decimal.ROUND_UP)) + + return None + + def humanReadable(self, places=2): + """ Return a string representation of this size with appropriate + size specifier and in the specified number of decimal places + (default: 2). + """ + if places < 1: + raise SizePlacesError("places= must be >1") + + totalLen = places + 2 + check = self._trimEnd("%d" % self._bytes) + + if len(check) == totalLen: + return "%s b" % check + + bytes = decimal.Decimal(self._bytes) + rounder = decimal.Decimal("." + "1".zfill(places)) + + for val, prefix, abbr in self._prefixes: + check = bytes / decimal.Decimal(val) + + i = places + while i > 0: + rounder = decimal.Decimal("." + "1".zfill(i)) + newcheck = str(check.quantize(rounder)) + + if len(newcheck) == totalLen: + if abbr: + return newcheck + " " + abbr + "b" + else: + return newcheck + " " + prefix + "bytes" + + i -= 1 + + return None -- 1.7.2.2 _______________________________________________ Anaconda-devel-list mailing list Anaconda-devel-list@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/anaconda-devel-list