diff --git a/common/utils.py b/common/utils.py index ec74aca..2c376b3 100644 --- a/common/utils.py +++ b/common/utils.py @@ -114,6 +114,17 @@ def format_float_or_int(number: int | float): return int(number) if float(number).is_integer() else f"{number:03.2f}" +def label_with_details(name: str, *details: object, separator: str = ", ") -> str: + """Build a ``"Name (detail, detail)"`` label from a name and optional details. + + Falsy details (``None``, ``""``, ``0``) are dropped; the rest are stringified + and joined with ``separator`` inside parentheses. With no details remaining, + the bare ``name`` is returned without parentheses. + """ + present = [str(detail) for detail in details if detail] + return f"{name} ({separator.join(present)})" if present else name + + OperatorType = Literal["|", "&"] diff --git a/tests/test_utils.py b/tests/test_utils.py new file mode 100644 index 0000000..7b3823a --- /dev/null +++ b/tests/test_utils.py @@ -0,0 +1,25 @@ +import unittest + +from common.utils import label_with_details + + +class LabelWithDetailsTest(unittest.TestCase): + def test_all_parts_present(self): + self.assertEqual( + label_with_details("Mario", "Steam", 2020), "Mario (Steam, 2020)" + ) + + def test_some_parts_falsy(self): + self.assertEqual(label_with_details("Mario", None, 2020), "Mario (2020)") + self.assertEqual(label_with_details("Mario", "Steam", None), "Mario (Steam)") + + def test_all_parts_falsy(self): + self.assertEqual(label_with_details("Mario", None, "", 0), "Mario") + + def test_no_details(self): + self.assertEqual(label_with_details("Mario"), "Mario") + + def test_custom_separator(self): + self.assertEqual( + label_with_details("Mario", "a", "b", separator=" / "), "Mario (a / b)" + )