mkdocstrings-nim 0.1.0__py3-none-any.whl
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.
- mkdocstrings_handlers/__init__.py +2 -0
- mkdocstrings_handlers/nim/__init__.py +27 -0
- mkdocstrings_handlers/nim/collector.py +267 -0
- mkdocstrings_handlers/nim/docstring.py +129 -0
- mkdocstrings_handlers/nim/extractor/extractor.nim +244 -0
- mkdocstrings_handlers/nim/extractor/nimdocinfo.nim +16 -0
- mkdocstrings_handlers/nim/handler.py +163 -0
- mkdocstrings_handlers/nim/templates/material/_base/attribute.html.jinja +128 -0
- mkdocstrings_handlers/nim/templates/material/_base/backlinks.html.jinja +66 -0
- mkdocstrings_handlers/nim/templates/material/_base/children.html.jinja +197 -0
- mkdocstrings_handlers/nim/templates/material/_base/class.html.jinja +288 -0
- mkdocstrings_handlers/nim/templates/material/_base/const.html.jinja +31 -0
- mkdocstrings_handlers/nim/templates/material/_base/docstring/admonition.html.jinja +20 -0
- mkdocstrings_handlers/nim/templates/material/_base/docstring/attributes.html.jinja +109 -0
- mkdocstrings_handlers/nim/templates/material/_base/docstring/classes.html.jinja +86 -0
- mkdocstrings_handlers/nim/templates/material/_base/docstring/examples.html.jinja +28 -0
- mkdocstrings_handlers/nim/templates/material/_base/docstring/functions.html.jinja +92 -0
- mkdocstrings_handlers/nim/templates/material/_base/docstring/modules.html.jinja +86 -0
- mkdocstrings_handlers/nim/templates/material/_base/docstring/other_parameters.html.jinja +109 -0
- mkdocstrings_handlers/nim/templates/material/_base/docstring/parameters.html.jinja +175 -0
- mkdocstrings_handlers/nim/templates/material/_base/docstring/raises.html.jinja +102 -0
- mkdocstrings_handlers/nim/templates/material/_base/docstring/receives.html.jinja +122 -0
- mkdocstrings_handlers/nim/templates/material/_base/docstring/returns.html.jinja +122 -0
- mkdocstrings_handlers/nim/templates/material/_base/docstring/type_aliases.html.jinja +86 -0
- mkdocstrings_handlers/nim/templates/material/_base/docstring/type_parameters.html.jinja +208 -0
- mkdocstrings_handlers/nim/templates/material/_base/docstring/warns.html.jinja +102 -0
- mkdocstrings_handlers/nim/templates/material/_base/docstring/yields.html.jinja +122 -0
- mkdocstrings_handlers/nim/templates/material/_base/docstring.html.jinja +59 -0
- mkdocstrings_handlers/nim/templates/material/_base/expression.html.jinja +172 -0
- mkdocstrings_handlers/nim/templates/material/_base/func.html.jinja +80 -0
- mkdocstrings_handlers/nim/templates/material/_base/iterator.html.jinja +80 -0
- mkdocstrings_handlers/nim/templates/material/_base/labels.html.jinja +25 -0
- mkdocstrings_handlers/nim/templates/material/_base/language.html.jinja +18 -0
- mkdocstrings_handlers/nim/templates/material/_base/languages/en.html.jinja +56 -0
- mkdocstrings_handlers/nim/templates/material/_base/languages/ja.html.jinja +56 -0
- mkdocstrings_handlers/nim/templates/material/_base/languages/zh.html.jinja +56 -0
- mkdocstrings_handlers/nim/templates/material/_base/macro.html.jinja +80 -0
- mkdocstrings_handlers/nim/templates/material/_base/module.html.jinja +30 -0
- mkdocstrings_handlers/nim/templates/material/_base/proc.html.jinja +80 -0
- mkdocstrings_handlers/nim/templates/material/_base/signature.html.jinja +135 -0
- mkdocstrings_handlers/nim/templates/material/_base/summary/attributes.html.jinja +23 -0
- mkdocstrings_handlers/nim/templates/material/_base/summary/classes.html.jinja +23 -0
- mkdocstrings_handlers/nim/templates/material/_base/summary/functions.html.jinja +23 -0
- mkdocstrings_handlers/nim/templates/material/_base/summary/modules.html.jinja +23 -0
- mkdocstrings_handlers/nim/templates/material/_base/summary/type_aliases.html.jinja +23 -0
- mkdocstrings_handlers/nim/templates/material/_base/summary.html.jinja +30 -0
- mkdocstrings_handlers/nim/templates/material/_base/template.html.jinja +80 -0
- mkdocstrings_handlers/nim/templates/material/_base/type.html.jinja +31 -0
- mkdocstrings_handlers/nim/templates/material/_base/type_alias.html.jinja +117 -0
- mkdocstrings_handlers/nim/templates/material/_base/type_parameters.html.jinja +87 -0
- mkdocstrings_handlers/nim/templates/material/attribute.html.jinja +1 -0
- mkdocstrings_handlers/nim/templates/material/backlinks.html.jinja +1 -0
- mkdocstrings_handlers/nim/templates/material/children.html.jinja +1 -0
- mkdocstrings_handlers/nim/templates/material/class.html.jinja +1 -0
- mkdocstrings_handlers/nim/templates/material/const.html.jinja +1 -0
- mkdocstrings_handlers/nim/templates/material/docstring/admonition.html.jinja +1 -0
- mkdocstrings_handlers/nim/templates/material/docstring/attributes.html.jinja +1 -0
- mkdocstrings_handlers/nim/templates/material/docstring/classes.html.jinja +1 -0
- mkdocstrings_handlers/nim/templates/material/docstring/examples.html.jinja +1 -0
- mkdocstrings_handlers/nim/templates/material/docstring/functions.html.jinja +1 -0
- mkdocstrings_handlers/nim/templates/material/docstring/modules.html.jinja +1 -0
- mkdocstrings_handlers/nim/templates/material/docstring/other_parameters.html.jinja +1 -0
- mkdocstrings_handlers/nim/templates/material/docstring/parameters.html.jinja +1 -0
- mkdocstrings_handlers/nim/templates/material/docstring/raises.html.jinja +1 -0
- mkdocstrings_handlers/nim/templates/material/docstring/receives.html.jinja +1 -0
- mkdocstrings_handlers/nim/templates/material/docstring/returns.html.jinja +1 -0
- mkdocstrings_handlers/nim/templates/material/docstring/type_aliases.html.jinja +1 -0
- mkdocstrings_handlers/nim/templates/material/docstring/type_parameters.html.jinja +1 -0
- mkdocstrings_handlers/nim/templates/material/docstring/warns.html.jinja +1 -0
- mkdocstrings_handlers/nim/templates/material/docstring/yields.html.jinja +1 -0
- mkdocstrings_handlers/nim/templates/material/docstring.html.jinja +1 -0
- mkdocstrings_handlers/nim/templates/material/expression.html.jinja +1 -0
- mkdocstrings_handlers/nim/templates/material/func.html.jinja +1 -0
- mkdocstrings_handlers/nim/templates/material/iterator.html.jinja +1 -0
- mkdocstrings_handlers/nim/templates/material/labels.html.jinja +1 -0
- mkdocstrings_handlers/nim/templates/material/language.html.jinja +1 -0
- mkdocstrings_handlers/nim/templates/material/languages/en.html.jinja +1 -0
- mkdocstrings_handlers/nim/templates/material/languages/ja.html.jinja +1 -0
- mkdocstrings_handlers/nim/templates/material/languages/zh.html.jinja +1 -0
- mkdocstrings_handlers/nim/templates/material/macro.html.jinja +1 -0
- mkdocstrings_handlers/nim/templates/material/module.html.jinja +1 -0
- mkdocstrings_handlers/nim/templates/material/proc.html.jinja +1 -0
- mkdocstrings_handlers/nim/templates/material/signature.html.jinja +1 -0
- mkdocstrings_handlers/nim/templates/material/style.css +236 -0
- mkdocstrings_handlers/nim/templates/material/summary/attributes.html.jinja +1 -0
- mkdocstrings_handlers/nim/templates/material/summary/classes.html.jinja +1 -0
- mkdocstrings_handlers/nim/templates/material/summary/functions.html.jinja +1 -0
- mkdocstrings_handlers/nim/templates/material/summary/modules.html.jinja +1 -0
- mkdocstrings_handlers/nim/templates/material/summary/type_aliases.html.jinja +1 -0
- mkdocstrings_handlers/nim/templates/material/summary.html.jinja +1 -0
- mkdocstrings_handlers/nim/templates/material/template.html.jinja +1 -0
- mkdocstrings_handlers/nim/templates/material/type.html.jinja +1 -0
- mkdocstrings_handlers/nim/templates/material/type_alias.html.jinja +1 -0
- mkdocstrings_handlers/nim/templates/material/type_parameters.html.jinja +1 -0
- mkdocstrings_nim-0.1.0.dist-info/METADATA +116 -0
- mkdocstrings_nim-0.1.0.dist-info/RECORD +99 -0
- mkdocstrings_nim-0.1.0.dist-info/WHEEL +4 -0
- mkdocstrings_nim-0.1.0.dist-info/entry_points.txt +2 -0
- mkdocstrings_nim-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""Nim handler for mkdocstrings."""
|
|
2
|
+
|
|
3
|
+
from mkdocstrings_handlers.nim.handler import NimHandler, get_handler
|
|
4
|
+
from mkdocstrings_handlers.nim.collector import NimCollector, NimModule, NimEntry, NimParam
|
|
5
|
+
from mkdocstrings_handlers.nim.docstring import (
|
|
6
|
+
parse_docstring,
|
|
7
|
+
DocstringStyle,
|
|
8
|
+
ParsedDocstring,
|
|
9
|
+
ParamDoc,
|
|
10
|
+
ReturnsDoc,
|
|
11
|
+
RaisesDoc,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
__all__ = [
|
|
15
|
+
"NimHandler",
|
|
16
|
+
"get_handler",
|
|
17
|
+
"NimCollector",
|
|
18
|
+
"NimModule",
|
|
19
|
+
"NimEntry",
|
|
20
|
+
"NimParam",
|
|
21
|
+
"parse_docstring",
|
|
22
|
+
"DocstringStyle",
|
|
23
|
+
"ParsedDocstring",
|
|
24
|
+
"ParamDoc",
|
|
25
|
+
"ReturnsDoc",
|
|
26
|
+
"RaisesDoc",
|
|
27
|
+
]
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
"""Collector for Nim documentation."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import shutil
|
|
7
|
+
import subprocess
|
|
8
|
+
import sys
|
|
9
|
+
import tempfile
|
|
10
|
+
from dataclasses import dataclass, field
|
|
11
|
+
from importlib.resources import files, as_file
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Any
|
|
14
|
+
|
|
15
|
+
from mkdocstrings import CollectionError
|
|
16
|
+
|
|
17
|
+
# Cache directory for compiled nimdocinfo binary
|
|
18
|
+
_CACHE_DIR = Path(tempfile.gettempdir()) / "mkdocstrings-nim-cache"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass
|
|
22
|
+
class NimParam:
|
|
23
|
+
"""A Nim parameter."""
|
|
24
|
+
name: str
|
|
25
|
+
type: str
|
|
26
|
+
description: str = ""
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass
|
|
30
|
+
class NimEntry:
|
|
31
|
+
"""A documented Nim entry (proc, type, const, etc.)."""
|
|
32
|
+
name: str
|
|
33
|
+
kind: str
|
|
34
|
+
line: int
|
|
35
|
+
signature: str
|
|
36
|
+
doc: str = ""
|
|
37
|
+
params: list[NimParam] = field(default_factory=list)
|
|
38
|
+
returns: str = ""
|
|
39
|
+
returns_doc: str = ""
|
|
40
|
+
pragmas: list[str] = field(default_factory=list)
|
|
41
|
+
raises: list[str] = field(default_factory=list)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@dataclass
|
|
45
|
+
class NimModule:
|
|
46
|
+
"""A documented Nim module."""
|
|
47
|
+
module: str
|
|
48
|
+
file: str
|
|
49
|
+
doc: str = ""
|
|
50
|
+
entries: list[NimEntry] = field(default_factory=list)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class NimCollector:
|
|
54
|
+
"""Collects documentation from Nim source files."""
|
|
55
|
+
|
|
56
|
+
def __init__(self, paths: list[str], base_dir: Path):
|
|
57
|
+
"""Initialize the collector.
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
paths: Search paths for Nim source files.
|
|
61
|
+
base_dir: Base directory of the project.
|
|
62
|
+
"""
|
|
63
|
+
self.paths = paths
|
|
64
|
+
self.base_dir = base_dir
|
|
65
|
+
self._cache: dict[str, NimModule] = {}
|
|
66
|
+
# Use importlib.resources for reliable path resolution
|
|
67
|
+
extractor_files = files("mkdocstrings_handlers.nim").joinpath("extractor")
|
|
68
|
+
self._nimdocinfo_source = extractor_files.joinpath("nimdocinfo.nim")
|
|
69
|
+
|
|
70
|
+
def _resolve_identifier(self, identifier: str) -> Path:
|
|
71
|
+
"""Resolve a module identifier to a file path.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
identifier: Module identifier like 'lockfreequeues.ops'
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
Path to the Nim source file.
|
|
78
|
+
|
|
79
|
+
Raises:
|
|
80
|
+
CollectionError: If the file cannot be found.
|
|
81
|
+
"""
|
|
82
|
+
# Convert dots to path separators
|
|
83
|
+
rel_path = identifier.replace(".", "/") + ".nim"
|
|
84
|
+
|
|
85
|
+
for search_path in self.paths:
|
|
86
|
+
full_path = self.base_dir / search_path / rel_path
|
|
87
|
+
if full_path.exists():
|
|
88
|
+
return full_path
|
|
89
|
+
|
|
90
|
+
# Try without nested path (just filename)
|
|
91
|
+
filename = identifier.split(".")[-1] + ".nim"
|
|
92
|
+
for search_path in self.paths:
|
|
93
|
+
full_path = self.base_dir / search_path / filename
|
|
94
|
+
if full_path.exists():
|
|
95
|
+
return full_path
|
|
96
|
+
|
|
97
|
+
raise CollectionError(f"Could not find Nim file for identifier: {identifier}")
|
|
98
|
+
|
|
99
|
+
def _ensure_nimdocinfo_compiled(self) -> Path:
|
|
100
|
+
"""Ensure nimdocinfo is compiled and return path to binary.
|
|
101
|
+
|
|
102
|
+
Copies Nim source files to a cache directory and compiles them there.
|
|
103
|
+
This avoids writing to the installed package directory.
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
Path to the compiled nimdocinfo binary.
|
|
107
|
+
|
|
108
|
+
Raises:
|
|
109
|
+
CollectionError: If compilation fails.
|
|
110
|
+
"""
|
|
111
|
+
_CACHE_DIR.mkdir(parents=True, exist_ok=True)
|
|
112
|
+
|
|
113
|
+
# Get the extractor package files
|
|
114
|
+
extractor_pkg = files("mkdocstrings_handlers.nim").joinpath("extractor")
|
|
115
|
+
|
|
116
|
+
# Copy source files to cache if needed
|
|
117
|
+
with as_file(extractor_pkg.joinpath("nimdocinfo.nim")) as src_main:
|
|
118
|
+
with as_file(extractor_pkg.joinpath("extractor.nim")) as src_extractor:
|
|
119
|
+
cache_main = _CACHE_DIR / "nimdocinfo.nim"
|
|
120
|
+
cache_extractor = _CACHE_DIR / "extractor.nim"
|
|
121
|
+
cache_binary = _CACHE_DIR / "nimdocinfo"
|
|
122
|
+
|
|
123
|
+
# Check if we need to recompile (source newer than binary)
|
|
124
|
+
needs_compile = not cache_binary.exists()
|
|
125
|
+
if not needs_compile:
|
|
126
|
+
binary_mtime = cache_binary.stat().st_mtime
|
|
127
|
+
if src_main.stat().st_mtime > binary_mtime:
|
|
128
|
+
needs_compile = True
|
|
129
|
+
elif src_extractor.stat().st_mtime > binary_mtime:
|
|
130
|
+
needs_compile = True
|
|
131
|
+
|
|
132
|
+
if needs_compile:
|
|
133
|
+
# Copy source files
|
|
134
|
+
shutil.copy2(src_main, cache_main)
|
|
135
|
+
shutil.copy2(src_extractor, cache_extractor)
|
|
136
|
+
|
|
137
|
+
# Compile (first run is slow, ~15s; subsequent runs use cache)
|
|
138
|
+
result = subprocess.run(
|
|
139
|
+
["nim", "c", "--outdir:" + str(_CACHE_DIR), str(cache_main)],
|
|
140
|
+
capture_output=True,
|
|
141
|
+
text=True,
|
|
142
|
+
timeout=120, # First compile can be slow
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
if result.returncode != 0:
|
|
146
|
+
raise CollectionError(
|
|
147
|
+
f"Failed to compile nimdocinfo:\n{result.stderr}"
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
return cache_binary
|
|
151
|
+
|
|
152
|
+
def _run_nimdocinfo(self, filepath: Path) -> dict[str, Any]:
|
|
153
|
+
"""Run nimdocinfo on a Nim file.
|
|
154
|
+
|
|
155
|
+
Args:
|
|
156
|
+
filepath: Path to the Nim source file.
|
|
157
|
+
|
|
158
|
+
Returns:
|
|
159
|
+
Parsed JSON output from nimdocinfo.
|
|
160
|
+
|
|
161
|
+
Raises:
|
|
162
|
+
CollectionError: If nimdocinfo fails.
|
|
163
|
+
"""
|
|
164
|
+
try:
|
|
165
|
+
binary_path = self._ensure_nimdocinfo_compiled()
|
|
166
|
+
|
|
167
|
+
result = subprocess.run(
|
|
168
|
+
[str(binary_path), str(filepath)],
|
|
169
|
+
capture_output=True,
|
|
170
|
+
text=True,
|
|
171
|
+
cwd=str(self.base_dir),
|
|
172
|
+
timeout=60,
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
if result.returncode != 0:
|
|
176
|
+
raise CollectionError(
|
|
177
|
+
f"nimdocinfo failed:\n{result.stderr}\n\n"
|
|
178
|
+
f"To debug, run manually:\n"
|
|
179
|
+
f" {binary_path} {filepath}"
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
# Parse JSON from stdout
|
|
183
|
+
lines = result.stdout.strip().split("\n")
|
|
184
|
+
json_lines = []
|
|
185
|
+
in_json = False
|
|
186
|
+
for line in lines:
|
|
187
|
+
if line.strip().startswith("{"):
|
|
188
|
+
in_json = True
|
|
189
|
+
if in_json:
|
|
190
|
+
json_lines.append(line)
|
|
191
|
+
if in_json and line.strip().startswith("}") and len(json_lines) > 1:
|
|
192
|
+
try:
|
|
193
|
+
return json.loads("\n".join(json_lines))
|
|
194
|
+
except json.JSONDecodeError:
|
|
195
|
+
continue
|
|
196
|
+
|
|
197
|
+
if not json_lines:
|
|
198
|
+
raise CollectionError(f"No JSON output from nimdocinfo: {result.stdout}")
|
|
199
|
+
|
|
200
|
+
return json.loads("\n".join(json_lines))
|
|
201
|
+
|
|
202
|
+
except FileNotFoundError:
|
|
203
|
+
raise CollectionError(
|
|
204
|
+
"Nim compiler not found. Install from https://nim-lang.org/install.html\n"
|
|
205
|
+
"Then verify installation: nim --version"
|
|
206
|
+
)
|
|
207
|
+
except subprocess.TimeoutExpired:
|
|
208
|
+
raise CollectionError(
|
|
209
|
+
f"nimdocinfo timed out processing {filepath}. "
|
|
210
|
+
"The file may be too complex or have circular imports."
|
|
211
|
+
)
|
|
212
|
+
except json.JSONDecodeError as e:
|
|
213
|
+
raise CollectionError(f"Invalid JSON from nimdocinfo: {e}")
|
|
214
|
+
|
|
215
|
+
def _parse_module(self, data: dict[str, Any]) -> NimModule:
|
|
216
|
+
"""Parse JSON data into NimModule.
|
|
217
|
+
|
|
218
|
+
Args:
|
|
219
|
+
data: JSON data from nimdocinfo.
|
|
220
|
+
|
|
221
|
+
Returns:
|
|
222
|
+
Parsed NimModule.
|
|
223
|
+
"""
|
|
224
|
+
entries = []
|
|
225
|
+
for entry_data in data.get("entries", []):
|
|
226
|
+
params = [
|
|
227
|
+
NimParam(name=p["name"], type=p["type"])
|
|
228
|
+
for p in entry_data.get("params", [])
|
|
229
|
+
]
|
|
230
|
+
|
|
231
|
+
entries.append(NimEntry(
|
|
232
|
+
name=entry_data["name"],
|
|
233
|
+
kind=entry_data["kind"],
|
|
234
|
+
line=entry_data["line"],
|
|
235
|
+
signature=entry_data["signature"],
|
|
236
|
+
doc=entry_data.get("doc", ""),
|
|
237
|
+
params=params,
|
|
238
|
+
returns=entry_data.get("returns", ""),
|
|
239
|
+
pragmas=entry_data.get("pragmas", []),
|
|
240
|
+
raises=entry_data.get("raises", []),
|
|
241
|
+
))
|
|
242
|
+
|
|
243
|
+
return NimModule(
|
|
244
|
+
module=data["module"],
|
|
245
|
+
file=data["file"],
|
|
246
|
+
doc=data.get("doc", ""),
|
|
247
|
+
entries=entries,
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
def collect(self, identifier: str) -> NimModule:
|
|
251
|
+
"""Collect documentation for a module identifier.
|
|
252
|
+
|
|
253
|
+
Args:
|
|
254
|
+
identifier: Module identifier like 'lockfreequeues.ops'
|
|
255
|
+
|
|
256
|
+
Returns:
|
|
257
|
+
NimModule with documentation.
|
|
258
|
+
"""
|
|
259
|
+
if identifier in self._cache:
|
|
260
|
+
return self._cache[identifier]
|
|
261
|
+
|
|
262
|
+
filepath = self._resolve_identifier(identifier)
|
|
263
|
+
data = self._run_nimdocinfo(filepath)
|
|
264
|
+
module = self._parse_module(data)
|
|
265
|
+
|
|
266
|
+
self._cache[identifier] = module
|
|
267
|
+
return module
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"""Docstring parsing for Nim documentation."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from enum import Enum
|
|
7
|
+
from typing import Optional
|
|
8
|
+
|
|
9
|
+
import docstring_parser
|
|
10
|
+
from docstring_parser import DocstringStyle as DPStyle
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class DocstringStyle(Enum):
|
|
14
|
+
"""Supported docstring styles."""
|
|
15
|
+
RST = "rst"
|
|
16
|
+
GOOGLE = "google"
|
|
17
|
+
NUMPY = "numpy"
|
|
18
|
+
EPYDOC = "epydoc"
|
|
19
|
+
AUTO = "auto"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass
|
|
23
|
+
class ParamDoc:
|
|
24
|
+
"""Parsed parameter documentation."""
|
|
25
|
+
name: str
|
|
26
|
+
description: str = ""
|
|
27
|
+
type: str = ""
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass
|
|
31
|
+
class ReturnsDoc:
|
|
32
|
+
"""Parsed return value documentation."""
|
|
33
|
+
description: str = ""
|
|
34
|
+
type: str = ""
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@dataclass
|
|
38
|
+
class RaisesDoc:
|
|
39
|
+
"""Parsed raises documentation."""
|
|
40
|
+
type: str
|
|
41
|
+
description: str = ""
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@dataclass
|
|
45
|
+
class ParsedDocstring:
|
|
46
|
+
"""Parsed docstring with structured sections."""
|
|
47
|
+
description: str = ""
|
|
48
|
+
params: list[ParamDoc] = field(default_factory=list)
|
|
49
|
+
returns: Optional[ReturnsDoc] = None
|
|
50
|
+
raises: list[RaisesDoc] = field(default_factory=list)
|
|
51
|
+
examples: list[str] = field(default_factory=list)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
# Map our style enum to docstring_parser's style enum
|
|
55
|
+
_STYLE_MAP = {
|
|
56
|
+
DocstringStyle.RST: DPStyle.REST,
|
|
57
|
+
DocstringStyle.GOOGLE: DPStyle.GOOGLE,
|
|
58
|
+
DocstringStyle.NUMPY: DPStyle.NUMPYDOC,
|
|
59
|
+
DocstringStyle.EPYDOC: DPStyle.EPYDOC,
|
|
60
|
+
DocstringStyle.AUTO: DPStyle.AUTO,
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def parse_docstring(doc: str, style: DocstringStyle = DocstringStyle.RST) -> ParsedDocstring:
|
|
65
|
+
"""Parse a docstring according to the specified style.
|
|
66
|
+
|
|
67
|
+
Uses the docstring_parser library for robust parsing of RST, Google,
|
|
68
|
+
and NumPy docstring formats.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
doc: Raw docstring text.
|
|
72
|
+
style: Docstring style to use for parsing.
|
|
73
|
+
|
|
74
|
+
Returns:
|
|
75
|
+
Parsed docstring structure.
|
|
76
|
+
"""
|
|
77
|
+
if not doc:
|
|
78
|
+
return ParsedDocstring()
|
|
79
|
+
|
|
80
|
+
dp_style = _STYLE_MAP.get(style, DPStyle.REST)
|
|
81
|
+
|
|
82
|
+
try:
|
|
83
|
+
parsed = docstring_parser.parse(doc, style=dp_style)
|
|
84
|
+
except Exception:
|
|
85
|
+
# Fall back to auto-detection if specified style fails
|
|
86
|
+
try:
|
|
87
|
+
parsed = docstring_parser.parse(doc)
|
|
88
|
+
except Exception:
|
|
89
|
+
# If all parsing fails, just return description
|
|
90
|
+
return ParsedDocstring(description=doc.strip())
|
|
91
|
+
|
|
92
|
+
result = ParsedDocstring()
|
|
93
|
+
|
|
94
|
+
# Extract description (short + long)
|
|
95
|
+
desc_parts = []
|
|
96
|
+
if parsed.short_description:
|
|
97
|
+
desc_parts.append(parsed.short_description)
|
|
98
|
+
if parsed.long_description:
|
|
99
|
+
desc_parts.append(parsed.long_description)
|
|
100
|
+
result.description = "\n\n".join(desc_parts)
|
|
101
|
+
|
|
102
|
+
# Extract parameters
|
|
103
|
+
for param in parsed.params:
|
|
104
|
+
result.params.append(ParamDoc(
|
|
105
|
+
name=param.arg_name,
|
|
106
|
+
description=param.description or "",
|
|
107
|
+
type=param.type_name or "",
|
|
108
|
+
))
|
|
109
|
+
|
|
110
|
+
# Extract returns
|
|
111
|
+
if parsed.returns:
|
|
112
|
+
result.returns = ReturnsDoc(
|
|
113
|
+
description=parsed.returns.description or "",
|
|
114
|
+
type=parsed.returns.type_name or "",
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
# Extract raises
|
|
118
|
+
for raises in parsed.raises:
|
|
119
|
+
result.raises.append(RaisesDoc(
|
|
120
|
+
type=raises.type_name or "",
|
|
121
|
+
description=raises.description or "",
|
|
122
|
+
))
|
|
123
|
+
|
|
124
|
+
# Extract examples
|
|
125
|
+
for example in parsed.examples:
|
|
126
|
+
if example.description:
|
|
127
|
+
result.examples.append(example.description)
|
|
128
|
+
|
|
129
|
+
return result
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
## AST extraction logic for nimdocinfo
|
|
2
|
+
import std/[json, strutils, sequtils, os]
|
|
3
|
+
import compiler/[ast, parser, idents, options, pathutils, lineinfos, msgs, renderer, llstream]
|
|
4
|
+
|
|
5
|
+
type
|
|
6
|
+
DocEntry* = object
|
|
7
|
+
name*: string
|
|
8
|
+
kind*: string
|
|
9
|
+
line*: int
|
|
10
|
+
signature*: string
|
|
11
|
+
params*: seq[tuple[name, typ: string]]
|
|
12
|
+
returns*: string
|
|
13
|
+
pragmas*: seq[string]
|
|
14
|
+
raises*: seq[string]
|
|
15
|
+
doc*: string
|
|
16
|
+
|
|
17
|
+
ModuleDoc* = object
|
|
18
|
+
module*: string
|
|
19
|
+
file*: string
|
|
20
|
+
doc*: string
|
|
21
|
+
entries*: seq[DocEntry]
|
|
22
|
+
|
|
23
|
+
proc extractDocComment(n: PNode): string =
|
|
24
|
+
## Extract doc comment from a node
|
|
25
|
+
if n == nil:
|
|
26
|
+
return ""
|
|
27
|
+
if n.comment.len > 0:
|
|
28
|
+
return n.comment.strip
|
|
29
|
+
return ""
|
|
30
|
+
|
|
31
|
+
proc extractPragmas(n: PNode): seq[string] =
|
|
32
|
+
## Extract pragma names from a pragma node
|
|
33
|
+
result = @[]
|
|
34
|
+
if n == nil or n.kind != nkPragma:
|
|
35
|
+
return
|
|
36
|
+
for child in n:
|
|
37
|
+
if child.kind == nkIdent:
|
|
38
|
+
result.add($child.ident.s)
|
|
39
|
+
elif child.kind == nkExprColonExpr and child[0].kind == nkIdent:
|
|
40
|
+
result.add($child[0].ident.s)
|
|
41
|
+
|
|
42
|
+
proc extractRaises(n: PNode): seq[string] =
|
|
43
|
+
## Extract exception types from raises pragma
|
|
44
|
+
## e.g., {.raises: [ValueError, IOError].}
|
|
45
|
+
result = @[]
|
|
46
|
+
if n == nil or n.kind != nkPragma:
|
|
47
|
+
return
|
|
48
|
+
for child in n:
|
|
49
|
+
if child.kind == nkExprColonExpr and child.len >= 2:
|
|
50
|
+
if child[0].kind == nkIdent and $child[0].ident.s == "raises":
|
|
51
|
+
let bracket = child[1]
|
|
52
|
+
if bracket.kind == nkBracket:
|
|
53
|
+
for exc in bracket:
|
|
54
|
+
if exc.kind == nkIdent:
|
|
55
|
+
result.add($exc.ident.s)
|
|
56
|
+
elif exc.kind == nkDotExpr:
|
|
57
|
+
# Handle qualified names like system.Exception
|
|
58
|
+
result.add($exc)
|
|
59
|
+
|
|
60
|
+
proc extractParams(n: PNode): seq[tuple[name, typ: string]] =
|
|
61
|
+
## Extract parameter list from formal params
|
|
62
|
+
result = @[]
|
|
63
|
+
if n == nil or n.kind != nkFormalParams:
|
|
64
|
+
return
|
|
65
|
+
# Skip first child (return type)
|
|
66
|
+
for i in 1..<n.len:
|
|
67
|
+
let param = n[i]
|
|
68
|
+
if param.kind == nkIdentDefs:
|
|
69
|
+
let typNode = param[^2]
|
|
70
|
+
let typStr = if typNode.kind == nkEmpty: "auto" else: $typNode
|
|
71
|
+
# All names except last two (type and default)
|
|
72
|
+
for j in 0..<param.len - 2:
|
|
73
|
+
if param[j].kind == nkIdent:
|
|
74
|
+
result.add(($param[j].ident.s, typStr))
|
|
75
|
+
|
|
76
|
+
proc extractReturnType(n: PNode): string =
|
|
77
|
+
## Extract return type from formal params
|
|
78
|
+
if n == nil or n.kind != nkFormalParams or n.len == 0:
|
|
79
|
+
return ""
|
|
80
|
+
let retNode = n[0]
|
|
81
|
+
if retNode.kind == nkEmpty:
|
|
82
|
+
return ""
|
|
83
|
+
return $retNode
|
|
84
|
+
|
|
85
|
+
const
|
|
86
|
+
# Nim AST indices for routine (proc/func/etc) nodes
|
|
87
|
+
routineNameIdx = 0
|
|
88
|
+
routinePatternIdx = 1
|
|
89
|
+
routineGenericParamsIdx = 2
|
|
90
|
+
routineParamsIdx = 3
|
|
91
|
+
routinePragmasIdx = 4
|
|
92
|
+
routineBodyIdx = 6
|
|
93
|
+
|
|
94
|
+
proc renderSignature(n: PNode, kind: string, name: string): string =
|
|
95
|
+
## Render full signature as string
|
|
96
|
+
result = kind & " " & name
|
|
97
|
+
if n.len > routineParamsIdx and n[routineParamsIdx].kind == nkFormalParams:
|
|
98
|
+
let params = n[routineParamsIdx]
|
|
99
|
+
var paramStrs: seq[string] = @[]
|
|
100
|
+
for i in 1..<params.len:
|
|
101
|
+
paramStrs.add($params[i])
|
|
102
|
+
result &= "(" & paramStrs.join("; ") & ")"
|
|
103
|
+
let ret = extractReturnType(n[routineParamsIdx])
|
|
104
|
+
if ret.len > 0:
|
|
105
|
+
result &= ": " & ret
|
|
106
|
+
|
|
107
|
+
proc extractName(n: PNode): string =
|
|
108
|
+
## Extract name from a name node (handles postfix for exported symbols)
|
|
109
|
+
if n.kind == nkIdent:
|
|
110
|
+
return $n.ident.s
|
|
111
|
+
elif n.kind == nkPostfix and n.len >= 2 and n[1].kind == nkIdent:
|
|
112
|
+
return $n[1].ident.s
|
|
113
|
+
elif n.kind == nkPostfix and n.len >= 2 and n[1].kind == nkAccQuoted:
|
|
114
|
+
# Handle quoted operators like `+`*
|
|
115
|
+
if n[1].len > 0 and n[1][0].kind == nkIdent:
|
|
116
|
+
return $n[1][0].ident.s
|
|
117
|
+
return ""
|
|
118
|
+
|
|
119
|
+
proc extractProcDoc(n: PNode): string =
|
|
120
|
+
## Extract doc comment from a proc definition
|
|
121
|
+
## The doc comment can be on the proc node itself or on the first statement in the body
|
|
122
|
+
result = extractDocComment(n)
|
|
123
|
+
if result.len == 0 and n.len > routineBodyIdx:
|
|
124
|
+
let body = n[routineBodyIdx]
|
|
125
|
+
if body != nil and body.kind == nkStmtList and body.len > 0:
|
|
126
|
+
# Check if first statement has a comment
|
|
127
|
+
result = extractDocComment(body[0])
|
|
128
|
+
if result.len == 0 and body[0].kind == nkCommentStmt:
|
|
129
|
+
result = body[0].comment.strip
|
|
130
|
+
|
|
131
|
+
proc extractProc(n: PNode, kind: string): DocEntry =
|
|
132
|
+
## Extract documentation from a proc/func/etc definition
|
|
133
|
+
result.name = extractName(n[routineNameIdx])
|
|
134
|
+
result.kind = kind
|
|
135
|
+
result.line = n.info.line.int
|
|
136
|
+
result.doc = extractProcDoc(n)
|
|
137
|
+
|
|
138
|
+
if n.len > routineParamsIdx:
|
|
139
|
+
result.params = extractParams(n[routineParamsIdx])
|
|
140
|
+
result.returns = extractReturnType(n[routineParamsIdx])
|
|
141
|
+
|
|
142
|
+
# Extract pragmas at the correct index
|
|
143
|
+
if n.len > routinePragmasIdx and n[routinePragmasIdx].kind == nkPragma:
|
|
144
|
+
result.pragmas = extractPragmas(n[routinePragmasIdx])
|
|
145
|
+
result.raises = extractRaises(n[routinePragmasIdx])
|
|
146
|
+
|
|
147
|
+
result.signature = renderSignature(n, kind, result.name)
|
|
148
|
+
|
|
149
|
+
proc extractType(n: PNode): DocEntry =
|
|
150
|
+
## Extract documentation from a type definition
|
|
151
|
+
result.kind = "type"
|
|
152
|
+
result.doc = extractDocComment(n)
|
|
153
|
+
result.line = n.info.line.int
|
|
154
|
+
result.name = extractName(n[0])
|
|
155
|
+
result.signature = "type " & result.name
|
|
156
|
+
|
|
157
|
+
proc extractConst(n: PNode): DocEntry =
|
|
158
|
+
## Extract documentation from a const definition
|
|
159
|
+
result.kind = "const"
|
|
160
|
+
result.doc = extractDocComment(n)
|
|
161
|
+
result.line = n.info.line.int
|
|
162
|
+
result.name = extractName(n[0])
|
|
163
|
+
result.signature = "const " & result.name
|
|
164
|
+
|
|
165
|
+
proc walkAst(n: PNode, entries: var seq[DocEntry]) =
|
|
166
|
+
## Walk AST and collect documentation entries
|
|
167
|
+
if n == nil:
|
|
168
|
+
return
|
|
169
|
+
|
|
170
|
+
case n.kind
|
|
171
|
+
of nkProcDef:
|
|
172
|
+
entries.add extractProc(n, "proc")
|
|
173
|
+
of nkFuncDef:
|
|
174
|
+
entries.add extractProc(n, "func")
|
|
175
|
+
of nkIteratorDef:
|
|
176
|
+
entries.add extractProc(n, "iterator")
|
|
177
|
+
of nkTemplateDef:
|
|
178
|
+
entries.add extractProc(n, "template")
|
|
179
|
+
of nkMacroDef:
|
|
180
|
+
entries.add extractProc(n, "macro")
|
|
181
|
+
of nkTypeDef:
|
|
182
|
+
entries.add extractType(n)
|
|
183
|
+
of nkConstDef:
|
|
184
|
+
entries.add extractConst(n)
|
|
185
|
+
else:
|
|
186
|
+
for child in n:
|
|
187
|
+
walkAst(child, entries)
|
|
188
|
+
|
|
189
|
+
proc extractModule*(filepath: string): ModuleDoc =
|
|
190
|
+
## Extract all documentation from a Nim source file
|
|
191
|
+
result.file = filepath
|
|
192
|
+
result.module = filepath.splitFile.name
|
|
193
|
+
result.entries = @[]
|
|
194
|
+
|
|
195
|
+
# Parse the file
|
|
196
|
+
var conf = newConfigRef()
|
|
197
|
+
conf.verbosity = 0
|
|
198
|
+
|
|
199
|
+
let fileIdx = fileInfoIdx(conf, AbsoluteFile(filepath))
|
|
200
|
+
var parser: Parser
|
|
201
|
+
|
|
202
|
+
let source = readFile(filepath)
|
|
203
|
+
openParser(parser, fileIdx, llStreamOpen(source), newIdentCache(), conf)
|
|
204
|
+
|
|
205
|
+
let ast = parseAll(parser)
|
|
206
|
+
closeParser(parser)
|
|
207
|
+
|
|
208
|
+
# Extract module doc comment
|
|
209
|
+
if ast.len > 0 and ast[0].comment.len > 0:
|
|
210
|
+
result.doc = ast[0].comment.strip
|
|
211
|
+
|
|
212
|
+
walkAst(ast, result.entries)
|
|
213
|
+
|
|
214
|
+
proc toJson*(doc: ModuleDoc): JsonNode =
|
|
215
|
+
## Convert module documentation to JSON
|
|
216
|
+
result = %*{
|
|
217
|
+
"module": doc.module,
|
|
218
|
+
"file": doc.file,
|
|
219
|
+
"doc": doc.doc,
|
|
220
|
+
"entries": []
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
for entry in doc.entries:
|
|
224
|
+
var entryJson = %*{
|
|
225
|
+
"name": entry.name,
|
|
226
|
+
"kind": entry.kind,
|
|
227
|
+
"line": entry.line,
|
|
228
|
+
"signature": entry.signature,
|
|
229
|
+
"doc": entry.doc
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if entry.params.len > 0:
|
|
233
|
+
entryJson["params"] = %entry.params.mapIt(%*{"name": it.name, "type": it.typ})
|
|
234
|
+
|
|
235
|
+
if entry.returns.len > 0:
|
|
236
|
+
entryJson["returns"] = %entry.returns
|
|
237
|
+
|
|
238
|
+
if entry.pragmas.len > 0:
|
|
239
|
+
entryJson["pragmas"] = %entry.pragmas
|
|
240
|
+
|
|
241
|
+
if entry.raises.len > 0:
|
|
242
|
+
entryJson["raises"] = %entry.raises
|
|
243
|
+
|
|
244
|
+
result["entries"].add entryJson
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
## nimdocinfo - Extract documentation from Nim source files
|
|
2
|
+
import std/[json, os]
|
|
3
|
+
import extractor
|
|
4
|
+
|
|
5
|
+
when isMainModule:
|
|
6
|
+
if paramCount() < 1:
|
|
7
|
+
echo "Usage: nimdocinfo <file.nim>"
|
|
8
|
+
quit(1)
|
|
9
|
+
|
|
10
|
+
let filepath = paramStr(1)
|
|
11
|
+
if not fileExists(filepath):
|
|
12
|
+
echo "Error: File not found: ", filepath
|
|
13
|
+
quit(1)
|
|
14
|
+
|
|
15
|
+
let doc = extractModule(filepath)
|
|
16
|
+
echo doc.toJson.pretty
|