cpdocs 0.3.7__tar.gz → 0.3.9__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 (173) hide show
  1. cpdocs-0.3.9/.github/workflows/docs-tag-rebuild.yml +59 -0
  2. {cpdocs-0.3.7 → cpdocs-0.3.9}/PKG-INFO +52 -3
  3. {cpdocs-0.3.7 → cpdocs-0.3.9}/docs/cpdocs.yml +1 -1
  4. {cpdocs-0.3.7 → cpdocs-0.3.9}/pyproject.toml +1 -3
  5. cpdocs-0.3.9/readme.md +86 -0
  6. {cpdocs-0.3.7 → cpdocs-0.3.9}/src/cpdocs/__init__.py +1 -1
  7. {cpdocs-0.3.7 → cpdocs-0.3.9}/src/cpdocs/build.py +108 -61
  8. {cpdocs-0.3.7 → cpdocs-0.3.9}/src/cpdocs/config.py +24 -1
  9. {cpdocs-0.3.7 → cpdocs-0.3.9}/src/cpdocs/providers/contract.py +39 -5
  10. {cpdocs-0.3.7 → cpdocs-0.3.9}/src/cpdocs/rendering/html.py +37 -7
  11. cpdocs-0.3.9/test/test_language_configurations.py +128 -0
  12. cpdocs-0.3.9/test/test_tagged_docs_ci.py +76 -0
  13. cpdocs-0.3.9/uv.lock +835 -0
  14. cpdocs-0.3.7/readme.md +0 -36
  15. cpdocs-0.3.7/uv.lock +0 -776
  16. {cpdocs-0.3.7 → cpdocs-0.3.9}/.github/workflows/docs.yml +0 -0
  17. {cpdocs-0.3.7 → cpdocs-0.3.9}/.github/workflows/test.yml +0 -0
  18. {cpdocs-0.3.7 → cpdocs-0.3.9}/.gitignore +0 -0
  19. {cpdocs-0.3.7 → cpdocs-0.3.9}/.reuse/templates/bordered.jinja2 +0 -0
  20. {cpdocs-0.3.7 → cpdocs-0.3.9}/LICENSES/Apache-2.0.txt +0 -0
  21. {cpdocs-0.3.7 → cpdocs-0.3.9}/REUSE.toml +0 -0
  22. {cpdocs-0.3.7 → cpdocs-0.3.9}/docs/api/index.md +0 -0
  23. {cpdocs-0.3.7 → cpdocs-0.3.9}/docs/web/examples/index.md +0 -0
  24. {cpdocs-0.3.7 → cpdocs-0.3.9}/docs/web/explanation/architecture.md +0 -0
  25. {cpdocs-0.3.7 → cpdocs-0.3.9}/docs/web/explanation/feature-sets.md +0 -0
  26. {cpdocs-0.3.7 → cpdocs-0.3.9}/docs/web/how-to/ci.md +0 -0
  27. {cpdocs-0.3.7 → cpdocs-0.3.9}/docs/web/how-to/feature-sets.md +0 -0
  28. {cpdocs-0.3.7 → cpdocs-0.3.9}/docs/web/how-to/generated-headers.md +0 -0
  29. {cpdocs-0.3.7 → cpdocs-0.3.9}/docs/web/how-to/other-build-systems.md +0 -0
  30. {cpdocs-0.3.7 → cpdocs-0.3.9}/docs/web/how-to/properdocs.md +0 -0
  31. {cpdocs-0.3.7 → cpdocs-0.3.9}/docs/web/index.md +0 -0
  32. {cpdocs-0.3.7 → cpdocs-0.3.9}/docs/web/reference/api.md +0 -0
  33. {cpdocs-0.3.7 → cpdocs-0.3.9}/docs/web/reference/cli.md +0 -0
  34. {cpdocs-0.3.7 → cpdocs-0.3.9}/docs/web/reference/cmake.md +0 -0
  35. {cpdocs-0.3.7 → cpdocs-0.3.9}/docs/web/reference/comments.md +0 -0
  36. {cpdocs-0.3.7 → cpdocs-0.3.9}/docs/web/reference/configuration.md +0 -0
  37. {cpdocs-0.3.7 → cpdocs-0.3.9}/docs/web/reference/contract.md +0 -0
  38. {cpdocs-0.3.7 → cpdocs-0.3.9}/docs/web/reference/extraction.md +0 -0
  39. {cpdocs-0.3.7 → cpdocs-0.3.9}/docs/web/reference/links.md +0 -0
  40. {cpdocs-0.3.7 → cpdocs-0.3.9}/docs/web/tutorials/first-reference.md +0 -0
  41. {cpdocs-0.3.7 → cpdocs-0.3.9}/examples/basic-cpp/.gitignore +0 -0
  42. {cpdocs-0.3.7 → cpdocs-0.3.9}/examples/basic-cpp/CMakeLists.txt +0 -0
  43. {cpdocs-0.3.7 → cpdocs-0.3.9}/examples/basic-cpp/docs/api/index.md +0 -0
  44. {cpdocs-0.3.7 → cpdocs-0.3.9}/examples/basic-cpp/docs/cpdocs.yml +0 -0
  45. {cpdocs-0.3.7 → cpdocs-0.3.9}/examples/basic-cpp/docs/web/api.md +0 -0
  46. {cpdocs-0.3.7 → cpdocs-0.3.9}/examples/basic-cpp/docs/web/index.md +0 -0
  47. {cpdocs-0.3.7 → cpdocs-0.3.9}/examples/basic-cpp/include/acme/math.hpp +0 -0
  48. {cpdocs-0.3.7 → cpdocs-0.3.9}/examples/basic-cpp/properdocs.yml +0 -0
  49. {cpdocs-0.3.7 → cpdocs-0.3.9}/examples/basic-cpp/readme.md +0 -0
  50. {cpdocs-0.3.7 → cpdocs-0.3.9}/examples/basic-cpp/src/math.cpp +0 -0
  51. {cpdocs-0.3.7 → cpdocs-0.3.9}/examples/c-basic/.gitignore +0 -0
  52. {cpdocs-0.3.7 → cpdocs-0.3.9}/examples/c-basic/CMakeLists.txt +0 -0
  53. {cpdocs-0.3.7 → cpdocs-0.3.9}/examples/c-basic/docs/api/index.md +0 -0
  54. {cpdocs-0.3.7 → cpdocs-0.3.9}/examples/c-basic/docs/cpdocs.yml +0 -0
  55. {cpdocs-0.3.7 → cpdocs-0.3.9}/examples/c-basic/docs/web/api.md +0 -0
  56. {cpdocs-0.3.7 → cpdocs-0.3.9}/examples/c-basic/docs/web/index.md +0 -0
  57. {cpdocs-0.3.7 → cpdocs-0.3.9}/examples/c-basic/include/ring/ring.h +0 -0
  58. {cpdocs-0.3.7 → cpdocs-0.3.9}/examples/c-basic/properdocs.yml +0 -0
  59. {cpdocs-0.3.7 → cpdocs-0.3.9}/examples/c-basic/readme.md +0 -0
  60. {cpdocs-0.3.7 → cpdocs-0.3.9}/examples/c-basic/src/ring.c +0 -0
  61. {cpdocs-0.3.7 → cpdocs-0.3.9}/examples/cpp-features/.gitignore +0 -0
  62. {cpdocs-0.3.7 → cpdocs-0.3.9}/examples/cpp-features/CMakeLists.txt +0 -0
  63. {cpdocs-0.3.7 → cpdocs-0.3.9}/examples/cpp-features/docs/api/index.md +0 -0
  64. {cpdocs-0.3.7 → cpdocs-0.3.9}/examples/cpp-features/docs/cpdocs-minibuild.yml +0 -0
  65. {cpdocs-0.3.7 → cpdocs-0.3.9}/examples/cpp-features/docs/cpdocs.yml +0 -0
  66. {cpdocs-0.3.7 → cpdocs-0.3.9}/examples/cpp-features/docs/web/api.md +0 -0
  67. {cpdocs-0.3.7 → cpdocs-0.3.9}/examples/cpp-features/docs/web/index.md +0 -0
  68. {cpdocs-0.3.7 → cpdocs-0.3.9}/examples/cpp-features/include/demo/solver.hpp +0 -0
  69. {cpdocs-0.3.7 → cpdocs-0.3.9}/examples/cpp-features/properdocs.yml +0 -0
  70. {cpdocs-0.3.7 → cpdocs-0.3.9}/examples/cpp-features/readme.md +0 -0
  71. {cpdocs-0.3.7 → cpdocs-0.3.9}/examples/cpp-features/tools/minibuild.py +0 -0
  72. {cpdocs-0.3.7 → cpdocs-0.3.9}/examples/cpp-templates/.gitignore +0 -0
  73. {cpdocs-0.3.7 → cpdocs-0.3.9}/examples/cpp-templates/CMakeLists.txt +0 -0
  74. {cpdocs-0.3.7 → cpdocs-0.3.9}/examples/cpp-templates/docs/api/index.md +0 -0
  75. {cpdocs-0.3.7 → cpdocs-0.3.9}/examples/cpp-templates/docs/cpdocs.yml +0 -0
  76. {cpdocs-0.3.7 → cpdocs-0.3.9}/examples/cpp-templates/docs/web/api.md +0 -0
  77. {cpdocs-0.3.7 → cpdocs-0.3.9}/examples/cpp-templates/docs/web/index.md +0 -0
  78. {cpdocs-0.3.7 → cpdocs-0.3.9}/examples/cpp-templates/include/meta/meta.hpp +0 -0
  79. {cpdocs-0.3.7 → cpdocs-0.3.9}/examples/cpp-templates/properdocs.yml +0 -0
  80. {cpdocs-0.3.7 → cpdocs-0.3.9}/examples/cpp-templates/readme.md +0 -0
  81. {cpdocs-0.3.7 → cpdocs-0.3.9}/examples/fortran-basic/CMakeLists.txt +0 -0
  82. {cpdocs-0.3.7 → cpdocs-0.3.9}/examples/fortran-basic/docs/api.md +0 -0
  83. {cpdocs-0.3.7 → cpdocs-0.3.9}/examples/fortran-basic/docs/cpdocs.yml +0 -0
  84. {cpdocs-0.3.7 → cpdocs-0.3.9}/examples/fortran-basic/docs/index.md +0 -0
  85. {cpdocs-0.3.7 → cpdocs-0.3.9}/examples/fortran-basic/lib/normalize.f90 +0 -0
  86. {cpdocs-0.3.7 → cpdocs-0.3.9}/examples/fortran-basic/mod/geometry.f90 +0 -0
  87. {cpdocs-0.3.7 → cpdocs-0.3.9}/examples/fortran-basic/properdocs.yml +0 -0
  88. {cpdocs-0.3.7 → cpdocs-0.3.9}/examples/fortran-basic/readme.md +0 -0
  89. {cpdocs-0.3.7 → cpdocs-0.3.9}/examples/generated-cpp/.gitignore +0 -0
  90. {cpdocs-0.3.7 → cpdocs-0.3.9}/examples/generated-cpp/CMakeLists.txt +0 -0
  91. {cpdocs-0.3.7 → cpdocs-0.3.9}/examples/generated-cpp/docs/api/index.md +0 -0
  92. {cpdocs-0.3.7 → cpdocs-0.3.9}/examples/generated-cpp/docs/cpdocs.yml +0 -0
  93. {cpdocs-0.3.7 → cpdocs-0.3.9}/examples/generated-cpp/docs/web/api.md +0 -0
  94. {cpdocs-0.3.7 → cpdocs-0.3.9}/examples/generated-cpp/docs/web/index.md +0 -0
  95. {cpdocs-0.3.7 → cpdocs-0.3.9}/examples/generated-cpp/generated-template/generated.hpp +0 -0
  96. {cpdocs-0.3.7 → cpdocs-0.3.9}/examples/generated-cpp/include/acme/core.hpp +0 -0
  97. {cpdocs-0.3.7 → cpdocs-0.3.9}/examples/generated-cpp/properdocs.yml +0 -0
  98. {cpdocs-0.3.7 → cpdocs-0.3.9}/examples/generated-cpp/readme.md +0 -0
  99. {cpdocs-0.3.7 → cpdocs-0.3.9}/examples/generated-cpp/src/generated.cpp +0 -0
  100. {cpdocs-0.3.7 → cpdocs-0.3.9}/examples/mixed/.gitignore +0 -0
  101. {cpdocs-0.3.7 → cpdocs-0.3.9}/examples/mixed/CMakeLists.txt +0 -0
  102. {cpdocs-0.3.7 → cpdocs-0.3.9}/examples/mixed/docs/api/index.md +0 -0
  103. {cpdocs-0.3.7 → cpdocs-0.3.9}/examples/mixed/docs/cpdocs.yml +0 -0
  104. {cpdocs-0.3.7 → cpdocs-0.3.9}/examples/mixed/docs/web/api.md +0 -0
  105. {cpdocs-0.3.7 → cpdocs-0.3.9}/examples/mixed/docs/web/index.md +0 -0
  106. {cpdocs-0.3.7 → cpdocs-0.3.9}/examples/mixed/include/geom/vector.hpp +0 -0
  107. {cpdocs-0.3.7 → cpdocs-0.3.9}/examples/mixed/properdocs.yml +0 -0
  108. {cpdocs-0.3.7 → cpdocs-0.3.9}/examples/mixed/python/pygeom/__init__.py +0 -0
  109. {cpdocs-0.3.7 → cpdocs-0.3.9}/examples/mixed/readme.md +0 -0
  110. {cpdocs-0.3.7 → cpdocs-0.3.9}/examples/mixed/src/geom.cpp +0 -0
  111. {cpdocs-0.3.7 → cpdocs-0.3.9}/examples/python-basic/.gitignore +0 -0
  112. {cpdocs-0.3.7 → cpdocs-0.3.9}/examples/python-basic/docs/api/index.md +0 -0
  113. {cpdocs-0.3.7 → cpdocs-0.3.9}/examples/python-basic/docs/cpdocs.yml +0 -0
  114. {cpdocs-0.3.7 → cpdocs-0.3.9}/examples/python-basic/docs/web/api.md +0 -0
  115. {cpdocs-0.3.7 → cpdocs-0.3.9}/examples/python-basic/docs/web/index.md +0 -0
  116. {cpdocs-0.3.7 → cpdocs-0.3.9}/examples/python-basic/properdocs.yml +0 -0
  117. {cpdocs-0.3.7 → cpdocs-0.3.9}/examples/python-basic/pyproject.toml +0 -0
  118. {cpdocs-0.3.7 → cpdocs-0.3.9}/examples/python-basic/readme.md +0 -0
  119. {cpdocs-0.3.7 → cpdocs-0.3.9}/examples/python-basic/src/dice/__init__.py +0 -0
  120. {cpdocs-0.3.7 → cpdocs-0.3.9}/examples/python-basic/src/dice/_rng.py +0 -0
  121. {cpdocs-0.3.7 → cpdocs-0.3.9}/examples/python-basic/src/dice/core.py +0 -0
  122. {cpdocs-0.3.7 → cpdocs-0.3.9}/examples/rust-basic/Cargo.lock +0 -0
  123. {cpdocs-0.3.7 → cpdocs-0.3.9}/examples/rust-basic/Cargo.toml +0 -0
  124. {cpdocs-0.3.7 → cpdocs-0.3.9}/examples/rust-basic/docs/api.md +0 -0
  125. {cpdocs-0.3.7 → cpdocs-0.3.9}/examples/rust-basic/docs/cpdocs.yml +0 -0
  126. {cpdocs-0.3.7 → cpdocs-0.3.9}/examples/rust-basic/docs/index.md +0 -0
  127. {cpdocs-0.3.7 → cpdocs-0.3.9}/examples/rust-basic/properdocs.yml +0 -0
  128. {cpdocs-0.3.7 → cpdocs-0.3.9}/examples/rust-basic/readme.md +0 -0
  129. {cpdocs-0.3.7 → cpdocs-0.3.9}/examples/rust-basic/rust-toolchain.toml +0 -0
  130. {cpdocs-0.3.7 → cpdocs-0.3.9}/examples/rust-basic/src/geometry.rs +0 -0
  131. {cpdocs-0.3.7 → cpdocs-0.3.9}/examples/rust-basic/src/internal.rs +0 -0
  132. {cpdocs-0.3.7 → cpdocs-0.3.9}/examples/rust-basic/src/lib.rs +0 -0
  133. {cpdocs-0.3.7 → cpdocs-0.3.9}/properdocs.yml +0 -0
  134. {cpdocs-0.3.7 → cpdocs-0.3.9}/src/cpdocs/__main__.py +0 -0
  135. {cpdocs-0.3.7 → cpdocs-0.3.9}/src/cpdocs/cli.py +0 -0
  136. {cpdocs-0.3.7 → cpdocs-0.3.9}/src/cpdocs/contract.py +0 -0
  137. {cpdocs-0.3.7 → cpdocs-0.3.9}/src/cpdocs/languages/__init__.py +0 -0
  138. {cpdocs-0.3.7 → cpdocs-0.3.9}/src/cpdocs/languages/base.py +0 -0
  139. {cpdocs-0.3.7 → cpdocs-0.3.9}/src/cpdocs/languages/cpp/__init__.py +0 -0
  140. {cpdocs-0.3.7 → cpdocs-0.3.9}/src/cpdocs/languages/cpp/extract.py +0 -0
  141. {cpdocs-0.3.7 → cpdocs-0.3.9}/src/cpdocs/languages/cpp/render.py +0 -0
  142. {cpdocs-0.3.7 → cpdocs-0.3.9}/src/cpdocs/languages/fortran/__init__.py +0 -0
  143. {cpdocs-0.3.7 → cpdocs-0.3.9}/src/cpdocs/languages/fortran/extract.py +0 -0
  144. {cpdocs-0.3.7 → cpdocs-0.3.9}/src/cpdocs/languages/fortran/render.py +0 -0
  145. {cpdocs-0.3.7 → cpdocs-0.3.9}/src/cpdocs/languages/python/__init__.py +0 -0
  146. {cpdocs-0.3.7 → cpdocs-0.3.9}/src/cpdocs/languages/python/extract.py +0 -0
  147. {cpdocs-0.3.7 → cpdocs-0.3.9}/src/cpdocs/languages/python/render.py +0 -0
  148. {cpdocs-0.3.7 → cpdocs-0.3.9}/src/cpdocs/languages/rust/__init__.py +0 -0
  149. {cpdocs-0.3.7 → cpdocs-0.3.9}/src/cpdocs/languages/rust/extract.py +0 -0
  150. {cpdocs-0.3.7 → cpdocs-0.3.9}/src/cpdocs/languages/rust/render.py +0 -0
  151. {cpdocs-0.3.7 → cpdocs-0.3.9}/src/cpdocs/model.py +0 -0
  152. {cpdocs-0.3.7 → cpdocs-0.3.9}/src/cpdocs/plugins/__init__.py +0 -0
  153. {cpdocs-0.3.7 → cpdocs-0.3.9}/src/cpdocs/plugins/properdocs/__init__.py +0 -0
  154. {cpdocs-0.3.7 → cpdocs-0.3.9}/src/cpdocs/plugins/properdocs/core.py +0 -0
  155. {cpdocs-0.3.7 → cpdocs-0.3.9}/src/cpdocs/plugins/properdocs/plugin.py +0 -0
  156. {cpdocs-0.3.7 → cpdocs-0.3.9}/src/cpdocs/providers/__init__.py +0 -0
  157. {cpdocs-0.3.7 → cpdocs-0.3.9}/src/cpdocs/providers/cargo.py +0 -0
  158. {cpdocs-0.3.7 → cpdocs-0.3.9}/src/cpdocs/providers/cmake.py +0 -0
  159. {cpdocs-0.3.7 → cpdocs-0.3.9}/src/cpdocs/providers/python.py +0 -0
  160. {cpdocs-0.3.7 → cpdocs-0.3.9}/src/cpdocs/rendering/__init__.py +0 -0
  161. {cpdocs-0.3.7 → cpdocs-0.3.9}/src/cpdocs/rendering/common.py +0 -0
  162. {cpdocs-0.3.7 → cpdocs-0.3.9}/src/cpdocs/versioning.py +0 -0
  163. {cpdocs-0.3.7 → cpdocs-0.3.9}/test/conftest.py +0 -0
  164. {cpdocs-0.3.7 → cpdocs-0.3.9}/test/test_api_reference.py +0 -0
  165. {cpdocs-0.3.7 → cpdocs-0.3.9}/test/test_build.py +0 -0
  166. {cpdocs-0.3.7 → cpdocs-0.3.9}/test/test_config.py +0 -0
  167. {cpdocs-0.3.7 → cpdocs-0.3.9}/test/test_contract.py +0 -0
  168. {cpdocs-0.3.7 → cpdocs-0.3.9}/test/test_examples.py +0 -0
  169. {cpdocs-0.3.7 → cpdocs-0.3.9}/test/test_feature_set_signatures.py +0 -0
  170. {cpdocs-0.3.7 → cpdocs-0.3.9}/test/test_properdocs_core.py +0 -0
  171. {cpdocs-0.3.7 → cpdocs-0.3.9}/test/test_rust_example.py +0 -0
  172. {cpdocs-0.3.7 → cpdocs-0.3.9}/test/test_rust_provider.py +0 -0
  173. {cpdocs-0.3.7 → cpdocs-0.3.9}/test/test_versioning.py +0 -0
@@ -0,0 +1,59 @@
1
+ # --------------------------------------------------------------------------------------------------
2
+ # SPDX-License-Identifier: Apache-2.0
3
+ # SPDX-FileCopyrightText: 2026 cpdocs developers
4
+ # --------------------------------------------------------------------------------------------------
5
+ name: historical tag documentation rebuild
6
+
7
+ # GitHub loads push workflows from the pushed tag. Older tags carry the original
8
+ # documentation workflow, which attempts to run the old cpdocs implementation
9
+ # against the current branch. This workflow lives on the default branch and is
10
+ # triggered when an old documentation run completes, even if that run failed.
11
+ on:
12
+ workflow_run:
13
+ workflows: [documentation]
14
+ types: [completed]
15
+
16
+ permissions:
17
+ contents: read
18
+
19
+ jobs:
20
+ rebuild:
21
+ if: github.event.workflow_run.event == 'push'
22
+ runs-on: ubuntu-latest
23
+ permissions:
24
+ contents: read
25
+ actions: write
26
+ steps:
27
+ - uses: actions/checkout@v4
28
+ with:
29
+ ref: ${{ github.event.repository.default_branch }}
30
+ fetch-depth: 0
31
+
32
+ - name: Detect legacy tag workflow
33
+ id: legacy
34
+ env:
35
+ HEAD_SHA: ${{ github.event.workflow_run.head_sha }}
36
+ run: |
37
+ # A branch push (or a PR) must not trigger another docs build.
38
+ if ! git tag --points-at "$HEAD_SHA" | grep -q .; then
39
+ exit 0
40
+ fi
41
+
42
+ # Current tagged workflows already dispatch a default-branch build.
43
+ # Avoid dispatching a duplicate for those tags.
44
+ if git show "$HEAD_SHA:.github/workflows/docs.yml" 2>/dev/null \
45
+ | grep -q 'rebuild-for-tag:'; then
46
+ exit 0
47
+ fi
48
+
49
+ echo 'rebuild=true' >> "$GITHUB_OUTPUT"
50
+
51
+ - name: Build tagged API from the current documentation tool
52
+ if: steps.legacy.outputs.rebuild == 'true'
53
+ env:
54
+ GH_TOKEN: ${{ github.token }}
55
+ DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
56
+ run: |
57
+ gh workflow run docs.yml \
58
+ --repo "$GITHUB_REPOSITORY" \
59
+ --ref "$DEFAULT_BRANCH"
@@ -1,16 +1,15 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: cpdocs
3
- Version: 0.3.7
3
+ Version: 0.3.9
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>
7
7
  License-Expression: Apache-2.0
8
8
  Requires-Python: >=3.11
9
+ Requires-Dist: keyso
9
10
  Requires-Dist: markdown-it-py>=3
10
11
  Requires-Dist: pygments>=2.17
11
12
  Requires-Dist: pyyaml>=6
12
- Provides-Extra: properdocs
13
- Requires-Dist: properdocs>=1.6.7; extra == 'properdocs'
14
13
  Description-Content-Type: text/markdown
15
14
 
16
15
  <!--
@@ -35,6 +34,56 @@ cpdocs versions # document the configured versions
35
34
  properdocs build # project website with the versioned API mounted
36
35
  ```
37
36
 
37
+ ## Language configurations
38
+
39
+ The optional `languages` field in `docs/cpdocs.yml` is a [Keyso configuration
40
+ space](https://ambhora.github.io/keyso/language/syntax/). cpdocs expands it using pykeyso,
41
+ then runs `build.feature-sets` once per concrete configuration and `build.build` once per
42
+ feature set discovered for that configuration. Both command templates receive dotted placeholders
43
+ from the resulting nested dictionaries, such as `{{cxx.standard}}`, `{{c.standard}}`,
44
+ `{{fortran.standard}}`, `{{python.version}}`, or `{{rust.edition}}`. The build system decides
45
+ how to apply each value; cpdocs does not translate them into compiler flags.
46
+
47
+ ```yaml
48
+ languages:
49
+ _product:
50
+ - cxx:
51
+ _nested:
52
+ standard: [17, 20, 23]
53
+ - python:
54
+ _nested:
55
+ version: ["3.11", "3.12"]
56
+
57
+ build:
58
+ feature-sets: >-
59
+ cmake -S "{{source}}" -B "{{tmpdir}}"
60
+ -DCMAKE_CXX_STANDARD="{{cxx.standard}}"
61
+ -DPROJECT_FEATURE_SETS_OUTPUT="{{output-file}}"
62
+ build: >-
63
+ cmake -S "{{source}}" -B "{{tmpdir}}"
64
+ -DCMAKE_CXX_STANDARD="{{cxx.standard}}"
65
+ -DPROJECT_FEATURE_SET="{{feature-set}}"
66
+ -DPROJECT_CPDOCS_OUTPUT="{{output-file}}"
67
+ && cmake --build "{{tmpdir}}" --target project.cpdocs
68
+ ```
69
+
70
+ Use `_set` for explicitly paired combinations rather than the `_product` Cartesian product:
71
+
72
+ ```yaml
73
+ languages:
74
+ _set:
75
+ - cxx: {standard: 17}
76
+ python: {version: "3.11"}
77
+ - cxx: {standard: 23}
78
+ python: {version: "3.12"}
79
+ ```
80
+
81
+ Each concrete configuration has its own work directory. The merged API records both the
82
+ language configuration and feature set in symbol availability, including overload signatures.
83
+ If `languages` is omitted, cpdocs performs the original single build configuration, and the
84
+ existing `{{source}}`, `{{tmpdir}}`, `{{output-file}}`, `{{feature-set}}`, and `{{python}}`
85
+ placeholders continue to work unchanged.
86
+
38
87
  The configuration lives in `docs/cpdocs.yml`, the API introduction in `docs/api/index.md` and the
39
88
  ProperDocs pages in `docs/web`. The documentation starts at `docs/web/index.md`; the `examples/`
40
89
  directory holds standalone projects that the test suite builds.
@@ -13,7 +13,7 @@ build:
13
13
  --source "{{source}}" --feature-set "{{feature-set}}" --output "{{output-file}}"
14
14
  --package cpdocs --package-source src/cpdocs
15
15
 
16
- versions: [main]
16
+ versions: [main, 0.3.7, 0.0.1]
17
17
 
18
18
  site:
19
19
  copyright: Copyright © cpdocs developers · Apache-2.0
@@ -18,11 +18,9 @@ dependencies = [
18
18
  "markdown-it-py>=3",
19
19
  "Pygments>=2.17",
20
20
  "PyYAML>=6",
21
+ "keyso"
21
22
  ]
22
23
 
23
- [project.optional-dependencies]
24
- properdocs = ["properdocs>=1.6.7"]
25
-
26
24
  [project.urls]
27
25
  Source = "https://github.com/ambhora/cpdocs"
28
26
 
cpdocs-0.3.9/readme.md ADDED
@@ -0,0 +1,86 @@
1
+ <!--
2
+ SPDX-FileCopyrightText: 2026 cpdocs developers
3
+ SPDX-License-Identifier: Apache-2.0
4
+ -->
5
+ # cpdocs
6
+
7
+ cpdocs generates semantic, versioned API documentation for C, C++, CUDA, HIP, Fortran, Python and Rust projects.
8
+
9
+ It asks the project's build system, through a small JSON contract, for the concrete feature sets to
10
+ document. It builds each feature set independently, consumes the resulting manifest, extracts the API
11
+ with Clang for C-family languages, source modules for Fortran, `ast` for Python or rustdoc JSON for
12
+ Rust, and merges the results. Implementation files from the manifest are kept in the file hierarchy
13
+ without becoming API entities.
14
+ Feature-set availability and API language are tracked independently. The versions
15
+ to publish are an explicit ordered list in `cpdocs.yml`.
16
+
17
+ ```console
18
+ cpdocs build # document the working tree into build/cpdocs/current
19
+ cpdocs versions # document the configured versions
20
+ properdocs build # project website with the versioned API mounted
21
+ ```
22
+
23
+ ## Language configurations
24
+
25
+ The optional `languages` field in `docs/cpdocs.yml` is a [Keyso configuration
26
+ space](https://ambhora.github.io/keyso/language/syntax/). cpdocs expands it using pykeyso,
27
+ then runs `build.feature-sets` once per concrete configuration and `build.build` once per
28
+ feature set discovered for that configuration. Both command templates receive dotted placeholders
29
+ from the resulting nested dictionaries, such as `{{cxx.standard}}`, `{{c.standard}}`,
30
+ `{{fortran.standard}}`, `{{python.version}}`, or `{{rust.edition}}`. The build system decides
31
+ how to apply each value; cpdocs does not translate them into compiler flags.
32
+
33
+ ```yaml
34
+ languages:
35
+ _product:
36
+ - cxx:
37
+ _nested:
38
+ standard: [17, 20, 23]
39
+ - python:
40
+ _nested:
41
+ version: ["3.11", "3.12"]
42
+
43
+ build:
44
+ feature-sets: >-
45
+ cmake -S "{{source}}" -B "{{tmpdir}}"
46
+ -DCMAKE_CXX_STANDARD="{{cxx.standard}}"
47
+ -DPROJECT_FEATURE_SETS_OUTPUT="{{output-file}}"
48
+ build: >-
49
+ cmake -S "{{source}}" -B "{{tmpdir}}"
50
+ -DCMAKE_CXX_STANDARD="{{cxx.standard}}"
51
+ -DPROJECT_FEATURE_SET="{{feature-set}}"
52
+ -DPROJECT_CPDOCS_OUTPUT="{{output-file}}"
53
+ && cmake --build "{{tmpdir}}" --target project.cpdocs
54
+ ```
55
+
56
+ Use `_set` for explicitly paired combinations rather than the `_product` Cartesian product:
57
+
58
+ ```yaml
59
+ languages:
60
+ _set:
61
+ - cxx: {standard: 17}
62
+ python: {version: "3.11"}
63
+ - cxx: {standard: 23}
64
+ python: {version: "3.12"}
65
+ ```
66
+
67
+ Each concrete configuration has its own work directory. The merged API records both the
68
+ language configuration and feature set in symbol availability, including overload signatures.
69
+ If `languages` is omitted, cpdocs performs the original single build configuration, and the
70
+ existing `{{source}}`, `{{tmpdir}}`, `{{output-file}}`, `{{feature-set}}`, and `{{python}}`
71
+ placeholders continue to work unchanged.
72
+
73
+ The configuration lives in `docs/cpdocs.yml`, the API introduction in `docs/api/index.md` and the
74
+ ProperDocs pages in `docs/web`. The documentation starts at `docs/web/index.md`; the `examples/`
75
+ directory holds standalone projects that the test suite builds.
76
+
77
+ ## Development
78
+
79
+ ```console
80
+ uv sync
81
+ uv run pytest
82
+ uv run ruff check && uv run ruff format --check
83
+ ```
84
+
85
+ The C and C++ tests need `clang++`, `cmake` and `ninja`. Building the Rust example/site also needs
86
+ the nightly toolchain pinned by `examples/rust-basic/rust-toolchain.toml`.
@@ -4,4 +4,4 @@
4
4
  # --------------------------------------------------------------------------------------------------
5
5
  """Language-neutral semantic API documentation generator."""
6
6
 
7
- __version__ = "0.3.7"
7
+ __version__ = "0.3.9"
@@ -31,7 +31,11 @@ from .languages.fortran.extract import extract_fortran_graph
31
31
  from .languages.python.extract import build_python_graph
32
32
  from .languages.rust.extract import build_rust_graph
33
33
  from .model import ApiGraph, merge_graphs, relative_source_path
34
- from .providers.contract import discover_feature_sets, resolve_manifest
34
+ from .providers.contract import (
35
+ discover_feature_sets,
36
+ language_template_values,
37
+ resolve_manifest,
38
+ )
35
39
  from .rendering.html import render_html_site
36
40
  from .versioning import SelectedRef, current_branch, select_refs
37
41
 
@@ -348,72 +352,114 @@ def build_graph(
348
352
  work: Path,
349
353
  version: str,
350
354
  ) -> ApiGraph:
351
- """Discover every feature set, build each one independently, and merge the API graphs."""
352
-
353
- feature_sets = discover_feature_sets(config.feature_sets, source=source, work=work)
355
+ """Build every allowed language configuration and its discovered feature sets."""
354
356
 
355
357
  per_feature_set: list[tuple[str, ApiGraph]] = []
356
358
  metadata: dict[str, object] = {}
357
359
  languages: set[str] = set()
358
360
  units: dict[tuple[str, str], None] = {}
359
361
  configuration_options: dict[str, list[dict[str, object]]] = {}
360
-
361
- for feature_set in feature_sets.feature_sets:
362
- manifest = resolve_manifest(config.build, feature_set, source=source, work=work)
363
- if manifest.options:
364
- configuration_options[feature_set.key] = [dict(option) for option in manifest.options]
365
- languages.update(unit.language for unit in manifest.units if unit.language is not None)
366
- for unit in manifest.units:
367
- units[(unit.id, unit.language)] = None
368
- feature_set_language_values = sorted(
369
- {unit.language for unit in manifest.units if unit.language is not None}
362
+ all_feature_selections: list[dict[str, object]] = []
363
+ project: str | None = None
364
+ project_version = ""
365
+ generator = ""
366
+ multiple_languages = len(config.language_configurations) > 1
367
+
368
+ for index, configuration in enumerate(config.language_configurations, 1):
369
+ template_values = language_template_values(configuration)
370
+ # Preserve the existing work directory layout for projects with a single configuration.
371
+ language_work = (
372
+ work / "language-configurations" / f"language-{index:03d}"
373
+ if multiple_languages
374
+ else work
370
375
  )
371
- if len(feature_set_language_values) == 1:
372
- feature_set_language = feature_set_language_values[0]
373
- elif feature_set_language_values:
374
- feature_set_language = "mixed"
375
- else:
376
- feature_set_language = "none"
377
- unit_graphs = []
378
- for unit in manifest.units:
379
- unit_graph = extract_unit(
380
- unit,
381
- project=feature_sets.project,
382
- version=version,
383
- project_root=source,
384
- feature_set=feature_set.key,
385
- work=work / "feature-sets" / feature_set.key,
386
- config=config,
376
+ feature_sets = discover_feature_sets(
377
+ config.feature_sets,
378
+ source=source,
379
+ work=language_work,
380
+ template_values=template_values,
381
+ )
382
+ if project is None:
383
+ project = feature_sets.project
384
+ project_version = feature_sets.version
385
+ generator = feature_sets.generator
386
+ elif project != feature_sets.project:
387
+ raise RuntimeError(
388
+ f"build.feature-sets returned inconsistent projects: "
389
+ f"{project!r} and {feature_sets.project!r}"
387
390
  )
388
- display_paths = unit_graph.metadata.setdefault("source_display_paths", {})
389
- assert isinstance(display_paths, dict)
390
- for path, display in _api_display_paths(unit, unit_graph, project_root=source).items():
391
- display_paths.setdefault(path, display)
392
391
 
393
- browse_files, generated_files, browse_display_paths = _collect_browse_files(
394
- unit, project_root=source
392
+ for feature_set in feature_sets.feature_sets:
393
+ key = (
394
+ f"language-{index:03d}--{feature_set.key}"
395
+ if multiple_languages
396
+ else feature_set.key
395
397
  )
396
- for path, content in browse_files.items():
397
- unit_graph.files.setdefault(path, content)
398
- for path, display in browse_display_paths.items():
399
- display_paths.setdefault(path, display)
400
- if generated_files:
401
- existing = unit_graph.metadata.setdefault("generated_files", [])
402
- assert isinstance(existing, list)
403
- existing.extend(path for path in generated_files if path not in existing)
404
- unit_graphs.append(unit_graph)
405
- graph = _combine(
406
- unit_graphs,
407
- project=feature_sets.project,
408
- version=version,
409
- language=feature_set_language,
410
- )
411
- graph.feature_sets[feature_set.key] = {
412
- "features": list(feature_set.features),
413
- "selection": feature_set.selection,
414
- }
415
- _merge_metadata(metadata, graph.metadata)
416
- per_feature_set.append((feature_set.key, graph))
398
+ manifest = resolve_manifest(
399
+ config.build,
400
+ feature_set,
401
+ source=source,
402
+ work=language_work,
403
+ template_values=template_values,
404
+ )
405
+ if manifest.options:
406
+ configuration_options[key] = [dict(option) for option in manifest.options]
407
+ languages.update(unit.language for unit in manifest.units if unit.language is not None)
408
+ for unit in manifest.units:
409
+ units[(unit.id, unit.language)] = None
410
+ feature_set_language_values = sorted(
411
+ {unit.language for unit in manifest.units if unit.language is not None}
412
+ )
413
+ if len(feature_set_language_values) == 1:
414
+ feature_set_language = feature_set_language_values[0]
415
+ elif feature_set_language_values:
416
+ feature_set_language = "mixed"
417
+ else:
418
+ feature_set_language = "none"
419
+ unit_graphs = []
420
+ for unit in manifest.units:
421
+ unit_graph = extract_unit(
422
+ unit,
423
+ project=feature_sets.project,
424
+ version=version,
425
+ project_root=source,
426
+ feature_set=key,
427
+ work=language_work / "feature-sets" / feature_set.key,
428
+ config=config,
429
+ )
430
+ display_paths = unit_graph.metadata.setdefault("source_display_paths", {})
431
+ assert isinstance(display_paths, dict)
432
+ for path, display in _api_display_paths(
433
+ unit, unit_graph, project_root=source
434
+ ).items():
435
+ display_paths.setdefault(path, display)
436
+
437
+ browse_files, generated_files, browse_display_paths = _collect_browse_files(
438
+ unit, project_root=source
439
+ )
440
+ for path, content in browse_files.items():
441
+ unit_graph.files.setdefault(path, content)
442
+ for path, display in browse_display_paths.items():
443
+ display_paths.setdefault(path, display)
444
+ if generated_files:
445
+ existing = unit_graph.metadata.setdefault("generated_files", [])
446
+ assert isinstance(existing, list)
447
+ existing.extend(path for path in generated_files if path not in existing)
448
+ unit_graphs.append(unit_graph)
449
+ graph = _combine(
450
+ unit_graphs,
451
+ project=feature_sets.project,
452
+ version=version,
453
+ language=feature_set_language,
454
+ )
455
+ graph.feature_sets[key] = {
456
+ "features": list(feature_set.features),
457
+ "selection": feature_set.selection,
458
+ "languages": configuration,
459
+ }
460
+ _merge_metadata(metadata, graph.metadata)
461
+ per_feature_set.append((key, graph))
462
+ all_feature_selections.append(feature_set.selection)
417
463
 
418
464
  language_values = sorted(languages)
419
465
  if len(language_values) == 1:
@@ -423,17 +469,18 @@ def build_graph(
423
469
  else:
424
470
  language = "none"
425
471
  merged = merge_graphs(
426
- per_feature_set, project=feature_sets.project, version=version, language=language
472
+ per_feature_set, project=project or "", version=version, language=language
427
473
  )
428
- metadata["feature_sets"] = [feature_set.selection for feature_set in feature_sets.feature_sets]
474
+ metadata["feature_sets"] = all_feature_selections
475
+ metadata["language_configurations"] = list(config.language_configurations)
429
476
  sorted_units = sorted(units, key=lambda value: (value[0], value[1] or ""))
430
477
  metadata["units"] = [
431
478
  {"id": unit_id, "language": unit_language} for unit_id, unit_language in sorted_units
432
479
  ]
433
480
  if configuration_options:
434
481
  metadata["configuration_options"] = configuration_options
435
- metadata["generator"] = feature_sets.generator
436
- metadata["project_version"] = feature_sets.version
482
+ metadata["generator"] = generator
483
+ metadata["project_version"] = project_version
437
484
  merged.metadata = metadata
438
485
 
439
486
  _apply_visibility(merged, config)
@@ -28,6 +28,7 @@ class CpdocsConfig:
28
28
  project_root: Path
29
29
  feature_sets: str
30
30
  build: str
31
+ language_configurations: tuple[dict[str, Any], ...]
31
32
  versions: tuple[str, ...]
32
33
  index: Path | None
33
34
  copyright: str | None
@@ -86,6 +87,27 @@ def _command(value: object, name: str) -> str:
86
87
  return value.strip()
87
88
 
88
89
 
90
+ def _language_configurations(raw: dict[str, Any]) -> tuple[dict[str, Any], ...]:
91
+ """Expand the optional Keyso language space, preserving the original default build."""
92
+
93
+ if "languages" not in raw:
94
+ return ({},)
95
+ expression = _mapping(raw["languages"], "languages")
96
+ try:
97
+ import keyso
98
+ except ImportError as error:
99
+ raise RuntimeError("cpdocs.yml: languages requires the keyso package") from error
100
+ try:
101
+ configurations = keyso.expand(expression)
102
+ except Exception as error:
103
+ raise RuntimeError(f"cpdocs.yml: invalid languages Keyso space: {error}") from error
104
+ if not configurations:
105
+ raise RuntimeError("cpdocs.yml: languages must expand to at least one configuration")
106
+ if not all(isinstance(configuration, dict) for configuration in configurations):
107
+ raise RuntimeError("cpdocs.yml: languages must expand to dictionaries")
108
+ return tuple(configurations)
109
+
110
+
89
111
  def load_config(path: Path | str = DEFAULT_CONFIG) -> CpdocsConfig:
90
112
  config_file = Path(path).expanduser().resolve()
91
113
  if not config_file.is_file():
@@ -97,7 +119,7 @@ def load_config(path: Path | str = DEFAULT_CONFIG) -> CpdocsConfig:
97
119
  raise RuntimeError(
98
120
  f"cpdocs.yml: contract must be {CONTRACT_VERSION}, found {raw.get('contract')!r}"
99
121
  )
100
- known = {"contract", "build", "versions", "site", "api", "checks"}
122
+ known = {"contract", "build", "languages", "versions", "site", "api", "checks"}
101
123
  unknown = sorted(set(raw) - known)
102
124
  if unknown:
103
125
  raise RuntimeError(f"cpdocs.yml: unknown top-level keys: {', '.join(unknown)}")
@@ -123,6 +145,7 @@ def load_config(path: Path | str = DEFAULT_CONFIG) -> CpdocsConfig:
123
145
  project_root=root,
124
146
  feature_sets=_command(build.get("feature-sets"), "build.feature-sets"),
125
147
  build=_command(build.get("build"), "build.build"),
148
+ language_configurations=_language_configurations(raw),
126
149
  versions=_versions(raw.get("versions")),
127
150
  index=index if index.is_file() else None,
128
151
  copyright=str(copyright_value) if copyright_value is not None else None,
@@ -22,7 +22,31 @@ from ..contract import (
22
22
  write_feature_set,
23
23
  )
24
24
 
25
- _PLACEHOLDER = re.compile(r"\{\{([a-z][a-z0-9-]*)\}\}")
25
+ _PLACEHOLDER = re.compile(r"\{\{\s*([a-z][a-z0-9_.-]*)\s*\}\}")
26
+
27
+
28
+ def language_template_values(configuration: dict[str, object]) -> dict[str, str]:
29
+ """Flatten concrete Keyso values into dotted command-template placeholders."""
30
+
31
+ result: dict[str, str] = {}
32
+
33
+ def visit(path: str, value: object) -> None:
34
+ if isinstance(value, dict):
35
+ for key, child in value.items():
36
+ if not isinstance(key, str) or not re.fullmatch(r"[a-z][a-z0-9_-]*", key):
37
+ raise RuntimeError(f"cpdocs.yml: invalid language property {key!r}")
38
+ visit(f"{path}.{key}" if path else key, child)
39
+ elif isinstance(value, (str, int, float, bool)) and not isinstance(value, complex):
40
+ if not path:
41
+ raise RuntimeError("cpdocs.yml: language configuration needs a property name")
42
+ if path in {"source", "tmpdir", "output-file", "feature-set", "python"}:
43
+ raise RuntimeError(f"cpdocs.yml: language property {path!r} is reserved")
44
+ result[path] = str(value).lower() if isinstance(value, bool) else str(value)
45
+ else:
46
+ raise RuntimeError(f"cpdocs.yml: language property {path!r} must be a scalar value")
47
+
48
+ visit("", configuration)
49
+ return result
26
50
 
27
51
 
28
52
  def _expand(command: str, values: dict[str, str], *, allowed: set[str], name: str) -> str:
@@ -71,7 +95,9 @@ def _run_command(
71
95
  )
72
96
 
73
97
 
74
- def discover_feature_sets(command: str, *, source: Path, work: Path) -> FeatureSets:
98
+ def discover_feature_sets(
99
+ command: str, *, source: Path, work: Path, template_values: dict[str, str] | None = None
100
+ ) -> FeatureSets:
75
101
  """Run ``build.feature-sets`` and read ``feature-sets.json``."""
76
102
 
77
103
  directory = work / "feature-sets"
@@ -87,8 +113,9 @@ def discover_feature_sets(command: str, *, source: Path, work: Path) -> FeatureS
87
113
  "tmpdir": str(tmpdir.resolve()),
88
114
  "output-file": str(output.resolve()),
89
115
  "python": sys.executable,
116
+ **(template_values or {}),
90
117
  },
91
- allowed={"source", "tmpdir", "output-file", "python"},
118
+ allowed={"source", "tmpdir", "output-file", "python"} | set(template_values or {}),
92
119
  name="feature-sets",
93
120
  log=directory / "log.txt",
94
121
  )
@@ -96,7 +123,12 @@ def discover_feature_sets(command: str, *, source: Path, work: Path) -> FeatureS
96
123
 
97
124
 
98
125
  def resolve_manifest(
99
- command: str, feature_set: FeatureSetDeclaration, *, source: Path, work: Path
126
+ command: str,
127
+ feature_set: FeatureSetDeclaration,
128
+ *,
129
+ source: Path,
130
+ work: Path,
131
+ template_values: dict[str, str] | None = None,
100
132
  ) -> Manifest:
101
133
  """Run ``build.build`` for one feature set and read ``manifest.json``."""
102
134
 
@@ -116,8 +148,10 @@ def resolve_manifest(
116
148
  "feature-set": str(feature_set_file.resolve()),
117
149
  "output-file": str(output.resolve()),
118
150
  "python": sys.executable,
151
+ **(template_values or {}),
119
152
  },
120
- allowed={"source", "tmpdir", "feature-set", "output-file", "python"},
153
+ allowed={"source", "tmpdir", "feature-set", "output-file", "python"}
154
+ | set(template_values or {}),
121
155
  name="build",
122
156
  log=directory / "log.txt",
123
157
  )