diff --git a/src/humanize/lists.py b/src/humanize/lists.py index 36e8ab73..fe4aef60 100644 --- a/src/humanize/lists.py +++ b/src/humanize/lists.py @@ -10,7 +10,7 @@ __all__ = ["natural_list"] -def natural_list(items: Iterable[Any]) -> str: +def natural_list(items: Iterable[Any], oxford_comma: bool = False) -> str: """Natural list. Convert an iterable of items into a human-readable string with commas and 'and'. @@ -18,6 +18,8 @@ def natural_list(items: Iterable[Any]) -> str: Examples: >>> natural_list(["one", "two", "three"]) 'one, two and three' + >>> natural_list(["one", "two", "three"], oxford_comma=True) + 'one, two, and three' >>> natural_list(["one", "two"]) 'one and two' >>> natural_list(["one"]) @@ -25,6 +27,8 @@ def natural_list(items: Iterable[Any]) -> str: Args: items (Iterable): An iterable of items. + oxford_comma (bool): Include a comma before 'and' for three or more items. + Defaults to False. Returns: str: A string with commas and 'and' in the right places. @@ -37,4 +41,5 @@ def natural_list(items: Iterable[Any]) -> str: elif len(item_list) == 2: return f"{item_list[0]} and {item_list[1]}" else: - return ", ".join(item_list[:-1]) + f" and {item_list[-1]}" + separator = ", and " if oxford_comma else " and " + return ", ".join(item_list[:-1]) + separator + item_list[-1] diff --git a/tests/test_lists.py b/tests/test_lists.py index 9b3c1b77..67c5fda2 100644 --- a/tests/test_lists.py +++ b/tests/test_lists.py @@ -29,3 +29,26 @@ ) def test_natural_list(test_args: Iterable[Any], expected: str) -> None: assert humanize.natural_list(*test_args) == expected + + +@pytest.mark.parametrize("oxford_comma", [False, True]) +@pytest.mark.parametrize( + "items, without_comma, with_comma", + [ + ([], "", ""), + (["one"], "one", "one"), + (["one", "two"], "one and two", "one and two"), + (["one", "two", "three"], "one, two and three", "one, two, and three"), + ([1, 2, 3, 4], "1, 2, 3 and 4", "1, 2, 3, and 4"), + ], +) +def test_natural_list_oxford_comma( + items: list[Any], without_comma: str, with_comma: str, oxford_comma: bool +) -> None: + expected = with_comma if oxford_comma else without_comma + assert humanize.natural_list(items, oxford_comma=oxford_comma) == expected + + +def test_natural_list_oxford_comma_generator() -> None: + items = (item for item in ["one", "two", "three"]) + assert humanize.natural_list(items, oxford_comma=True) == "one, two, and three"