Skip to content
Open
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
3 changes: 2 additions & 1 deletion src/extensions/score_draw_uml_funcs/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,8 @@


def setup(app: Sphinx) -> dict[str, object]:
app.config.needs_render_context = draw_uml_function_context
for key, value in draw_uml_function_context.items():
app.config.needs_render_context.setdefault(key, value)
return {
"version": "0.1",
"parallel_read_safe": True,
Expand Down
21 changes: 15 additions & 6 deletions src/extensions/score_layout/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
import sphinx_options
from sphinx.application import Sphinx

from src.helper_lib import config_setdefault

logger = logging.getLogger(__name__)


Expand All @@ -35,11 +37,18 @@ def setup(app: Sphinx) -> dict[str, str | bool]:
def update_config(app: Sphinx, _config: Any):
logger.debug("score_layout update_config called")

app.config.needs_layouts = sphinx_options.needs_layouts
app.config.needs_global_options = sphinx_options.needs_global_options
app.config.html_theme = html_options.html_theme
app.config.html_context = html_options.return_html_context(app)
app.config.html_theme_options = html_options.return_html_theme_options(app)
# Merge: user's entries take precedence over our defaults
app.config.needs_layouts = {**sphinx_options.needs_layouts, **app.config.needs_layouts}
app.config.needs_global_options = {
**sphinx_options.needs_global_options,
**app.config.needs_global_options,
}
config_setdefault(app.config, "html_theme", html_options.html_theme)
app.config.html_context = {**html_options.return_html_context(app), **app.config.html_context}
app.config.html_theme_options = {
**html_options.return_html_theme_options(app),
**app.config.html_theme_options,
}

logger.debug(f"score_layout __file__: {__file__}")

Expand All @@ -49,7 +58,7 @@ def update_config(app: Sphinx, _config: Any):
app.config.html_static_path.append(str(score_layout_path / "assets"))

puml = score_layout_path / "assets" / "puml-theme-score.puml"
app.config.needs_flow_configs = {"score_config": f"!include {puml}"}
app.config.needs_flow_configs.setdefault("score_config", f"!include {puml}")

app.add_css_file("css/score.css", priority=500)
app.add_css_file("css/score_needs.css", priority=500)
Expand Down
20 changes: 11 additions & 9 deletions src/extensions/score_metamodel/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@

from sphinx.application import Sphinx
from sphinx_needs import logging

from src.helper_lib import config_setdefault
from sphinx_needs.data import NeedsView, SphinxNeedsData
from sphinx_needs.need_item import NeedItem

Expand Down Expand Up @@ -231,25 +233,25 @@ def postprocess_need_links(needs_types_list: list[ScoreNeedType]):

def setup(app: Sphinx) -> dict[str, str | bool]:
app.add_config_value("external_needs_source", "", rebuild="env")
app.config.needs_id_required = True
app.config.needs_id_regex = "^[A-Za-z0-9_-]{6,}"
config_setdefault(app.config, "needs_id_required", True)
config_setdefault(app.config, "needs_id_regex", "^[A-Za-z0-9_-]{6,}")

# load metamodel.yaml via ruamel.yaml
metamodel = load_metamodel_data()

# Assign everything to Sphinx config
app.config.needs_types = metamodel.needs_types
app.config.needs_extra_links = metamodel.needs_extra_links
app.config.needs_extra_options = metamodel.needs_extra_options
# Extend sphinx-needs config rather than overwriting
app.config.needs_types += metamodel.needs_types
app.config.needs_extra_links += metamodel.needs_extra_links
app.config.needs_extra_options += metamodel.needs_extra_options
app.config.graph_checks = metamodel.needs_graph_check
app.config.prohibited_words_checks = metamodel.prohibited_words_checks

# app.config.stop_words = metamodel["stop_words"]
# app.config.weak_words = metamodel["weak_words"]
# Ensure that 'needs.json' is always build.
app.config.needs_build_json = True
app.config.needs_reproducible_json = True
app.config.needs_json_remove_defaults = True
config_setdefault(app.config, "needs_build_json", True)
config_setdefault(app.config, "needs_reproducible_json", True)
config_setdefault(app.config, "needs_json_remove_defaults", True)

# sphinx-collections runs on default prio 500.
# We need to populate the sphinx-collections config before that happens.
Expand Down
11 changes: 6 additions & 5 deletions src/extensions/score_plantuml.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
from sphinx.application import Sphinx
from sphinx.util import logging

from src.helper_lib import get_runfiles_dir
from src.helper_lib import config_setdefault, get_runfiles_dir

logger = logging.getLogger(__name__)

Expand All @@ -53,10 +53,11 @@ def find_correct_path(runfiles: Path) -> Path:


def setup(app: Sphinx):
app.config.plantuml = str(find_correct_path(get_runfiles_dir()))
app.config.plantuml_output_format = "svg_obj"
app.config.plantuml_syntax_error_image = True
app.config.needs_build_needumls = "_plantuml_sources"
if not app.config.plantuml:
app.config.plantuml = str(find_correct_path(get_runfiles_dir()))
config_setdefault(app.config, "plantuml_output_format", "svg_obj")
config_setdefault(app.config, "plantuml_syntax_error_image", True)
config_setdefault(app.config, "needs_build_needumls", "_plantuml_sources")

logger.debug(f"PlantUML binary found at {app.config.plantuml}")

Expand Down
7 changes: 4 additions & 3 deletions src/extensions/score_source_code_linker/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,14 +173,15 @@ def setup_source_code_linker(app: Sphinx, ws_root: Path):

# Define need_string_links here to not have it in conf.py
# source_code_link and testlinks have the same schema
app.config.needs_string_links = {
"source_code_linker": {
app.config.needs_string_links.setdefault(
"source_code_linker",
{
"regex": r"(?P<url>.+)<>(?P<name>.+)",
"link_url": "{{url}}",
"link_name": "{{name}}",
"options": ["source_code_link", "testlink"],
},
}
)

score_sourcelinks_json = os.environ.get("SCORE_SOURCELINKS")
if score_sourcelinks_json:
Expand Down
42 changes: 21 additions & 21 deletions src/extensions/score_sphinx_bundle/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
# *******************************************************************************
from sphinx.application import Sphinx

from src.helper_lib import config_setdefault

# Note: order matters!
# Extensions are loaded in this order.
# e.g. plantuml MUST be loaded before sphinx-needs
Expand All @@ -33,43 +35,41 @@


def setup(app: Sphinx) -> dict[str, object]:
app.config.html_copy_source = False
app.config.html_show_sourcelink = False
config_setdefault(app.config, "html_copy_source", False)
config_setdefault(app.config, "html_show_sourcelink", False)

# Global settings
# Note: the "sub-extensions" also set their own config values

# Same as current VS Code extension
app.config.mermaid_version = "11.6.0"

# enable "..."-syntax in markdown
app.config.myst_enable_extensions = ["colon_fence"]
config_setdefault(app.config, "mermaid_version", "11.6.0")

app.config.exclude_patterns = [
# The following entries are not required when building the documentation via
# 'bazel build //:docs', as that command runs in a sandboxed environment.
# However, when building the documentation via 'bazel run //:docs' or esbonio,
# these entries are required to prevent the build from failing.
"bazel-*",
".venv*",
]
# The following entries are not required when building the documentation via
# 'bazel build //:docs', as that command runs in a sandboxed environment.
# However, when building the documentation via 'bazel run //:docs' or esbonio,
# these entries are required to prevent the build from failing.
app.config.exclude_patterns += ["bazel-*", ".venv*"]

# Enable markdown rendering
app.config.source_suffix = {
".rst": "restructuredtext",
".md": "markdown",
}
app.config.source_suffix.setdefault(".rst", "restructuredtext")
app.config.source_suffix.setdefault(".md", "markdown")

app.config.templates_path = ["templates"]
if "templates" not in app.config.templates_path:
app.config.templates_path += ["templates"]

app.config.numfig = True
config_setdefault(app.config, "numfig", True)

app.config.author = "S-CORE"
if not app.config.author:
app.config.author = "S-CORE"

# Load the actual extensions list
for e in score_extensions:
app.setup_extension(e)

# enable "..."-syntax in markdown β€” must come after myst_parser is loaded above
if "colon_fence" not in app.config.myst_enable_extensions:
app.config.myst_enable_extensions = set(app.config.myst_enable_extensions) | {"colon_fence"}

return {
"version": "3.0.0",
# Keep this in sync with the score_docs_as_code version in MODULE.bazel
Expand Down
22 changes: 13 additions & 9 deletions src/extensions/score_sync_toml/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@

from sphinx.application import Sphinx

from src.helper_lib import config_setdefault


def setup(app: Sphinx) -> dict[str, str | bool]:
"""
Expand All @@ -22,29 +24,31 @@ def setup(app: Sphinx) -> dict[str, str | bool]:
See https://needs-config-writer.useblocks.com
"""

app.config.needscfg_outpath = "ubproject.toml"
config_setdefault(app.config, "needscfg_outpath", "ubproject.toml")
"""Write to the confdir directory."""

app.config.needscfg_overwrite = True
config_setdefault(app.config, "needscfg_overwrite", True)
"""Any changes to the shared/local configuration updates the generated config."""

app.config.needscfg_write_all = True
config_setdefault(app.config, "needscfg_write_all", True)
"""Write full config, so the final configuration is visible in one file."""

app.config.needscfg_exclude_defaults = True
config_setdefault(app.config, "needscfg_exclude_defaults", True)
"""Exclude default values from the generated configuration."""

# This is disabled for right now as it causes a lot of issues
# While we are not using the generated file anywhere
app.config.needscfg_warn_on_diff = False
config_setdefault(app.config, "needscfg_warn_on_diff", False)
"""Running Sphinx with -W will fail the CI for uncommitted TOML changes."""

app.config.needscfg_merge_toml_files = [
str(Path(__file__).parent / "shared.toml"),
]
app.config.needscfg_merge_toml_files = (
app.config.needscfg_merge_toml_files or []
) + [str(Path(__file__).parent / "shared.toml")]
"""Merge the static TOML file into the generated configuration."""

app.config.needscfg_relative_path_fields = [
app.config.needscfg_relative_path_fields = (
app.config.needscfg_relative_path_fields or []
) + [
"needs_external_needs[*].json_path",
{
"field": "needs_flow_configs.score_config",
Expand Down
12 changes: 12 additions & 0 deletions src/helper_lib/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,25 @@
import subprocess
import sys
from pathlib import Path
from typing import Any

from runfiles import Runfiles
from sphinx.config import Config
from sphinx_needs.logging import get_logger

LOGGER = get_logger(__name__)


def config_setdefault(config: Config, name: str, value: Any) -> None:
"""Set a Sphinx config value only if the user hasn't explicitly set it in conf.py."""

# Sphinx has no public API for this check. We use ``_raw_config`` which is the
# de-facto standard across the ecosystem (Furo, RTD-theme, etc.). If Sphinx
# ever adds a public alternative, update this single function.
if name not in config._raw_config:
setattr(config, name, value)


def find_ws_root() -> Path | None:
"""
Find the current MODULE.bazel workspace root directory.
Expand Down
21 changes: 21 additions & 0 deletions src/helper_lib/test_helper_lib.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,34 @@
import pytest

from src.helper_lib import (
config_setdefault,
get_current_git_hash,
get_github_repo_info,
get_runfiles_dir,
parse_remote_git_output,
)


class _FakeConfig:
"""Minimal stand-in for sphinx.config.Config sufficient to test config_setdefault."""

def __init__(self, raw: dict):
self._raw_config = raw


def test_config_setdefault_sets_when_not_in_raw_config():
cfg = _FakeConfig(raw={})
config_setdefault(cfg, "html_copy_source", False)
assert cfg.html_copy_source is False


def test_config_setdefault_does_not_overwrite_user_value():
cfg = _FakeConfig(raw={"html_copy_source": True})
cfg.html_copy_source = True
config_setdefault(cfg, "html_copy_source", False)
assert cfg.html_copy_source is True


@pytest.fixture
def temp_dir():
"""Create a temporary directory for tests."""
Expand Down
Loading