From 25f2561105640030a2be1886b996c52c40722ba9 Mon Sep 17 00:00:00 2001 From: juiwenchen Date: Tue, 23 Sep 2025 13:33:18 +0200 Subject: [PATCH 1/8] added id field for src-trace --- src/sphinx_codelinks/sphinx_extension/directives/src_trace.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/sphinx_codelinks/sphinx_extension/directives/src_trace.py b/src/sphinx_codelinks/sphinx_extension/directives/src_trace.py index 57db7f0..95507f2 100644 --- a/src/sphinx_codelinks/sphinx_extension/directives/src_trace.py +++ b/src/sphinx_codelinks/sphinx_extension/directives/src_trace.py @@ -93,6 +93,7 @@ def run(self) -> list[nodes.Node]: validate_option(self.options) project = self.options["project"] + id = self.options.get("id") title = self.arguments[0] # get source tracing config src_trace_sphinx_config = CodeLinksConfig.from_sphinx(self.env.config) @@ -109,6 +110,8 @@ def run(self) -> list[nodes.Node]: target_dir = out_dir / src_dir.name extra_options = {"project": project} + if id: + extra_options["id"] = id source_files = self.get_src_files(self.options, src_dir, src_discover_config) # add source files into the dependency From cf0661d29d4c63c5e03a327ed5d06718226156d9 Mon Sep 17 00:00:00 2001 From: juiwenchen Date: Tue, 23 Sep 2025 13:43:24 +0200 Subject: [PATCH 2/8] adapt id_requred --- docs/conf.py | 4 ++++ docs/source/basics/quickstart.rst | 1 + docs/source/components/directive.rst | 4 ++++ docs/ubproject.toml | 5 ----- tests/doc_test/minimum_config/index.rst | 1 + 5 files changed, 10 insertions(+), 5 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index a70fc08..a003cce 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -79,4 +79,8 @@ html_context = {"repository": "useblocks/sphinx-codelinks"} html_css_files = ["furo.css"] +# Sphinx-Needs configuration +needs_from_toml = "ubproject.toml" + +# Src-trace configuration src_trace_config_from_toml = "./src_trace.toml" diff --git a/docs/source/basics/quickstart.rst b/docs/source/basics/quickstart.rst index 7dec73c..23427f9 100644 --- a/docs/source/basics/quickstart.rst +++ b/docs/source/basics/quickstart.rst @@ -46,6 +46,7 @@ Example ------- .. src-trace:: dummy src + :id: SRC_000 :project: src .. note:: **local-url** is not working on the website as it only supports local browse diff --git a/docs/source/components/directive.rst b/docs/source/components/directive.rst index 182af8a..320393c 100644 --- a/docs/source/components/directive.rst +++ b/docs/source/components/directive.rst @@ -52,12 +52,14 @@ The ``src-trace`` directive can be used with the **file** option: .. code-block:: rst .. src-trace:: dcdc demo_1 + :id: SRC_001 :project: dcdc :file: ./charge/demo_1.cpp The needs defined in source code are extracted and rendered to: .. src-trace:: dcdc demo_1 + :id: SRC_001 :project: dcdc :file: ./charge/demo_1.cpp @@ -66,12 +68,14 @@ The ``src-trace`` directive can be used with the **directory** option: .. code-block:: rst .. src-trace:: dcdc charge + :id: SRC_001 :project: dcdc :directory: ./discharge The needs defined in source code are extracted and rendered to: .. src-trace:: dcdc charge + :id: SRC_002 :project: dcdc :directory: ./discharge diff --git a/docs/ubproject.toml b/docs/ubproject.toml index 5429bcf..841fb98 100644 --- a/docs/ubproject.toml +++ b/docs/ubproject.toml @@ -5,8 +5,3 @@ ignore = ["block.title_line"] [needs] id_required = true - -[[needs.types]] -directive = "my-req" -title = "My Requirement" -prefix = "M_" diff --git a/tests/doc_test/minimum_config/index.rst b/tests/doc_test/minimum_config/index.rst index 1e26a6b..243479d 100644 --- a/tests/doc_test/minimum_config/index.rst +++ b/tests/doc_test/minimum_config/index.rst @@ -1,2 +1,3 @@ .. src-trace:: dummy src + :id: SRC_000 :project: src From 687c55031c57d8880021b52d6f31c49be17641de Mon Sep 17 00:00:00 2001 From: juiwenchen Date: Tue, 23 Sep 2025 13:46:28 +0200 Subject: [PATCH 3/8] updated snapshot --- .../test_build_html[sphinx_project2-source_code2].doctree.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/__snapshots__/test_src_trace/test_build_html[sphinx_project2-source_code2].doctree.xml b/tests/__snapshots__/test_src_trace/test_build_html[sphinx_project2-source_code2].doctree.xml index 513ce92..d458e1b 100644 --- a/tests/__snapshots__/test_src_trace/test_build_html[sphinx_project2-source_code2].doctree.xml +++ b/tests/__snapshots__/test_src_trace/test_build_html[sphinx_project2-source_code2].doctree.xml @@ -1,5 +1,5 @@ - - + + From b0f8a08f97d03f4edbe19dd63212d01a794dfffc Mon Sep 17 00:00:00 2001 From: juiwenchen Date: Wed, 24 Sep 2025 17:43:53 +0200 Subject: [PATCH 4/8] add hashed id when not given --- docs/source/basics/quickstart.rst | 1 - .../sphinx_extension/directives/src_trace.py | 43 ++++++++++++++++++- ...[sphinx_project2-source_code2].doctree.xml | 4 +- ...[sphinx_project3-source_code3].doctree.xml | 5 +++ tests/doc_test/id_required/conf.py | 30 +++++++++++++ tests/doc_test/id_required/dummy_src.cpp | 7 +++ tests/doc_test/id_required/index.rst | 2 + tests/doc_test/id_required/src_trace.toml | 2 + tests/doc_test/minimum_config/index.rst | 1 - tests/test_src_trace.py | 4 ++ 10 files changed, 93 insertions(+), 6 deletions(-) create mode 100644 tests/__snapshots__/test_src_trace/test_build_html[sphinx_project3-source_code3].doctree.xml create mode 100644 tests/doc_test/id_required/conf.py create mode 100644 tests/doc_test/id_required/dummy_src.cpp create mode 100644 tests/doc_test/id_required/index.rst create mode 100644 tests/doc_test/id_required/src_trace.toml diff --git a/docs/source/basics/quickstart.rst b/docs/source/basics/quickstart.rst index 23427f9..7dec73c 100644 --- a/docs/source/basics/quickstart.rst +++ b/docs/source/basics/quickstart.rst @@ -46,7 +46,6 @@ Example ------- .. src-trace:: dummy src - :id: SRC_000 :project: src .. note:: **local-url** is not working on the website as it only supports local browse diff --git a/src/sphinx_codelinks/sphinx_extension/directives/src_trace.py b/src/sphinx_codelinks/sphinx_extension/directives/src_trace.py index 95507f2..4cb09df 100644 --- a/src/sphinx_codelinks/sphinx_extension/directives/src_trace.py +++ b/src/sphinx_codelinks/sphinx_extension/directives/src_trace.py @@ -1,4 +1,5 @@ from collections.abc import Callable +import hashlib import os from pathlib import Path from typing import Any, ClassVar, cast @@ -7,6 +8,7 @@ from docutils.parsers.rst import directives from packaging.version import Version import sphinx +from sphinx.config import Config as _SphinxConfig from sphinx.util.docutils import SphinxDirective from sphinx_needs.api import add_need # type: ignore[import-untyped] from sphinx_needs.utils import add_doc # type: ignore[import-untyped] @@ -33,6 +35,42 @@ logger = logging.getLogger(__name__) +def _check_id( + config: _SphinxConfig, + id: str | None, + src_strings: list[str], + options: dict[str, str], + extra_options: dict[str, str], +) -> None: + """Check and set the id for the need. + + src_strings[0] is always the title. + src_strings[1] is always the project. + """ + if config.needs_id_required: + if id: + extra_options["id"] = id + else: + if "directory" in options: + src_strings.append(options["directory"]) + if "file" in options: + src_strings.append(options["file"]) + + extra_options["id"] = _make_hashed_id("SRCTRACE_", src_strings, config) + + +def _make_hashed_id( + type_prefix: str, src_strings: list[str], config: _SphinxConfig +) -> str: + """Create an ID based on the type and title of the need.""" + full_title = src_strings[0] # title is always the first element + hashable_content = "_".join(src_strings) + hashed = hashlib.sha256(hashable_content.encode("UTF-8")).hexdigest().upper() + if config.needs_id_from_title: + hashed = full_title.upper().replace(" ", "_") + "_" + hashed + return f"{type_prefix}{hashed[: config.needs_id_length]}" + + def get_rel_path(doc_path: Path, code_path: Path, base_dir: Path) -> tuple[Path, Path]: """Get the relative path from the document to the source code file and vice versa.""" doc_depth = len(doc_path.parents) - 1 @@ -110,8 +148,9 @@ def run(self) -> list[nodes.Node]: target_dir = out_dir / src_dir.name extra_options = {"project": project} - if id: - extra_options["id"] = id + + _check_id(self.env.config, id, [title, project], self.options, extra_options) + source_files = self.get_src_files(self.options, src_dir, src_discover_config) # add source files into the dependency diff --git a/tests/__snapshots__/test_src_trace/test_build_html[sphinx_project2-source_code2].doctree.xml b/tests/__snapshots__/test_src_trace/test_build_html[sphinx_project2-source_code2].doctree.xml index d458e1b..513ce92 100644 --- a/tests/__snapshots__/test_src_trace/test_build_html[sphinx_project2-source_code2].doctree.xml +++ b/tests/__snapshots__/test_src_trace/test_build_html[sphinx_project2-source_code2].doctree.xml @@ -1,5 +1,5 @@ - - + + diff --git a/tests/__snapshots__/test_src_trace/test_build_html[sphinx_project3-source_code3].doctree.xml b/tests/__snapshots__/test_src_trace/test_build_html[sphinx_project3-source_code3].doctree.xml new file mode 100644 index 0000000..4f4c85e --- /dev/null +++ b/tests/__snapshots__/test_src_trace/test_build_html[sphinx_project3-source_code3].doctree.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/tests/doc_test/id_required/conf.py b/tests/doc_test/id_required/conf.py new file mode 100644 index 0000000..6d85ccf --- /dev/null +++ b/tests/doc_test/id_required/conf.py @@ -0,0 +1,30 @@ +# Configuration file for the Sphinx documentation builder. +# +# For the full list of built-in configuration values, see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Project information ----------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information + +project = "source-tracing-demo" +copyright = "2025, useblocks" +author = "useblocks" + +# -- General configuration --------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration + +extensions = ["sphinx_needs", "sphinx_codelinks"] + +templates_path = ["_templates"] +exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] + +src_trace_config_from_toml = "src_trace.toml" + +# -- Options for HTML output ------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output + +html_theme = "alabaster" +html_static_path = ["_static"] + +# Sphinx-Needs configuration +needs_id_required = True diff --git a/tests/doc_test/id_required/dummy_src.cpp b/tests/doc_test/id_required/dummy_src.cpp new file mode 100644 index 0000000..af1e002 --- /dev/null +++ b/tests/doc_test/id_required/dummy_src.cpp @@ -0,0 +1,7 @@ +#include + +// @ title here, IMPL_1, impl +void singleLineExample() +{ + std::cout << "Single-line comment example" << std::endl; +} diff --git a/tests/doc_test/id_required/index.rst b/tests/doc_test/id_required/index.rst new file mode 100644 index 0000000..1e26a6b --- /dev/null +++ b/tests/doc_test/id_required/index.rst @@ -0,0 +1,2 @@ +.. src-trace:: dummy src + :project: src diff --git a/tests/doc_test/id_required/src_trace.toml b/tests/doc_test/id_required/src_trace.toml new file mode 100644 index 0000000..0561780 --- /dev/null +++ b/tests/doc_test/id_required/src_trace.toml @@ -0,0 +1,2 @@ +[codelinks.projects.src] +remote_url_pattern = "https://github.com/useblocks/sphinx-codelinks/blob/{commit}/{path}#L{line}" diff --git a/tests/doc_test/minimum_config/index.rst b/tests/doc_test/minimum_config/index.rst index 243479d..1e26a6b 100644 --- a/tests/doc_test/minimum_config/index.rst +++ b/tests/doc_test/minimum_config/index.rst @@ -1,3 +1,2 @@ .. src-trace:: dummy src - :id: SRC_000 :project: src diff --git a/tests/test_src_trace.py b/tests/test_src_trace.py index ea1cd8d..519eb80 100644 --- a/tests/test_src_trace.py +++ b/tests/test_src_trace.py @@ -187,6 +187,10 @@ def test_src_tracing_config_positive(make_app: Callable[..., SphinxTestApp], tmp Path("doc_test") / "minimum_config", Path("doc_test") / "minimum_config", ), + ( + Path("doc_test") / "id_required", + Path("doc_test") / "id_required", + ), ], ) def test_build_html( From 44068d7b1e78bdbef0754bd472372ddbc994c716 Mon Sep 17 00:00:00 2001 From: juiwenchen Date: Thu, 25 Sep 2025 10:20:30 +0200 Subject: [PATCH 5/8] changed extra to additional --- .../sphinx_extension/directives/src_trace.py | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/sphinx_codelinks/sphinx_extension/directives/src_trace.py b/src/sphinx_codelinks/sphinx_extension/directives/src_trace.py index 4cb09df..0e0b941 100644 --- a/src/sphinx_codelinks/sphinx_extension/directives/src_trace.py +++ b/src/sphinx_codelinks/sphinx_extension/directives/src_trace.py @@ -40,7 +40,7 @@ def _check_id( id: str | None, src_strings: list[str], options: dict[str, str], - extra_options: dict[str, str], + additional_options: dict[str, str], ) -> None: """Check and set the id for the need. @@ -49,14 +49,14 @@ def _check_id( """ if config.needs_id_required: if id: - extra_options["id"] = id + additional_options["id"] = id else: if "directory" in options: src_strings.append(options["directory"]) if "file" in options: src_strings.append(options["file"]) - extra_options["id"] = _make_hashed_id("SRCTRACE_", src_strings, config) + additional_options["id"] = _make_hashed_id("SRCTRACE_", src_strings, config) def _make_hashed_id( @@ -147,9 +147,11 @@ def run(self) -> list[nodes.Node]: # the directory where the source files are copied to target_dir = out_dir / src_dir.name - extra_options = {"project": project} + additional_options = {"project": project} - _check_id(self.env.config, id, [title, project], self.options, extra_options) + _check_id( + self.env.config, id, [title, project], self.options, additional_options + ) source_files = self.get_src_files(self.options, src_dir, src_discover_config) @@ -174,7 +176,7 @@ def run(self) -> list[nodes.Node]: lineno=self.lineno, # The line number where the directive is used need_type="srctrace", # The type of the need title=title, # The title of the need - **extra_options, + **additional_options, ) needs.extend(src_trace_need) @@ -242,7 +244,7 @@ def run(self) -> list[nodes.Node]: def get_src_files( self, - extra_options: dict[str, str], + additional_options: dict[str, str], src_dir: Path, src_discover_config: SourceDiscoverConfig, ) -> list[Path]: @@ -252,14 +254,14 @@ def get_src_files( file: str = self.options["file"] filepath = src_dir / file source_files.append(filepath.resolve()) - extra_options["file"] = file + additional_options["file"] = file else: directory = self.options.get("directory") if directory is None: # when neither "file" and "directory" are given, the project root dir is by default directory = "./" else: - extra_options["directory"] = directory + additional_options["directory"] = directory dir_path = src_dir / directory # create a new config for the specified directory src_discover = SourceDiscoverConfig( From 8b4f5a76b825b7e929183efb5267efae5a4e6df1 Mon Sep 17 00:00:00 2001 From: juiwenchen Date: Thu, 25 Sep 2025 14:04:04 +0200 Subject: [PATCH 6/8] checked SN extension --- .../sphinx_extension/source_tracing.py | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/sphinx_codelinks/sphinx_extension/source_tracing.py b/src/sphinx_codelinks/sphinx_extension/source_tracing.py index 1daeb01..6d06707 100644 --- a/src/sphinx_codelinks/sphinx_extension/source_tracing.py +++ b/src/sphinx_codelinks/sphinx_extension/source_tracing.py @@ -35,7 +35,33 @@ logger = logging.getLogger(__name__) +def _check_sphinx_needs_dependency(app: Sphinx) -> bool: + """Check if sphinx-needs is configured as an extension.""" + # Check if sphinx-needs is in the extensions list + if "sphinx_needs" not in app.config.extensions: + error_msg = ( + "sphinx-codelinks requires sphinx-needs to be configured as an extension.\n" + "Please add 'sphinx_needs' to your extensions list in conf.py:\n" + " extensions = ['sphinx_needs', 'sphinx_codelinks', ...]\n" + f"Current extensions: {app.config.extensions}" + ) + logger.error(error_msg) + return False + return True + + def setup(app: Sphinx) -> dict[str, Any]: # type: ignore[explicit-any] + # Check if sphinx-needs is available and properly configured + if not _check_sphinx_needs_dependency(app): + logger.error( + "Failed to initialize sphinx-codelinks due to missing sphinx-needs dependency" + ) + return { + "version": "builtin", + "parallel_read_safe": True, + "parallel_write_safe": True, + } + app.add_node(SourceTracing) app.add_directive("src-trace", SourceTracingDirective) CodeLinksConfig.add_config_values(app) From b655f7cbeb98bf8932755815ffa7597526723bf1 Mon Sep 17 00:00:00 2001 From: juiwenchen <46341773+juiwenchen@users.noreply.github.com> Date: Thu, 25 Sep 2025 14:42:56 +0200 Subject: [PATCH 7/8] Update src/sphinx_codelinks/sphinx_extension/source_tracing.py Co-authored-by: Marco Heinemann --- src/sphinx_codelinks/sphinx_extension/source_tracing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sphinx_codelinks/sphinx_extension/source_tracing.py b/src/sphinx_codelinks/sphinx_extension/source_tracing.py index 6d06707..4f1281a 100644 --- a/src/sphinx_codelinks/sphinx_extension/source_tracing.py +++ b/src/sphinx_codelinks/sphinx_extension/source_tracing.py @@ -38,7 +38,7 @@ def _check_sphinx_needs_dependency(app: Sphinx) -> bool: """Check if sphinx-needs is configured as an extension.""" # Check if sphinx-needs is in the extensions list - if "sphinx_needs" not in app.config.extensions: + if "sphinx_needs" not in app.extensions: error_msg = ( "sphinx-codelinks requires sphinx-needs to be configured as an extension.\n" "Please add 'sphinx_needs' to your extensions list in conf.py:\n" From 3f98782117c57380ede10efc21c51106d2abc763 Mon Sep 17 00:00:00 2001 From: juiwenchen Date: Thu, 25 Sep 2025 14:48:37 +0200 Subject: [PATCH 8/8] adapted error msg --- .../sphinx_extension/source_tracing.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/sphinx_codelinks/sphinx_extension/source_tracing.py b/src/sphinx_codelinks/sphinx_extension/source_tracing.py index 4f1281a..37504da 100644 --- a/src/sphinx_codelinks/sphinx_extension/source_tracing.py +++ b/src/sphinx_codelinks/sphinx_extension/source_tracing.py @@ -36,14 +36,15 @@ def _check_sphinx_needs_dependency(app: Sphinx) -> bool: - """Check if sphinx-needs is configured as an extension.""" - # Check if sphinx-needs is in the extensions list + """Check if sphinx-needs is actually loaded as an extension.""" + # Check if sphinx-needs is in the loaded extensions if "sphinx_needs" not in app.extensions: error_msg = ( - "sphinx-codelinks requires sphinx-needs to be configured as an extension.\n" - "Please add 'sphinx_needs' to your extensions list in conf.py:\n" + "sphinx-codelinks requires sphinx-needs to be loaded as an extension.\n" + "Please ensure 'sphinx_needs' is properly installed and added to your extensions list in conf.py:\n" " extensions = ['sphinx_needs', 'sphinx_codelinks', ...]\n" - f"Current extensions: {app.config.extensions}" + f"Currently loaded extensions: {list(app.extensions.keys())}\n" + f"Configured extensions: {app.config.extensions}" ) logger.error(error_msg) return False