From 0ce521d79f5910c98a629312013647abb71a924e Mon Sep 17 00:00:00 2001 From: Arnau Casau Date: Thu, 5 Dec 2024 18:34:59 +0100 Subject: [PATCH 1/8] Initial structure --- scripts/image-tester/pyproject.toml | 17 +++++ .../src/sphinx-alt-text-validator/__init__.py | 63 ++++++++++++++++ .../verify_images.py | 73 +++++++++++++++++++ 3 files changed, 153 insertions(+) create mode 100644 scripts/image-tester/pyproject.toml create mode 100644 scripts/image-tester/src/sphinx-alt-text-validator/__init__.py create mode 100755 scripts/image-tester/src/sphinx-alt-text-validator/verify_images.py diff --git a/scripts/image-tester/pyproject.toml b/scripts/image-tester/pyproject.toml new file mode 100644 index 00000000000..ce7d6cc3d62 --- /dev/null +++ b/scripts/image-tester/pyproject.toml @@ -0,0 +1,17 @@ +[build-system] +requires = [ "hatchling",] +build-backend = "hatchling.build" + +[project] +name = "sphinx-alt-text-validator" +version = "0.0.1" +description = "This is a tool that helps improving the accessibility of a project that uses Sphinx to build their documentation by detecting images without alt text defined" +requires-python = ">=3.8" +license = "Apache-2.0" +dependencies = [] + +[[project.authors]] +name = "Qiskit docs team" + +[project.scripts] +sphinx-alt-text-validator = "sphinx-alt-text-validator:main" diff --git a/scripts/image-tester/src/sphinx-alt-text-validator/__init__.py b/scripts/image-tester/src/sphinx-alt-text-validator/__init__.py new file mode 100644 index 00000000000..b3fc18c00b8 --- /dev/null +++ b/scripts/image-tester/src/sphinx-alt-text-validator/__init__.py @@ -0,0 +1,63 @@ +#!/usr/bin/env python3 +# This code is part of Qiskit. +# +# (C) Copyright IBM 2024 +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +from verify_images import validate_image +import multiprocessing +import glob +import sys +import argparse + + +def main() -> None: + parser = argparse.ArgumentParser(prog='verify_images.py') + parser.add_argument('-f', "--folder", required=True) + parser.add_argument('-s', "--skip", nargs='+') + args = parser.parse_args() + + + skip_list = args.skip if args.skip is not None else [] + files = glob.glob(f"{args.folder}/**/*.py", recursive=True) + filtered_files = [file for file in files if file not in skip_list] + print(filtered_files) + + with multiprocessing.Pool() as pool: + results = pool.map(validate_image, filtered_files) + + failed_files = {file: image_errors for file, image_errors in results if image_errors} + + if not failed_files: + print("✅ All images have alt text") + sys.exit(0) + + print("💔 Some images are missing the alt text", file=sys.stderr) + + for file, image_errors in failed_files.items(): + print(f"\nErrors found in {file}:", file=sys.stderr) + + for image_error in image_errors: + print(image_error, file=sys.stderr) + + print( + "\nAlt text is crucial for making documentation accessible to all users.", + "It should serve the same purpose as the images on the page,", + "conveying the same meaning rather than describing visual characteristics.", + "When an image contains words that are important to understanding the content,", + "the alt text should include those words as well.", + file=sys.stderr, + ) + + sys.exit(1) + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/scripts/image-tester/src/sphinx-alt-text-validator/verify_images.py b/scripts/image-tester/src/sphinx-alt-text-validator/verify_images.py new file mode 100755 index 00000000000..acde6277f30 --- /dev/null +++ b/scripts/image-tester/src/sphinx-alt-text-validator/verify_images.py @@ -0,0 +1,73 @@ +#!/usr/bin/env python3 +# This code is part of Qiskit. +# +# (C) Copyright IBM 2024 +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""Utility script to verify that all images have alt text""" + +from pathlib import Path + +def is_image(line: str) -> bool: + """Determine if a line is an image""" + return line.strip().startswith((".. image:", ".. plot:")) + + +def is_option(line: str) -> bool: + """Determine if a line is an option""" + return line.strip().startswith(":") + + +def is_valid_image(options: list[str]) -> bool: + """Validate one single image""" + alt_exists = any(option.strip().startswith(":alt:") for option in options) + nofigs_exists = any(option.strip().startswith(":nofigs:") for option in options) + + # Only `.. plot::`` directives without the `:nofigs:` option are required to have alt text. + # Meanwhile, all `.. image::` directives need alt text and they don't have a `:nofigs:` option. + return alt_exists or nofigs_exists + + +def validate_image(file_path: str) -> tuple[str, list[str]]: + """Validate all the images of a single file""" + + if file_path: + return [file_path, []] + + invalid_images: list[str] = [] + + lines = Path(file_path).read_text().splitlines() + + image_found = False + options: list[str] = [] + + for line_index, line in enumerate(lines): + if image_found: + if is_option(line): + options.append(line) + continue + + # Else, the prior image_found has no more options so we should determine if it was valid. + # + # Note that, either way, we do not early exit out of the loop iteration because this `line` + # might be the start of a new image. + if not is_valid_image(options): + image_line = line_index - len(options) + invalid_images.append( + f"- Error in line {image_line}: {lines[image_line-1].strip()}" + ) + + image_found = is_image(line) + options = [] + + return (file_path, invalid_images) + + + From e4fbdc979c89aaa821c5d7db67eef6a21039e11a Mon Sep 17 00:00:00 2001 From: Arnau Casau Date: Thu, 5 Dec 2024 19:09:56 +0100 Subject: [PATCH 2/8] new pyproject.toml --- scripts/image-tester/pyproject.toml | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/scripts/image-tester/pyproject.toml b/scripts/image-tester/pyproject.toml index ce7d6cc3d62..ad48d73b0d9 100644 --- a/scripts/image-tester/pyproject.toml +++ b/scripts/image-tester/pyproject.toml @@ -1,17 +1,21 @@ [build-system] -requires = [ "hatchling",] +requires = ["hatchling"] build-backend = "hatchling.build" [project] name = "sphinx-alt-text-validator" version = "0.0.1" +authors = [ + { name="Qiskit docs team" }, +] description = "This is a tool that helps improving the accessibility of a project that uses Sphinx to build their documentation by detecting images without alt text defined" requires-python = ">=3.8" license = "Apache-2.0" -dependencies = [] - -[[project.authors]] -name = "Qiskit docs team" +classifiers = [ + "Programming Language :: Python :: 3", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", +] [project.scripts] sphinx-alt-text-validator = "sphinx-alt-text-validator:main" From dddaba563f7d48c98506c276a3b985644fcb9534 Mon Sep 17 00:00:00 2001 From: Arnau Casau Date: Thu, 5 Dec 2024 19:18:31 +0100 Subject: [PATCH 3/8] rename folder --- scripts/image-tester/pyproject.toml | 2 +- .../__init__.py | 0 .../verify_images.py | 0 3 files changed, 1 insertion(+), 1 deletion(-) rename scripts/image-tester/src/{sphinx-alt-text-validator => sphinx_alt_text_validator}/__init__.py (100%) rename scripts/image-tester/src/{sphinx-alt-text-validator => sphinx_alt_text_validator}/verify_images.py (100%) diff --git a/scripts/image-tester/pyproject.toml b/scripts/image-tester/pyproject.toml index ad48d73b0d9..284060e57e9 100644 --- a/scripts/image-tester/pyproject.toml +++ b/scripts/image-tester/pyproject.toml @@ -18,4 +18,4 @@ classifiers = [ ] [project.scripts] -sphinx-alt-text-validator = "sphinx-alt-text-validator:main" +sphinx-alt-text-validator = "sphinx_alt_text_validator:main" diff --git a/scripts/image-tester/src/sphinx-alt-text-validator/__init__.py b/scripts/image-tester/src/sphinx_alt_text_validator/__init__.py similarity index 100% rename from scripts/image-tester/src/sphinx-alt-text-validator/__init__.py rename to scripts/image-tester/src/sphinx_alt_text_validator/__init__.py diff --git a/scripts/image-tester/src/sphinx-alt-text-validator/verify_images.py b/scripts/image-tester/src/sphinx_alt_text_validator/verify_images.py similarity index 100% rename from scripts/image-tester/src/sphinx-alt-text-validator/verify_images.py rename to scripts/image-tester/src/sphinx_alt_text_validator/verify_images.py From 16c01357d1d1e55fcd6e6248e973b3cb4741c34d Mon Sep 17 00:00:00 2001 From: Arnau Casau Date: Thu, 5 Dec 2024 19:20:20 +0100 Subject: [PATCH 4/8] fix path --- scripts/image-tester/src/sphinx_alt_text_validator/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/image-tester/src/sphinx_alt_text_validator/__init__.py b/scripts/image-tester/src/sphinx_alt_text_validator/__init__.py index b3fc18c00b8..dc52fbea912 100644 --- a/scripts/image-tester/src/sphinx_alt_text_validator/__init__.py +++ b/scripts/image-tester/src/sphinx_alt_text_validator/__init__.py @@ -11,7 +11,7 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -from verify_images import validate_image +from .verify_images import validate_image import multiprocessing import glob import sys From 0b4afb99fb38dbef5b5e2d1661d35b2d3b394098 Mon Sep 17 00:00:00 2001 From: Arnau Casau <47946624+arnaucasau@users.noreply.github.com> Date: Sun, 8 Dec 2024 13:29:47 +0100 Subject: [PATCH 5/8] Remove print --- scripts/image-tester/src/sphinx_alt_text_validator/__init__.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/scripts/image-tester/src/sphinx_alt_text_validator/__init__.py b/scripts/image-tester/src/sphinx_alt_text_validator/__init__.py index dc52fbea912..2e681f9e8fa 100644 --- a/scripts/image-tester/src/sphinx_alt_text_validator/__init__.py +++ b/scripts/image-tester/src/sphinx_alt_text_validator/__init__.py @@ -28,7 +28,6 @@ def main() -> None: skip_list = args.skip if args.skip is not None else [] files = glob.glob(f"{args.folder}/**/*.py", recursive=True) filtered_files = [file for file in files if file not in skip_list] - print(filtered_files) with multiprocessing.Pool() as pool: results = pool.map(validate_image, filtered_files) @@ -60,4 +59,4 @@ def main() -> None: if __name__ == "__main__": - main() \ No newline at end of file + main() From 943fc256d31b0fcca8d5668159d0278d89ad272b Mon Sep 17 00:00:00 2001 From: Arnau Casau <47946624+arnaucasau@users.noreply.github.com> Date: Sun, 8 Dec 2024 13:31:19 +0100 Subject: [PATCH 6/8] Fix script --- .../src/sphinx_alt_text_validator/verify_images.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/scripts/image-tester/src/sphinx_alt_text_validator/verify_images.py b/scripts/image-tester/src/sphinx_alt_text_validator/verify_images.py index acde6277f30..f861dcce85a 100755 --- a/scripts/image-tester/src/sphinx_alt_text_validator/verify_images.py +++ b/scripts/image-tester/src/sphinx_alt_text_validator/verify_images.py @@ -38,9 +38,6 @@ def is_valid_image(options: list[str]) -> bool: def validate_image(file_path: str) -> tuple[str, list[str]]: """Validate all the images of a single file""" - if file_path: - return [file_path, []] - invalid_images: list[str] = [] lines = Path(file_path).read_text().splitlines() From e79dd82c16a9ad73c39eb2fe08d19d9f078d3b1b Mon Sep 17 00:00:00 2001 From: Arnau Casau Date: Mon, 9 Dec 2024 10:45:52 +0100 Subject: [PATCH 7/8] remove src folder --- scripts/image-tester/pyproject.toml | 2 +- .../sphinx_alt_text_validator/__init__.py | 15 ++++++--------- .../sphinx_alt_text_validator/verify_images.py | 4 +--- 3 files changed, 8 insertions(+), 13 deletions(-) rename scripts/image-tester/{src => }/sphinx_alt_text_validator/__init__.py (85%) rename scripts/image-tester/{src => }/sphinx_alt_text_validator/verify_images.py (99%) diff --git a/scripts/image-tester/pyproject.toml b/scripts/image-tester/pyproject.toml index 284060e57e9..0ae01404546 100644 --- a/scripts/image-tester/pyproject.toml +++ b/scripts/image-tester/pyproject.toml @@ -13,7 +13,7 @@ requires-python = ">=3.8" license = "Apache-2.0" classifiers = [ "Programming Language :: Python :: 3", - "License :: OSI Approved :: MIT License", + "License :: OSI Approved :: Apache Software License", "Operating System :: OS Independent", ] diff --git a/scripts/image-tester/src/sphinx_alt_text_validator/__init__.py b/scripts/image-tester/sphinx_alt_text_validator/__init__.py similarity index 85% rename from scripts/image-tester/src/sphinx_alt_text_validator/__init__.py rename to scripts/image-tester/sphinx_alt_text_validator/__init__.py index 2e681f9e8fa..008b3ce8a05 100644 --- a/scripts/image-tester/src/sphinx_alt_text_validator/__init__.py +++ b/scripts/image-tester/sphinx_alt_text_validator/__init__.py @@ -19,12 +19,11 @@ def main() -> None: - parser = argparse.ArgumentParser(prog='verify_images.py') - parser.add_argument('-f', "--folder", required=True) - parser.add_argument('-s', "--skip", nargs='+') + parser = argparse.ArgumentParser(prog="verify_images.py") + parser.add_argument("-f", "--folder", required=True) + parser.add_argument("-s", "--skip", nargs="+") args = parser.parse_args() - skip_list = args.skip if args.skip is not None else [] files = glob.glob(f"{args.folder}/**/*.py", recursive=True) filtered_files = [file for file in files if file not in skip_list] @@ -32,7 +31,9 @@ def main() -> None: with multiprocessing.Pool() as pool: results = pool.map(validate_image, filtered_files) - failed_files = {file: image_errors for file, image_errors in results if image_errors} + failed_files = { + file: image_errors for file, image_errors in results if image_errors + } if not failed_files: print("✅ All images have alt text") @@ -56,7 +57,3 @@ def main() -> None: ) sys.exit(1) - - -if __name__ == "__main__": - main() diff --git a/scripts/image-tester/src/sphinx_alt_text_validator/verify_images.py b/scripts/image-tester/sphinx_alt_text_validator/verify_images.py similarity index 99% rename from scripts/image-tester/src/sphinx_alt_text_validator/verify_images.py rename to scripts/image-tester/sphinx_alt_text_validator/verify_images.py index f861dcce85a..2b8e2b7ba13 100755 --- a/scripts/image-tester/src/sphinx_alt_text_validator/verify_images.py +++ b/scripts/image-tester/sphinx_alt_text_validator/verify_images.py @@ -15,6 +15,7 @@ from pathlib import Path + def is_image(line: str) -> bool: """Determine if a line is an image""" return line.strip().startswith((".. image:", ".. plot:")) @@ -65,6 +66,3 @@ def validate_image(file_path: str) -> tuple[str, list[str]]: options = [] return (file_path, invalid_images) - - - From ad331bad293d4a834712b8ce18066c4f8fe7c19e Mon Sep 17 00:00:00 2001 From: Arnau Casau <47946624+arnaucasau@users.noreply.github.com> Date: Mon, 9 Dec 2024 11:46:38 +0100 Subject: [PATCH 8/8] Update scripts/image-tester/sphinx_alt_text_validator/__init__.py Co-authored-by: Frank Harkins --- scripts/image-tester/sphinx_alt_text_validator/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/image-tester/sphinx_alt_text_validator/__init__.py b/scripts/image-tester/sphinx_alt_text_validator/__init__.py index 008b3ce8a05..f4ef84c5358 100644 --- a/scripts/image-tester/sphinx_alt_text_validator/__init__.py +++ b/scripts/image-tester/sphinx_alt_text_validator/__init__.py @@ -24,7 +24,7 @@ def main() -> None: parser.add_argument("-s", "--skip", nargs="+") args = parser.parse_args() - skip_list = args.skip if args.skip is not None else [] + skip_list = args.skip or [] files = glob.glob(f"{args.folder}/**/*.py", recursive=True) filtered_files = [file for file in files if file not in skip_list]