cpdocs 0.2.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.2.0 → cpdocs-0.3.1}/PKG-INFO +1 -1
- {cpdocs-0.2.0 → cpdocs-0.3.1}/docs/web/reference/cli.md +5 -4
- {cpdocs-0.2.0 → cpdocs-0.3.1}/src/cpdocs/__init__.py +1 -1
- {cpdocs-0.2.0 → cpdocs-0.3.1}/src/cpdocs/build.py +93 -12
- {cpdocs-0.2.0 → cpdocs-0.3.1}/src/cpdocs/clang_backend.py +73 -3
- {cpdocs-0.2.0 → cpdocs-0.3.1}/src/cpdocs/cli.py +12 -2
- {cpdocs-0.2.0 → cpdocs-0.3.1}/src/cpdocs/html_renderer.py +16 -2
- {cpdocs-0.2.0 → cpdocs-0.3.1}/src/cpdocs/model.py +9 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/src/cpdocs/properdocs_plugin.py +11 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/src/cpdocs/render.py +7 -2
- {cpdocs-0.2.0 → cpdocs-0.3.1}/test/test_api_reference.py +14 -3
- {cpdocs-0.2.0 → cpdocs-0.3.1}/test/test_build.py +44 -1
- cpdocs-0.2.0/src/cpdocs/html_renderer.py.rej +0 -12
- {cpdocs-0.2.0 → cpdocs-0.3.1}/.github/workflows/docs.yml +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/.github/workflows/test.yml +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/.gitignore +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/LICENSES/Apache-2.0.txt +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/REUSE.toml +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/docs/api/index.md +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/docs/cpdocs.yml +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/docs/web/examples/index.md +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/docs/web/explanation/architecture.md +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/docs/web/explanation/feature-sets.md +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/docs/web/how-to/ci.md +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/docs/web/how-to/feature-sets.md +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/docs/web/how-to/generated-headers.md +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/docs/web/how-to/other-build-systems.md +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/docs/web/how-to/properdocs.md +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/docs/web/index.md +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/docs/web/reference/api.md +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/docs/web/reference/cmake.md +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/docs/web/reference/comments.md +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/docs/web/reference/configuration.md +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/docs/web/reference/contract.md +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/docs/web/reference/extraction.md +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/docs/web/reference/links.md +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/docs/web/tutorials/first-reference.md +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/examples/basic-cpp/.gitignore +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/examples/basic-cpp/CMakeLists.txt +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/examples/basic-cpp/docs/api/index.md +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/examples/basic-cpp/docs/cpdocs.yml +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/examples/basic-cpp/docs/web/api.md +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/examples/basic-cpp/docs/web/index.md +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/examples/basic-cpp/include/acme/math.hpp +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/examples/basic-cpp/properdocs.yml +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/examples/basic-cpp/readme.md +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/examples/basic-cpp/src/math.cpp +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/examples/c-basic/.gitignore +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/examples/c-basic/CMakeLists.txt +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/examples/c-basic/docs/api/index.md +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/examples/c-basic/docs/cpdocs.yml +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/examples/c-basic/docs/web/api.md +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/examples/c-basic/docs/web/index.md +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/examples/c-basic/include/ring/ring.h +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/examples/c-basic/properdocs.yml +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/examples/c-basic/readme.md +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/examples/c-basic/src/ring.c +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/examples/cpp-features/.gitignore +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/examples/cpp-features/CMakeLists.txt +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/examples/cpp-features/docs/api/index.md +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/examples/cpp-features/docs/cpdocs-minibuild.yml +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/examples/cpp-features/docs/cpdocs.yml +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/examples/cpp-features/docs/web/api.md +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/examples/cpp-features/docs/web/index.md +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/examples/cpp-features/include/demo/solver.hpp +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/examples/cpp-features/properdocs.yml +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/examples/cpp-features/readme.md +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/examples/cpp-features/tools/minibuild.py +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/examples/cpp-templates/.gitignore +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/examples/cpp-templates/CMakeLists.txt +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/examples/cpp-templates/docs/api/index.md +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/examples/cpp-templates/docs/cpdocs.yml +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/examples/cpp-templates/docs/web/api.md +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/examples/cpp-templates/docs/web/index.md +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/examples/cpp-templates/include/meta/meta.hpp +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/examples/cpp-templates/properdocs.yml +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/examples/cpp-templates/readme.md +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/examples/fortran-basic/CMakeLists.txt +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/examples/fortran-basic/docs/cpdocs.yml +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/examples/fortran-basic/docs/index.md +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/examples/fortran-basic/lib/normalize.f90 +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/examples/fortran-basic/mod/geometry.f90 +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/examples/fortran-basic/properdocs.yml +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/examples/fortran-basic/readme.md +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/examples/generated-cpp/.gitignore +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/examples/generated-cpp/CMakeLists.txt +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/examples/generated-cpp/docs/api/index.md +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/examples/generated-cpp/docs/cpdocs.yml +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/examples/generated-cpp/docs/web/api.md +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/examples/generated-cpp/docs/web/index.md +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/examples/generated-cpp/generated-template/generated.hpp +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/examples/generated-cpp/include/acme/core.hpp +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/examples/generated-cpp/properdocs.yml +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/examples/generated-cpp/readme.md +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/examples/generated-cpp/src/generated.cpp +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/examples/mixed/.gitignore +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/examples/mixed/CMakeLists.txt +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/examples/mixed/docs/api/index.md +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/examples/mixed/docs/cpdocs.yml +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/examples/mixed/docs/web/api.md +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/examples/mixed/docs/web/index.md +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/examples/mixed/include/geom/vector.hpp +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/examples/mixed/properdocs.yml +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/examples/mixed/python/pygeom/__init__.py +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/examples/mixed/readme.md +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/examples/mixed/src/geom.cpp +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/examples/python-basic/.gitignore +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/examples/python-basic/docs/api/index.md +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/examples/python-basic/docs/cpdocs.yml +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/examples/python-basic/docs/web/api.md +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/examples/python-basic/docs/web/index.md +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/examples/python-basic/properdocs.yml +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/examples/python-basic/pyproject.toml +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/examples/python-basic/readme.md +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/examples/python-basic/src/dice/__init__.py +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/examples/python-basic/src/dice/_rng.py +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/examples/python-basic/src/dice/core.py +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/examples/rust-basic/Cargo.lock +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/examples/rust-basic/Cargo.toml +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/examples/rust-basic/docs/api.md +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/examples/rust-basic/docs/cpdocs.yml +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/examples/rust-basic/docs/index.md +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/examples/rust-basic/properdocs.yml +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/examples/rust-basic/readme.md +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/examples/rust-basic/rust-toolchain.toml +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/examples/rust-basic/src/geometry.rs +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/examples/rust-basic/src/internal.rs +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/examples/rust-basic/src/lib.rs +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/properdocs.yml +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/pyproject.toml +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/readme.md +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/src/cpdocs/__main__.py +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/src/cpdocs/config.py +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/src/cpdocs/contract.py +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/src/cpdocs/fortran_backend.py +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/src/cpdocs/properdocs_core.py +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/src/cpdocs/python_backend.py +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/src/cpdocs/python_manifest.py +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/src/cpdocs/rust_backend.py +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/src/cpdocs/rust_manifest.py +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/src/cpdocs/versioning.py +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/test/conftest.py +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/test/test_api_reference.py.rej +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/test/test_config.py +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/test/test_contract.py +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/test/test_examples.py +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/test/test_feature_set_signatures.py +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/test/test_properdocs_core.py +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/test/test_rust_example.py +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/test/test_rust_provider.py +0 -0
- {cpdocs-0.2.0 → cpdocs-0.3.1}/test/test_versioning.py +0 -0
- {cpdocs-0.2.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
|
+
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>
|
|
@@ -5,8 +5,8 @@ SPDX-License-Identifier: Apache-2.0
|
|
|
5
5
|
# Command line
|
|
6
6
|
|
|
7
7
|
```text
|
|
8
|
-
cpdocs build [--config docs/cpdocs.yml] [--output build] [--label main]
|
|
9
|
-
cpdocs versions [--config docs/cpdocs.yml] [--output build]
|
|
8
|
+
cpdocs build [--config docs/cpdocs.yml] [--output build] [--tmp-directory DIR] [--label main]
|
|
9
|
+
cpdocs versions [--config docs/cpdocs.yml] [--output build] [--tmp-directory DIR]
|
|
10
10
|
cpdocs manifest python feature-sets ...
|
|
11
11
|
cpdocs manifest python build ...
|
|
12
12
|
cpdocs rust feature-sets --source <project> --output <feature-sets.json>
|
|
@@ -26,5 +26,6 @@ declared Cargo feature is treated as a disjoint feature set and documented once;
|
|
|
26
26
|
specific combinations can list them in `[package.metadata.cpdocs]`. `rust build` runs Cargo rustdoc
|
|
27
27
|
with nightly JSON output and writes a normal cpdocs manifest.
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
`<output>/cpdocs/work/<version>/feature-sets/`.
|
|
29
|
+
By default, feature-set inputs, manifests, scratch/build directories and command logs are kept below
|
|
30
|
+
`<output>/cpdocs/work/<version>/feature-sets/`. Pass `--tmp-directory DIR` to keep this workspace
|
|
31
|
+
under `DIR/cpdocs/work/` while leaving rendered output under `<output>/cpdocs/`.
|
|
@@ -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
|
|
@@ -47,13 +47,16 @@ _CONTAINER_KINDS = {"namespace", "package", "module"}
|
|
|
47
47
|
|
|
48
48
|
@dataclasses.dataclass(frozen=True)
|
|
49
49
|
class Layout:
|
|
50
|
-
"""
|
|
50
|
+
"""Published and temporary directories used by cpdocs."""
|
|
51
51
|
|
|
52
52
|
root: Path
|
|
53
|
+
work_root: Path
|
|
53
54
|
|
|
54
55
|
@classmethod
|
|
55
|
-
def for_output(cls, output: Path) -> Layout:
|
|
56
|
-
|
|
56
|
+
def for_output(cls, output: Path, *, tmp_directory: Path | None = None) -> Layout:
|
|
57
|
+
root = (output / "cpdocs").resolve()
|
|
58
|
+
work_root = (tmp_directory.resolve() / "cpdocs" if tmp_directory else root)
|
|
59
|
+
return cls(root=root, work_root=work_root)
|
|
57
60
|
|
|
58
61
|
@property
|
|
59
62
|
def current(self) -> Path:
|
|
@@ -64,7 +67,7 @@ class Layout:
|
|
|
64
67
|
return self.root / "versions"
|
|
65
68
|
|
|
66
69
|
def work(self, ref: str) -> Path:
|
|
67
|
-
return self.
|
|
70
|
+
return self.work_root / "work" / ref.replace("/", "__")
|
|
68
71
|
|
|
69
72
|
|
|
70
73
|
|
|
@@ -196,8 +199,8 @@ def _browse_logical_path(
|
|
|
196
199
|
try:
|
|
197
200
|
return path.resolve().relative_to(project_root.resolve()).as_posix()
|
|
198
201
|
except (OSError, ValueError):
|
|
199
|
-
#
|
|
200
|
-
#
|
|
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``.
|
|
201
204
|
base = Path("_generated") / unit.id / category
|
|
202
205
|
try:
|
|
203
206
|
relative = (
|
|
@@ -208,11 +211,69 @@ def _browse_logical_path(
|
|
|
208
211
|
return (base / relative).as_posix()
|
|
209
212
|
|
|
210
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
|
+
|
|
211
271
|
def _collect_browse_files(
|
|
212
272
|
unit: UnitManifest, *, project_root: Path
|
|
213
|
-
) -> tuple[dict[str, str], list[str]]:
|
|
273
|
+
) -> tuple[dict[str, str], list[str], dict[str, str]]:
|
|
214
274
|
files: dict[str, str] = {}
|
|
215
275
|
generated: list[str] = []
|
|
276
|
+
display_paths: dict[str, str] = {}
|
|
216
277
|
# Public headers are already present in ``graph.sources`` after API extraction. These roots
|
|
217
278
|
# add implementation-only material without duplicating public headers in the file hierarchy.
|
|
218
279
|
groups = (
|
|
@@ -239,9 +300,13 @@ def _collect_browse_files(
|
|
|
239
300
|
path, project_root=project_root, unit=unit, category=category, root=root
|
|
240
301
|
)
|
|
241
302
|
files.setdefault(logical, content)
|
|
303
|
+
display_paths.setdefault(
|
|
304
|
+
logical,
|
|
305
|
+
_semantic_display_path(path, root=root, generated=unit.generated),
|
|
306
|
+
)
|
|
242
307
|
if unit.generated and logical not in generated:
|
|
243
308
|
generated.append(logical)
|
|
244
|
-
return files, generated
|
|
309
|
+
return files, generated, display_paths
|
|
245
310
|
|
|
246
311
|
|
|
247
312
|
def _generated_sources(unit: UnitManifest, graph: ApiGraph) -> list[str]:
|
|
@@ -322,6 +387,11 @@ def _merge_metadata(target: dict[str, object], source: dict[str, object]) -> Non
|
|
|
322
387
|
existing = target.setdefault(key, [])
|
|
323
388
|
assert isinstance(existing, list)
|
|
324
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)
|
|
325
395
|
else:
|
|
326
396
|
target.setdefault(key, value)
|
|
327
397
|
|
|
@@ -424,9 +494,18 @@ def build_graph(
|
|
|
424
494
|
work=work / "feature-sets" / feature_set.key,
|
|
425
495
|
config=config,
|
|
426
496
|
)
|
|
427
|
-
|
|
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
|
+
)
|
|
428
505
|
for path, content in browse_files.items():
|
|
429
506
|
unit_graph.files.setdefault(path, content)
|
|
507
|
+
for path, display in browse_display_paths.items():
|
|
508
|
+
display_paths.setdefault(path, display)
|
|
430
509
|
if generated_files:
|
|
431
510
|
existing = unit_graph.metadata.setdefault("generated_files", [])
|
|
432
511
|
assert isinstance(existing, list)
|
|
@@ -505,10 +584,11 @@ def build_current(
|
|
|
505
584
|
output: Path,
|
|
506
585
|
version: str = "main",
|
|
507
586
|
project_docs_root_depth: int = 2,
|
|
587
|
+
tmp_directory: Path | None = None,
|
|
508
588
|
) -> ApiGraph:
|
|
509
589
|
"""Document the working tree into ``<output>/cpdocs/current``."""
|
|
510
590
|
|
|
511
|
-
layout = Layout.for_output(output)
|
|
591
|
+
layout = Layout.for_output(output, tmp_directory=tmp_directory)
|
|
512
592
|
work = layout.work("current")
|
|
513
593
|
graph = build_graph(
|
|
514
594
|
config,
|
|
@@ -610,10 +690,11 @@ def build_versions(
|
|
|
610
690
|
output: Path,
|
|
611
691
|
project_docs_root_depth: int = 2,
|
|
612
692
|
include_working_tree: bool = False,
|
|
693
|
+
tmp_directory: Path | None = None,
|
|
613
694
|
) -> list[str]:
|
|
614
695
|
"""Document the explicitly configured ``versions`` in order."""
|
|
615
696
|
|
|
616
|
-
layout = Layout.for_output(output)
|
|
697
|
+
layout = Layout.for_output(output, tmp_directory=tmp_directory)
|
|
617
698
|
project_root = config.project_root.resolve()
|
|
618
699
|
subdirectory = project_root.relative_to(_repository_root(project_root))
|
|
619
700
|
selected = select_refs(project_root, config.versions)
|
|
@@ -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),
|
|
@@ -34,6 +34,11 @@ def _parser() -> argparse.ArgumentParser:
|
|
|
34
34
|
default=Path("build"),
|
|
35
35
|
help="output root; cpdocs writes below <output>/cpdocs (default: build)",
|
|
36
36
|
)
|
|
37
|
+
command.add_argument(
|
|
38
|
+
"--tmp-directory",
|
|
39
|
+
type=_path,
|
|
40
|
+
help="temporary workspace root; defaults to the output root",
|
|
41
|
+
)
|
|
37
42
|
return command
|
|
38
43
|
|
|
39
44
|
build = documentation("build", "document the working tree")
|
|
@@ -110,10 +115,15 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
110
115
|
else:
|
|
111
116
|
config = load_config(arguments.config)
|
|
112
117
|
output = arguments.output.resolve()
|
|
118
|
+
tmp_directory = (
|
|
119
|
+
arguments.tmp_directory.resolve() if arguments.tmp_directory is not None else None
|
|
120
|
+
)
|
|
113
121
|
if arguments.command == "build":
|
|
114
|
-
build_current(
|
|
122
|
+
build_current(
|
|
123
|
+
config, output=output, version=arguments.label, tmp_directory=tmp_directory
|
|
124
|
+
)
|
|
115
125
|
else:
|
|
116
|
-
build_versions(config, output=output)
|
|
126
|
+
build_versions(config, output=output, tmp_directory=tmp_directory)
|
|
117
127
|
except RuntimeError as error:
|
|
118
128
|
parser.exit(1, f"cpdocs: error: {error}\n")
|
|
119
129
|
return 0
|
|
@@ -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.
|
|
@@ -44,6 +44,7 @@ class CpdocsPlugin(BasePlugin):
|
|
|
44
44
|
config_scheme = (
|
|
45
45
|
("config", c.Type(str, default=str(DEFAULT_CONFIG))),
|
|
46
46
|
("output", c.Type(str, default="build")),
|
|
47
|
+
("tmp_directory", c.Type(str, default="")),
|
|
47
48
|
("mount", c.Type(str, default="reference/api")),
|
|
48
49
|
("subsites", c.ListOfItems(c.Type(str), default=[])),
|
|
49
50
|
)
|
|
@@ -69,6 +70,15 @@ class CpdocsPlugin(BasePlugin):
|
|
|
69
70
|
path = Path(str(self.config.get("output", "build")))
|
|
70
71
|
return path if path.is_absolute() else self._project_root / path
|
|
71
72
|
|
|
73
|
+
@property
|
|
74
|
+
def tmp_directory(self) -> Path | None:
|
|
75
|
+
assert self._project_root is not None
|
|
76
|
+
value = str(self.config.get("tmp_directory", "")).strip()
|
|
77
|
+
if not value:
|
|
78
|
+
return None
|
|
79
|
+
path = Path(value).expanduser()
|
|
80
|
+
return path if path.is_absolute() else self._project_root / path
|
|
81
|
+
|
|
72
82
|
def on_startup(self, *, command: str, dirty: bool = False) -> None:
|
|
73
83
|
del dirty
|
|
74
84
|
self._serve_active = command == "serve" or os.environ.get("_CPDOCS_SERVE_IN_SITU") == "1"
|
|
@@ -221,6 +231,7 @@ class CpdocsPlugin(BasePlugin):
|
|
|
221
231
|
output=self.output,
|
|
222
232
|
project_docs_root_depth=len(PurePosixPath(self.mount).parts),
|
|
223
233
|
include_working_tree=self._serve_active,
|
|
234
|
+
tmp_directory=self.tmp_directory,
|
|
224
235
|
)
|
|
225
236
|
except RuntimeError as error:
|
|
226
237
|
raise PluginError(f"cpdocs: {error}") from error
|
|
@@ -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",
|
|
@@ -153,3 +175,24 @@ def test_obsolete_cpdocs_cmake_placeholder_is_rejected(tmp_path: Path) -> None:
|
|
|
153
175
|
)
|
|
154
176
|
with pytest.raises(RuntimeError, match="cpdocs-cmake"):
|
|
155
177
|
build_current(load_config(config), output=project / "build")
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def test_tmp_directory_separates_work_from_output(tmp_path, monkeypatch):
|
|
181
|
+
project = tmp_path / "project"
|
|
182
|
+
project.mkdir()
|
|
183
|
+
config_file = project / "cpdocs.yml"
|
|
184
|
+
config_file.write_text(
|
|
185
|
+
"contract: 3\nproject: demo\nlanguages: [python]\n"
|
|
186
|
+
"build:\n"
|
|
187
|
+
" feature-sets: >-\n"
|
|
188
|
+
" python -c \"import pathlib; pathlib.Path(r'{{output-file}}').write_text('[]')\"\n"
|
|
189
|
+
" build: >-\n"
|
|
190
|
+
" python -c \"import pathlib; pathlib.Path(r'{{output-file}}').write_text('[]')\"\n",
|
|
191
|
+
encoding="utf-8",
|
|
192
|
+
)
|
|
193
|
+
# Layout behavior is the contract under test; avoid coupling this regression to providers.
|
|
194
|
+
from cpdocs.build import Layout
|
|
195
|
+
|
|
196
|
+
layout = Layout.for_output(project / "build", tmp_directory=tmp_path / "scratch")
|
|
197
|
+
assert layout.root == (project / "build" / "cpdocs").resolve()
|
|
198
|
+
assert layout.work("main") == (tmp_path / "scratch" / "cpdocs" / "work" / "main").resolve()
|
|
@@ -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
|