diff --git a/.gitignore b/.gitignore index bddede8..7ab546c 100644 --- a/.gitignore +++ b/.gitignore @@ -152,12 +152,8 @@ dmypy.json # Cython debug symbols cython_debug/ -# PyCharm -# JetBrains specific template is maintained in a separate JetBrains.gitignore that can -# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore -# and can be added to the global gitignore or merged into this file. For a more nuclear -# option (not recommended) you can uncomment the following to ignore the entire idea folder. -#.idea/ +# Jetbrains IDEs +.idea/ # hatch-vcs src/*/_version.py diff --git a/docs/index.md b/docs/index.md index 80d4bb2..e1c002b 100644 --- a/docs/index.md +++ b/docs/index.md @@ -6,6 +6,7 @@ Welcome to the humanize API reference. - [Time](time.md) - [Filesize](filesize.md) - [I18n](i18n.md) +- [Lists](lists.md) {% include-markdown "../README.md" diff --git a/docs/lists.md b/docs/lists.md new file mode 100644 index 0000000..eab0e08 --- /dev/null +++ b/docs/lists.md @@ -0,0 +1,3 @@ +# Lists + +::: humanize.lists diff --git a/mkdocs.yml b/mkdocs.yml index e818401..8605493 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -21,6 +21,7 @@ nav: - Time: time.md - Filesize: filesize.md - Internationalisation: i18n.md + - Lists: lists.md plugins: - search diff --git a/src/humanize/__init__.py b/src/humanize/__init__.py index 60390e2..224c483 100644 --- a/src/humanize/__init__.py +++ b/src/humanize/__init__.py @@ -4,6 +4,7 @@ from humanize.filesize import naturalsize from humanize.i18n import activate, deactivate, decimal_separator, thousands_separator +from humanize.lists import natural_list from humanize.number import ( apnumber, clamp, @@ -38,6 +39,7 @@ "naturaldate", "naturalday", "naturaldelta", + "natural_list", "naturalsize", "naturaltime", "ordinal", diff --git a/src/humanize/lists.py b/src/humanize/lists.py new file mode 100644 index 0000000..1573be8 --- /dev/null +++ b/src/humanize/lists.py @@ -0,0 +1,34 @@ +"""Lists related humanization.""" + +from __future__ import annotations + +from typing import Any + +__all__ = ["natural_list"] + + +def natural_list(items: list[Any]) -> str: + """Natural list. + + Convert a list of items into a human-readable string with commas and 'and'. + + Examples: + >>> natural_list(["one", "two", "three"]) + 'one, two and three' + >>> natural_list(["one", "two"]) + 'one and two' + >>> natural_list(["one"]) + 'one' + + Args: + items (list): An iterable of items. + + Returns: + str: A string with commas and 'and' in the right places. + """ + if len(items) == 1: + return str(items[0]) + elif len(items) == 2: + return f"{str(items[0])} and {str(items[1])}" + else: + return ", ".join(str(item) for item in items[:-1]) + f" and {str(items[-1])}" diff --git a/tests/test_lists.py b/tests/test_lists.py new file mode 100644 index 0000000..a39d344 --- /dev/null +++ b/tests/test_lists.py @@ -0,0 +1,23 @@ +from __future__ import annotations + +import pytest + +import humanize + + +@pytest.mark.parametrize( + "test_args, expected", + [ + ([["1", "2", "3"]], "1, 2 and 3"), + ([["one", "two", "three"]], "one, two and three"), + ([["one", "two"]], "one and two"), + ([["one"]], "one"), + ([[""]], ""), + ([[1, 2, 3]], "1, 2 and 3"), + ([[1, "two"]], "1 and two"), + ], +) +def test_natural_list( + test_args: list[str] | list[int] | list[str | int], expected: str +) -> None: + assert humanize.natural_list(*test_args) == expected