#!/usr/bin/env python3 """Utilities for dealing with "text".""" from collections import defaultdict import math import sys from typing import List, NamedTuple from ansi import fg, reset import exec_utils class RowsColumns(NamedTuple): rows: int columns: int def get_console_rows_columns() -> RowsColumns: rows, columns = exec_utils.cmd("stty size").split() return RowsColumns(int(rows), int(columns)) def progress_graph( current: int, total: int, *, width=70, fgcolor=fg("school bus yellow"), left_end="[", right_end="]", redraw=True, ) -> None: percent = current / total ret = "\r" if redraw else "\n" bar = bar_graph( percent, include_text = True, width = width, fgcolor = fgcolor, left_end = left_end, right_end = right_end) print( bar, end=ret, flush=True, file=sys.stderr) def bar_graph( percentage: float, *, include_text=True, width=70, fgcolor=fg("school bus yellow"), left_end="[", right_end="]", ) -> None: if percentage < 0.0 or percentage > 1.0: raise ValueError(percentage) if include_text: text = f"{percentage*100.0:2.1f}%" else: text = "" whole_width = math.floor(percentage * width) if whole_width == width: whole_width -= 1 part_char = "▉" else: remainder_width = (percentage * width) % 1 part_width = math.floor(remainder_width * 8) part_char = [" ", "▏", "▎", "▍", "▌", "▋", "▊", "▉"][part_width] return ( left_end + fgcolor + "█" * whole_width + part_char + " " * (width - whole_width - 1) + reset() + right_end + " " + text) def distribute_strings( strings: List[str], *, width: int = 80, alignment: str = "c", padding: str = " ", ) -> str: subwidth = math.floor(width / len(strings)) retval = "" for string in strings: string = justify_string( string, width=subwidth, alignment=alignment, padding=padding ) retval += string return retval def justify_string_by_chunk( string: str, width: int = 80, padding: str = " " ) -> str: padding = padding[0] first, *rest, last = string.split() w = width - (len(first) + 1 + len(last) + 1) retval = ( first + padding + distribute_strings(rest, width=w, padding=padding) ) while len(retval) + len(last) < width: retval += padding retval += last return retval def justify_string( string: str, *, width: int = 80, alignment: str = "c", padding: str = " " ) -> str: alignment = alignment[0] padding = padding[0] while len(string) < width: if alignment == "l": string += padding elif alignment == "r": string = padding + string elif alignment == "j": return justify_string_by_chunk( string, width=width, padding=padding ) elif alignment == "c": if len(string) % 2 == 0: string += padding else: string = padding + string else: raise ValueError return string def justify_text(text: str, *, width: int = 80, alignment: str = "c") -> str: print("-" * width) retval = "" line = "" for word in text.split(): if len(line) + len(word) > width: line = line[1:] line = justify_string(line, width=width, alignment=alignment) retval = retval + "\n" + line line = "" line = line + " " + word if len(line) > 0: retval += "\n" + line[1:] return retval[1:] def generate_padded_columns(text: List[str]) -> str: max_width = defaultdict(int) for line in text: for pos, word in enumerate(line.split()): max_width[pos] = max(max_width[pos], len(word)) for line in text: out = "" for pos, word in enumerate(line.split()): width = max_width[pos] word = justify_string(word, width=width, alignment='l') out += f'{word} ' yield out