# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io> # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library. If not, see <http://www.gnu.org/licenses/>. def seconds_to_human(s, separator=""): """ Converts number of seconds passed to a human-readable interval such as 1w4d18h35m59s """ s = int(s) year = 60 * 60 * 24 * 365.25 week = 60 * 60 * 24 * 7 day = 60 * 60 * 24 hour = 60 * 60 result = [] years = s // year if years > 0: result.append(f'{int(years)}y') s = int(s % year) weeks = s // week if weeks > 0: result.append(f'{weeks}w') s = s % week days = s // day if days > 0: result.append(f'{days}d') s = s % day hours = s // hour if hours > 0: result.append(f'{hours}h') s = s % hour minutes = s // 60 if minutes > 0: result.append(f'{minutes}m') s = s % 60 seconds = s if seconds > 0: result.append(f'{seconds}s') return separator.join(result) def bytes_to_human(bytes, initial_exponent=0, precision=2, int_below_exponent=0): """ Converts a value in bytes to a human-readable size string like 640 KB The initial_exponent parameter is the exponent of 2, e.g. 10 (1024) for kilobytes, 20 (1024 * 1024) for megabytes. """ if bytes == 0: return "0 B" from math import log2 bytes = bytes * (2**initial_exponent) # log2 is a float, while range checking requires an int exponent = int(log2(bytes)) if exponent < int_below_exponent: precision = 0 if exponent < 10: value = bytes suffix = "B" elif exponent in range(10, 20): value = bytes / 1024 suffix = "KB" elif exponent in range(20, 30): value = bytes / 1024**2 suffix = "MB" elif exponent in range(30, 40): value = bytes / 1024**3 suffix = "GB" else: value = bytes / 1024**4 suffix = "TB" # Add a new case when the first machine with petabyte RAM # hits the market. size_string = "{0:.{1}f} {2}".format(value, precision, suffix) return size_string def human_to_bytes(value): """ Converts a data amount with a unit suffix to bytes, like 2K to 2048 """ from re import match as re_match res = re_match(r'^\s*(\d+(?:\.\d+)?)\s*([a-zA-Z]+)\s*$', value) if not res: raise ValueError(f"'{value}' is not a valid data amount") else: amount = float(res.group(1)) unit = res.group(2).lower() if unit == 'b': res = amount elif (unit == 'k') or (unit == 'kb'): res = amount * 1024 elif (unit == 'm') or (unit == 'mb'): res = amount * 1024**2 elif (unit == 'g') or (unit == 'gb'): res = amount * 1024**3 elif (unit == 't') or (unit == 'tb'): res = amount * 1024**4 else: raise ValueError(f"Unsupported data unit '{unit}'") # There cannot be fractional bytes, so we convert them to integer. # However, truncating causes problems with conversion back to human unit, # so we round instead -- that seems to work well enough. return round(res) def mac_to_eui64(mac, prefix=None): """ Convert a MAC address to a EUI64 address or, with prefix provided, a full IPv6 address. Thankfully copied from https://gist.github.com/wido/f5e32576bb57b5cc6f934e177a37a0d3 """ import re from ipaddress import ip_network # http://tools.ietf.org/html/rfc4291#section-2.5.1 eui64 = re.sub(r'[.:-]', '', mac).lower() eui64 = eui64[0:6] + 'fffe' + eui64[6:] eui64 = hex(int(eui64[0:2], 16) ^ 2)[2:].zfill(2) + eui64[2:] if prefix is None: return ':'.join(re.findall(r'.{4}', eui64)) else: try: net = ip_network(prefix, strict=False) euil = int('0x{0}'.format(eui64), 16) return str(net[euil]) except: # pylint: disable=bare-except return def convert_data(data) -> dict | list | tuple | str | int | float | bool | None: """Filter and convert multiple types of data to types usable in CLI/API WARNING: Must not be used for anything except formatting output for API or CLI On the output allowed everything supported in JSON. Args: data (Any): input data Returns: dict | list | tuple | str | int | float | bool | None: converted data """ from base64 import b64encode # return original data for types which do not require conversion if isinstance(data, str | int | float | bool | None): return data if isinstance(data, list): list_tmp = [] for item in data: list_tmp.append(convert_data(item)) return list_tmp if isinstance(data, tuple): list_tmp = list(data) tuple_tmp = tuple(convert_data(list_tmp)) return tuple_tmp if isinstance(data, bytes | bytearray): try: return data.decode() except UnicodeDecodeError: return b64encode(data).decode() if isinstance(data, set | frozenset): list_tmp = convert_data(list(data)) return list_tmp if isinstance(data, dict): dict_tmp = {} for key, value in data.items(): dict_tmp[key] = convert_data(value) return dict_tmp # do not return anything for other types # which cannot be converted to JSON # for example: complex | range | memoryview return