mkdocs-cdoc 1.0.0__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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Pawel Sikora
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,14 @@
1
+ Metadata-Version: 2.1
2
+ Name: mkdocs-cdoc
3
+ Version: 1.0.0
4
+ Summary: UNKNOWN
5
+ Home-page: https://github.com/pawelsikora/mkdocs-cdoc/
6
+ Author: Pawel Sikora
7
+ Author-email: sikor6@gmail.com
8
+ License: UNKNOWN
9
+ Keywords: mkdocs cdoc hawkmoth documentation gtk-doc python
10
+ Platform: UNKNOWN
11
+ License-File: LICENSE
12
+
13
+ UNKNOWN
14
+
@@ -0,0 +1,372 @@
1
+ # mkdocs-cdoc
2
+
3
+ [![CI](https://github.com/pawelsikora/mkdocs-cdoc/actions/workflows/ci.yml/badge.svg)](https://github.com/pawelsikora/mkdocs-cdoc/actions/workflows/ci.yml)
4
+ [![Python](https://img.shields.io/pypi/pyversions/mkdocs-cdoc)](https://pypi.org/project/mkdocs-cdoc/)
5
+ [![PyPI](https://img.shields.io/pypi/v/mkdocs-cdoc)](https://pypi.org/project/mkdocs-cdoc/)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
+ [![MkDocs](https://img.shields.io/badge/MkDocs-plugin-blue)](https://www.mkdocs.org/)
8
+ [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
9
+
10
+ **Generate browsable API documentation from C/C++ source comments directly in MkDocs** — with cross-referencing, symbol indexing, and basic support for test catalogs.
11
+
12
+ Parses `/** ... */` doc comments using libclang (with a regex fallback), converts reST and gtk-doc markup to Markdown, and builds a fully linked API reference with per-symbol anchors, an A–Z index, and automatic cross-references across all your source files and hand-written pages.
13
+
14
+ ---
15
+
16
+ ## Features
17
+
18
+ - **Autodoc from C/C++ sources** — scans source trees, extracts doc comments, generates per-file pages with function signatures, parameter tables, and struct/enum member listings.
19
+ - **Cross-reference everything** — `:func:`, `:struct:`, `:file:`, backtick auto-linking. Works across generated pages and hand-written Markdown alike.
20
+ - **Multiple source groups** — separate nav sections for core libraries, drivers, tests, etc., each with their own A–Z index and overview.
21
+ - **IGT test catalog** — parses `TEST:` / `SUBTEST:` structured comments and `igt_subtest()` / `igt_describe()` macros to build test documentation with metadata tables, "By Category" / "By Functionality" index pages, and per-subtest anchors.
22
+ - **gtk-doc migration** — converts `#TypeName`, `function()`, `%CONSTANT`, `@param` markup at parse time, plus a CLI for one-time batch conversion of source files.
23
+ - **Clang + regex** — uses libclang when available for accurate signatures and types, falls back to regex parsing so it works everywhere.
24
+
25
+ ---
26
+
27
+ ## Install
28
+
29
+ ```bash
30
+ pip install mkdocs-cdoc
31
+ ```
32
+
33
+ For full clang-based parsing (recommended):
34
+
35
+ ```bash
36
+ # Ubuntu / Debian
37
+ sudo apt install python3-clang libclang-dev
38
+
39
+ # Or via pip
40
+ pip install mkdocs-cdoc[clang]
41
+ ```
42
+
43
+ ## Quick start
44
+
45
+ Minimal setup — point it at a source directory:
46
+
47
+ ```yaml
48
+ plugins:
49
+ - cdoc:
50
+ source_root: src/
51
+ ```
52
+
53
+ Multiple source trees with their own nav sections:
54
+
55
+ ```yaml
56
+ plugins:
57
+ - cdoc:
58
+ project_name: My Project
59
+ version_file: version.json
60
+
61
+ sources:
62
+ - root: src/core
63
+ nav_title: Core API
64
+ output_dir: api/core
65
+
66
+ - root: src/drivers
67
+ nav_title: Driver API
68
+ output_dir: api/drivers
69
+ extensions: [".c"]
70
+ ```
71
+
72
+ The `version_file` is scanned for a line matching `version: 'X.Y'` (or `VERSION = "1.2.3"`, `"version": "2.0"`, etc.) — it works with JSON, YAML, Python, meson.build, or any file with a version key-value pair.
73
+
74
+ With multiple source groups, a top-level overview page is generated automatically showing the project name, a version badge, and links to each group. Individual source file pages are generated and reachable via cross-references and the index — they just don't crowd the nav:
75
+
76
+ ```
77
+ API Reference
78
+ Core API
79
+ Overview
80
+ Driver API
81
+ Overview
82
+ ```
83
+
84
+ ## Source group options
85
+
86
+ Each entry under `sources:` accepts:
87
+
88
+ | Option | Default | Description |
89
+ |--------|---------|-------------|
90
+ | `root` | *(required)* | Path to the source tree |
91
+ | `nav_title` | `API (<dirname>)` | Nav section heading |
92
+ | `output_dir` | `api/<dirname>` | Where generated pages go |
93
+ | `extensions` | `[".c", ".h"]` | File extensions to scan |
94
+ | `exclude` | `[]` | Glob patterns to skip |
95
+ | `clang_args` | `[]` | Extra flags, appended to global `clang_args` |
96
+ | `index` | `true` | Generate an overview page |
97
+ | `pages` | `[]` | Extra hand-written pages to include in the nav |
98
+ | `igt` | — | IGT test framework options ([see below](#test-documentation-igt-gpu-tools)) |
99
+
100
+ ## Test documentation (IGT GPU Tools)
101
+
102
+ The plugin has built-in support for test source trees that use structured doc comments and test macros. While designed around [IGT GPU Tools](https://gitlab.freedesktop.org/drm/igt-gpu-tools) conventions, the approach works for any C test codebase that follows a similar comment structure.
103
+
104
+ ### Enabling test mode
105
+
106
+ Add an `igt:` block to any source group:
107
+
108
+ ```yaml
109
+ sources:
110
+ - root: tests
111
+ nav_title: Tests
112
+ output_dir: api/tests
113
+ extensions: [".c"]
114
+ igt:
115
+ group_by: [category, mega_feature, sub_category, functionality]
116
+ ```
117
+
118
+ That's all you need. The presence of `igt:` enables test parsing for that source group.
119
+
120
+ ### IGT GPU Tools options
121
+
122
+ | Option | Default | Description |
123
+ |--------|---------|-------------|
124
+ | `group_by` | `[]` | Metadata fields to generate "By …" index pages for |
125
+ | `fields` | same as `group_by` | Which metadata fields to show on each test page |
126
+ | `extract_steps` | `false` | Parse subtest code bodies for step-by-step tables |
127
+
128
+ ### What it parses
129
+
130
+ **Structured doc comments** at file scope — either in a main `TEST:` block or standalone `SUBTEST:` blocks above functions:
131
+
132
+ ```c
133
+ /**
134
+ * TEST: kms_addfb
135
+ * Category: Display
136
+ * Mega feature: KMS
137
+ * Sub-category: Framebuffer
138
+ * Description: Tests for the DRM framebuffer creation ioctl.
139
+ *
140
+ * SUBTEST: basic
141
+ * Description: Check if addfb2 works with a valid handle.
142
+ * Functionality: addfb
143
+ */
144
+ ```
145
+
146
+ Standalone subtest blocks are also supported, placed above individual functions:
147
+
148
+ ```c
149
+ /**
150
+ * SUBTEST: attach-debug-metadata
151
+ * Functionality: metadata
152
+ * Description:
153
+ * Read debug metadata when vm_bind has it attached.
154
+ */
155
+ static void test_metadata_attach(int fd, unsigned int flags) { ... }
156
+ ```
157
+
158
+ **Test macros** in code — `igt_subtest()`, `igt_describe()`, and `igt_subtest_with_dynamic()`:
159
+
160
+ ```c
161
+ igt_describe("Check that invalid legacy set-property calls are "
162
+ "correctly rejected by the kernel.");
163
+ igt_subtest("invalid-properties-legacy") {
164
+ ...
165
+ }
166
+ ```
167
+
168
+ Both sources are merged: subtests from doc comments and code are combined, with `igt_describe()` taking priority for descriptions, then standalone `SUBTEST:` blocks, then the main `TEST:` block.
169
+
170
+ Multi-line `igt_describe()` strings (concatenated across lines) are handled. Format-string subtests like `igt_subtest("%s")` are automatically excluded.
171
+
172
+ ### Metadata fields
173
+
174
+ Any `Key: Value` pair in the doc comment becomes a metadata field. Common conventions:
175
+
176
+ | Field | Level | Typical values |
177
+ |-------|-------|----------------|
178
+ | `Category` | test | Core, Display, … |
179
+ | `Mega feature` | test | KMS, Memory Management, … |
180
+ | `Sub-category` | test | GEM, Framebuffer, … |
181
+ | `Functionality` | subtest | addfb, gem_create, … |
182
+ | `Description` | both | Free-form text (supports multi-line) |
183
+
184
+ Test-level fields (like Category) group all tests in a file. Subtest-level fields (like Functionality) group individual subtests and produce a different table layout on the "By …" pages.
185
+
186
+ ### Generated pages
187
+
188
+ The nav sidebar shows:
189
+
190
+ ```
191
+ Tests
192
+ Overview ← file table + A–Z symbol index
193
+ By Category ← tests grouped by Category field
194
+ By Mega Feature ← tests grouped by Mega Feature field
195
+ By Sub Category ← tests grouped by Sub-category field
196
+ By Functionality ← subtests grouped by Functionality field
197
+ ```
198
+
199
+ Each **"By …" page** groups tests under headings matching the field values. Under each value, every test gets its own subheading with a table of its subtests and descriptions.
200
+
201
+ Each **test page** shows a metadata table, the full subtest listing (with anchor links per subtest), and optionally step-by-step tables when `extract_steps: true`.
202
+
203
+ ### Adapting for your own test framework
204
+
205
+ The `igt:` parser key specifically enables the IGT-style parser, but the comment format is generic enough for any C test suite. If your tests use `/** ... */` comments with structured `TEST:` / `SUBTEST:` blocks and key-value metadata, they will work as-is. You only need IGT-specific macros (`igt_subtest`, `igt_describe`) if you want code-level subtest discovery.
206
+
207
+ To use it with a custom test tree:
208
+
209
+ 1. Add `/** TEST: my_test_name */` comments with any metadata fields you like.
210
+ 2. Optionally add `/** SUBTEST: name */` blocks with per-subtest metadata.
211
+ 3. Set `group_by` to whichever field names you used.
212
+
213
+ ### Cross-referencing tests
214
+
215
+ Tests and subtests are registered in the symbol registry:
216
+
217
+ ```markdown
218
+ See :test:`kms_addfb` for framebuffer tests.
219
+ The :subtest:`kms_addfb@basic` subtest covers the happy path.
220
+ ```
221
+
222
+ The `test@subtest` notation mirrors IGT's `--run-subtest` convention. Short-form `:subtest:`basic`` works if the subtest name is unique across all tests.
223
+
224
+ ## Cross-references
225
+
226
+ The plugin builds a symbol registry at build time and resolves references across all pages — generated API pages and hand-written docs alike.
227
+
228
+ ### reST roles in doc comments
229
+
230
+ Use reST roles (portable to Sphinx):
231
+
232
+ ```c
233
+ /**
234
+ * Initialize the engine.
235
+ *
236
+ * Must be called before :func:`engine_run`. Configure with
237
+ * :struct:`engine_config` first.
238
+ *
239
+ * :param flags: Init flags.
240
+ * :returns: 0 on success.
241
+ */
242
+ int engine_init(unsigned int flags);
243
+ ```
244
+
245
+ Available roles: `:func:`, `:struct:`, `:union:`, `:enum:`, `:macro:`, `:type:`, `:var:`, `:const:`, `:member:`, `:class:`, `:file:`, `:test:`, `:subtest:`. Domain-qualified forms like `:c:func:` also work.
246
+
247
+ For struct members use dot notation: `:member:`engine_config.debug``. For files, use the bare filename if unique or qualify with the group: `:file:`core/engine.h``.
248
+
249
+ ### reST roles in Markdown pages
250
+
251
+ The same roles work in hand-written Markdown:
252
+
253
+ ```markdown
254
+ Call :func:`engine_init` with the appropriate flags, then pass
255
+ an :struct:`engine_config` to :func:`engine_run`.
256
+ ```
257
+
258
+ ### Auto-linking
259
+
260
+ When `auto_xref` is enabled (the default), backticked identifiers that match known symbols become links automatically:
261
+
262
+ ```markdown
263
+ Call `engine_init()` first, then create an `engine_config` and
264
+ pass it to `engine_run()`.
265
+ ```
266
+
267
+ Trailing `()` signals a function reference. Bare backticked names link if they match a struct, enum, type, etc. Filenames with C/C++ extensions also auto-link. Unknown names stay as plain code.
268
+
269
+ ## Rendering
270
+
271
+ **Parameter tables** — function parameters render as a table. When type information is available (from clang), a Type column is added with cross-reference links for struct/custom types.
272
+
273
+ **Pointer return types** — functions returning pointers (e.g. `char *get_name(...)`) render as "Pointer to `char`" rather than showing `*` in the name.
274
+
275
+ **Example sections** — if a doc comment contains `Example:`, the description and code render side-by-side, similar to the Stripe API documentation layout.
276
+
277
+ **Underscore-prefixed symbols** — functions with `_` or `__` prefix sort by the name without the prefix. `__engine_reset` sorts under **E**.
278
+
279
+ ## gtk-doc migration
280
+
281
+ If your codebase uses gtk-doc markup:
282
+
283
+ ```yaml
284
+ plugins:
285
+ - cdoc:
286
+ convert_gtkdoc: true
287
+ ```
288
+
289
+ | gtk-doc | Converts to |
290
+ |---------|------------|
291
+ | `function_name()` | `:func:`function_name`` |
292
+ | `#TypeName` | `:type:`TypeName`` |
293
+ | `#Struct.field` | `:member:`Struct.field`` |
294
+ | `%CONSTANT` | `:const:`CONSTANT`` |
295
+ | `@param: desc` | `:param param: desc` |
296
+ | `Returns:` | `:returns:` |
297
+ | `\|[ code ]\|` | fenced code block |
298
+
299
+ Batch conversion CLI to permanently migrate source files:
300
+
301
+ ```bash
302
+ python -m mkdocs_cdoc.convert src/
303
+ python -m mkdocs_cdoc.convert src/ --dry-run
304
+ python -m mkdocs_cdoc.convert src/ --backup
305
+ ```
306
+
307
+ ## Inline directives
308
+
309
+ Pull specific symbols into hand-written pages:
310
+
311
+ ```markdown
312
+ ::: c:autofunction
313
+ :file: engine.h
314
+ :name: engine_init
315
+
316
+ ::: c:autodoc
317
+ :file: uart.c
318
+ ```
319
+
320
+ Full directive list: `autodoc`, `autofunction`, `autostruct`, `autounion`, `autoenum`, `automacro`, `autovar`, `autotype`.
321
+
322
+ ## Global config reference
323
+
324
+ | Option | Default | Description |
325
+ |--------|---------|-------------|
326
+ | `project_name` | `""` | Project name on the top-level overview page |
327
+ | `version_file` | `""` | File to extract version from |
328
+ | `sources` | `[]` | List of source group configs (see above) |
329
+ | `clang_args` | `[]` | Global clang flags (merged with per-group flags) |
330
+ | `convert_rst` | `true` | Convert reST markup to Markdown |
331
+ | `convert_gtkdoc` | `false` | Convert gtk-doc markup to reST at parse time |
332
+ | `auto_xref` | `true` | Auto-link backticked symbol names |
333
+ | `appendix_code_usages` | `false` | Append a "Referenced by" section to each symbol |
334
+ | `heading_level` | `2` | Heading depth for symbols |
335
+ | `members` | `true` | Show struct/union/enum members |
336
+ | `show_source_link` | `false` | Append `[source]` links |
337
+ | `source_uri` | `""` | Template: `https://github.com/you/repo/blob/main/{filename}#L{line}` |
338
+ | `fallback_parser` | `true` | Use regex parser when clang is unavailable |
339
+ | `language` | `"c"` | Source language (`c` or `cpp`) |
340
+
341
+ For single-source setups without `sources:`, these shortcuts are available: `source_root`, `autodoc_output_dir`, `autodoc_nav_title`, `autodoc_extensions`, `autodoc_exclude`, `autodoc_index`, `autodoc_pages`.
342
+
343
+ ## Example: GTK-doc based project config
344
+
345
+ ```yaml
346
+ plugins:
347
+ - cdoc:
348
+ project_name: my-project
349
+ version_file: meson.build
350
+ clang_args: ["-Ilib", "-Itests", "-Iinclude"]
351
+
352
+ sources:
353
+ - root: lib
354
+ nav_title: Core API
355
+ output_dir: reference_api/lib
356
+
357
+ - root: tests
358
+ nav_title: Tests
359
+ output_dir: reference_api/tests
360
+ extensions: [".c"]
361
+
362
+ convert_rst: true
363
+ convert_gtkdoc: true
364
+ ```
365
+
366
+ ## Credits
367
+
368
+ This project was inspired by the work of [Jani Nikula](https://github.com/jnikula) and the [Hawkmoth](https://github.com/jnikula/hawkmoth) project, which provides Sphinx-based autodoc for C. The core idea of extracting C doc comments through libclang originates there. mkdocs-cdoc adapts and extends that concept for the MkDocs ecosystem, adding multi-source-group navigation, cross-reference resolution, IGT GPU Tools test catalog generation, and gtk-doc migration tooling.
369
+
370
+ ## License
371
+
372
+ MIT — see [LICENSE](LICENSE) for details.
@@ -0,0 +1,9 @@
1
+ """
2
+ mkdocs-cdoc — C/C++ API Documentation for MkDocs.
3
+
4
+ Extracts documentation from C and C++ source comments and renders them
5
+ as browsable API reference pages in MkDocs, with cross-referencing,
6
+ symbol indexing, and IGT GPU Tools test catalog support.
7
+ """
8
+
9
+ __version__ = "1.0.0"
@@ -0,0 +1,96 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Batch convert gtk-doc markup to reST in C/C++ doc comments.
4
+
5
+ Usage:
6
+ python -m mkdocs_cdoc.convert src/
7
+ python -m mkdocs_cdoc.convert src/engine.h --dry-run
8
+ python -m mkdocs_cdoc.convert src/ --ext .c .h --backup
9
+ """
10
+
11
+ import argparse
12
+ import os
13
+ import re
14
+ import shutil
15
+ import sys
16
+
17
+ from .parser import gtkdoc_to_rst
18
+
19
+ _BLOCK_COMMENT_RE = re.compile(r"(/\*\*.*?\*/)", re.DOTALL)
20
+
21
+
22
+ def convert_file(path, dry_run=False, backup=False):
23
+ with open(path, "r", encoding="utf-8", errors="replace") as f:
24
+ original = f.read()
25
+
26
+ def convert_match(m):
27
+ full = m.group(1)
28
+ if not full.startswith("/**"):
29
+ return full
30
+ prefix = full[:3]
31
+ suffix = full[-2:]
32
+ inner = full[3:-2]
33
+ converted = gtkdoc_to_rst(inner)
34
+ return prefix + converted + suffix
35
+
36
+ result = _BLOCK_COMMENT_RE.sub(convert_match, original)
37
+
38
+ if result == original:
39
+ return False
40
+
41
+ if dry_run:
42
+ return True
43
+
44
+ if backup:
45
+ shutil.copy2(path, path + ".bak")
46
+
47
+ with open(path, "w", encoding="utf-8") as f:
48
+ f.write(result)
49
+ return True
50
+
51
+
52
+ def main():
53
+ p = argparse.ArgumentParser(description="Convert gtk-doc markup to reST in C/C++ doc comments")
54
+ p.add_argument("path", help="File or directory to convert")
55
+ p.add_argument(
56
+ "--ext",
57
+ nargs="+",
58
+ default=[".c", ".h", ".cpp", ".hpp"],
59
+ help="File extensions to process (default: .c .h .cpp .hpp)",
60
+ )
61
+ p.add_argument(
62
+ "--dry-run", action="store_true", help="Show what would change without modifying files"
63
+ )
64
+ p.add_argument("--backup", action="store_true", help="Create .bak files before modifying")
65
+ args = p.parse_args()
66
+
67
+ target = args.path
68
+ exts = set(e if e.startswith(".") else f".{e}" for e in args.ext)
69
+
70
+ files = []
71
+ if os.path.isfile(target):
72
+ files.append(target)
73
+ elif os.path.isdir(target):
74
+ for dirpath, _, fnames in os.walk(target):
75
+ for fn in sorted(fnames):
76
+ _, ext = os.path.splitext(fn)
77
+ if ext.lower() in exts:
78
+ files.append(os.path.join(dirpath, fn))
79
+ else:
80
+ print(f"error: {target} not found", file=sys.stderr)
81
+ sys.exit(1)
82
+
83
+ changed = 0
84
+ for fpath in files:
85
+ was_changed = convert_file(fpath, dry_run=args.dry_run, backup=args.backup)
86
+ if was_changed:
87
+ changed += 1
88
+ tag = "[dry-run] " if args.dry_run else ""
89
+ print(f"{tag}converted: {fpath}")
90
+
91
+ total = len(files)
92
+ print(f"\n{changed}/{total} files {'would be ' if args.dry_run else ''}modified")
93
+
94
+
95
+ if __name__ == "__main__":
96
+ main()