Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,7 @@
extensions = [
"score_sphinx_bundle",
]

score_any_folder_mapping = {
"../src/extensions/docs": "internals/extensions",
}
29 changes: 29 additions & 0 deletions docs/how-to/any_folder.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
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!**
1 change: 1 addition & 0 deletions docs/how-to/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@ Here you find practical guides on how to use docs-as-code.
other_modules
source_to_doc_links
test_to_doc_links
any_folder
1 change: 1 addition & 0 deletions src/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ 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",
Expand Down
11 changes: 11 additions & 0 deletions src/extensions/docs/any_folder.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
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.
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,15 @@ Hello there
`ubCode <https://ubcode.useblocks.com>`__ 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::
Expand All @@ -83,3 +92,4 @@ Hello there
Source Code Linker <source_code_linker>
Extension Guide <extension_guide>
Sync TOML <sync_toml>
Any Folder <any_folder>
50 changes: 50 additions & 0 deletions src/extensions/score_any_folder/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# *******************************************************************************
# 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"],
)
96 changes: 96 additions & 0 deletions src/extensions/score_any_folder/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
# *******************************************************************************
# 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. A
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__)


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)
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:
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)
logger.debug("score_any_folder: created symlink %s -> %s", link, source)
12 changes: 12 additions & 0 deletions src/extensions/score_any_folder/tests/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# *******************************************************************************
# 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
# *******************************************************************************
Loading
Loading