diff --git a/src/biocgenerics/__init__.py b/src/biocgenerics/__init__.py index bbab50f..7e9a78c 100644 --- a/src/biocgenerics/__init__.py +++ b/src/biocgenerics/__init__.py @@ -20,3 +20,4 @@ from .combine_cols import combine_cols from .combine_rows import combine_rows from .rownames import rownames, set_rownames +from .show_as_cell import show_as_cell, format_table diff --git a/src/biocgenerics/show_as_cell.py b/src/biocgenerics/show_as_cell.py new file mode 100644 index 0000000..378e78a --- /dev/null +++ b/src/biocgenerics/show_as_cell.py @@ -0,0 +1,113 @@ +from functools import singledispatch +from typing import Any, List, Optional, Sequence + +__author__ = "Aaron Lun" +__copyright__ = "LTLA" +__license__ = "MIT" + + +@singledispatch +def show_as_cell(x: Any, indices: Sequence[int]) -> List[str]: + """Show the contents of ``x`` as a cell of a table, typically for use in the ``__str__`` method of a class that + contains ``x``. + + Args: + x: + Any object. By default, we assume that it can be treated as + a sequence, with a valid ``__getitem__`` method for an index. + + indices: + List of indices to be extracted. + + Returns: + List of strings of length equal to ``indices``, containing a + string summary of each of the specified elements of ``x``. + """ + output = [] + for i in indices: + output.append(str(x[i])) + return output + + +def _get_max_width(col: List[str]): + width = 0 + for y in col: + if len(y) > width: + width = len(y) + return width + + +def format_table( + columns: List[Sequence[str]], + floating_names: Optional[Sequence[str]] = None, + sep: str = " ", + window: Optional[int] = None, +) -> str: + """Pretty-print a table with wrapping columns. + + Args: + columns: List of list of strings, where each inner list is the same length. + Strings are typically generated by :py:meth:`~show_as_cell`. + + floating_names: List of strings to be added to the left of the table. This is + printed repeatedly for each set of wrapped columns. + + sep: Separator between columns. + + window: Size of the terminal window, in characters. We attempt to determine + this automatically, otherwise it is set to 150. + + Returns: + str: String containing the pretty-printed table. + """ + if window is None: + import os + + try: + window = os.get_terminal_size().columns + except Exception as _: + window = 150 + + if len(columns) == 0: + raise ValueError("At least one column should be supplied in 'columns'.") + n = len(columns[0]) + + floatwidth = 0 + if floating_names is not None: + floatwidth = _get_max_width(floating_names) + new_floating_names = [] + for y in floating_names: + new_floating_names.append(y.rjust(floatwidth)) + floating_names = new_floating_names + + output = "" + + def reinitialize(): + if floating_names is None: + return [""] * n + else: + return floating_names[:] + + contents = reinitialize() + init = True + used = floatwidth + + for col in columns: + width = _get_max_width(col) + + if not init and used + width + len(sep) > window: + for line in contents: + output += line + "\n" + contents = reinitialize() + init = True + used = floatwidth + + for i, y in enumerate(col): + if used > 0: + contents[i] += sep + contents[i] += y.rjust(width) + used += width + len(sep) + init = False + + output += "\n".join(contents) + return output diff --git a/tests/test_show_as_cell.py b/tests/test_show_as_cell.py new file mode 100644 index 0000000..e2c937c --- /dev/null +++ b/tests/test_show_as_cell.py @@ -0,0 +1,22 @@ +from biocgenerics import show_as_cell, format_table + + +def test_show_as_cell(): + assert show_as_cell([1, 2, 3, 4], range(4)) == ["1", "2", "3", "4"] + assert show_as_cell([1, 2, 3, 4], [1, 3]) == ["2", "4"] + + +def test_format_table(): + contents = [ + ["asdasd", "1", "2", "3", "4"], + [""] + ["|"] * 4, + ["asyudgausydga", "A", "B", "C", "D"], + ] + print(format_table(contents)) + print(format_table(contents, floating_names=["", "aarg", "boo", "ffoo", "stuff"])) + print(format_table(contents, window=10)) + print( + format_table( + contents, window=10, floating_names=["", "AAAR", "BBBB", "XXX", "STUFF"] + ) + )