Source code for structa.format

# structa: an application for analyzing repetitive data structures
#
# Copyright (c) 2020-2021 Dave Jones <dave@waveform.org.uk>
#
# SPDX-License-Identifier: GPL-2.0-or-later

from math import log
from itertools import tee
from datetime import datetime


def pairwise(iterable):
    """
    Taken from the recipe in the documentation for :mod:`itertools`.
    """
    a, b = tee(iterable)
    next(b, None)
    return zip(a, b)


[docs]def format_chars(chars, range_sep='-', list_sep=''): """ Given a set of *chars*, returns a compressed string representation of all values in the set. For example: >>> char_ranges({'a', 'b'}) 'ab' >>> char_ranges({'a', 'b', 'c'}) 'a-c' >>> char_ranges({'a', 'b', 'c', 'd', 'h'}) 'a-dh' >>> char_ranges({'a', 'b', 'c', 'd', 'h', 'i'}) 'a-dh-i' *range_sep* and *list_sep* can be optionally specified to customize the strings used to separate ranges and lists of ranges respectively. """ # TODO Handle special case of solo range_sep at end and escaping of # control chars if len(chars) == 0: return '' elif len(chars) == 1: return '{0}'.format(*chars) elif len(chars) == 2: return '{0}{sep}{1}'.format(*sorted(chars), sep=list_sep) else: ranges = [] start = None for i, j in pairwise(sorted(chars)): if start is None: start = i if j > chr(ord(i) + 1): ranges.append((start, i)) start = j if j == chr(ord(i) + 1): ranges.append((start, j)) else: ranges.append((j, j)) return list_sep.join( ('{start}{sep}{finish}' if finish > start else '{start}').format( start=start, finish=finish, sep=range_sep) for start, finish in ranges )
[docs]def format_int(i): """ Reduce *i* by some appropriate power of 1000 and suffix it with an appropriate Greek qualifier (K for kilo, M for mega, etc). For example:: >>> format_int(0) '0' >>> format_int(10) '10' >>> format_int(1000) '1.0K' >>> format_int(1600) '1.6K' >>> format_int(2**32) '4.3G' """ suffixes = ('', 'K', 'M', 'G', 'T', 'P') try: index = min(len(suffixes) - 1, int(log(abs(i), 1000))) except ValueError: return '0' if not index: return str(i) else: return '{value:.1f}{suffix}'.format( value=(i / 1000 ** index), suffix=suffixes[index])
[docs]def format_repr(self, **override): """ Returns a :func:`repr` style string for *self* in the form ``class(name=value, name=value, ...)``. .. note:: At present, this function does *not* handle recursive structures unlike :func:`reprlib.recursive_repr`. """ args = ( arg for cls in self.__class__.mro() if cls is not object for arg in cls.__slots__ ) return '{self.__class__.__name__}({args})'.format( self=self, args=', '.join( '{arg}={value}'.format( arg=arg, value=override.get(arg, repr(getattr(self, arg)))) for arg in args if arg not in override or override[arg] is not None))
[docs]def format_sample(value): """ Format a scalar value for output. The *value* can be a :class:`str`, :class:`int`, :class:`float`, :class:`bool`, :class:`~datetime.datetime`, or :data:`None`. The result is a :class:`str` containing a "nicely" formatted representation of the value. For example:: >>> format_sample(1.0) '1' >>> format_sample(1.5) '1.5' >>> format_sample(200000000000) '200.0G' >>> format_sample(200000000000.0) '2e+11' >>> format_sample(None) 'null' >>> format_sample(False) 'false' >>> format_sample('foo') '"foo"' >>> format_sample(datetime.now()) '2021-08-16 14:05:04' """ try: return { datetime: lambda: '{0:%Y-%m-%d %H:%M:%S}'.format(value), float: lambda: '{0:.7g}'.format(value), int: lambda: format_int(value), bool: lambda: ('false', 'true')[value], str: lambda: '"{}"'.format(value.replace('"', '""')), type(None): lambda: 'null', }[type(value)]() except KeyError: raise ValueError('invalid type for value {!r}'.format(value))