projects
/
pyutils.git
/ blobdiff
commit
grep
author
committer
pickaxe
?
search:
re
summary
|
shortlog
|
log
|
commit
|
commitdiff
|
tree
raw
|
inline
| side by side
Adds a __repr__ to graph.
[pyutils.git]
/
src
/
pyutils
/
text_utils.py
diff --git
a/src/pyutils/text_utils.py
b/src/pyutils/text_utils.py
index c4c56015c9d7d23427915f1d7c4d8fe6a2e51611..f696c59bd0d651876aeaf74475c79bbe35326536 100644
(file)
--- a/
src/pyutils/text_utils.py
+++ b/
src/pyutils/text_utils.py
@@
-49,11
+49,14
@@
def get_console_rows_columns() -> RowsColumns:
Returns:
The number of rows/columns on the current console or None
if we can't tell or an error occurred.
Returns:
The number of rows/columns on the current console or None
if we can't tell or an error occurred.
+
+ Raises:
+ Exception: if the console size can't be determined.
"""
from pyutils.exec_utils import cmd
"""
from pyutils.exec_utils import cmd
- rows: Union[Optional[str], int] = os.environ.get(
'LINES'
, None)
- cols: Union[Optional[str], int] = os.environ.get(
'COLUMNS'
, None)
+ rows: Union[Optional[str], int] = os.environ.get(
"LINES"
, None)
+ cols: Union[Optional[str], int] = os.environ.get(
"COLUMNS"
, None)
if not rows or not cols:
try:
size = os.get_terminal_size()
if not rows or not cols:
try:
size = os.get_terminal_size()
@@
-64,7
+67,7
@@
def get_console_rows_columns() -> RowsColumns:
cols = None
if not rows or not cols:
cols = None
if not rows or not cols:
- logger.debug(
'Rows: %s, cols: %s, trying stty.'
, rows, cols)
+ logger.debug(
"Rows: %s, cols: %s, trying stty."
, rows, cols)
try:
rows, cols = cmd(
"stty size",
try:
rows, cols = cmd(
"stty size",
@@
-75,7
+78,7
@@
def get_console_rows_columns() -> RowsColumns:
cols = None
if not rows or not cols:
cols = None
if not rows or not cols:
- raise Exception(
'Can\'t determine console size?!'
)
+ raise Exception(
"Can't determine console size?!"
)
return RowsColumns(int(rows), int(cols))
return RowsColumns(int(rows), int(cols))
@@
-96,12
+99,12
@@
def bar_graph(
current: int,
total: int,
*,
current: int,
total: int,
*,
- width
=
70,
+ width
: int =
70,
text: BarGraphText = BarGraphText.PERCENTAGE,
text: BarGraphText = BarGraphText.PERCENTAGE,
- fgcolor
=
fg("school bus yellow"),
- left_end
=
"[",
- right_end
=
"]",
- redraw
=
True,
+ fgcolor
: str =
fg("school bus yellow"),
+ left_end
: str =
"[",
+ right_end
: str =
"]",
+ redraw
: bool =
True,
) -> None:
"""Draws a progress graph at the current cursor position.
) -> None:
"""Draws a progress graph at the current cursor position.
@@
-143,9
+146,9
@@
def _make_bar_graph_text(
if text == BarGraphText.NONE:
return ""
elif text == BarGraphText.PERCENTAGE:
if text == BarGraphText.NONE:
return ""
elif text == BarGraphText.PERCENTAGE:
- return f
'{percentage:.1f}'
+ return f
"{percentage:.1f}"
elif text == BarGraphText.FRACTION:
elif text == BarGraphText.FRACTION:
- return f
'{current} / {total}'
+ return f
"{current} / {total}"
raise ValueError(text)
raise ValueError(text)
@@
-154,11
+157,11
@@
def bar_graph_string(
total: int,
*,
text: BarGraphText = BarGraphText.PERCENTAGE,
total: int,
*,
text: BarGraphText = BarGraphText.PERCENTAGE,
- width
=
70,
- fgcolor
=
fg("school bus yellow"),
- reset_seq
=
reset(),
- left_end
=
"[",
- right_end
=
"]",
+ width
: int =
70,
+ fgcolor
: str =
fg("school bus yellow"),
+ reset_seq
: str =
reset(),
+ left_end
: str =
"[",
+ right_end
: str =
"]",
) -> str:
"""Returns a string containing a bar graph.
) -> str:
"""Returns a string containing a bar graph.
@@
-172,6
+175,9
@@
def bar_graph_string(
left_end: the character at the left side of the graph
right_end: the character at the right side of the graph
left_end: the character at the left side of the graph
right_end: the character at the right side of the graph
+ Raises:
+ ValueError: if percentage is invalid
+
See also :meth:`bar_graph`, :meth:`sparkline`.
>>> bar_graph_string(5, 10, fgcolor='', reset_seq='')
See also :meth:`bar_graph`, :meth:`sparkline`.
>>> bar_graph_string(5, 10, fgcolor='', reset_seq='')
@@
-185,7
+191,7
@@
def bar_graph_string(
percentage = 0.0
if percentage < 0.0 or percentage > 1.0:
raise ValueError(percentage)
percentage = 0.0
if percentage < 0.0 or percentage > 1.0:
raise ValueError(percentage)
- t
e
xt = _make_bar_graph_text(text, current, total, percentage)
+ txt = _make_bar_graph_text(text, current, total, percentage)
whole_width = math.floor(percentage * width)
if whole_width == width:
whole_width -= 1
whole_width = math.floor(percentage * width)
if whole_width == width:
whole_width -= 1
@@
-205,7
+211,7
@@
def bar_graph_string(
+ reset_seq
+ right_end
+ " "
+ reset_seq
+ right_end
+ " "
- + t
e
xt
+ + txt
)
)
@@
-232,12
+238,12
@@
def sparkline(numbers: List[float]) -> Tuple[float, float, str]:
(73, 104, '█▇▆▆▃▂▄▁')
"""
(73, 104, '█▇▆▆▃▂▄▁')
"""
- _bar =
'▁▂▃▄▅▆▇█'
# Unicode: 9601, 9602, 9603, 9604, 9605, 9606, 9607, 9608
+ _bar =
"▁▂▃▄▅▆▇█"
# Unicode: 9601, 9602, 9603, 9604, 9605, 9606, 9607, 9608
barcount = len(_bar)
min_num, max_num = min(numbers), max(numbers)
span = max_num - min_num
barcount = len(_bar)
min_num, max_num = min(numbers), max(numbers)
span = max_num - min_num
- sline =
''
.join(
+ sline =
""
.join(
_bar[min([barcount - 1, int((n - min_num) / span * barcount)])] for n in numbers
)
return min_num, max_num, sline
_bar[min([barcount - 1, int((n - min_num) / span * barcount)])] for n in numbers
)
return min_num, max_num, sline
@@
-265,11
+271,11
@@
def distribute_strings(
>>> distribute_strings(['this', 'is', 'a', 'test'], width=40)
' this is a test '
"""
>>> distribute_strings(['this', 'is', 'a', 'test'], width=40)
' this is a test '
"""
- ret =
' ' + ' '.join(strings) + ' '
+ ret =
" " + " ".join(strings) + " "
assert len(string_utils.strip_ansi_sequences(ret)) < width
x = 0
while len(string_utils.strip_ansi_sequences(ret)) < width:
assert len(string_utils.strip_ansi_sequences(ret)) < width
x = 0
while len(string_utils.strip_ansi_sequences(ret)) < width:
- spaces = [m.start() for m in re.finditer(r
' ([^ ]|$)'
, ret)]
+ spaces = [m.start() for m in re.finditer(r
" ([^ ]|$)"
, ret)]
where = spaces[x]
before = ret[:where]
after = ret[where:]
where = spaces[x]
before = ret[:where]
after = ret[where:]
@@
-325,6
+331,9
@@
def justify_string(
* 'r' = right alignment
padding: the padding character to use while justifying
* 'r' = right alignment
padding: the padding character to use while justifying
+ Raises:
+ ValueError: if alignment argument is invalid.
+
>>> justify_string('This is another test', width=40, alignment='c')
' This is another test '
>>> justify_string('This is another test', width=40, alignment='l')
>>> justify_string('This is another test', width=40, alignment='c')
' This is another test '
>>> justify_string('This is another test', width=40, alignment='l')
@@
-349,7
+358,7
@@
def justify_string(
else:
string = padding + string
else:
else:
string = padding + string
else:
- raise ValueError
+ raise ValueError
('alignment must be l, r, j, or c.')
return string
return string
@@
-379,10
+388,10
@@
def justify_text(
'This is a test of the emergency\\nbroadcast system. This is only a test.'
"""
'This is a test of the emergency\\nbroadcast system. This is only a test.'
"""
- retval =
''
- indent =
''
+ retval =
""
+ indent =
""
if indent_by > 0:
if indent_by > 0:
- indent +=
' '
* indent_by
+ indent +=
" "
* indent_by
line = indent
for word in text.split():
line = indent
for word in text.split():
@@
-392,11
+401,11
@@
def justify_text(
) > width:
line = line[1:]
line = justify_string(line, width=width, alignment=alignment)
) > width:
line = line[1:]
line = justify_string(line, width=width, alignment=alignment)
- retval = retval +
'\n'
+ line
+ retval = retval +
"\n"
+ line
line = indent
line = indent
- line = line +
' '
+ word
+ line = line +
" "
+ word
if len(string_utils.strip_ansi_sequences(line)) > 0:
if len(string_utils.strip_ansi_sequences(line)) > 0:
- if alignment !=
'j'
:
+ if alignment !=
"j"
:
retval += "\n" + justify_string(line[1:], width=width, alignment=alignment)
else:
retval += "\n" + line[1:]
retval += "\n" + justify_string(line[1:], width=width, alignment=alignment)
else:
retval += "\n" + line[1:]
@@
-435,8
+444,8
@@
def generate_padded_columns(text: List[str]) -> Generator:
out = ""
for pos, word in enumerate(line.split()):
width = max_width[pos]
out = ""
for pos, word in enumerate(line.split()):
width = max_width[pos]
- word = justify_string(word, width=width, alignment=
'l'
)
- out += f
'{word} '
+ word = justify_string(word, width=width, alignment=
"l"
)
+ out += f
"{word} "
yield out
yield out
@@
-450,13
+459,13
@@
def wrap_string(text: str, n: int) -> str:
The wrapped form of text
"""
chunks = text.split()
The wrapped form of text
"""
chunks = text.split()
- out =
''
+ out =
""
width = 0
for chunk in chunks:
if width + len(string_utils.strip_ansi_sequences(chunk)) > n:
width = 0
for chunk in chunks:
if width + len(string_utils.strip_ansi_sequences(chunk)) > n:
- out +=
'\n'
+ out +=
"\n"
width = 0
width = 0
- out += chunk +
' '
+ out += chunk +
" "
width += len(string_utils.strip_ansi_sequences(chunk)) + 1
return out
width += len(string_utils.strip_ansi_sequences(chunk)) + 1
return out
@@
-483,7
+492,7
@@
class Indenter(contextlib.AbstractContextManager):
self,
*,
pad_prefix: Optional[str] = None,
self,
*,
pad_prefix: Optional[str] = None,
- pad_char: str =
' '
,
+ pad_char: str =
" "
,
pad_count: int = 4,
):
"""Construct an Indenter.
pad_count: int = 4,
):
"""Construct an Indenter.
@@
-497,7
+506,7
@@
class Indenter(contextlib.AbstractContextManager):
if pad_prefix is not None:
self.pad_prefix = pad_prefix
else:
if pad_prefix is not None:
self.pad_prefix = pad_prefix
else:
- self.pad_prefix =
''
+ self.pad_prefix =
""
self.padding = pad_char * pad_count
def __enter__(self):
self.padding = pad_char * pad_count
def __enter__(self):
@@
-511,8
+520,8
@@
class Indenter(contextlib.AbstractContextManager):
return False
def print(self, *arg, **kwargs):
return False
def print(self, *arg, **kwargs):
- text = string_utils.sprintf(*arg, **kwargs)
- print(self.pad_prefix + self.padding * self.level + text, end=
''
)
+ text = string_utils.
_
sprintf(*arg, **kwargs)
+ print(self.pad_prefix + self.padding * self.level + text, end=
""
)
def header(
def header(
@@
-520,7
+529,7
@@
def header(
*,
width: Optional[int] = None,
align: Optional[str] = None,
*,
width: Optional[int] = None,
align: Optional[str] = None,
- style: Optional[str] =
'solid'
,
+ style: Optional[str] =
"solid"
,
color: Optional[str] = None,
):
"""
color: Optional[str] = None,
):
"""
@@
-531,6
+540,7
@@
def header(
width: how wide to make the header
align: "left" or "right"
style: "ascii", "solid" or "dashed"
width: how wide to make the header
align: "left" or "right"
style: "ascii", "solid" or "dashed"
+ color: what color to use, if any
Returns:
The header as a string.
Returns:
The header as a string.
@@
-544,15
+554,15
@@
def header(
except Exception:
width = 80
if not align:
except Exception:
width = 80
if not align:
- align =
'left'
+ align =
"left"
if not style:
if not style:
- style =
'ascii'
+ style =
"ascii"
text_len = len(string_utils.strip_ansi_sequences(title))
text_len = len(string_utils.strip_ansi_sequences(title))
- if align ==
'left'
:
+ if align ==
"left"
:
left = 4
right = width - (left + text_len + 4)
left = 4
right = width - (left + text_len + 4)
- elif align ==
'right'
:
+ elif align ==
"right"
:
right = 4
left = width - (right + text_len + 4)
else:
right = 4
left = width - (right + text_len + 4)
else:
@@
-561,31
+571,31
@@
def header(
while left + text_len + 4 + right < width:
right += 1
while left + text_len + 4 + right < width:
right += 1
- if style ==
'solid'
:
- line_char =
'━'
- begin =
''
- end =
''
- elif style ==
'dashed'
:
- line_char =
'┅'
- begin =
''
- end =
''
+ if style ==
"solid"
:
+ line_char =
"━"
+ begin =
""
+ end =
""
+ elif style ==
"dashed"
:
+ line_char =
"┅"
+ begin =
""
+ end =
""
else:
else:
- line_char =
'-'
- begin =
'['
- end =
']'
+ line_char =
"-"
+ begin =
"["
+ end =
"]"
if color:
col = color
reset_seq = reset()
else:
if color:
col = color
reset_seq = reset()
else:
- col =
''
- reset_seq =
''
+ col =
""
+ reset_seq =
""
return (
line_char * left
+ begin
+ col
return (
line_char * left
+ begin
+ col
- +
' '
+ +
" "
+ title
+ title
- +
' '
+ +
" "
+ reset_seq
+ end
+ line_char * right
+ reset_seq
+ end
+ line_char * right
@@
-597,7
+607,7
@@
def box(
text: Optional[str] = None,
*,
width: int = 80,
text: Optional[str] = None,
*,
width: int = 80,
- color: str =
''
,
+ color: str =
""
,
) -> str:
"""
Make a nice unicode box (optionally with color) around some text.
) -> str:
"""
Make a nice unicode box (optionally with color) around some text.
@@
-623,7
+633,7
@@
def box(
"""
assert width > 4
if text is not None:
"""
assert width > 4
if text is not None:
- text = justify_text(text, width=width - 4, alignment=
'l'
)
+ text = justify_text(text, width=width - 4, alignment=
"l"
)
return preformatted_box(title, text, width=width, color=color)
return preformatted_box(title, text, width=width, color=color)
@@
-631,8
+641,8
@@
def preformatted_box(
title: Optional[str] = None,
text: Optional[str] = None,
*,
title: Optional[str] = None,
text: Optional[str] = None,
*,
- width
=
80,
- color: str =
''
,
+ width
: int =
80,
+ color: str =
""
,
) -> str:
"""Creates a nice box with rounded corners and returns it as a string.
) -> str:
"""Creates a nice box with rounded corners and returns it as a string.
@@
-658,41
+668,41
@@
def preformatted_box(
╰──────────────────╯
"""
assert width > 4
╰──────────────────╯
"""
assert width > 4
- ret =
''
- if color ==
''
:
- rset =
''
+ ret =
""
+ if color ==
""
:
+ rset =
""
else:
rset = reset()
w = width - 2
else:
rset = reset()
w = width - 2
- ret += color +
'╭' + '─' * w + '╮' + rset + '\n'
+ ret += color +
"╭" + "─" * w + "╮" + rset + "\n"
if title is not None:
ret += (
color
if title is not None:
ret += (
color
- +
'│'
+ +
"│"
+ rset
+ rset
- + justify_string(title, width=w, alignment=
'c'
)
+ + justify_string(title, width=w, alignment=
"c"
)
+ color
+ color
- +
'│'
+ +
"│"
+ rset
+ rset
- +
'\n'
+ +
"\n"
)
)
- ret += color +
'│' + ' ' * w + '│' + rset + '\n'
+ ret += color +
"│" + " " * w + "│" + rset + "\n"
if text is not None:
if text is not None:
- for line in text.split(
'\n'
):
+ for line in text.split(
"\n"
):
tw = len(string_utils.strip_ansi_sequences(line))
assert tw <= w
ret += (
color
tw = len(string_utils.strip_ansi_sequences(line))
assert tw <= w
ret += (
color
- +
'│ '
+ +
"│ "
+ rset
+ line
+ rset
+ line
- +
' '
* (w - tw - 2)
+ +
" "
* (w - tw - 2)
+ color
+ color
- +
' │'
+ +
" │"
+ rset
+ rset
- +
'\n'
+ +
"\n"
)
)
- ret += color +
'╰' + '─' * w + '╯' + rset + '\n'
+ ret += color +
"╰" + "─" * w + "╯" + rset + "\n"
return ret
return ret
@@
-701,7
+711,7
@@
def print_box(
text: Optional[str] = None,
*,
width: int = 80,
text: Optional[str] = None,
*,
width: int = 80,
- color: str =
''
,
+ color: str =
""
,
) -> None:
"""Draws a box with nice rounded corners.
) -> None:
"""Draws a box with nice rounded corners.
@@
-731,10
+741,10
@@
def print_box(
│ OK │
╰────╯
"""
│ OK │
╰────╯
"""
- print(preformatted_box(title, text, width=width, color=color), end=
''
)
+ print(preformatted_box(title, text, width=width, color=color), end=
""
)
-if __name__ ==
'__main__'
:
+if __name__ ==
"__main__"
:
import doctest
doctest.testmod()
import doctest
doctest.testmod()