From dee9d4668976805427bd23dd1f5ddbec52cf547f Mon Sep 17 00:00:00 2001 From: James Bennett Date: Wed, 7 Aug 2024 00:14:20 -0700 Subject: [PATCH] Add first draft of names() function (refs #20). --- .pre-commit-config.yaml | 4 ++-- docs/changelog.rst | 11 ++++++++++ docs/conf.py | 7 ++++++ docs/contents.rst | 6 +++++ noxfile.py | 23 ++++++++++++++++++++ src/webcolors/__init__.py | 5 +++-- src/webcolors/_definitions.py | 41 +++++++++++++++++++++++++++++++++++ tests/test_conversion.py | 24 ++++++++++++++++++++ 8 files changed, 117 insertions(+), 4 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e2626f1..ee9101f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,13 +16,13 @@ repos: - id: end-of-file-fixer - id: trailing-whitespace - repo: https://github.com/psf/black - rev: 24.4.2 + rev: 24.8.0 hooks: - id: black language_version: python3.8 name: black (Python formatter) - repo: https://github.com/pycqa/flake8 - rev: 7.0.0 + rev: 7.1.1 hooks: - id: flake8 name: flake8 (Python linter) diff --git a/docs/changelog.rst b/docs/changelog.rst index a22e6df..776a637 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -56,6 +56,17 @@ The API stability/deprecation policy for this library is as follows: Releases under CalVer --------------------- +Version 24.8.0 +~~~~~~~~~~~~~~ + +*Under development* + +* Added the :func:`~webcolors.names` function to allow retrieving lists of + color names. The underlying mappings of color names/values still are not + supported API; to obtain the color value corresponding to a name, use the + appropriate conversion function. + + Version 24.6.0 ~~~~~~~~~~~~~~ diff --git a/docs/conf.py b/docs/conf.py index e273d04..6ca49d3 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -5,6 +5,7 @@ """ +import doctest import sys from importlib.metadata import version as get_version @@ -50,6 +51,12 @@ # Doctest configuration. doctest_global_setup = "from webcolors import *" +doctest_default_flags = ( + doctest.DONT_ACCEPT_TRUE_FOR_1 + | doctest.ELLIPSIS + | doctest.IGNORE_EXCEPTION_DETAIL + | doctest.NORMALIZE_WHITESPACE +) # OGP metadata configuration. ogp_enable_meta_description = True diff --git a/docs/contents.rst b/docs/contents.rst index fb0895a..bbd6cec 100644 --- a/docs/contents.rst +++ b/docs/contents.rst @@ -93,6 +93,12 @@ specifications. Represents the HTML 4 specification. Value is ``"html4"``. +Informative functions +--------------------- + +.. autofunction:: names + + Normalization functions ----------------------- diff --git a/noxfile.py b/noxfile.py index 95a3175..052e9e9 100644 --- a/noxfile.py +++ b/noxfile.py @@ -185,6 +185,29 @@ def docs_spellcheck(session: nox.Session) -> None: clean() +@nox.session(python=["3.11"], tags=["docs", "tests"]) +def docs_test(session: nox.Session) -> None: + """ + Run the code samples in the documentation with doctest, to ensure they are + correct. + + """ + session.install(".[docs]") + session.run( + f"python{session.python}", + "-Im", + "sphinx", + "-c", + "docs/", + "-b", + "doctest", + "-d", + "docs/_build/doctrees/html", + "docs/", + "docs/_build/html", + ) + + # Code formatting checks. # # These checks do *not* reformat code -- that happens in pre-commit hooks -- but will diff --git a/src/webcolors/__init__.py b/src/webcolors/__init__.py index f03ae57..d7395c9 100644 --- a/src/webcolors/__init__.py +++ b/src/webcolors/__init__.py @@ -23,7 +23,7 @@ rgb_to_name, rgb_to_rgb_percent, ) -from ._definitions import CSS2, CSS3, CSS21, HTML4 +from ._definitions import CSS2, CSS3, CSS21, HTML4, names from ._html5 import ( html5_parse_legacy_color, html5_parse_simple_color, @@ -36,7 +36,7 @@ ) from ._types import HTML5SimpleColor, IntegerRGB, IntTuple, PercentRGB, PercentTuple -__version__ = "24.6.0" +__version__ = "24.8.0a1" __all__ = [ "HTML4", @@ -49,6 +49,7 @@ "hex_to_name", "hex_to_rgb", "hex_to_rgb_percent", + "names", "rgb_to_hex", "rgb_to_name", "rgb_to_rgb_percent", diff --git a/src/webcolors/_definitions.py b/src/webcolors/_definitions.py index 199665e..5394509 100644 --- a/src/webcolors/_definitions.py +++ b/src/webcolors/_definitions.py @@ -6,6 +6,7 @@ # SPDX-License-Identifier: BSD-3-Clause import re +from typing import List def _reversedict(dict_to_reverse: dict) -> dict: @@ -299,3 +300,43 @@ def _get_hex_to_name_map(spec: str): if spec not in _SUPPORTED_SPECIFICATIONS: raise ValueError(_SPECIFICATION_ERROR_TEMPLATE.format(spec=spec)) return _hex_to_names[spec] + + +def names(spec: str = CSS3) -> List[str]: + """ + Return the list of valid color names for the given specification. + + The color names will be normalized to all-lowercase, and will be returned in + alphabetical order. + + .. note:: **Spelling variants** + + Some values representing named gray colors can map to either of two names in + CSS3, because it supports both ``"gray"`` and ``"grey"`` spelling variants for + those colors. Functions which produce a name from a color value in other formats + all normalize to the ``"gray"`` spelling for consistency with earlier CSS and + HTML specifications which only supported ``"gray"``. Here, however, *all* valid + names are returned, including -- for CSS3 -- both variant spellings for each of + the affected ``"gray"``/``"grey"`` colors. + + Examples: + + .. doctest:: + + >>> names(spec=HTML4) + ['aqua', 'black', 'blue', 'fuchsia', 'gray', 'green', + 'lime', 'maroon', 'navy', 'olive', 'purple', 'red', + 'silver', 'teal', 'white', 'yellow'] + >>> names(spec="CSS1") + Traceback (most recent call last): + ... + ValueError: "CSS1" is not a supported specification ... + + + :raises ValueError: when the given spec is not supported. + + """ + if spec not in _SUPPORTED_SPECIFICATIONS: + raise ValueError(_SPECIFICATION_ERROR_TEMPLATE.format(spec=spec)) + mapping = _names_to_hex[spec] + return list(sorted(mapping.keys())) diff --git a/tests/test_conversion.py b/tests/test_conversion.py index 88c43ba..0febecc 100644 --- a/tests/test_conversion.py +++ b/tests/test_conversion.py @@ -411,3 +411,27 @@ def test_spelling_variants(self): (webcolors.rgb_percent_to_name, percent_tuple), ): assert name == converter(value, spec=webcolors.CSS3) + + def test_names_valid(self): + """ + names() correctly returns the set of names for a given spec. + + """ + for spec, mapping in ( + (webcolors.HTML4, webcolors._definitions._HTML4_NAMES_TO_HEX), + (webcolors.CSS2, webcolors._definitions._CSS2_NAMES_TO_HEX), + (webcolors.CSS21, webcolors._definitions._CSS21_NAMES_TO_HEX), + (webcolors.CSS3, webcolors._definitions._CSS3_NAMES_TO_HEX), + ): + with self.subTest(spec=spec): + assert webcolors.names(spec) == list(sorted(mapping.keys())) + + def test_names_invalid(self): + """ + names() raises ValueError when asked for an unsupported spec. + + """ + for spec in ("CSS0", "HTML12", "random"): + with self.subTest(spec=spec): + with self.assertRaises(ValueError): + webcolors.names(spec)