Skip to content

Commit 7e455e8

Browse files
committed
feat: Implement C templates (prototype)
1 parent 1eb1ab3 commit 7e455e8

18 files changed

Lines changed: 270 additions & 34 deletions

README.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,22 @@ A C handler for mkdocstrings.
1111
This project is available to sponsors only, through my Insiders program.
1212
See Insiders [explanation](https://mkdocstrings.github.io/c/insiders/)
1313
and [installation instructions](https://mkdocstrings.github.io/c/insiders/installation/).
14+
15+
## Usage
16+
17+
With the following header file:
18+
19+
```c title="hello.h"
20+
--8<-- "docs/snippets/hello.h"
21+
```
22+
23+
Generate docs for this file with this instruction in one of your Markdown page:
24+
25+
```md
26+
::: path/to/hello.h
27+
```
28+
29+
This will generate the following HTML:
30+
31+
::: docs/snippets/hello.h
32+
handler: c

docs/snippets/hello.h

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#ifndef HELLO_H
2+
#define HELLO_H // Header guard for `hello.h`.
3+
4+
// Some macro that holds something.
5+
#define SOME_MACRO 123
6+
7+
// Pointer to an integer.
8+
typedef const int* foo_t;
9+
10+
/*
11+
* My cool function that does something **cool**!
12+
*
13+
* @param[in] x Input thingy!
14+
* @param[out] y Output thingy!
15+
* @param z Some other thing
16+
*/
17+
void foo(int* x, int* y, foo_t z);
18+
19+
const foo_t x = ((foo_t) 0); // Random NULL pointer
20+
21+
#endif

mkdocs.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,10 @@ plugins:
116116
- coverage
117117
- mkdocstrings:
118118
handlers:
119+
c:
120+
options:
121+
show_symbol_type_heading: true
122+
show_symbol_type_toc: true
119123
python:
120124
import:
121125
- https://docs.python.org/3/objects.inv

pyproject.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ classifiers = [
2929
"Typing :: Typed",
3030
]
3131
dependencies = [
32-
"mkdocstrings>=0.18",
32+
"mkdocstrings>=0.25",
33+
"pycparser>=2.20",
3334
]
3435

3536
[project.urls]

src/mkdocstrings_handlers/c/handler.py

Lines changed: 42 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -161,12 +161,12 @@ def extract_macros(code: str) -> tuple[list[Macro], str]:
161161
return macros, "\n".join(extracted)
162162

163163

164-
class InOut(Enum):
164+
class InOut(str, Enum):
165165
"""Enumeration for parameter direction."""
166166

167-
UNSPECIFIED = 0
168-
IN = 1
169-
OUT = 2
167+
UNSPECIFIED = "unspecified"
168+
IN = "in"
169+
OUT = "out"
170170

171171

172172
@dataclass
@@ -320,13 +320,13 @@ class CodeDoc:
320320
typedefs: dict[str, DocType]
321321

322322

323-
class TypeDecl(Enum):
323+
class TypeDecl(str, Enum):
324324
"""Enumeration for type declarations."""
325325

326-
NORMAL = 0
327-
POINTER = 1
328-
ARRAY = 2
329-
FUNCTION = 3
326+
NORMAL = "normal"
327+
POINTER = "pointer"
328+
ARRAY = "array"
329+
FUNCTION = "function"
330330

331331

332332
@dataclass
@@ -413,6 +413,18 @@ def tp_ref_to_str(ref: TypeRef, qualname: str) -> str:
413413
return f"{ret} (*{qualname})({', '.join(params)})"
414414

415415

416+
def typedef_to_str(decl: DocType) -> str:
417+
"""Convert a typedef to a string.
418+
419+
Parameters:
420+
decl: The typedef to convert.
421+
422+
Returns:
423+
The string representation of the typedef.
424+
"""
425+
return tp_ref_to_str(decl.tp, decl.name)
426+
427+
416428
def desc(doc: Docstring | None) -> str:
417429
"""Get the description from a docstring.
418430
@@ -441,9 +453,9 @@ def lookup_type_html(data: CodeDoc, tp: TypeRef, *, name: str | None = None) ->
441453
"""
442454
tp_str = ""
443455

444-
for name, doctype in data.typedefs.items():
456+
for type_name, doctype in data.typedefs.items():
445457
if doctype.tp == tp:
446-
tp_str = f'<a href="#type-{name}">{name}</a>'
458+
tp_str = f'<a href="#type-{type_name}">{type_name}</a>'
447459

448460
return f'<code>{tp_str or tp_ref_to_str(tp, name or "unknown")}</code>'
449461

@@ -469,6 +481,8 @@ class CHandler(BaseHandler):
469481
default_config: ClassVar[dict] = {
470482
"show_root_heading": False,
471483
"show_root_toc_entry": True,
484+
"show_symbol_type_heading": True,
485+
"show_symbol_type_toc_entry": True,
472486
"heading_level": 2,
473487
}
474488
"""The default configuration options.
@@ -480,7 +494,7 @@ class CHandler(BaseHandler):
480494
**`heading_level`** | `int` | The initial heading level to use. | `2`
481495
"""
482496

483-
def collect(self, identifier: str, config: MutableMapping[str, Any]) -> CollectorItem: # noqa: ARG002
497+
def collect(self, identifier: str, config: MutableMapping[str, Any]) -> CollectorItem:
484498
"""Collect data given an identifier and selection configuration.
485499
486500
In the implementation, you typically call a subprocess that returns JSON, and load that JSON again into
@@ -496,6 +510,9 @@ def collect(self, identifier: str, config: MutableMapping[str, Any]) -> Collecto
496510
Returns:
497511
Anything you want, as long as you can feed it to the `render` method.
498512
"""
513+
if config.get("fallback", False):
514+
raise CollectionError("Not loading additional headers during fallback")
515+
499516
source = Path(identifier).read_text(encoding="utf-8")
500517
comments_list, source = extract_comments(source)
501518
macros_list, source = extract_macros(source)
@@ -573,7 +590,7 @@ def collect(self, identifier: str, config: MutableMapping[str, Any]) -> Collecto
573590
# def get_templates_dir(self, handler: str | None = None) -> Path:
574591
# return Path.cwd()
575592

576-
def render(self, data: CodeDoc, config: Mapping[str, Any]) -> str: # noqa: ARG002
593+
def render(self, data: CodeDoc, config: Mapping[str, Any]) -> str:
577594
"""Render a template using provided data and configuration options.
578595
579596
Parameters:
@@ -584,13 +601,15 @@ def render(self, data: CodeDoc, config: Mapping[str, Any]) -> str: # noqa: ARG0
584601
Returns:
585602
The rendered template as HTML.
586603
"""
587-
# final_config = {**self.default_config, **config}
588-
# heading_level = final_config["heading_level"]
589-
# template = self.env.get_template(f"{data...}.html.jinja")
590-
# return template.render(
591-
# **{"config": final_config, data...: data, "heading_level": heading_level, "root": True},
592-
# )
593-
raise PluginError("Implement me!")
604+
final_config = {**self.default_config, **config}
605+
heading_level = final_config["heading_level"]
606+
template = self.env.get_template("header.html.jinja")
607+
return template.render(
608+
config=final_config,
609+
header=data,
610+
heading_level=heading_level,
611+
root=True,
612+
)
594613

595614
def update_env(self, md: Markdown, config: dict) -> None:
596615
"""Update the Jinja environment with any custom settings/filters/options for this handler.
@@ -604,6 +623,9 @@ def update_env(self, md: Markdown, config: dict) -> None:
604623
self.env.trim_blocks = True
605624
self.env.lstrip_blocks = True
606625
self.env.keep_trailing_newline = False
626+
self.env.filters["typedef_to_str"] = typedef_to_str
627+
self.env.filters["lookup_type_html"] = lookup_type_html
628+
self.env.filters["zip"] = zip
607629

608630

609631
def get_handler(
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
{% filter heading(
2+
heading_level,
3+
id=func.name,
4+
class="doc doc-heading",
5+
toc_label=('<code class="doc-symbol doc-symbol-toc doc-symbol-function"></code>&nbsp;'|safe if config.show_symbol_type_toc else '') + func.name,
6+
) %}
7+
{% if config.show_symbol_type_heading %}<code class="doc-symbol doc-symbol-heading doc-symbol-function"></code>{% endif %}
8+
{{ header | lookup_type_html(func.ret) | safe }}
9+
<code>{{ func.name }}</code>
10+
{% if func.args %}
11+
(
12+
{%- for param in func.args %}
13+
{{ header | lookup_type_html(param.tp, name=param.name) | safe }}<code>{{ param.name }}</code>{{ ", " if not loop.last else "" }}
14+
{% endfor -%}
15+
)
16+
{% else %}
17+
void
18+
{% endif %}
19+
{% endfilter %}
20+
{{ func.doc.desc | default("No description provided.") | convert_markdown(func.name) }}
21+
{% if func.doc and func.doc.params %}
22+
<table>
23+
<tr>
24+
<th>Name</th>
25+
<th>Type</th>
26+
<th>Description</th>
27+
</tr>
28+
{% for doc, param in func.doc.params | zip(func.args) %}
29+
<tr>
30+
<td>{{ doc.name }}</td>
31+
<td>{{ header | lookup_type_html(param.tp, name=param.name) | safe }}{{ "" if doc.in_out == "unspecified" else "[" ~ doc.in_out ~ "]" }}</td>
32+
<td>{{ doc.desc | default("No description provided.") | convert_markdown(func.name) }}</td>
33+
</tr>
34+
{% endfor %}
35+
</table>
36+
{% endif %}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{% filter heading(
2+
heading_level,
3+
id=var.name,
4+
class="doc doc-heading",
5+
toc_label=('<code class="doc-symbol doc-symbol-toc doc-symbol-var"></code>&nbsp;'|safe if config.show_symbol_type_toc else '') + var.name,
6+
) %}
7+
{% if config.show_symbol_type_heading %}<code class="doc-symbol doc-symbol-heading doc-symbol-var"></code>{% endif %}
8+
{{ header | lookup_type_html(var.tp) | safe }}
9+
<code>{{ var.name }}</code>
10+
{% endfilter %}
11+
{{ var.doc.desc | default("No description provided.") | convert_markdown(var.name) }}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{% if header.macros %}
2+
{% for macro in header.macros %}
3+
{% include "macro.html.jinja" with context %}
4+
{% endfor %}
5+
{% endif %}
6+
7+
{% if header.typedefs %}
8+
{% for tp in header.typedefs.values() %}
9+
{% include "typedef.html.jinja" with context %}
10+
{% endfor %}
11+
{% endif %}
12+
13+
{% if header.global_vars %}
14+
{% for var in header.global_vars %}
15+
{% include "global_var.html.jinja" with context %}
16+
{% endfor %}
17+
{% endif %}
18+
19+
{% if header.functions %}
20+
{% for func in header.functions %}
21+
{% include "function.html.jinja" with context %}
22+
{% endfor %}
23+
{% endif %}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{% filter heading(
2+
heading_level,
3+
id=macro.name,
4+
class="doc doc-heading",
5+
toc_label=('<code class="doc-symbol doc-symbol-toc doc-symbol-macro"></code>&nbsp;'|safe if config.show_symbol_type_toc else '') + macro.name,
6+
) %}
7+
{% if config.show_symbol_type_heading %}<code class="doc-symbol doc-symbol-heading doc-symbol-macro"></code>{% endif %}
8+
<code>{{ macro.name }}</code>
9+
{% if macro.content %}<code>{{ macro.content }}</code>{% endif %}
10+
{% endfilter %}
11+
{{ macro.doc.desc | default("No description provided.") | convert_markdown(macro.name) }}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{% filter heading(
2+
heading_level,
3+
id=tp.name,
4+
class="doc doc-heading",
5+
toc_label=('<code class="doc-symbol doc-symbol-toc doc-symbol-typedef"></code>&nbsp;'|safe if config.show_symbol_type_toc else '') + tp.name,
6+
) %}
7+
{% if config.show_symbol_type_heading %}<code class="doc-symbol doc-symbol-heading doc-symbol-typedef"></code>{% endif %}
8+
<code>{{ tp.name }}</code>
9+
<code>{{ tp | typedef_to_str }}</code>
10+
{% endfilter %}
11+
{{ tp.doc.desc | default("No description provided.") | convert_markdown(tp.name) }}

0 commit comments

Comments
 (0)