Initial revision
[python_utils.git] / text_utils.py
1 #!/usr/bin/env python3
2
3 """Utilities for dealing with "text"."""
4
5 import math
6 import sys
7 from typing import List, NamedTuple
8
9 from ansi import fg, reset
10 import exec_utils
11
12
13 class RowsColumns(NamedTuple):
14     rows: int
15     columns: int
16
17
18 def get_console_rows_columns() -> RowsColumns:
19     rows, columns = exec_utils.cmd("stty size").split()
20     return RowsColumns(int(rows), int(columns))
21
22
23 def progress_graph(
24     current: int,
25     total: int,
26     *,
27     width=70,
28     fgcolor=fg("school bus yellow"),
29     left_end="[",
30     right_end="]",
31     redraw=True,
32 ) -> None:
33     percent = current / total
34     ret = "\r" if redraw else "\n"
35     bar = bar_graph(
36         percent,
37         include_text = True,
38         width = width,
39         fgcolor = fgcolor,
40         left_end = left_end,
41         right_end = right_end)
42     print(
43         bar,
44         end=ret,
45         flush=True,
46         file=sys.stderr)
47
48
49 def bar_graph(
50     percentage: float,
51     *,
52     include_text=True,
53     width=70,
54     fgcolor=fg("school bus yellow"),
55     left_end="[",
56     right_end="]",
57 ) -> None:
58     if percentage < 0.0 or percentage > 1.0:
59         raise ValueError(percentage)
60     if include_text:
61         text = f"{percentage*100.0:2.1f}%"
62     else:
63         text = ""
64     whole_width = math.floor(percentage * width)
65     if whole_width == width:
66         whole_width -= 1
67         part_char = "▉"
68     else:
69         remainder_width = (percentage * width) % 1
70         part_width = math.floor(remainder_width * 8)
71         part_char = [" ", "▏", "▎", "▍", "▌", "▋", "▊", "▉"][part_width]
72     return (
73         left_end +
74         fgcolor +
75         "█" * whole_width + part_char +
76         " " * (width - whole_width - 1) +
77         reset() +
78         right_end + " " +
79         text)
80
81
82 def distribute_strings(
83     strings: List[str],
84     *,
85     width: int = 80,
86     alignment: str = "c",
87     padding: str = " ",
88 ) -> str:
89     subwidth = math.floor(width / len(strings))
90     retval = ""
91     for string in strings:
92         string = justify_string(
93             string, width=subwidth, alignment=alignment, padding=padding
94         )
95         retval += string
96     return retval
97
98
99 def justify_string_by_chunk(
100     string: str, width: int = 80, padding: str = " "
101 ) -> str:
102     padding = padding[0]
103     first, *rest, last = string.split()
104     w = width - (len(first) + 1 + len(last) + 1)
105     retval = (
106         first + padding + distribute_strings(rest, width=w, padding=padding)
107     )
108     while len(retval) + len(last) < width:
109         retval += padding
110     retval += last
111     return retval
112
113
114 def justify_string(
115     string: str, *, width: int = 80, alignment: str = "c", padding: str = " "
116 ) -> str:
117     alignment = alignment[0]
118     padding = padding[0]
119     while len(string) < width:
120         if alignment == "l":
121             string += padding
122         elif alignment == "r":
123             string = padding + string
124         elif alignment == "j":
125             return justify_string_by_chunk(
126                 string,
127                 width=width,
128                 padding=padding
129             )
130         elif alignment == "c":
131             if len(string) % 2 == 0:
132                 string += padding
133             else:
134                 string = padding + string
135         else:
136             raise ValueError
137     return string
138
139
140 def justify_text(text: str, *, width: int = 80, alignment: str = "c") -> str:
141     print("-" * width)
142     retval = ""
143     line = ""
144     for word in text.split():
145         if len(line) + len(word) > width:
146             line = line[1:]
147             line = justify_string(line, width=width, alignment=alignment)
148             retval = retval + "\n" + line
149             line = ""
150         line = line + " " + word
151     if len(line) > 0:
152         retval += "\n" + line[1:]
153     return retval[1:]