cpdocs 0.3.0__tar.gz → 0.3.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {cpdocs-0.3.0 → cpdocs-0.3.1}/PKG-INFO +1 -1
- {cpdocs-0.3.0 → cpdocs-0.3.1}/src/cpdocs/__init__.py +1 -1
- {cpdocs-0.3.0 → cpdocs-0.3.1}/src/cpdocs/build.py +82 -6
- {cpdocs-0.3.0 → cpdocs-0.3.1}/src/cpdocs/clang_backend.py +73 -3
- {cpdocs-0.3.0 → cpdocs-0.3.1}/src/cpdocs/html_renderer.py +16 -2
- {cpdocs-0.3.0 → cpdocs-0.3.1}/src/cpdocs/model.py +9 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/src/cpdocs/render.py +7 -2
- {cpdocs-0.3.0 → cpdocs-0.3.1}/test/test_api_reference.py +14 -3
- {cpdocs-0.3.0 → cpdocs-0.3.1}/test/test_build.py +23 -1
- cpdocs-0.3.0/src/cpdocs/html_renderer.py.rej +0 -12
- {cpdocs-0.3.0 → cpdocs-0.3.1}/.github/workflows/docs.yml +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/.github/workflows/test.yml +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/.gitignore +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/LICENSES/Apache-2.0.txt +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/REUSE.toml +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/docs/api/index.md +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/docs/cpdocs.yml +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/docs/web/examples/index.md +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/docs/web/explanation/architecture.md +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/docs/web/explanation/feature-sets.md +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/docs/web/how-to/ci.md +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/docs/web/how-to/feature-sets.md +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/docs/web/how-to/generated-headers.md +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/docs/web/how-to/other-build-systems.md +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/docs/web/how-to/properdocs.md +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/docs/web/index.md +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/docs/web/reference/api.md +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/docs/web/reference/cli.md +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/docs/web/reference/cmake.md +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/docs/web/reference/comments.md +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/docs/web/reference/configuration.md +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/docs/web/reference/contract.md +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/docs/web/reference/extraction.md +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/docs/web/reference/links.md +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/docs/web/tutorials/first-reference.md +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/examples/basic-cpp/.gitignore +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/examples/basic-cpp/CMakeLists.txt +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/examples/basic-cpp/docs/api/index.md +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/examples/basic-cpp/docs/cpdocs.yml +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/examples/basic-cpp/docs/web/api.md +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/examples/basic-cpp/docs/web/index.md +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/examples/basic-cpp/include/acme/math.hpp +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/examples/basic-cpp/properdocs.yml +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/examples/basic-cpp/readme.md +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/examples/basic-cpp/src/math.cpp +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/examples/c-basic/.gitignore +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/examples/c-basic/CMakeLists.txt +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/examples/c-basic/docs/api/index.md +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/examples/c-basic/docs/cpdocs.yml +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/examples/c-basic/docs/web/api.md +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/examples/c-basic/docs/web/index.md +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/examples/c-basic/include/ring/ring.h +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/examples/c-basic/properdocs.yml +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/examples/c-basic/readme.md +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/examples/c-basic/src/ring.c +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/examples/cpp-features/.gitignore +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/examples/cpp-features/CMakeLists.txt +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/examples/cpp-features/docs/api/index.md +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/examples/cpp-features/docs/cpdocs-minibuild.yml +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/examples/cpp-features/docs/cpdocs.yml +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/examples/cpp-features/docs/web/api.md +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/examples/cpp-features/docs/web/index.md +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/examples/cpp-features/include/demo/solver.hpp +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/examples/cpp-features/properdocs.yml +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/examples/cpp-features/readme.md +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/examples/cpp-features/tools/minibuild.py +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/examples/cpp-templates/.gitignore +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/examples/cpp-templates/CMakeLists.txt +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/examples/cpp-templates/docs/api/index.md +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/examples/cpp-templates/docs/cpdocs.yml +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/examples/cpp-templates/docs/web/api.md +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/examples/cpp-templates/docs/web/index.md +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/examples/cpp-templates/include/meta/meta.hpp +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/examples/cpp-templates/properdocs.yml +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/examples/cpp-templates/readme.md +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/examples/fortran-basic/CMakeLists.txt +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/examples/fortran-basic/docs/cpdocs.yml +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/examples/fortran-basic/docs/index.md +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/examples/fortran-basic/lib/normalize.f90 +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/examples/fortran-basic/mod/geometry.f90 +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/examples/fortran-basic/properdocs.yml +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/examples/fortran-basic/readme.md +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/examples/generated-cpp/.gitignore +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/examples/generated-cpp/CMakeLists.txt +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/examples/generated-cpp/docs/api/index.md +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/examples/generated-cpp/docs/cpdocs.yml +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/examples/generated-cpp/docs/web/api.md +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/examples/generated-cpp/docs/web/index.md +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/examples/generated-cpp/generated-template/generated.hpp +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/examples/generated-cpp/include/acme/core.hpp +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/examples/generated-cpp/properdocs.yml +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/examples/generated-cpp/readme.md +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/examples/generated-cpp/src/generated.cpp +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/examples/mixed/.gitignore +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/examples/mixed/CMakeLists.txt +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/examples/mixed/docs/api/index.md +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/examples/mixed/docs/cpdocs.yml +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/examples/mixed/docs/web/api.md +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/examples/mixed/docs/web/index.md +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/examples/mixed/include/geom/vector.hpp +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/examples/mixed/properdocs.yml +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/examples/mixed/python/pygeom/__init__.py +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/examples/mixed/readme.md +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/examples/mixed/src/geom.cpp +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/examples/python-basic/.gitignore +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/examples/python-basic/docs/api/index.md +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/examples/python-basic/docs/cpdocs.yml +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/examples/python-basic/docs/web/api.md +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/examples/python-basic/docs/web/index.md +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/examples/python-basic/properdocs.yml +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/examples/python-basic/pyproject.toml +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/examples/python-basic/readme.md +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/examples/python-basic/src/dice/__init__.py +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/examples/python-basic/src/dice/_rng.py +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/examples/python-basic/src/dice/core.py +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/examples/rust-basic/Cargo.lock +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/examples/rust-basic/Cargo.toml +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/examples/rust-basic/docs/api.md +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/examples/rust-basic/docs/cpdocs.yml +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/examples/rust-basic/docs/index.md +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/examples/rust-basic/properdocs.yml +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/examples/rust-basic/readme.md +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/examples/rust-basic/rust-toolchain.toml +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/examples/rust-basic/src/geometry.rs +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/examples/rust-basic/src/internal.rs +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/examples/rust-basic/src/lib.rs +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/properdocs.yml +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/pyproject.toml +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/readme.md +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/src/cpdocs/__main__.py +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/src/cpdocs/cli.py +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/src/cpdocs/config.py +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/src/cpdocs/contract.py +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/src/cpdocs/fortran_backend.py +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/src/cpdocs/properdocs_core.py +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/src/cpdocs/properdocs_plugin.py +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/src/cpdocs/python_backend.py +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/src/cpdocs/python_manifest.py +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/src/cpdocs/rust_backend.py +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/src/cpdocs/rust_manifest.py +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/src/cpdocs/versioning.py +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/test/conftest.py +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/test/test_api_reference.py.rej +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/test/test_config.py +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/test/test_contract.py +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/test/test_examples.py +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/test/test_feature_set_signatures.py +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/test/test_properdocs_core.py +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/test/test_rust_example.py +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/test/test_rust_provider.py +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/test/test_versioning.py +0 -0
- {cpdocs-0.3.0 → cpdocs-0.3.1}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: cpdocs
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.1
|
|
4
4
|
Summary: Semantic, versioned API documentation for C, C++, CUDA, HIP, Fortran, Python and Rust projects
|
|
5
5
|
Project-URL: Source, https://github.com/ambhora/cpdocs
|
|
6
6
|
Author-email: Jayesh Badwaik <j.badwaik@fz-juelich.de>
|
|
@@ -36,7 +36,7 @@ from .contract import (
|
|
|
36
36
|
)
|
|
37
37
|
from .fortran_backend import extract_fortran_graph
|
|
38
38
|
from .html_renderer import render_html_site
|
|
39
|
-
from .model import ApiGraph, merge_graphs
|
|
39
|
+
from .model import ApiGraph, merge_graphs, relative_source_path
|
|
40
40
|
from .python_backend import build_python_graph
|
|
41
41
|
from .rust_backend import build_rust_graph
|
|
42
42
|
from .versioning import SelectedRef, current_branch, select_refs
|
|
@@ -199,8 +199,8 @@ def _browse_logical_path(
|
|
|
199
199
|
try:
|
|
200
200
|
return path.resolve().relative_to(project_root.resolve()).as_posix()
|
|
201
201
|
except (OSError, ValueError):
|
|
202
|
-
#
|
|
203
|
-
#
|
|
202
|
+
# Keep a stable internal key for files outside the checkout. This path is not presented to
|
|
203
|
+
# readers; rendering uses the semantic layout recorded in ``source_display_paths``.
|
|
204
204
|
base = Path("_generated") / unit.id / category
|
|
205
205
|
try:
|
|
206
206
|
relative = (
|
|
@@ -211,11 +211,69 @@ def _browse_logical_path(
|
|
|
211
211
|
return (base / relative).as_posix()
|
|
212
212
|
|
|
213
213
|
|
|
214
|
+
def _semantic_display_path(path: Path, *, root: Path, generated: bool) -> str:
|
|
215
|
+
"""Return the compiler-facing layout path shown in the file browser.
|
|
216
|
+
|
|
217
|
+
Manifest roots are semantic roots. A file below ``.../src/cuda/include`` is therefore shown
|
|
218
|
+
below ``include`` and a file below ``.../src/cuda/lib`` below ``lib``. Build-generated roots
|
|
219
|
+
use the same flattened layout under one ``_generated`` prefix.
|
|
220
|
+
"""
|
|
221
|
+
|
|
222
|
+
try:
|
|
223
|
+
if root.is_dir():
|
|
224
|
+
relative = path.resolve().relative_to(root.resolve())
|
|
225
|
+
root_name = root.name
|
|
226
|
+
else:
|
|
227
|
+
relative = Path(path.name)
|
|
228
|
+
root_name = root.parent.name
|
|
229
|
+
except (OSError, ValueError):
|
|
230
|
+
relative = Path(path.name)
|
|
231
|
+
root_name = root.name if root.name != path.name else root.parent.name
|
|
232
|
+
|
|
233
|
+
base = Path("_generated") if generated else Path()
|
|
234
|
+
if root_name:
|
|
235
|
+
base /= root_name
|
|
236
|
+
return (base / relative).as_posix()
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def _api_display_paths(
|
|
240
|
+
unit: UnitManifest, graph: ApiGraph, *, project_root: Path
|
|
241
|
+
) -> dict[str, str]:
|
|
242
|
+
"""Map API source keys to their semantic include/module layout."""
|
|
243
|
+
|
|
244
|
+
values: dict[str, str] = {}
|
|
245
|
+
for api_root in unit.api:
|
|
246
|
+
root = api_root.path
|
|
247
|
+
if not root.is_dir():
|
|
248
|
+
continue
|
|
249
|
+
for path in sorted(root.rglob("*")):
|
|
250
|
+
if not path.is_file():
|
|
251
|
+
continue
|
|
252
|
+
try:
|
|
253
|
+
root_relative = path.resolve().relative_to(root.resolve()).as_posix()
|
|
254
|
+
except (OSError, ValueError):
|
|
255
|
+
root_relative = path.name
|
|
256
|
+
project_relative = relative_source_path(path, project_root)
|
|
257
|
+
logical = next(
|
|
258
|
+
(candidate for candidate in (root_relative, project_relative) if candidate in graph.sources),
|
|
259
|
+
None,
|
|
260
|
+
)
|
|
261
|
+
if logical is not None:
|
|
262
|
+
values.setdefault(
|
|
263
|
+
logical,
|
|
264
|
+
_semantic_display_path(
|
|
265
|
+
path, root=root, generated=unit.generated or api_root.generated
|
|
266
|
+
),
|
|
267
|
+
)
|
|
268
|
+
return values
|
|
269
|
+
|
|
270
|
+
|
|
214
271
|
def _collect_browse_files(
|
|
215
272
|
unit: UnitManifest, *, project_root: Path
|
|
216
|
-
) -> tuple[dict[str, str], list[str]]:
|
|
273
|
+
) -> tuple[dict[str, str], list[str], dict[str, str]]:
|
|
217
274
|
files: dict[str, str] = {}
|
|
218
275
|
generated: list[str] = []
|
|
276
|
+
display_paths: dict[str, str] = {}
|
|
219
277
|
# Public headers are already present in ``graph.sources`` after API extraction. These roots
|
|
220
278
|
# add implementation-only material without duplicating public headers in the file hierarchy.
|
|
221
279
|
groups = (
|
|
@@ -242,9 +300,13 @@ def _collect_browse_files(
|
|
|
242
300
|
path, project_root=project_root, unit=unit, category=category, root=root
|
|
243
301
|
)
|
|
244
302
|
files.setdefault(logical, content)
|
|
303
|
+
display_paths.setdefault(
|
|
304
|
+
logical,
|
|
305
|
+
_semantic_display_path(path, root=root, generated=unit.generated),
|
|
306
|
+
)
|
|
245
307
|
if unit.generated and logical not in generated:
|
|
246
308
|
generated.append(logical)
|
|
247
|
-
return files, generated
|
|
309
|
+
return files, generated, display_paths
|
|
248
310
|
|
|
249
311
|
|
|
250
312
|
def _generated_sources(unit: UnitManifest, graph: ApiGraph) -> list[str]:
|
|
@@ -325,6 +387,11 @@ def _merge_metadata(target: dict[str, object], source: dict[str, object]) -> Non
|
|
|
325
387
|
existing = target.setdefault(key, [])
|
|
326
388
|
assert isinstance(existing, list)
|
|
327
389
|
existing.extend(item for item in value if item not in existing)
|
|
390
|
+
elif isinstance(value, dict):
|
|
391
|
+
existing = target.setdefault(key, {})
|
|
392
|
+
assert isinstance(existing, dict)
|
|
393
|
+
for item_key, item_value in value.items():
|
|
394
|
+
existing.setdefault(item_key, item_value)
|
|
328
395
|
else:
|
|
329
396
|
target.setdefault(key, value)
|
|
330
397
|
|
|
@@ -427,9 +494,18 @@ def build_graph(
|
|
|
427
494
|
work=work / "feature-sets" / feature_set.key,
|
|
428
495
|
config=config,
|
|
429
496
|
)
|
|
430
|
-
|
|
497
|
+
display_paths = unit_graph.metadata.setdefault("source_display_paths", {})
|
|
498
|
+
assert isinstance(display_paths, dict)
|
|
499
|
+
for path, display in _api_display_paths(unit, unit_graph, project_root=source).items():
|
|
500
|
+
display_paths.setdefault(path, display)
|
|
501
|
+
|
|
502
|
+
browse_files, generated_files, browse_display_paths = _collect_browse_files(
|
|
503
|
+
unit, project_root=source
|
|
504
|
+
)
|
|
431
505
|
for path, content in browse_files.items():
|
|
432
506
|
unit_graph.files.setdefault(path, content)
|
|
507
|
+
for path, display in browse_display_paths.items():
|
|
508
|
+
display_paths.setdefault(path, display)
|
|
433
509
|
if generated_files:
|
|
434
510
|
existing = unit_graph.metadata.setdefault("generated_files", [])
|
|
435
511
|
assert isinstance(existing, list)
|
|
@@ -21,6 +21,7 @@ from pathlib import Path
|
|
|
21
21
|
|
|
22
22
|
from .contract import ParseOptions
|
|
23
23
|
from .model import (
|
|
24
|
+
ApiAttribute,
|
|
24
25
|
ApiEntity,
|
|
25
26
|
ApiGraph,
|
|
26
27
|
ApiParameter,
|
|
@@ -351,10 +352,79 @@ def _qualifiers(node: dict[str, object]) -> tuple[str, ...]:
|
|
|
351
352
|
return tuple(values)
|
|
352
353
|
|
|
353
354
|
|
|
354
|
-
|
|
355
|
+
_ATTRIBUTE_NAMES = {
|
|
356
|
+
"WarnUnusedResultAttr": "nodiscard",
|
|
357
|
+
"DeprecatedAttr": "deprecated",
|
|
358
|
+
"UnusedAttr": "maybe_unused",
|
|
359
|
+
"CXX11NoReturnAttr": "noreturn",
|
|
360
|
+
"NoUniqueAddressAttr": "no_unique_address",
|
|
361
|
+
"CarriesDependencyAttr": "carries_dependency",
|
|
362
|
+
"LikelyAttr": "likely",
|
|
363
|
+
"UnlikelyAttr": "unlikely",
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
def _attribute_name(node: dict[str, object]) -> str:
|
|
368
|
+
kind = str(node.get("kind", ""))
|
|
369
|
+
if kind in _ATTRIBUTE_NAMES:
|
|
370
|
+
return _ATTRIBUTE_NAMES[kind]
|
|
371
|
+
if not kind.endswith("Attr"):
|
|
372
|
+
return ""
|
|
373
|
+
stem = kind[:-4]
|
|
374
|
+
return re.sub(r"(?<!^)(?=[A-Z])", "_", stem).lower()
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
def _attribute_spelling(node: dict[str, object], path: Path | None) -> str:
|
|
378
|
+
"""Recover the complete ``[[...]]`` specifier containing one Clang Attr node."""
|
|
379
|
+
|
|
380
|
+
if path is None:
|
|
381
|
+
return ""
|
|
382
|
+
offsets = _range_offsets(node)
|
|
383
|
+
if offsets is None:
|
|
384
|
+
return ""
|
|
385
|
+
data = _SOURCE_BYTES.get(path)
|
|
386
|
+
if data is None:
|
|
387
|
+
try:
|
|
388
|
+
data = path.read_bytes()
|
|
389
|
+
except OSError:
|
|
390
|
+
return ""
|
|
391
|
+
_SOURCE_BYTES[path] = data
|
|
392
|
+
start, stop = offsets
|
|
393
|
+
left = data.rfind(b"[[", max(0, start - 2048), start + 1)
|
|
394
|
+
if left < 0:
|
|
395
|
+
return ""
|
|
396
|
+
right = data.find(b"]]", left + 2, min(len(data), stop + 2048))
|
|
397
|
+
if right < 0 or not (left <= start and stop <= right + 2):
|
|
398
|
+
return ""
|
|
399
|
+
return re.sub(r"\s+", " ", data[left : right + 2].decode("utf-8", errors="replace")).strip()
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
def _attributes(node: dict[str, object], path: Path | None) -> tuple[ApiAttribute, ...]:
|
|
403
|
+
children = node.get("inner")
|
|
404
|
+
if not isinstance(children, list):
|
|
405
|
+
return ()
|
|
406
|
+
values: list[ApiAttribute] = []
|
|
407
|
+
for child in children:
|
|
408
|
+
if not isinstance(child, dict) or not str(child.get("kind", "")).endswith("Attr"):
|
|
409
|
+
continue
|
|
410
|
+
name = _attribute_name(child)
|
|
411
|
+
if not name:
|
|
412
|
+
continue
|
|
413
|
+
attribute = ApiAttribute(name=name, spelling=_attribute_spelling(child, path))
|
|
414
|
+
if attribute not in values:
|
|
415
|
+
values.append(attribute)
|
|
416
|
+
return tuple(values)
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
def _signature(node: dict[str, object], path: Path | None) -> ApiSignature:
|
|
355
420
|
parameters = _parameters(node)
|
|
356
421
|
qualifiers = _qualifiers(node)
|
|
357
|
-
return ApiSignature(
|
|
422
|
+
return ApiSignature(
|
|
423
|
+
parameters=parameters,
|
|
424
|
+
returns=_return_type(node),
|
|
425
|
+
qualifiers=qualifiers,
|
|
426
|
+
attributes=_attributes(node, path),
|
|
427
|
+
)
|
|
358
428
|
|
|
359
429
|
|
|
360
430
|
# --------------------------------------------------------------------------------------------------
|
|
@@ -704,7 +774,7 @@ def _walk_ast(
|
|
|
704
774
|
parent=parent_entity.id if parent_entity else None,
|
|
705
775
|
signatures=[
|
|
706
776
|
replace(
|
|
707
|
-
_signature(node),
|
|
777
|
+
_signature(node, path),
|
|
708
778
|
template=template_header,
|
|
709
779
|
documentation=_attached_comment(node),
|
|
710
780
|
source=_location(node, path, project_root, public_roots),
|
|
@@ -137,7 +137,13 @@ def _entity_href(source: Path, entity: ApiEntity, graph: ApiGraph) -> str:
|
|
|
137
137
|
|
|
138
138
|
|
|
139
139
|
def _display_source_path(source_path: str, graph: ApiGraph) -> str:
|
|
140
|
-
"""Return the
|
|
140
|
+
"""Return the semantic path shown to readers while preserving internal source keys for links."""
|
|
141
|
+
display_paths = graph.metadata.get("source_display_paths", {})
|
|
142
|
+
if isinstance(display_paths, dict):
|
|
143
|
+
value = display_paths.get(source_path)
|
|
144
|
+
if isinstance(value, str) and value:
|
|
145
|
+
return value
|
|
146
|
+
|
|
141
147
|
source_root = str(graph.metadata.get("source_root") or "").strip("/")
|
|
142
148
|
logical = PurePosixPath(source_path)
|
|
143
149
|
if not source_root:
|
|
@@ -610,7 +616,15 @@ def _signature_card(
|
|
|
610
616
|
declaration = _highlight_declaration(
|
|
611
617
|
spelling, entity.language, entity=entity, graph=graph, document_path=document
|
|
612
618
|
)
|
|
613
|
-
properties = list(
|
|
619
|
+
properties = list(
|
|
620
|
+
dict.fromkeys(
|
|
621
|
+
[
|
|
622
|
+
*entity.properties,
|
|
623
|
+
*signature.qualifiers,
|
|
624
|
+
*(attribute.name for attribute in signature.attributes),
|
|
625
|
+
]
|
|
626
|
+
)
|
|
627
|
+
)
|
|
614
628
|
rows = []
|
|
615
629
|
if properties:
|
|
616
630
|
rows.append(
|
|
@@ -24,11 +24,20 @@ class ApiParameter:
|
|
|
24
24
|
kind: str = ""
|
|
25
25
|
|
|
26
26
|
|
|
27
|
+
@dataclass(frozen=True)
|
|
28
|
+
class ApiAttribute:
|
|
29
|
+
"""One source-level attribute attached to an API declaration."""
|
|
30
|
+
|
|
31
|
+
name: str
|
|
32
|
+
spelling: str = ""
|
|
33
|
+
|
|
34
|
+
|
|
27
35
|
@dataclass(frozen=True)
|
|
28
36
|
class ApiSignature:
|
|
29
37
|
parameters: tuple[ApiParameter, ...] = ()
|
|
30
38
|
returns: str = ""
|
|
31
39
|
qualifiers: tuple[str, ...] = ()
|
|
40
|
+
attributes: tuple[ApiAttribute, ...] = ()
|
|
32
41
|
spelling: str = ""
|
|
33
42
|
template: str = ""
|
|
34
43
|
# Documentation and location of this overload; not part of its identity.
|
|
@@ -631,8 +631,12 @@ def _signature_spelling(entity: ApiEntity, signature: ApiSignature) -> str:
|
|
|
631
631
|
|
|
632
632
|
|
|
633
633
|
def _plain_signature_spelling(entity: ApiEntity, signature: ApiSignature) -> str:
|
|
634
|
+
attribute_spellings = list(
|
|
635
|
+
dict.fromkeys(attribute.spelling for attribute in signature.attributes if attribute.spelling)
|
|
636
|
+
)
|
|
637
|
+
attribute_prefix = " ".join(attribute_spellings)
|
|
634
638
|
if signature.spelling:
|
|
635
|
-
return signature.spelling
|
|
639
|
+
return f"{attribute_prefix} {signature.spelling}".strip()
|
|
636
640
|
parameters = _parameter_spelling(signature, entity.language)
|
|
637
641
|
if entity.language == "python":
|
|
638
642
|
prefix = "async def" if "async" in signature.qualifiers else "def"
|
|
@@ -656,7 +660,8 @@ def _plain_signature_spelling(entity: ApiEntity, signature: ApiSignature) -> str
|
|
|
656
660
|
result = (signature.returns + " ") if signature.returns else ""
|
|
657
661
|
suffix_values = [value for value in signature.qualifiers if value not in prefix_values]
|
|
658
662
|
suffix = (" " + " ".join(suffix_values)) if suffix_values else ""
|
|
659
|
-
|
|
663
|
+
declaration = f"{prefix}{result}{entity.qualified_name}({parameters}){suffix}".strip()
|
|
664
|
+
return f"{attribute_prefix} {declaration}".strip()
|
|
660
665
|
|
|
661
666
|
|
|
662
667
|
def _entity_declaration(entity: ApiEntity) -> str:
|
|
@@ -13,6 +13,7 @@ from cpdocs.clang_backend import extract_clang_graph
|
|
|
13
13
|
from cpdocs.contract import ParseOptions
|
|
14
14
|
from cpdocs.html_renderer import render_html_site
|
|
15
15
|
from cpdocs.model import (
|
|
16
|
+
ApiAttribute,
|
|
16
17
|
ApiEntity,
|
|
17
18
|
ApiGraph,
|
|
18
19
|
ApiParameter,
|
|
@@ -37,11 +38,11 @@ def test_cpp_semantic_profiles_merge_conditional_api(tmp_path: Path) -> None:
|
|
|
37
38
|
#ifdef USE_CUDA
|
|
38
39
|
/// Active backend.
|
|
39
40
|
#define DICE_BACKEND \"cuda\"
|
|
40
|
-
namespace dice { struct cuda_view {}; void foo(cuda_view); }
|
|
41
|
+
namespace dice { struct cuda_view {}; void foo(cuda_view); [[nodiscard]] int backend_name(); }
|
|
41
42
|
#else
|
|
42
43
|
/// Active backend.
|
|
43
44
|
#define DICE_BACKEND \"cpu\"
|
|
44
|
-
namespace dice { struct cpu_view {}; void foo(cpu_view); }
|
|
45
|
+
namespace dice { struct cpu_view {}; void foo(cpu_view); [[nodiscard]] int backend_name(); }
|
|
45
46
|
#endif
|
|
46
47
|
class visible { public: void yes(); private: void no(); };
|
|
47
48
|
""",
|
|
@@ -76,6 +77,10 @@ class visible { public: void yes(); private: void no(); };
|
|
|
76
77
|
foo = merged.entities[stable_entity_id("cpp", "function", "dice::foo")]
|
|
77
78
|
assert {signature.compact for signature in foo.signatures} == {"(cpu_view)", "(cuda_view)"}
|
|
78
79
|
assert set(foo.feature_sets) == {"cpu", "cuda"}
|
|
80
|
+
backend_name = merged.entities[stable_entity_id("cpp", "function", "dice::backend_name")]
|
|
81
|
+
assert backend_name.signatures[0].attributes == (
|
|
82
|
+
ApiAttribute(name="nodiscard", spelling="[[nodiscard]]"),
|
|
83
|
+
)
|
|
79
84
|
assert stable_entity_id("cpp", "method", "visible::yes") in merged.entities
|
|
80
85
|
assert stable_entity_id("cpp", "method", "visible::no") not in merged.entities
|
|
81
86
|
|
|
@@ -206,7 +211,11 @@ def test_renderer_generates_standalone_code_oriented_html_site(tmp_path: Path) -
|
|
|
206
211
|
qualified_name="dice::foo",
|
|
207
212
|
parent=namespace.id,
|
|
208
213
|
signatures=[
|
|
209
|
-
ApiSignature(
|
|
214
|
+
ApiSignature(
|
|
215
|
+
parameters=(ApiParameter("value", "int"),),
|
|
216
|
+
returns="void",
|
|
217
|
+
attributes=(ApiAttribute(name="nodiscard", spelling="[[nodiscard]]"),),
|
|
218
|
+
),
|
|
210
219
|
ApiSignature(parameters=(ApiParameter("value", "float"),), returns="void"),
|
|
211
220
|
],
|
|
212
221
|
documentation=(
|
|
@@ -315,6 +324,8 @@ def test_renderer_generates_standalone_code_oriented_html_site(tmp_path: Path) -
|
|
|
315
324
|
page = html_page(entity_document(function)).read_text(encoding="utf-8")
|
|
316
325
|
assert '<article class="api-signature-card"' in page
|
|
317
326
|
assert "foo(int)" in page
|
|
327
|
+
assert page.count("nodiscard") >= 2
|
|
328
|
+
assert "<span>nodiscard</span>" in page
|
|
318
329
|
assert "Available in feature sets" in page
|
|
319
330
|
assert "<code>cpu</code>" in page
|
|
320
331
|
assert "<strong>Related:</strong>" in page
|
|
@@ -10,7 +10,7 @@ from pathlib import Path
|
|
|
10
10
|
import pytest
|
|
11
11
|
|
|
12
12
|
from conftest import fake_project, git, init_repository
|
|
13
|
-
from cpdocs.build import build_current, build_versions
|
|
13
|
+
from cpdocs.build import _semantic_display_path, build_current, build_versions
|
|
14
14
|
from cpdocs.cli import main
|
|
15
15
|
from cpdocs.config import load_config
|
|
16
16
|
from cpdocs.model import stable_entity_id
|
|
@@ -27,6 +27,28 @@ def _no_ci_branch(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
|
27
27
|
monkeypatch.delenv(variable, raising=False)
|
|
28
28
|
|
|
29
29
|
|
|
30
|
+
def test_semantic_file_layout_flattens_manifest_roots(tmp_path: Path) -> None:
|
|
31
|
+
include = tmp_path / "src" / "cuda" / "include"
|
|
32
|
+
library = tmp_path / "src" / "cuda" / "lib"
|
|
33
|
+
header = include / "kprof" / "cuda.hpp"
|
|
34
|
+
source = library / "kprof" / "synchronize.cu"
|
|
35
|
+
header.parent.mkdir(parents=True)
|
|
36
|
+
source.parent.mkdir(parents=True)
|
|
37
|
+
header.write_text("#pragma once\n", encoding="utf-8")
|
|
38
|
+
source.write_text("// source\n", encoding="utf-8")
|
|
39
|
+
|
|
40
|
+
assert _semantic_display_path(header, root=include, generated=False) == "include/kprof/cuda.hpp"
|
|
41
|
+
assert _semantic_display_path(source, root=library, generated=False) == "lib/kprof/synchronize.cu"
|
|
42
|
+
assert (
|
|
43
|
+
_semantic_display_path(header, root=include, generated=True)
|
|
44
|
+
== "_generated/include/kprof/cuda.hpp"
|
|
45
|
+
)
|
|
46
|
+
assert (
|
|
47
|
+
_semantic_display_path(source, root=library, generated=True)
|
|
48
|
+
== "_generated/lib/kprof/synchronize.cu"
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
|
|
30
52
|
def test_build_runs_feature_sets_once_then_build_once_per_feature_set(tmp_path: Path) -> None:
|
|
31
53
|
project = fake_project(
|
|
32
54
|
tmp_path / "p",
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
diff a/src/cpdocs/html_renderer.py b/src/cpdocs/html_renderer.py (rejected hunks)
|
|
2
|
-
@@ -698,7 +698,9 @@ def _namespace_page(
|
|
3
|
-
label = child.member_label
|
|
4
|
-
body.append(
|
|
5
|
-
'<li>' + _kind_badge(child.kind)
|
|
6
|
-
- + f'<a href="{html.escape(_entity_href(document, child, graph))}">{html.escape(label)}</a></li>'
|
|
7
|
-
+ + f'<a href="{html.escape(_entity_href(document, child, graph))}">{html.escape(label)}</a>'
|
|
8
|
-
+ + _language_badge(child)
|
|
9
|
-
+ + '</li>'
|
|
10
|
-
)
|
|
11
|
-
body.extend(["</ul>", _availability(entity, graph, document), "</section>"])
|
|
12
|
-
return "".join(body), [("Members", "members", 1)]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|