diff --git a/docs/conf.py b/docs/conf.py index 48385fcf2..0255915c2 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -18,7 +18,3 @@ extensions = [ "score_sphinx_bundle", ] - -score_any_folder_mapping = { - "../src/extensions/docs": "internals/extensions", -} diff --git a/docs/how-to/any_folder.rst b/docs/how-to/any_folder.rst deleted file mode 100644 index 0e3c14276..000000000 --- a/docs/how-to/any_folder.rst +++ /dev/null @@ -1,29 +0,0 @@ -Use Any Folder for Documentation -================================ - -Generally, your documentation must be ``docs/``, -but the RST files for a module may live closer to the code they describe, -for example in ``src/my_module/docs/``. -You can symlink the folders by adding to your ``conf.py``: - -.. code-block:: python - - score_any_folder_mapping = { - "../score/containers/docs": "component/containers", - } - -With this configuration, all files in ``score/containers/docs/`` become available at ``docs/component/containers/``. - -If you have ``docs/component/overview.rst``, for example, -you can include the component documentation via ``toctree``: - -.. code-block:: rst - - .. toctree:: - - containers/index - -Only relative links are allowed. - -The symlinks will show up in your sources. -**Don't commit the symlinks to git!** diff --git a/docs/how-to/index.rst b/docs/how-to/index.rst index 866910c3f..36888a92e 100644 --- a/docs/how-to/index.rst +++ b/docs/how-to/index.rst @@ -16,4 +16,3 @@ Here you find practical guides on how to use docs-as-code. source_to_doc_links test_to_doc_links add_extensions - any_folder diff --git a/src/extensions/docs/data_flow.png b/docs/internals/extensions/data_flow.png similarity index 100% rename from src/extensions/docs/data_flow.png rename to docs/internals/extensions/data_flow.png diff --git a/src/extensions/docs/extension_guide.md b/docs/internals/extensions/extension_guide.md similarity index 100% rename from src/extensions/docs/extension_guide.md rename to docs/internals/extensions/extension_guide.md diff --git a/src/extensions/docs/header_service.md b/docs/internals/extensions/header_service.md similarity index 100% rename from src/extensions/docs/header_service.md rename to docs/internals/extensions/header_service.md diff --git a/src/extensions/docs/index.rst b/docs/internals/extensions/index.rst similarity index 67% rename from src/extensions/docs/index.rst rename to docs/internals/extensions/index.rst index a9c48b306..2ecec2d26 100644 --- a/src/extensions/docs/index.rst +++ b/docs/internals/extensions/index.rst @@ -1,16 +1,15 @@ -.. - # ******************************************************************************* - # Copyright (c) 2025 Contributors to the Eclipse Foundation - # - # See the NOTICE file(s) distributed with this work for additional - # information regarding copyright ownership. - # - # This program and the accompanying materials are made available under the - # terms of the Apache License Version 2.0 which is available at - # https://www.apache.org/licenses/LICENSE-2.0 - # - # SPDX-License-Identifier: Apache-2.0 - # ******************************************************************************* +.. # ******************************************************************************* + # Copyright (c) 2025 Contributors to the Eclipse Foundation + # + # See the NOTICE file(s) distributed with this work for additional + # information regarding copyright ownership. + # + # This program and the accompanying materials are made available under the + # terms of the Apache License Version 2.0 which is available at + # https://www.apache.org/licenses/LICENSE-2.0 + # + # SPDX-License-Identifier: Apache-2.0 + # ******************************************************************************* .. _extensions: @@ -72,16 +71,6 @@ Hello there `ubCode `__ VS Code extension. Getting IDE support for Sphinx-Needs in a Bazel context made easy. - .. grid-item-card:: - - Any Folder - ^^^^^^^^^^ - - Learn about the :doc:`any_folder` extension that creates symlinks - from arbitrary repository locations into the docs folder, - allowing Sphinx to discover and build source files - that live outside the documentation root. - .. toctree:: @@ -94,4 +83,3 @@ Hello there Source Code Linker Extension Guide Sync TOML - Any Folder diff --git a/src/extensions/docs/metamodel.md b/docs/internals/extensions/metamodel.md similarity index 100% rename from src/extensions/docs/metamodel.md rename to docs/internals/extensions/metamodel.md diff --git a/src/extensions/docs/rst_filebased_testing.md b/docs/internals/extensions/rst_filebased_testing.md similarity index 100% rename from src/extensions/docs/rst_filebased_testing.md rename to docs/internals/extensions/rst_filebased_testing.md diff --git a/src/extensions/docs/source_code_linker.md b/docs/internals/extensions/source_code_linker.md similarity index 100% rename from src/extensions/docs/source_code_linker.md rename to docs/internals/extensions/source_code_linker.md diff --git a/src/extensions/docs/sync_toml.rst b/docs/internals/extensions/sync_toml.rst similarity index 78% rename from src/extensions/docs/sync_toml.rst rename to docs/internals/extensions/sync_toml.rst index df2effb91..96a494d71 100644 --- a/src/extensions/docs/sync_toml.rst +++ b/docs/internals/extensions/sync_toml.rst @@ -1,17 +1,3 @@ -.. - # ******************************************************************************* - # Copyright (c) 2026 Contributors to the Eclipse Foundation - # - # See the NOTICE file(s) distributed with this work for additional - # information regarding copyright ownership. - # - # This program and the accompanying materials are made available under the - # terms of the Apache License Version 2.0 which is available at - # https://www.apache.org/licenses/LICENSE-2.0 - # - # SPDX-License-Identifier: Apache-2.0 - # ******************************************************************************* - .. _`toml_sync`: ubproject.toml sync diff --git a/src/BUILD b/src/BUILD index 71e3a43f3..44bd84836 100644 --- a/src/BUILD +++ b/src/BUILD @@ -37,7 +37,6 @@ filegroup( srcs = glob( ["*.py"], ) + [ - "//src/extensions/score_any_folder:all_sources", "//src/extensions/score_draw_uml_funcs:all_sources", "//src/extensions/score_header_service:all_sources", "//src/extensions/score_layout:all_sources", diff --git a/src/extensions/docs/any_folder.rst b/src/extensions/docs/any_folder.rst deleted file mode 100644 index d69864ba6..000000000 --- a/src/extensions/docs/any_folder.rst +++ /dev/null @@ -1,38 +0,0 @@ -.. - # ******************************************************************************* - # Copyright (c) 2026 Contributors to the Eclipse Foundation - # - # See the NOTICE file(s) distributed with this work for additional - # information regarding copyright ownership. - # - # This program and the accompanying materials are made available under the - # terms of the Apache License Version 2.0 which is available at - # https://www.apache.org/licenses/LICENSE-2.0 - # - # SPDX-License-Identifier: Apache-2.0 - # ******************************************************************************* - -Any Folder -========== - -The extension ``score_any_folder`` allows documentation roots to stay in ``docs/`` -while pulling in source files from anywhere else in the repository. - -It does this by creating symlinks inside the Sphinx source directory (``confdir``) that point to the configured external directories. -Sphinx then discovers and buildsthose files as if they were part of ``docs/`` from the start. - -The extension hooks into the ``builder-inited`` event, -which fires before Sphinx reads any documents. - -Difference to Sphinx-Collections --------------------------------- - -The extension `sphinx-collections `_ -is very similar to this extension. -We use it for including external modules -as it allows conditional inclusion -and we need to switch between "normal" and "combo" builds. - -The relevant difference is that this extension allows to include folders to anywhere -and is not restricted to a ``_collections/`` folder. -We consider this additional control over folder placement necessary. diff --git a/src/extensions/score_any_folder/BUILD b/src/extensions/score_any_folder/BUILD deleted file mode 100644 index ff0f1ae48..000000000 --- a/src/extensions/score_any_folder/BUILD +++ /dev/null @@ -1,51 +0,0 @@ -# ******************************************************************************* -# Copyright (c) 2026 Contributors to the Eclipse Foundation -# -# See the NOTICE file(s) distributed with this work for additional -# information regarding copyright ownership. -# -# This program and the accompanying materials are made available under the -# terms of the Apache License Version 2.0 which is available at -# https://www.apache.org/licenses/LICENSE-2.0 -# -# SPDX-License-Identifier: Apache-2.0 -# ******************************************************************************* - -load("@aspect_rules_py//py:defs.bzl", "py_library") -load("@docs_as_code_hub_env//:requirements.bzl", "requirement") -load("@score_tooling//:defs.bzl", "score_py_pytest") - -filegroup( - name = "sources", - srcs = glob(["*.py"]), -) - -filegroup( - name = "tests", - srcs = glob(["tests/*.py"]), -) - -filegroup( - name = "all_sources", - srcs = [ - ":sources", - ":tests", - ], - visibility = ["//visibility:public"], -) - -py_library( - name = "score_any_folder", - srcs = [":sources"], - imports = ["."], - visibility = ["//visibility:public"], - deps = [requirement("sphinx")], -) - -score_py_pytest( - name = "score_any_folder_tests", - size = "small", - srcs = glob(["tests/*.py"]), - deps = [":score_any_folder"], - pytest_config = "//:pyproject.toml", -) diff --git a/src/extensions/score_any_folder/__init__.py b/src/extensions/score_any_folder/__init__.py deleted file mode 100644 index 13526b775..000000000 --- a/src/extensions/score_any_folder/__init__.py +++ /dev/null @@ -1,118 +0,0 @@ -# ******************************************************************************* -# Copyright (c) 2026 Contributors to the Eclipse Foundation -# -# See the NOTICE file(s) distributed with this work for additional -# information regarding copyright ownership. -# -# This program and the accompanying materials are made available under the -# terms of the Apache License Version 2.0 which is available at -# https://www.apache.org/licenses/LICENSE-2.0 -# -# SPDX-License-Identifier: Apache-2.0 -# ******************************************************************************* -"""Sphinx extension that creates symlinks from arbitrary locations into the -documentation source directory, allowing sphinx-build to include source -files that live outside ``docs/``. - -Configuration in ``conf.py``:: - - score_any_folder_mapping = { - "../src/my_module/docs": "my_module", - } - -Each entry is a ``source: target`` pair where: - -* ``source`` – path to the directory to expose, relative to ``confdir`` - (the directory containing ``conf.py``). -* ``target`` – path of the symlink to create, relative to ``confdir``. - -The extension creates the symlinks on ``builder-inited``, -before Sphinx starts reading any documents. -Existing correct symlinks are left in place(idempotent); -a symlink pointing to the wrong target is replaced. - -Symlinks created by this extension are removed again on ``build-finished``. -Misconfigured pairs (absolute paths, non-symlink path at the target location) -are logged as errors and skipped. -""" - -from pathlib import Path - -from sphinx.application import Sphinx -from sphinx.util.logging import getLogger - -logger = getLogger(__name__) - -_APP_ATTRIBUTE = "_score_any_folder_created_links" - -def setup(app: Sphinx) -> dict[str, str | bool]: - app.add_config_value("score_any_folder_mapping", default={}, rebuild="env") - app.connect("builder-inited", _create_symlinks) - app.connect("build-finished", _cleanup_symlinks) - return { - "version": "0.1", - "parallel_read_safe": True, - "parallel_write_safe": True, - } - - -def _symlink_pairs(app: Sphinx) -> list[tuple[Path, Path]]: - """Return ``(resolved_source, link_path)`` pairs from the mapping.""" - confdir = Path(app.confdir) - pairs = [] - for source_rel, target_rel in app.config.score_any_folder_mapping.items(): - if Path(source_rel).is_absolute(): - logger.error( - "score_any_folder: source path must be relative, got: %r; skipping", - source_rel, - ) - continue - if Path(target_rel).is_absolute(): - logger.error( - "score_any_folder: target path must be relative, got: %r; skipping", - target_rel, - ) - continue - source = (confdir / source_rel).resolve() - link = confdir / target_rel - pairs.append((source, link)) - return pairs - - -def _create_symlinks(app: Sphinx) -> None: - created_links: set[Path] = set() - - for source, link in _symlink_pairs(app): - if link.is_symlink(): - if link.resolve() == source: - logger.debug("score_any_folder: symlink already correct: %s", link) - continue - logger.info( - "score_any_folder: replacing stale symlink %s -> %s", link, source - ) - link.unlink() - elif link.exists(): - logger.error( - "score_any_folder: target path already exists and is not a symlink: " - "%s; skipping", - link, - ) - continue - - link.parent.mkdir(parents=True, exist_ok=True) - link.symlink_to(source) - created_links.add(link) - logger.debug("score_any_folder: created symlink %s -> %s", link, source) - - setattr(app, _APP_ATTRIBUTE, created_links) - - -def _cleanup_symlinks(app: Sphinx, exception: Exception | None) -> None: - del exception - - created_links: set[Path] = getattr(app, _APP_ATTRIBUTE, set()) - for link in created_links: - if not link.is_symlink(): - continue - link.unlink() - logger.debug("score_any_folder: removed temporary symlink %s", link) diff --git a/src/extensions/score_any_folder/tests/__init__.py b/src/extensions/score_any_folder/tests/__init__.py deleted file mode 100644 index ca5de742e..000000000 --- a/src/extensions/score_any_folder/tests/__init__.py +++ /dev/null @@ -1,12 +0,0 @@ -# ******************************************************************************* -# Copyright (c) 2026 Contributors to the Eclipse Foundation -# -# See the NOTICE file(s) distributed with this work for additional -# information regarding copyright ownership. -# -# This program and the accompanying materials are made available under the -# terms of the Apache License Version 2.0 which is available at -# https://www.apache.org/licenses/LICENSE-2.0 -# -# SPDX-License-Identifier: Apache-2.0 -# ******************************************************************************* diff --git a/src/extensions/score_any_folder/tests/test_score_any_folder.py b/src/extensions/score_any_folder/tests/test_score_any_folder.py deleted file mode 100644 index 0f04d6ce5..000000000 --- a/src/extensions/score_any_folder/tests/test_score_any_folder.py +++ /dev/null @@ -1,177 +0,0 @@ -# ******************************************************************************* -# Copyright (c) 2026 Contributors to the Eclipse Foundation -# -# See the NOTICE file(s) distributed with this work for additional -# information regarding copyright ownership. -# -# This program and the accompanying materials are made available under the -# terms of the Apache License Version 2.0 which is available at -# https://www.apache.org/licenses/LICENSE-2.0 -# -# SPDX-License-Identifier: Apache-2.0 -# ******************************************************************************* -import os -from collections.abc import Callable, Generator -from contextlib import suppress -from pathlib import Path - -import pytest -from sphinx.testing.util import SphinxTestApp - - -def _make_app(srcdir: Path, outdir: Path) -> SphinxTestApp: - original_cwd = None - with suppress(FileNotFoundError): - original_cwd = os.getcwd() - os.chdir(srcdir) - try: - return SphinxTestApp( - freshenv=True, - srcdir=srcdir, - confdir=srcdir, - outdir=outdir, - buildername="html", - ) - finally: - if original_cwd is not None: - with suppress(FileNotFoundError, OSError): - os.chdir(original_cwd) - - -@pytest.fixture -def docs_dir(tmp_path: Path) -> Path: - d = tmp_path / "docs" - d.mkdir() - return d - - -@pytest.fixture -def make_sphinx_app( - docs_dir: Path, tmp_path: Path -) -> Generator[Callable[[dict[str, str]], SphinxTestApp], None, None]: - """Factory: writes conf + index, returns a SphinxTestApp, cleans up on teardown.""" - apps: list[SphinxTestApp] = [] - - def _factory(mapping: dict[str, str]) -> SphinxTestApp: - (docs_dir / "conf.py").write_text( - 'extensions = ["score_any_folder"]\n' - f"score_any_folder_mapping = {mapping!r}\n" - ) - (docs_dir / "index.rst").write_text("Root\n====\n") - app = _make_app(docs_dir, tmp_path / "out") - apps.append(app) - return app - - yield _factory - - for app in apps: - app.cleanup() - - -def test_symlink_exposes_files_at_target_path( - make_sphinx_app: Callable[[dict[str, str]], SphinxTestApp], - docs_dir: Path, - tmp_path: Path, -) -> None: - """Files in the source directory are readable via the symlinked target path.""" - src_docs = tmp_path / "src" / "module_docs" - src_docs.mkdir(parents=True) - content = "Remote Page\n===========\n\nContent here.\n" - (src_docs / "page.rst").write_text(content) - - make_sphinx_app({"../src/module_docs": "module"}) - - assert (docs_dir / "module" / "page.rst").read_text() == content - - -def test_symlink_is_idempotent( - make_sphinx_app: Callable[[dict[str, str]], SphinxTestApp], - docs_dir: Path, - tmp_path: Path, -) -> None: - """Build cleanup removes temporary links and a second build still succeeds.""" - src_docs = tmp_path / "external" - src_docs.mkdir() - - make_sphinx_app({"../external": "notes"}).build() - link = docs_dir / "notes" - assert not link.exists() - - make_sphinx_app({"../external": "notes"}).build() - - assert not link.exists() - - -def test_stale_symlink_is_replaced( - make_sphinx_app: Callable[[dict[str, str]], SphinxTestApp], - docs_dir: Path, - tmp_path: Path, -) -> None: - """A symlink pointing to a stale target is replaced with the correct one.""" - correct_src = tmp_path / "correct" - correct_src.mkdir() - wrong_target = tmp_path / "wrong" - wrong_target.mkdir() - (docs_dir / "module").symlink_to(wrong_target) - - make_sphinx_app({"../correct": "module"}) - - assert (docs_dir / "module").resolve() == correct_src.resolve() - - -def test_existing_non_symlink_logs_error_and_skips( - make_sphinx_app: Callable[[dict[str, str]], SphinxTestApp], - docs_dir: Path, - tmp_path: Path, -) -> None: - """A real directory at the target path is left untouched and an error is logged.""" - (tmp_path / "external").mkdir() - real_dir = docs_dir / "module" - real_dir.mkdir() - - app: SphinxTestApp = make_sphinx_app({"../external": "module"}) - - assert real_dir.is_dir() and not real_dir.is_symlink() - assert "not a symlink" in app.warning.getvalue() - - -def test_empty_mapping_is_a_no_op( - make_sphinx_app: Callable[[dict[str, str]], SphinxTestApp], docs_dir: Path -) -> None: - """An empty mapping produces no symlinks and no errors.""" - make_sphinx_app({}).build() - - assert [p for p in docs_dir.iterdir() if p.is_symlink()] == [] - - -def test_multiple_mappings( - make_sphinx_app: Callable[[dict[str, str]], SphinxTestApp], - docs_dir: Path, - tmp_path: Path, -) -> None: - """Multiple mapping entries each produce their own symlink.""" - for name in ("alpha", "beta"): - (tmp_path / name).mkdir() - - make_sphinx_app({"../alpha": "alpha", "../beta": "beta"}) - - for name in ("alpha", "beta"): - link = docs_dir / name - assert link.is_symlink(), f"symlink for {name!r} was not created" - assert link.resolve() == (tmp_path / name).resolve() - - -def test_target_in_subfolder( - make_sphinx_app: Callable[[dict[str, str]], SphinxTestApp], - docs_dir: Path, - tmp_path: Path, -) -> None: - """A target path with intermediate directories creates the parent dirs.""" - src_docs = tmp_path / "external" - src_docs.mkdir() - - make_sphinx_app({"../external": "foo/other"}) - - link = docs_dir / "foo" / "other" - assert link.is_symlink() - assert link.resolve() == src_docs.resolve() diff --git a/src/extensions/score_source_code_linker/xml_parser.py b/src/extensions/score_source_code_linker/xml_parser.py index cfa41f7f3..d4cf3d5cf 100644 --- a/src/extensions/score_source_code_linker/xml_parser.py +++ b/src/extensions/score_source_code_linker/xml_parser.py @@ -316,7 +316,7 @@ def build_test_needs_from_files( tcns: list[DataOfTestCase] = [] for file in xml_paths: # Last value can be ignored. The 'is_valid' function already prints infos - test_cases, tests_missing_all_props, _ = read_test_xml_file(file) + test_cases, tests_missing_all_props,_ = read_test_xml_file(file) non_prop_tests = ", ".join(n for n in tests_missing_all_props) if non_prop_tests: logger.info(f"Tests missing all properties: {non_prop_tests}") diff --git a/src/extensions/score_sphinx_bundle/BUILD b/src/extensions/score_sphinx_bundle/BUILD index 7fb69b719..c8d0a0a79 100644 --- a/src/extensions/score_sphinx_bundle/BUILD +++ b/src/extensions/score_sphinx_bundle/BUILD @@ -25,7 +25,6 @@ py_library( visibility = ["//visibility:public"], deps = all_requirements + [ "@score_docs_as_code//src/extensions:score_plantuml", - "@score_docs_as_code//src/extensions/score_any_folder", "@score_docs_as_code//src/extensions/score_draw_uml_funcs", "@score_docs_as_code//src/extensions/score_header_service", "@score_docs_as_code//src/extensions/score_layout", diff --git a/src/extensions/score_sphinx_bundle/__init__.py b/src/extensions/score_sphinx_bundle/__init__.py index 152088365..6ae04008b 100644 --- a/src/extensions/score_sphinx_bundle/__init__.py +++ b/src/extensions/score_sphinx_bundle/__init__.py @@ -31,7 +31,6 @@ "sphinxcontrib.mermaid", "needs_config_writer", "score_sync_toml", - "score_any_folder", ] diff --git a/src/helper_lib/test_helper_lib.py b/src/helper_lib/test_helper_lib.py index 5e335b2eb..0486821a5 100644 --- a/src/helper_lib/test_helper_lib.py +++ b/src/helper_lib/test_helper_lib.py @@ -29,7 +29,7 @@ class _FakeConfig: """Minimal stand-in for sphinx.config.Config.""" - def __init__(self, raw: dict[str, object]): + def __init__(self, raw: dict): self._raw_config = raw