cpdocs 0.3.0__tar.gz → 0.3.2__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.
Files changed (169) hide show
  1. {cpdocs-0.3.0 → cpdocs-0.3.2}/PKG-INFO +1 -1
  2. cpdocs-0.3.2/examples/fortran-basic/docs/api.md +12 -0
  3. {cpdocs-0.3.0 → cpdocs-0.3.2}/examples/fortran-basic/properdocs.yml +1 -0
  4. {cpdocs-0.3.0 → cpdocs-0.3.2}/pyproject.toml +1 -1
  5. {cpdocs-0.3.0 → cpdocs-0.3.2}/src/cpdocs/__init__.py +1 -1
  6. {cpdocs-0.3.0 → cpdocs-0.3.2}/src/cpdocs/build.py +93 -135
  7. {cpdocs-0.3.0 → cpdocs-0.3.2}/src/cpdocs/cli.py +4 -4
  8. {cpdocs-0.3.0 → cpdocs-0.3.2}/src/cpdocs/contract.py +5 -5
  9. cpdocs-0.3.2/src/cpdocs/languages/__init__.py +138 -0
  10. cpdocs-0.3.2/src/cpdocs/languages/base.py +63 -0
  11. cpdocs-0.3.2/src/cpdocs/languages/cpp/__init__.py +8 -0
  12. cpdocs-0.3.0/src/cpdocs/clang_backend.py → cpdocs-0.3.2/src/cpdocs/languages/cpp/extract.py +75 -5
  13. cpdocs-0.3.2/src/cpdocs/languages/cpp/render.py +125 -0
  14. cpdocs-0.3.2/src/cpdocs/languages/fortran/__init__.py +8 -0
  15. cpdocs-0.3.0/src/cpdocs/fortran_backend.py → cpdocs-0.3.2/src/cpdocs/languages/fortran/extract.py +1 -1
  16. cpdocs-0.3.2/src/cpdocs/languages/fortran/render.py +70 -0
  17. cpdocs-0.3.2/src/cpdocs/languages/python/__init__.py +8 -0
  18. cpdocs-0.3.0/src/cpdocs/python_backend.py → cpdocs-0.3.2/src/cpdocs/languages/python/extract.py +1 -1
  19. cpdocs-0.3.2/src/cpdocs/languages/python/render.py +119 -0
  20. cpdocs-0.3.2/src/cpdocs/languages/rust/__init__.py +8 -0
  21. cpdocs-0.3.0/src/cpdocs/rust_backend.py → cpdocs-0.3.2/src/cpdocs/languages/rust/extract.py +1 -1
  22. cpdocs-0.3.2/src/cpdocs/languages/rust/render.py +80 -0
  23. {cpdocs-0.3.0 → cpdocs-0.3.2}/src/cpdocs/model.py +9 -0
  24. cpdocs-0.3.2/src/cpdocs/plugins/__init__.py +3 -0
  25. cpdocs-0.3.2/src/cpdocs/plugins/properdocs/__init__.py +7 -0
  26. cpdocs-0.3.0/src/cpdocs/properdocs_plugin.py → cpdocs-0.3.2/src/cpdocs/plugins/properdocs/plugin.py +1 -1
  27. cpdocs-0.3.2/src/cpdocs/providers/__init__.py +3 -0
  28. cpdocs-0.3.0/src/cpdocs/rust_manifest.py → cpdocs-0.3.2/src/cpdocs/providers/cargo.py +1 -1
  29. cpdocs-0.3.2/src/cpdocs/providers/cmake.py +8 -0
  30. cpdocs-0.3.2/src/cpdocs/providers/contract.py +125 -0
  31. cpdocs-0.3.0/src/cpdocs/python_manifest.py → cpdocs-0.3.2/src/cpdocs/providers/python.py +1 -1
  32. cpdocs-0.3.2/src/cpdocs/rendering/__init__.py +7 -0
  33. cpdocs-0.3.0/src/cpdocs/render.py → cpdocs-0.3.2/src/cpdocs/rendering/common.py +29 -205
  34. cpdocs-0.3.0/src/cpdocs/html_renderer.py → cpdocs-0.3.2/src/cpdocs/rendering/html.py +73 -122
  35. {cpdocs-0.3.0 → cpdocs-0.3.2}/test/conftest.py +17 -0
  36. {cpdocs-0.3.0 → cpdocs-0.3.2}/test/test_api_reference.py +21 -10
  37. {cpdocs-0.3.0 → cpdocs-0.3.2}/test/test_build.py +23 -1
  38. {cpdocs-0.3.0 → cpdocs-0.3.2}/test/test_examples.py +3 -2
  39. {cpdocs-0.3.0 → cpdocs-0.3.2}/test/test_feature_set_signatures.py +1 -2
  40. {cpdocs-0.3.0 → cpdocs-0.3.2}/test/test_properdocs_core.py +4 -4
  41. {cpdocs-0.3.0 → cpdocs-0.3.2}/test/test_rust_provider.py +3 -3
  42. cpdocs-0.3.0/src/cpdocs/html_renderer.py.rej +0 -12
  43. cpdocs-0.3.0/test/test_api_reference.py.rej +0 -9
  44. {cpdocs-0.3.0 → cpdocs-0.3.2}/.github/workflows/docs.yml +0 -0
  45. {cpdocs-0.3.0 → cpdocs-0.3.2}/.github/workflows/test.yml +0 -0
  46. {cpdocs-0.3.0 → cpdocs-0.3.2}/.gitignore +0 -0
  47. {cpdocs-0.3.0 → cpdocs-0.3.2}/LICENSES/Apache-2.0.txt +0 -0
  48. {cpdocs-0.3.0 → cpdocs-0.3.2}/REUSE.toml +0 -0
  49. {cpdocs-0.3.0 → cpdocs-0.3.2}/docs/api/index.md +0 -0
  50. {cpdocs-0.3.0 → cpdocs-0.3.2}/docs/cpdocs.yml +0 -0
  51. {cpdocs-0.3.0 → cpdocs-0.3.2}/docs/web/examples/index.md +0 -0
  52. {cpdocs-0.3.0 → cpdocs-0.3.2}/docs/web/explanation/architecture.md +0 -0
  53. {cpdocs-0.3.0 → cpdocs-0.3.2}/docs/web/explanation/feature-sets.md +0 -0
  54. {cpdocs-0.3.0 → cpdocs-0.3.2}/docs/web/how-to/ci.md +0 -0
  55. {cpdocs-0.3.0 → cpdocs-0.3.2}/docs/web/how-to/feature-sets.md +0 -0
  56. {cpdocs-0.3.0 → cpdocs-0.3.2}/docs/web/how-to/generated-headers.md +0 -0
  57. {cpdocs-0.3.0 → cpdocs-0.3.2}/docs/web/how-to/other-build-systems.md +0 -0
  58. {cpdocs-0.3.0 → cpdocs-0.3.2}/docs/web/how-to/properdocs.md +0 -0
  59. {cpdocs-0.3.0 → cpdocs-0.3.2}/docs/web/index.md +0 -0
  60. {cpdocs-0.3.0 → cpdocs-0.3.2}/docs/web/reference/api.md +0 -0
  61. {cpdocs-0.3.0 → cpdocs-0.3.2}/docs/web/reference/cli.md +0 -0
  62. {cpdocs-0.3.0 → cpdocs-0.3.2}/docs/web/reference/cmake.md +0 -0
  63. {cpdocs-0.3.0 → cpdocs-0.3.2}/docs/web/reference/comments.md +0 -0
  64. {cpdocs-0.3.0 → cpdocs-0.3.2}/docs/web/reference/configuration.md +0 -0
  65. {cpdocs-0.3.0 → cpdocs-0.3.2}/docs/web/reference/contract.md +0 -0
  66. {cpdocs-0.3.0 → cpdocs-0.3.2}/docs/web/reference/extraction.md +0 -0
  67. {cpdocs-0.3.0 → cpdocs-0.3.2}/docs/web/reference/links.md +0 -0
  68. {cpdocs-0.3.0 → cpdocs-0.3.2}/docs/web/tutorials/first-reference.md +0 -0
  69. {cpdocs-0.3.0 → cpdocs-0.3.2}/examples/basic-cpp/.gitignore +0 -0
  70. {cpdocs-0.3.0 → cpdocs-0.3.2}/examples/basic-cpp/CMakeLists.txt +0 -0
  71. {cpdocs-0.3.0 → cpdocs-0.3.2}/examples/basic-cpp/docs/api/index.md +0 -0
  72. {cpdocs-0.3.0 → cpdocs-0.3.2}/examples/basic-cpp/docs/cpdocs.yml +0 -0
  73. {cpdocs-0.3.0 → cpdocs-0.3.2}/examples/basic-cpp/docs/web/api.md +0 -0
  74. {cpdocs-0.3.0 → cpdocs-0.3.2}/examples/basic-cpp/docs/web/index.md +0 -0
  75. {cpdocs-0.3.0 → cpdocs-0.3.2}/examples/basic-cpp/include/acme/math.hpp +0 -0
  76. {cpdocs-0.3.0 → cpdocs-0.3.2}/examples/basic-cpp/properdocs.yml +0 -0
  77. {cpdocs-0.3.0 → cpdocs-0.3.2}/examples/basic-cpp/readme.md +0 -0
  78. {cpdocs-0.3.0 → cpdocs-0.3.2}/examples/basic-cpp/src/math.cpp +0 -0
  79. {cpdocs-0.3.0 → cpdocs-0.3.2}/examples/c-basic/.gitignore +0 -0
  80. {cpdocs-0.3.0 → cpdocs-0.3.2}/examples/c-basic/CMakeLists.txt +0 -0
  81. {cpdocs-0.3.0 → cpdocs-0.3.2}/examples/c-basic/docs/api/index.md +0 -0
  82. {cpdocs-0.3.0 → cpdocs-0.3.2}/examples/c-basic/docs/cpdocs.yml +0 -0
  83. {cpdocs-0.3.0 → cpdocs-0.3.2}/examples/c-basic/docs/web/api.md +0 -0
  84. {cpdocs-0.3.0 → cpdocs-0.3.2}/examples/c-basic/docs/web/index.md +0 -0
  85. {cpdocs-0.3.0 → cpdocs-0.3.2}/examples/c-basic/include/ring/ring.h +0 -0
  86. {cpdocs-0.3.0 → cpdocs-0.3.2}/examples/c-basic/properdocs.yml +0 -0
  87. {cpdocs-0.3.0 → cpdocs-0.3.2}/examples/c-basic/readme.md +0 -0
  88. {cpdocs-0.3.0 → cpdocs-0.3.2}/examples/c-basic/src/ring.c +0 -0
  89. {cpdocs-0.3.0 → cpdocs-0.3.2}/examples/cpp-features/.gitignore +0 -0
  90. {cpdocs-0.3.0 → cpdocs-0.3.2}/examples/cpp-features/CMakeLists.txt +0 -0
  91. {cpdocs-0.3.0 → cpdocs-0.3.2}/examples/cpp-features/docs/api/index.md +0 -0
  92. {cpdocs-0.3.0 → cpdocs-0.3.2}/examples/cpp-features/docs/cpdocs-minibuild.yml +0 -0
  93. {cpdocs-0.3.0 → cpdocs-0.3.2}/examples/cpp-features/docs/cpdocs.yml +0 -0
  94. {cpdocs-0.3.0 → cpdocs-0.3.2}/examples/cpp-features/docs/web/api.md +0 -0
  95. {cpdocs-0.3.0 → cpdocs-0.3.2}/examples/cpp-features/docs/web/index.md +0 -0
  96. {cpdocs-0.3.0 → cpdocs-0.3.2}/examples/cpp-features/include/demo/solver.hpp +0 -0
  97. {cpdocs-0.3.0 → cpdocs-0.3.2}/examples/cpp-features/properdocs.yml +0 -0
  98. {cpdocs-0.3.0 → cpdocs-0.3.2}/examples/cpp-features/readme.md +0 -0
  99. {cpdocs-0.3.0 → cpdocs-0.3.2}/examples/cpp-features/tools/minibuild.py +0 -0
  100. {cpdocs-0.3.0 → cpdocs-0.3.2}/examples/cpp-templates/.gitignore +0 -0
  101. {cpdocs-0.3.0 → cpdocs-0.3.2}/examples/cpp-templates/CMakeLists.txt +0 -0
  102. {cpdocs-0.3.0 → cpdocs-0.3.2}/examples/cpp-templates/docs/api/index.md +0 -0
  103. {cpdocs-0.3.0 → cpdocs-0.3.2}/examples/cpp-templates/docs/cpdocs.yml +0 -0
  104. {cpdocs-0.3.0 → cpdocs-0.3.2}/examples/cpp-templates/docs/web/api.md +0 -0
  105. {cpdocs-0.3.0 → cpdocs-0.3.2}/examples/cpp-templates/docs/web/index.md +0 -0
  106. {cpdocs-0.3.0 → cpdocs-0.3.2}/examples/cpp-templates/include/meta/meta.hpp +0 -0
  107. {cpdocs-0.3.0 → cpdocs-0.3.2}/examples/cpp-templates/properdocs.yml +0 -0
  108. {cpdocs-0.3.0 → cpdocs-0.3.2}/examples/cpp-templates/readme.md +0 -0
  109. {cpdocs-0.3.0 → cpdocs-0.3.2}/examples/fortran-basic/CMakeLists.txt +0 -0
  110. {cpdocs-0.3.0 → cpdocs-0.3.2}/examples/fortran-basic/docs/cpdocs.yml +0 -0
  111. {cpdocs-0.3.0 → cpdocs-0.3.2}/examples/fortran-basic/docs/index.md +0 -0
  112. {cpdocs-0.3.0 → cpdocs-0.3.2}/examples/fortran-basic/lib/normalize.f90 +0 -0
  113. {cpdocs-0.3.0 → cpdocs-0.3.2}/examples/fortran-basic/mod/geometry.f90 +0 -0
  114. {cpdocs-0.3.0 → cpdocs-0.3.2}/examples/fortran-basic/readme.md +0 -0
  115. {cpdocs-0.3.0 → cpdocs-0.3.2}/examples/generated-cpp/.gitignore +0 -0
  116. {cpdocs-0.3.0 → cpdocs-0.3.2}/examples/generated-cpp/CMakeLists.txt +0 -0
  117. {cpdocs-0.3.0 → cpdocs-0.3.2}/examples/generated-cpp/docs/api/index.md +0 -0
  118. {cpdocs-0.3.0 → cpdocs-0.3.2}/examples/generated-cpp/docs/cpdocs.yml +0 -0
  119. {cpdocs-0.3.0 → cpdocs-0.3.2}/examples/generated-cpp/docs/web/api.md +0 -0
  120. {cpdocs-0.3.0 → cpdocs-0.3.2}/examples/generated-cpp/docs/web/index.md +0 -0
  121. {cpdocs-0.3.0 → cpdocs-0.3.2}/examples/generated-cpp/generated-template/generated.hpp +0 -0
  122. {cpdocs-0.3.0 → cpdocs-0.3.2}/examples/generated-cpp/include/acme/core.hpp +0 -0
  123. {cpdocs-0.3.0 → cpdocs-0.3.2}/examples/generated-cpp/properdocs.yml +0 -0
  124. {cpdocs-0.3.0 → cpdocs-0.3.2}/examples/generated-cpp/readme.md +0 -0
  125. {cpdocs-0.3.0 → cpdocs-0.3.2}/examples/generated-cpp/src/generated.cpp +0 -0
  126. {cpdocs-0.3.0 → cpdocs-0.3.2}/examples/mixed/.gitignore +0 -0
  127. {cpdocs-0.3.0 → cpdocs-0.3.2}/examples/mixed/CMakeLists.txt +0 -0
  128. {cpdocs-0.3.0 → cpdocs-0.3.2}/examples/mixed/docs/api/index.md +0 -0
  129. {cpdocs-0.3.0 → cpdocs-0.3.2}/examples/mixed/docs/cpdocs.yml +0 -0
  130. {cpdocs-0.3.0 → cpdocs-0.3.2}/examples/mixed/docs/web/api.md +0 -0
  131. {cpdocs-0.3.0 → cpdocs-0.3.2}/examples/mixed/docs/web/index.md +0 -0
  132. {cpdocs-0.3.0 → cpdocs-0.3.2}/examples/mixed/include/geom/vector.hpp +0 -0
  133. {cpdocs-0.3.0 → cpdocs-0.3.2}/examples/mixed/properdocs.yml +0 -0
  134. {cpdocs-0.3.0 → cpdocs-0.3.2}/examples/mixed/python/pygeom/__init__.py +0 -0
  135. {cpdocs-0.3.0 → cpdocs-0.3.2}/examples/mixed/readme.md +0 -0
  136. {cpdocs-0.3.0 → cpdocs-0.3.2}/examples/mixed/src/geom.cpp +0 -0
  137. {cpdocs-0.3.0 → cpdocs-0.3.2}/examples/python-basic/.gitignore +0 -0
  138. {cpdocs-0.3.0 → cpdocs-0.3.2}/examples/python-basic/docs/api/index.md +0 -0
  139. {cpdocs-0.3.0 → cpdocs-0.3.2}/examples/python-basic/docs/cpdocs.yml +0 -0
  140. {cpdocs-0.3.0 → cpdocs-0.3.2}/examples/python-basic/docs/web/api.md +0 -0
  141. {cpdocs-0.3.0 → cpdocs-0.3.2}/examples/python-basic/docs/web/index.md +0 -0
  142. {cpdocs-0.3.0 → cpdocs-0.3.2}/examples/python-basic/properdocs.yml +0 -0
  143. {cpdocs-0.3.0 → cpdocs-0.3.2}/examples/python-basic/pyproject.toml +0 -0
  144. {cpdocs-0.3.0 → cpdocs-0.3.2}/examples/python-basic/readme.md +0 -0
  145. {cpdocs-0.3.0 → cpdocs-0.3.2}/examples/python-basic/src/dice/__init__.py +0 -0
  146. {cpdocs-0.3.0 → cpdocs-0.3.2}/examples/python-basic/src/dice/_rng.py +0 -0
  147. {cpdocs-0.3.0 → cpdocs-0.3.2}/examples/python-basic/src/dice/core.py +0 -0
  148. {cpdocs-0.3.0 → cpdocs-0.3.2}/examples/rust-basic/Cargo.lock +0 -0
  149. {cpdocs-0.3.0 → cpdocs-0.3.2}/examples/rust-basic/Cargo.toml +0 -0
  150. {cpdocs-0.3.0 → cpdocs-0.3.2}/examples/rust-basic/docs/api.md +0 -0
  151. {cpdocs-0.3.0 → cpdocs-0.3.2}/examples/rust-basic/docs/cpdocs.yml +0 -0
  152. {cpdocs-0.3.0 → cpdocs-0.3.2}/examples/rust-basic/docs/index.md +0 -0
  153. {cpdocs-0.3.0 → cpdocs-0.3.2}/examples/rust-basic/properdocs.yml +0 -0
  154. {cpdocs-0.3.0 → cpdocs-0.3.2}/examples/rust-basic/readme.md +0 -0
  155. {cpdocs-0.3.0 → cpdocs-0.3.2}/examples/rust-basic/rust-toolchain.toml +0 -0
  156. {cpdocs-0.3.0 → cpdocs-0.3.2}/examples/rust-basic/src/geometry.rs +0 -0
  157. {cpdocs-0.3.0 → cpdocs-0.3.2}/examples/rust-basic/src/internal.rs +0 -0
  158. {cpdocs-0.3.0 → cpdocs-0.3.2}/examples/rust-basic/src/lib.rs +0 -0
  159. {cpdocs-0.3.0 → cpdocs-0.3.2}/properdocs.yml +0 -0
  160. {cpdocs-0.3.0 → cpdocs-0.3.2}/readme.md +0 -0
  161. {cpdocs-0.3.0 → cpdocs-0.3.2}/src/cpdocs/__main__.py +0 -0
  162. {cpdocs-0.3.0 → cpdocs-0.3.2}/src/cpdocs/config.py +0 -0
  163. /cpdocs-0.3.0/src/cpdocs/properdocs_core.py → /cpdocs-0.3.2/src/cpdocs/plugins/properdocs/core.py +0 -0
  164. {cpdocs-0.3.0 → cpdocs-0.3.2}/src/cpdocs/versioning.py +0 -0
  165. {cpdocs-0.3.0 → cpdocs-0.3.2}/test/test_config.py +0 -0
  166. {cpdocs-0.3.0 → cpdocs-0.3.2}/test/test_contract.py +0 -0
  167. {cpdocs-0.3.0 → cpdocs-0.3.2}/test/test_rust_example.py +0 -0
  168. {cpdocs-0.3.0 → cpdocs-0.3.2}/test/test_versioning.py +0 -0
  169. {cpdocs-0.3.0 → cpdocs-0.3.2}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: cpdocs
3
- Version: 0.3.0
3
+ Version: 0.3.2
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>
@@ -0,0 +1,12 @@
1
+ <!--
2
+ SPDX-FileCopyrightText: 2026 cpdocs developers
3
+ SPDX-License-Identifier: Apache-2.0
4
+ -->
5
+
6
+ # API reference
7
+
8
+ The API reference is generated from the public Fortran module for every documented feature set.
9
+
10
+ ## Versions
11
+
12
+ @apidocs-versions
@@ -20,3 +20,4 @@ theme:
20
20
 
21
21
  nav:
22
22
  - Home: index.md
23
+ - API reference: api.md
@@ -29,7 +29,7 @@ Source = "https://github.com/ambhora/cpdocs"
29
29
  cpdocs = "cpdocs.cli:main"
30
30
 
31
31
  [project.entry-points."properdocs.plugins"]
32
- cpdocs = "cpdocs.properdocs_plugin:CpdocsPlugin"
32
+ cpdocs = "cpdocs.plugins.properdocs.plugin:CpdocsPlugin"
33
33
 
34
34
  [dependency-groups]
35
35
  dev = ["pytest>=8", "ruff>=0.8", "properdocs>=1.6.7", "mkdocs-materialx"]
@@ -2,4 +2,4 @@
2
2
  # SPDX-License-Identifier: Apache-2.0
3
3
  """Language-neutral semantic API documentation generator."""
4
4
 
5
- __version__ = "0.3.0"
5
+ __version__ = "0.3.2"
@@ -15,35 +15,24 @@ import dataclasses
15
15
  import fnmatch
16
16
  import json
17
17
  import os
18
- import re
19
18
  import shutil
20
19
  import subprocess
21
20
  import sys
22
21
  import tempfile
23
22
  from pathlib import Path, PurePosixPath
24
23
 
25
- from .clang_backend import extract_clang_graph
26
- from .config import CpdocsConfig, load_config
27
- from .contract import (
28
- ContractError,
29
- FeatureSetDeclaration,
30
- FeatureSets,
31
- Manifest,
32
- UnitManifest,
33
- read_feature_sets,
34
- read_manifest,
35
- write_feature_set,
36
- )
37
- from .fortran_backend import extract_fortran_graph
38
- from .html_renderer import render_html_site
39
- from .model import ApiGraph, merge_graphs
40
- from .python_backend import build_python_graph
41
- from .rust_backend import build_rust_graph
24
+ from .config import CpdocsConfig
25
+ from .contract import ContractError, UnitManifest
26
+ from .languages import rendering_for
27
+ from .languages.cpp.extract import extract_clang_graph
28
+ from .languages.fortran.extract import extract_fortran_graph
29
+ from .languages.python.extract import build_python_graph
30
+ from .languages.rust.extract import build_rust_graph
31
+ from .model import ApiGraph, merge_graphs, relative_source_path
32
+ from .providers.contract import discover_feature_sets, resolve_manifest
33
+ from .rendering.html import render_html_site
42
34
  from .versioning import SelectedRef, current_branch, select_refs
43
35
 
44
- _PLACEHOLDER = re.compile(r"\{\{([a-z][a-z0-9-]*)\}\}")
45
- _CONTAINER_KINDS = {"namespace", "package", "module"}
46
-
47
36
 
48
37
  @dataclasses.dataclass(frozen=True)
49
38
  class Layout:
@@ -71,113 +60,6 @@ class Layout:
71
60
 
72
61
 
73
62
 
74
- # --------------------------------------------------------------------------------------------------
75
- # Talking to the build system
76
- # --------------------------------------------------------------------------------------------------
77
-
78
-
79
- def _expand(command: str, values: dict[str, str], *, allowed: set[str], name: str) -> str:
80
- placeholders = set(_PLACEHOLDER.findall(command))
81
- unknown = sorted(placeholders - allowed)
82
- if unknown:
83
- raise RuntimeError(
84
- f"cpdocs.yml: build.{name} uses unknown placeholders: {', '.join(unknown)}"
85
- )
86
- return _PLACEHOLDER.sub(lambda match: values[match.group(1)], command)
87
-
88
-
89
- def _run_command(
90
- command: str,
91
- *,
92
- source: Path,
93
- values: dict[str, str],
94
- allowed: set[str],
95
- name: str,
96
- log: Path,
97
- ) -> None:
98
- expanded = _expand(command, values, allowed=allowed, name=name)
99
- environment = dict(os.environ)
100
- # Commands invoked from a source checkout should be able to use cpdocs' own helper CLI just as
101
- # they can when cpdocs is installed. This is only an execution-environment convenience; the
102
- # build-system contract itself remains the JSON files and command placeholders.
103
- package_root = str(Path(__file__).resolve().parents[1])
104
- pythonpath = environment.get("PYTHONPATH")
105
- environment["PYTHONPATH"] = package_root + (os.pathsep + pythonpath if pythonpath else "")
106
- log.parent.mkdir(parents=True, exist_ok=True)
107
- with log.open("w", encoding="utf-8") as stream:
108
- stream.write("$ " + expanded + "\n")
109
- stream.flush()
110
- result = subprocess.run(
111
- expanded,
112
- cwd=source,
113
- shell=True,
114
- check=False,
115
- text=True,
116
- stdout=subprocess.PIPE,
117
- stderr=subprocess.STDOUT,
118
- env=environment,
119
- )
120
- stream.write(result.stdout)
121
- if result.returncode != 0:
122
- raise RuntimeError(
123
- f"build-system command failed (build.{name}):\n {expanded}\n\n"
124
- + result.stdout.rstrip()
125
- )
126
-
127
-
128
- def discover_feature_sets(command: str, *, source: Path, work: Path) -> FeatureSets:
129
- """Run ``build.feature-sets`` and read ``feature-sets.json``."""
130
-
131
- directory = work / "feature-sets"
132
- shutil.rmtree(directory, ignore_errors=True)
133
- tmpdir = directory / "tmp"
134
- tmpdir.mkdir(parents=True)
135
- output = directory / "feature-sets.json"
136
- _run_command(
137
- command,
138
- source=source.resolve(),
139
- values={
140
- "source": str(source.resolve()),
141
- "tmpdir": str(tmpdir.resolve()),
142
- "output-file": str(output.resolve()),
143
- "python": sys.executable,
144
- },
145
- allowed={"source", "tmpdir", "output-file", "python"},
146
- name="feature-sets",
147
- log=directory / "log.txt",
148
- )
149
- return read_feature_sets(output)
150
-
151
-
152
- def resolve_manifest(
153
- command: str, feature_set: FeatureSetDeclaration, *, source: Path, work: Path
154
- ) -> Manifest:
155
- """Run ``build.build`` once for one concrete feature set and read ``manifest.json``."""
156
-
157
- directory = work / "feature-sets" / feature_set.key
158
- shutil.rmtree(directory, ignore_errors=True)
159
- tmpdir = directory / "tmp"
160
- tmpdir.mkdir(parents=True)
161
- feature_set_file = directory / "feature-set.json"
162
- output = directory / "manifest.json"
163
- write_feature_set(feature_set_file, feature_set)
164
- _run_command(
165
- command,
166
- source=source.resolve(),
167
- values={
168
- "source": str(source.resolve()),
169
- "tmpdir": str(tmpdir.resolve()),
170
- "feature-set": str(feature_set_file.resolve()),
171
- "output-file": str(output.resolve()),
172
- "python": sys.executable,
173
- },
174
- allowed={"source", "tmpdir", "feature-set", "output-file", "python"},
175
- name="build",
176
- log=directory / "log.txt",
177
- )
178
- return read_manifest(output, source=source.resolve())
179
-
180
-
181
63
  # --------------------------------------------------------------------------------------------------
182
64
  # Extraction
183
65
  # --------------------------------------------------------------------------------------------------
@@ -199,8 +81,8 @@ def _browse_logical_path(
199
81
  try:
200
82
  return path.resolve().relative_to(project_root.resolve()).as_posix()
201
83
  except (OSError, ValueError):
202
- # Generated files live outside the source checkout. Give them a stable logical home rather
203
- # than leaking profile/build-directory paths into the published file hierarchy.
84
+ # Keep a stable internal key for files outside the checkout. This path is not presented to
85
+ # readers; rendering uses the semantic layout recorded in ``source_display_paths``.
204
86
  base = Path("_generated") / unit.id / category
205
87
  try:
206
88
  relative = (
@@ -211,11 +93,69 @@ def _browse_logical_path(
211
93
  return (base / relative).as_posix()
212
94
 
213
95
 
96
+ def _semantic_display_path(path: Path, *, root: Path, generated: bool) -> str:
97
+ """Return the compiler-facing layout path shown in the file browser.
98
+
99
+ Manifest roots are semantic roots. A file below ``.../src/cuda/include`` is therefore shown
100
+ below ``include`` and a file below ``.../src/cuda/lib`` below ``lib``. Build-generated roots
101
+ use the same flattened layout under one ``_generated`` prefix.
102
+ """
103
+
104
+ try:
105
+ if root.is_dir():
106
+ relative = path.resolve().relative_to(root.resolve())
107
+ root_name = root.name
108
+ else:
109
+ relative = Path(path.name)
110
+ root_name = root.parent.name
111
+ except (OSError, ValueError):
112
+ relative = Path(path.name)
113
+ root_name = root.name if root.name != path.name else root.parent.name
114
+
115
+ base = Path("_generated") if generated else Path()
116
+ if root_name:
117
+ base /= root_name
118
+ return (base / relative).as_posix()
119
+
120
+
121
+ def _api_display_paths(
122
+ unit: UnitManifest, graph: ApiGraph, *, project_root: Path
123
+ ) -> dict[str, str]:
124
+ """Map API source keys to their semantic include/module layout."""
125
+
126
+ values: dict[str, str] = {}
127
+ for api_root in unit.api:
128
+ root = api_root.path
129
+ if not root.is_dir():
130
+ continue
131
+ for path in sorted(root.rglob("*")):
132
+ if not path.is_file():
133
+ continue
134
+ try:
135
+ root_relative = path.resolve().relative_to(root.resolve()).as_posix()
136
+ except (OSError, ValueError):
137
+ root_relative = path.name
138
+ project_relative = relative_source_path(path, project_root)
139
+ logical = next(
140
+ (candidate for candidate in (root_relative, project_relative) if candidate in graph.sources),
141
+ None,
142
+ )
143
+ if logical is not None:
144
+ values.setdefault(
145
+ logical,
146
+ _semantic_display_path(
147
+ path, root=root, generated=unit.generated or api_root.generated
148
+ ),
149
+ )
150
+ return values
151
+
152
+
214
153
  def _collect_browse_files(
215
154
  unit: UnitManifest, *, project_root: Path
216
- ) -> tuple[dict[str, str], list[str]]:
155
+ ) -> tuple[dict[str, str], list[str], dict[str, str]]:
217
156
  files: dict[str, str] = {}
218
157
  generated: list[str] = []
158
+ display_paths: dict[str, str] = {}
219
159
  # Public headers are already present in ``graph.sources`` after API extraction. These roots
220
160
  # add implementation-only material without duplicating public headers in the file hierarchy.
221
161
  groups = (
@@ -242,9 +182,13 @@ def _collect_browse_files(
242
182
  path, project_root=project_root, unit=unit, category=category, root=root
243
183
  )
244
184
  files.setdefault(logical, content)
185
+ display_paths.setdefault(
186
+ logical,
187
+ _semantic_display_path(path, root=root, generated=unit.generated),
188
+ )
245
189
  if unit.generated and logical not in generated:
246
190
  generated.append(logical)
247
- return files, generated
191
+ return files, generated, display_paths
248
192
 
249
193
 
250
194
  def _generated_sources(unit: UnitManifest, graph: ApiGraph) -> list[str]:
@@ -325,6 +269,11 @@ def _merge_metadata(target: dict[str, object], source: dict[str, object]) -> Non
325
269
  existing = target.setdefault(key, [])
326
270
  assert isinstance(existing, list)
327
271
  existing.extend(item for item in value if item not in existing)
272
+ elif isinstance(value, dict):
273
+ existing = target.setdefault(key, {})
274
+ assert isinstance(existing, dict)
275
+ for item_key, item_value in value.items():
276
+ existing.setdefault(item_key, item_value)
328
277
  else:
329
278
  target.setdefault(key, value)
330
279
 
@@ -374,7 +323,8 @@ def _check_undocumented(graph: ApiGraph, level: str) -> None:
374
323
  missing = sorted(
375
324
  entity.qualified_name
376
325
  for entity in graph.entities.values()
377
- if entity.kind not in _CONTAINER_KINDS and not entity.documentation.strip()
326
+ if entity.kind not in rendering_for(entity.language).container_kinds
327
+ and not entity.documentation.strip()
378
328
  )
379
329
  if not missing:
380
330
  return
@@ -427,9 +377,18 @@ def build_graph(
427
377
  work=work / "feature-sets" / feature_set.key,
428
378
  config=config,
429
379
  )
430
- browse_files, generated_files = _collect_browse_files(unit, project_root=source)
380
+ display_paths = unit_graph.metadata.setdefault("source_display_paths", {})
381
+ assert isinstance(display_paths, dict)
382
+ for path, display in _api_display_paths(unit, unit_graph, project_root=source).items():
383
+ display_paths.setdefault(path, display)
384
+
385
+ browse_files, generated_files, browse_display_paths = _collect_browse_files(
386
+ unit, project_root=source
387
+ )
431
388
  for path, content in browse_files.items():
432
389
  unit_graph.files.setdefault(path, content)
390
+ for path, display in browse_display_paths.items():
391
+ display_paths.setdefault(path, display)
433
392
  if generated_files:
434
393
  existing = unit_graph.metadata.setdefault("generated_files", [])
435
394
  assert isinstance(existing, list)
@@ -688,7 +647,6 @@ __all__ = [
688
647
  "build_current",
689
648
  "build_graph",
690
649
  "build_versions",
691
- "cmake_module",
692
650
  "discover_feature_sets",
693
651
  "recorded_default_version",
694
652
  "resolve_manifest",
@@ -10,10 +10,10 @@ from pathlib import Path
10
10
  from . import __version__
11
11
  from .build import build_current, build_versions
12
12
  from .config import DEFAULT_CONFIG, load_config
13
- from .python_manifest import write_feature_sets as write_python_feature_sets
14
- from .python_manifest import write_manifest as write_python_manifest
15
- from .rust_manifest import write_feature_sets as write_rust_feature_sets
16
- from .rust_manifest import write_manifest as write_rust_manifest
13
+ from .providers.python import write_feature_sets as write_python_feature_sets
14
+ from .providers.python import write_manifest as write_python_manifest
15
+ from .providers.cargo import write_feature_sets as write_rust_feature_sets
16
+ from .providers.cargo import write_manifest as write_rust_manifest
17
17
 
18
18
 
19
19
  def _path(value: str) -> Path:
@@ -173,16 +173,16 @@ def read_manifest(path: Path, *, source: Path) -> Manifest:
173
173
  )
174
174
  )
175
175
 
176
- def paths(field: str) -> tuple[Path, ...]:
176
+ def paths(field: str, *, data: dict[str, Any], where: str) -> tuple[Path, ...]:
177
177
  return tuple(
178
178
  _path(item, source)
179
179
  for item in _string_tuple(data.get(field, []), f"{where}.{field}", path)
180
180
  )
181
181
 
182
- headers = paths("headers")
183
- sources = paths("sources")
184
- modules = paths("modules")
185
- executables = paths("executables")
182
+ headers = paths("headers", data=data, where=where)
183
+ sources = paths("sources", data=data, where=where)
184
+ modules = paths("modules", data=data, where=where)
185
+ executables = paths("executables", data=data, where=where)
186
186
  generated = bool(data.get("generated", False))
187
187
 
188
188
  parse: ParseOptions | None = None
@@ -0,0 +1,138 @@
1
+ # SPDX-FileCopyrightText: 2026 cpdocs developers
2
+ # SPDX-License-Identifier: Apache-2.0
3
+ """Programming-language adapters for extraction and presentation."""
4
+
5
+ from __future__ import annotations
6
+
7
+ from pathlib import Path
8
+
9
+ from .base import LanguageRendering
10
+ from .cpp.render import RENDERING as CPP
11
+ from .fortran.render import RENDERING as FORTRAN
12
+ from .python.render import RENDERING as PYTHON
13
+ from .rust.render import RENDERING as RUST
14
+
15
+ _RENDERINGS = {
16
+ "c": CPP,
17
+ "cpp": CPP,
18
+ "cuda": CPP,
19
+ "hip": CPP,
20
+ "fortran": FORTRAN,
21
+ "python": PYTHON,
22
+ "rust": RUST,
23
+ }
24
+
25
+ _LANGUAGE_LABELS = {
26
+ "c": "C",
27
+ "cpp": "C++",
28
+ "cuda": "CUDA",
29
+ "hip": "HIP",
30
+ "fortran": "Fortran",
31
+ "python": "Python",
32
+ "rust": "Rust",
33
+ }
34
+
35
+ _KIND_LABELS = {
36
+ "namespace": "namespace",
37
+ "module": "module",
38
+ "package": "package",
39
+ "class": "class",
40
+ "struct": "struct",
41
+ "derived_type": "derived type",
42
+ "union": "union",
43
+ "enum": "enum",
44
+ "trait": "trait",
45
+ "protocol": "protocol",
46
+ "type_alias": "type alias",
47
+ "concept": "concept",
48
+ "function": "function",
49
+ "subroutine": "subroutine",
50
+ "method": "method",
51
+ "constructor": "constructor",
52
+ "property": "property",
53
+ "attribute": "attribute",
54
+ "variable": "variable",
55
+ "constant": "constant",
56
+ "macro": "macro",
57
+ "deduction_guide": "deduction guide",
58
+ }
59
+
60
+ _KIND_MARKERS = {
61
+ "namespace": "N",
62
+ "module": "M",
63
+ "package": "P",
64
+ "class": "C",
65
+ "struct": "S",
66
+ "derived_type": "T",
67
+ "union": "U",
68
+ "enum": "E",
69
+ "trait": "T",
70
+ "protocol": "P",
71
+ "function": "F",
72
+ "subroutine": "F",
73
+ "method": "F",
74
+ "constructor": "F",
75
+ "type_alias": "T",
76
+ "variable": "V",
77
+ "attribute": "A",
78
+ "property": "P",
79
+ "constant": "C",
80
+ "macro": "D",
81
+ "concept": "K",
82
+ "deduction_guide": "G",
83
+ }
84
+
85
+
86
+ def rendering_for(language: str) -> LanguageRendering:
87
+ """Return rendering semantics for one programming language."""
88
+
89
+ try:
90
+ return _RENDERINGS[language]
91
+ except KeyError as error:
92
+ raise ValueError(f"unsupported API language: {language}") from error
93
+
94
+
95
+ def language_label(language: str) -> str:
96
+ return _LANGUAGE_LABELS.get(language, language)
97
+
98
+
99
+ def kind_label(kind: str, language: str | None = None) -> str:
100
+ if language in _RENDERINGS:
101
+ specific = _RENDERINGS[language].kind_labels.get(kind)
102
+ if specific is not None:
103
+ return specific
104
+ return _KIND_LABELS.get(kind, kind.replace("_", " "))
105
+
106
+
107
+ def kind_marker(kind: str, language: str | None = None) -> str:
108
+ if language in _RENDERINGS:
109
+ specific = _RENDERINGS[language].kind_markers.get(kind)
110
+ if specific is not None:
111
+ return specific
112
+ return _KIND_MARKERS.get(kind, "·")
113
+
114
+
115
+ def source_language(path: str) -> str:
116
+ suffix = Path(path).suffix.casefold()
117
+ if suffix in {".cu", ".cuh"}:
118
+ return "cuda"
119
+ if suffix == ".hip":
120
+ return "hip"
121
+ if suffix in {".c"}:
122
+ return "c"
123
+ if suffix in {".h", ".hh", ".hpp", ".hxx", ".cc", ".cpp", ".cxx"}:
124
+ return "cpp"
125
+ for language in ("fortran", "python", "rust"):
126
+ if suffix in _RENDERINGS[language].source_suffixes:
127
+ return language
128
+ return "text"
129
+
130
+
131
+ __all__ = [
132
+ "LanguageRendering",
133
+ "kind_label",
134
+ "kind_marker",
135
+ "language_label",
136
+ "rendering_for",
137
+ "source_language",
138
+ ]
@@ -0,0 +1,63 @@
1
+ # SPDX-FileCopyrightText: 2026 cpdocs developers
2
+ # SPDX-License-Identifier: Apache-2.0
3
+ """Language-specific presentation contract used by generic renderers."""
4
+
5
+ from __future__ import annotations
6
+
7
+ from collections.abc import Callable
8
+ from dataclasses import dataclass
9
+
10
+ from ..model import ApiEntity, ApiSignature
11
+
12
+
13
+ @dataclass(frozen=True)
14
+ class LanguageRendering:
15
+ """Rendering rules that belong to a programming language, not to HTML."""
16
+
17
+ separator: str
18
+ container_kinds: frozenset[str]
19
+ class_like_kinds: frozenset[str]
20
+ type_page_kinds: frozenset[str]
21
+ type_like_kinds: frozenset[str]
22
+ function_kinds: frozenset[str]
23
+ member_value_kinds: frozenset[str]
24
+ source_suffixes: frozenset[str]
25
+ kind_markers: dict[str, str]
26
+ kind_labels: dict[str, str]
27
+ signature_spelling: Callable[[ApiEntity, ApiSignature], str]
28
+ entity_declaration: Callable[[ApiEntity], str]
29
+ member_summary: Callable[[ApiEntity], str]
30
+ member_group: Callable[[ApiEntity], str | None]
31
+
32
+
33
+ def parameter_spelling(signature: ApiSignature) -> str:
34
+ """Render conventional typed parameters used by C-family, Fortran and Rust APIs."""
35
+
36
+ values: list[str] = []
37
+ for parameter in signature.parameters:
38
+ value = parameter.type or parameter.name
39
+ if parameter.name and parameter.type:
40
+ value += f" {parameter.name}"
41
+ if parameter.default is not None:
42
+ value += f" = {parameter.default}"
43
+ values.append(value)
44
+ return ", ".join(values)
45
+
46
+
47
+ def assigned_property(entity: ApiEntity) -> str:
48
+ """Return an ``= value`` property without its marker."""
49
+
50
+ return next((value[2:] for value in entity.properties if value.startswith("= ")), "")
51
+
52
+
53
+ def typed_property(entity: ApiEntity, *, ignored: frozenset[str] = frozenset()) -> str:
54
+ """Return the first property that represents a type rather than an assignment/flag."""
55
+
56
+ return next(
57
+ (
58
+ value
59
+ for value in entity.properties
60
+ if not value.startswith("=") and value not in ignored
61
+ ),
62
+ "",
63
+ )
@@ -0,0 +1,8 @@
1
+ # SPDX-FileCopyrightText: 2026 cpdocs developers
2
+ # SPDX-License-Identifier: Apache-2.0
3
+ """C-family extraction and rendering."""
4
+
5
+ from .extract import extract_clang_graph
6
+ from .render import RENDERING
7
+
8
+ __all__ = ["RENDERING", "extract_clang_graph"]