From 99c88f1deed46bd218831a46e0b7c7aad1b65a9e Mon Sep 17 00:00:00 2001 From: "Christopher J. Markiewicz" Date: Sun, 12 Jan 2025 10:35:50 -0500 Subject: [PATCH] rf(cmdline): Simplify table2string implementation The previous table2string implementation reimplemented Python formatting, calculating spaces for aligning left, right or center. This patch just translates our mini-language into the Python mini-language, and updates the tests. Previously, when centering required adding an odd number of spaces, we added the extra to the left, while Python adds to the right. This difference does not seem worth preserving. This also avoids allocating a numpy array in order to transpose by using the `zip(*list)` trick. --- nibabel/cmdline/tests/test_utils.py | 36 ++++++++++++++++----- nibabel/cmdline/utils.py | 50 +++++++++-------------------- 2 files changed, 43 insertions(+), 43 deletions(-) diff --git a/nibabel/cmdline/tests/test_utils.py b/nibabel/cmdline/tests/test_utils.py index 0efb5ee0b..954a3a257 100644 --- a/nibabel/cmdline/tests/test_utils.py +++ b/nibabel/cmdline/tests/test_utils.py @@ -28,17 +28,37 @@ def test_table2string(): - assert table2string([['A', 'B', 'C', 'D'], ['E', 'F', 'G', 'H']]) == 'A B C D\nE F G H\n' + # Trivial case should do something sensible + assert table2string([]) == '\n' assert ( table2string( - [ - ["Let's", 'Make', 'Tests', 'And'], - ['Have', 'Lots', 'Of', 'Fun'], - ['With', 'Python', 'Guys', '!'], - ] + [['A', 'B', 'C', 'D'], + ['E', 'F', 'G', 'H']] + ) == ( + 'A B C D\n' + 'E F G H\n' ) - == "Let's Make Tests And\n Have Lots Of Fun" + '\n With Python Guys !\n' - ) + ) # fmt: skip + assert ( + table2string( + [["Let's", 'Make', 'Tests', 'And'], + ['Have', 'Lots', 'Of', 'Fun'], + ['With', 'Python', 'Guys', '!']] + ) == ( + "Let's Make Tests And\n" + 'Have Lots Of Fun\n' + 'With Python Guys !\n' + ) + ) # fmt: skip + assert ( + table2string( + [['This', 'Table', '@lIs', 'Ragged'], + ['And', '@rit', 'uses', '@csome', 'alignment', 'markup']] + ) == ( + 'This Table Is Ragged\n' + 'And it uses some alignment markup\n' + ) + ) # fmt: skip def test_ap(): diff --git a/nibabel/cmdline/utils.py b/nibabel/cmdline/utils.py index d89cc5c96..a085d2e91 100644 --- a/nibabel/cmdline/utils.py +++ b/nibabel/cmdline/utils.py @@ -12,10 +12,6 @@ # global verbosity switch import re -from io import StringIO -from math import ceil - -import numpy as np verbose_level = 0 @@ -42,32 +38,28 @@ def table2string(table, out=None): table : list of lists of strings What is aimed to be printed out : None or stream - Where to print. If None -- will print and return string + Where to print. If None, return string Returns ------- string if out was None """ - print2string = out is None - if print2string: - out = StringIO() - # equalize number of elements in each row nelements_max = len(table) and max(len(x) for x in table) + table = [row + [''] * (nelements_max - len(row)) for row in table] for i, table_ in enumerate(table): table[i] += [''] * (nelements_max - len(table_)) - # figure out lengths within each column - atable = np.asarray(table) # eat whole entry while computing width for @w (for wide) markup_strip = re.compile('^@([lrc]|w.*)') - col_width = [max(len(markup_strip.sub('', x)) for x in column) for column in atable.T] - string = '' - for i, table_ in enumerate(table): - string_ = '' - for j, item in enumerate(table_): + col_width = [max(len(markup_strip.sub('', x)) for x in column) for column in zip(*table)] + trans = str.maketrans("lrcw", "<>^^") + lines = [] + for row in table: + line = [] + for item, width in zip(row, col_width): item = str(item) if item.startswith('@'): align = item[1] @@ -77,26 +69,14 @@ def table2string(table, out=None): else: align = 'c' - nspacesl = max(ceil((col_width[j] - len(item)) / 2.0), 0) - nspacesr = max(col_width[j] - nspacesl - len(item), 0) - - if align in ('w', 'c'): - pass - elif align == 'l': - nspacesl, nspacesr = 0, nspacesl + nspacesr - elif align == 'r': - nspacesl, nspacesr = nspacesl + nspacesr, 0 - else: - raise RuntimeError(f'Should not get here with align={align}') - - string_ += '%%%ds%%s%%%ds ' % (nspacesl, nspacesr) % ('', item, '') - string += string_.rstrip() + '\n' - out.write(string) + line.append(f'{item:{align.translate(trans)}{width}}') + lines.append(' '.join(line).rstrip()) - if print2string: - value = out.getvalue() - out.close() - return value + ret = '\n'.join(lines) + '\n' + if out is not None: + out.write(ret) + else: + return ret def ap(helplist, format_, sep=', '):