Fix typo in README.
[pyutils.git] / src / pyutils / text_utils.py
index 93355aa1450f837c3c05336185bf59578399795b..66f6f22dcd5dc8dcbd57777947002936e50e01f6 100644 (file)
@@ -3,7 +3,18 @@
 
 # © Copyright 2021-2022, Scott Gasch
 
-"""Utilities for dealing with "text"."""
+"""
+Utilities for dealing with and creating text chunks.  For example:
+
+    - Make a bar graph / progress graph,
+    - make a spark line,
+    - left, right, center, justify text,
+    - word wrap text,
+    - indent / dedent text,
+    - create a header line,
+    - draw a box around some text.
+
+"""
 
 import contextlib
 import enum
@@ -14,7 +25,7 @@ import re
 import sys
 from collections import defaultdict
 from dataclasses import dataclass
-from typing import Dict, Generator, List, Literal, Optional, Tuple
+from typing import Dict, Generator, List, Literal, Optional, Tuple, Union
 
 from pyutils import string_utils
 from pyutils.ansi import fg, reset
@@ -41,37 +52,26 @@ def get_console_rows_columns() -> RowsColumns:
     """
     from pyutils.exec_utils import cmd
 
-    rows: Optional[str] = os.environ.get('LINES', None)
-    cols: Optional[str] = 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:
-        logger.debug('Rows: %s, cols: %s, trying stty.', rows, cols)
         try:
-            rows, cols = cmd(
-                "stty size",
-                timeout_seconds=1.0,
-            ).split()
+            size = os.get_terminal_size()
+            rows = size.lines
+            cols = size.columns
         except Exception:
             rows = None
             cols = None
 
-    if rows is None:
-        logger.debug('Rows: %s, cols: %s, tput rows.', rows, cols)
+    if not rows or not cols:
+        logger.debug('Rows: %s, cols: %s, trying stty.', rows, cols)
         try:
-            rows = cmd(
-                "tput rows",
+            rows, cols = cmd(
+                "stty size",
                 timeout_seconds=1.0,
-            )
+            ).split()
         except Exception:
             rows = None
-
-    if cols is None:
-        logger.debug('Rows: %s, cols: %s, tput cols.', rows, cols)
-        try:
-            cols = cmd(
-                "tput cols",
-                timeout_seconds=1.0,
-            )
-        except Exception:
             cols = None
 
     if not rows or not cols:
@@ -116,6 +116,13 @@ def bar_graph(
         redraw: if True, omit a line feed after the carriage return
             so that subsequent calls to this method redraw the graph
             iteratively.
+
+    See also :meth:`bar_graph_string`, :meth:`sparkline`.
+
+    Example::
+
+        '[███████████████████████████████████                                   ] 0.5'
+
     """
     ret = "\r" if redraw else "\n"
     bar = bar_graph_string(
@@ -165,6 +172,8 @@ 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
 
+    See also :meth:`bar_graph`, :meth:`sparkline`.
+
     >>> bar_graph_string(5, 10, fgcolor='', reset_seq='')
     '[███████████████████████████████████                                   ] 0.5'
 
@@ -214,6 +223,8 @@ def sparkline(numbers: List[float]) -> Tuple[float, float, str]:
         * the maximum number in the population
         * a string representation of the population in a concise format
 
+    See also :meth:`bar_graph`, :meth:`bar_graph_string`.
+
     >>> sparkline([1, 2, 3, 5, 10, 3, 5, 7])
     (1, 10, '▁▁▂▄█▂▄▆')
 
@@ -249,6 +260,8 @@ def distribute_strings(
     Returns:
         The distributed, justified string.
 
+    See also :meth:`justify_string`, :meth:`justify_text`.
+
     >>> distribute_strings(['this', 'is', 'a', 'test'], width=40)
     '      this      is      a      test     '
     """
@@ -359,6 +372,8 @@ def justify_text(
     Returns:
         The justified text.
 
+    See also :meth:`justify_text`.
+
     >>> justify_text('This is a test of the emergency broadcast system.  This is only a test.',
     ...              width=40, alignment='j')  #doctest: +NORMALIZE_WHITESPACE
     'This  is    a  test  of   the  emergency\\nbroadcast system. This is only a test.'
@@ -596,6 +611,8 @@ def box(
     Returns:
         the box as a string
 
+    See also :meth:`print_box`, :meth:`preformatted_box`.
+
     >>> print(box('title', 'this is some text', width=20).strip())
     ╭──────────────────╮
     │       title      │
@@ -628,6 +645,8 @@ def preformatted_box(
     Returns:
         the box as a string
 
+    See also :meth:`print_box`, :meth:`box`.
+
     >>> print(preformatted_box('title', 'this\\nis\\nsome\\ntext', width=20).strip())
     ╭──────────────────╮
     │       title      │
@@ -686,6 +705,20 @@ def print_box(
 ) -> None:
     """Draws a box with nice rounded corners.
 
+    Args:
+        title: the title of the box
+        text: the text inside the box
+        width: the width of the box
+        color: the box's color
+
+    Returns:
+        None
+
+    Side-effects:
+        Prints a box with your text on the console to sys.stdout.
+
+    See also :meth:`preformatted_box`, :meth:`box`.
+
     >>> print_box('Title', 'This is text', width=30)
     ╭────────────────────────────╮
     │            Title           │