From 30c8ee3526617bb1972332035ea653c40fc1ce6e Mon Sep 17 00:00:00 2001 From: Mike Salvatore Date: Thu, 30 Jan 2025 19:23:41 -0500 Subject: [PATCH] Align options (#706) --- linodecli/configuration/helpers.py | 17 +++++++++++-- tests/unit/test_configuration.py | 41 ++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 2 deletions(-) diff --git a/linodecli/configuration/helpers.py b/linodecli/configuration/helpers.py index b9d502a04..cb0d5e9ea 100644 --- a/linodecli/configuration/helpers.py +++ b/linodecli/configuration/helpers.py @@ -3,8 +3,10 @@ """ import configparser +import math import os import webbrowser +from functools import partial from typing import Any, Callable, List, Optional LEGACY_CONFIG_NAME = ".linode-cli" @@ -142,13 +144,14 @@ def _default_thing_input( exists = current_value is not None idx_offset = int(exists) + 1 + pad = partial(_pad_index, total=len(things) + idx_offset) # If there is a current value, users should have the option to clear it if exists: - print(" 1 - No Default") + print(f"{pad(1)} - No Default") for ind, thing in enumerate(things): - print(f" {ind + idx_offset} - {thing}") + print(f"{pad(ind + idx_offset)} - {thing}") print() while True: @@ -184,6 +187,16 @@ def _default_thing_input( return things[choice_idx] +def _pad_index(idx: int, total: int) -> str: + # NOTE: The implementation of this function could be less opaque if we're + # willing to say, "There will never be a case where total > X, because no + # one could examine and choose from that many options." + max_padding = math.floor(math.log10(total)) + 1 + num_spaces = max_padding - math.floor(math.log10(idx)) + + return " " * num_spaces + str(idx) + + def _default_text_input( ask: str, default: Optional[str] = None, diff --git a/tests/unit/test_configuration.py b/tests/unit/test_configuration.py index 2cd96f4c6..474b083f3 100644 --- a/tests/unit/test_configuration.py +++ b/tests/unit/test_configuration.py @@ -519,6 +519,47 @@ def test_default_thing_input_out_of_range(self, monkeypatch): assert result == "foo" + def test_default_thing_spacing(self, monkeypatch): + stdout_buf = io.StringIO() + monkeypatch.setattr("sys.stdin", io.StringIO("1\n")) + + with contextlib.redirect_stdout(stdout_buf): + _default_thing_input( + "foo\n", [*range(1, 10_001)], "prompt text", "error text" + ) + + output_lines = stdout_buf.getvalue().splitlines() + + assert output_lines[3] == " 1 - 1" + assert output_lines[11] == " 9 - 9" + assert output_lines[12] == " 10 - 10" + assert output_lines[101] == " 99 - 99" + assert output_lines[102] == " 100 - 100" + assert output_lines[1001] == " 999 - 999" + assert output_lines[1002] == " 1000 - 1000" + assert output_lines[10_001] == " 9999 - 9999" + assert output_lines[10_002] == " 10000 - 10000" + + def test_default_thing_spacing_with_current(self, monkeypatch): + stdout_buf = io.StringIO() + monkeypatch.setattr("sys.stdin", io.StringIO("1\n")) + + with contextlib.redirect_stdout(stdout_buf): + _default_thing_input( + "foo\n", + [*range(1, 10)], + "prompt text", + "error text", + current_value="foo", + ) + + output_lines = stdout_buf.getvalue().splitlines() + + print(output_lines) + assert output_lines[4] == " 2 - 1" + assert output_lines[11] == " 9 - 8" + assert output_lines[12] == " 10 - 9" + def test_default_text_input_optional(self, monkeypatch): # No value specified stdout_buf = io.StringIO()