PICurv 0.1.0
A Parallel Particle-In-Cell Solver for Curvilinear LES
Loading...
Searching...
No Matches
Data Structures | Functions | Variables
audit_function_docs Namespace Reference

Data Structures

class  AuditFinding
 Represents one audit failure. More...
 

Functions

list[Path] _iter_c_files (tuple[Path,...] directories)
 Returns all C or header files below the configured directories.
 
list[Path] _iter_python_files ()
 Returns all Python source files covered by the audit.
 
list[str] _read_lines (Path path)
 Reads a text file into a list of lines.
 
str _relative_path (Path path)
 Returns a repository-relative path string.
 
tuple[int, int]|None _find_attached_doxygen_block (list[str] lines, int start_line)
 Finds the Doxygen block immediately attached to a declaration or definition.
 
list[str] _split_c_parameters (str signature)
 Splits a C signature parameter list into parameter names.
 
str _c_return_type (str signature, str symbol)
 Extracts the declared C return type prefix for one signature.
 
bool _return_tag_required (str return_type)
 Reports whether a Doxygen @return tag is required for a C symbol.
 
list[tuple[int, str, str]] _collect_c_signatures (Path path, str require_terminator)
 Collects C signatures from a header or source file.
 
list[AuditFinding_audit_c_header (Path path)
 Audits public C declarations in one header file.
 
list[AuditFinding_audit_c_source (Path path)
 Audits function definitions in one C source file.
 
list[str] _python_parameter_names (ast.FunctionDef|ast.AsyncFunctionDef node)
 Returns the meaningful Python parameter names for one function node.
 
bool _python_requires_return (ast.FunctionDef|ast.AsyncFunctionDef node)
 Reports whether one Python function should document a return value.
 
list[AuditFinding_audit_python_file (Path path)
 Audits Python function docstrings in one file.
 
list[AuditFinding_collect_findings ()
 Runs the full repository documentation audit.
 
None _print_findings (list[AuditFinding] findings)
 Prints findings in a grep-friendly format.
 
int main ()
 Runs the repository function documentation audit from the command line.
 

Variables

 REPO_ROOT = Path(__file__).resolve().parents[2]
 
tuple C_HEADER_DIRS = (REPO_ROOT / "include",)
 
tuple C_SOURCE_DIRS = (REPO_ROOT / "src", REPO_ROOT / "tests" / "c")
 
tuple PYTHON_DIRS = (REPO_ROOT / "picurv_cli", REPO_ROOT / "tests")
 
tuple PYTHON_EXTRA_FILES
 
 C_DECL_START_RE
 
 C_PARAM_RE = re.compile(r"@param(?:\[[^\]]+\])?\s+([A-Za-z_][A-Za-z0-9_]*)")
 

Function Documentation

◆ _iter_c_files()

list[Path] audit_function_docs._iter_c_files ( tuple[Path, ...]  directories)
protected

Returns all C or header files below the configured directories.

Parameters
[in]directoriesRoot directories to scan.
Returns
Sorted list of matching file paths.

Definition at line 63 of file audit_function_docs.py.

63def _iter_c_files(directories: tuple[Path, ...]) -> list[Path]:
64 """!
65 @brief Returns all C or header files below the configured directories.
66 @param[in] directories Root directories to scan.
67 @return Sorted list of matching file paths.
68 """
69
70 files: list[Path] = []
71 for directory in directories:
72 if not directory.exists():
73 continue
74 files.extend(sorted(path for path in directory.rglob("*") if path.suffix in {".c", ".h"}))
75 return sorted(files)
76
77
Here is the caller graph for this function:

◆ _iter_python_files()

list[Path] audit_function_docs._iter_python_files ( )
protected

Returns all Python source files covered by the audit.

Returns
Sorted list of Python-backed source files.

Definition at line 78 of file audit_function_docs.py.

78def _iter_python_files() -> list[Path]:
79 """!
80 @brief Returns all Python source files covered by the audit.
81 @return Sorted list of Python-backed source files.
82 """
83
84 files: set[Path] = set()
85 for directory in PYTHON_DIRS:
86 if not directory.exists():
87 continue
88 files.update(path for path in directory.rglob("*.py"))
89
90 for path in PYTHON_EXTRA_FILES:
91 if path.exists():
92 files.add(path)
93
94 return sorted(files)
95
96
Here is the caller graph for this function:

◆ _read_lines()

list[str] audit_function_docs._read_lines ( Path  path)
protected

Reads a text file into a list of lines.

Parameters
[in]pathPath to read.
Returns
File contents split into lines without trailing newline markers.

Definition at line 97 of file audit_function_docs.py.

97def _read_lines(path: Path) -> list[str]:
98 """!
99 @brief Reads a text file into a list of lines.
100 @param[in] path Path to read.
101 @return File contents split into lines without trailing newline markers.
102 """
103
104 return path.read_text(encoding="utf-8", errors="ignore").splitlines()
105
106
Here is the caller graph for this function:

◆ _relative_path()

str audit_function_docs._relative_path ( Path  path)
protected

Returns a repository-relative path string.

Parameters
[in]pathAbsolute or repository-local path.
Returns
POSIX-style repository-relative path.

Definition at line 107 of file audit_function_docs.py.

107def _relative_path(path: Path) -> str:
108 """!
109 @brief Returns a repository-relative path string.
110 @param[in] path Absolute or repository-local path.
111 @return POSIX-style repository-relative path.
112 """
113
114 return path.relative_to(REPO_ROOT).as_posix()
115
116
Here is the caller graph for this function:

◆ _find_attached_doxygen_block()

tuple[int, int] | None audit_function_docs._find_attached_doxygen_block ( list[str]  lines,
int  start_line 
)
protected

Finds the Doxygen block immediately attached to a declaration or definition.

Parameters
[in]linesFile content lines.
[in]start_line0-based line index where the symbol begins.
Returns
(start, end) line indices for the attached block, or None.

Definition at line 117 of file audit_function_docs.py.

117def _find_attached_doxygen_block(lines: list[str], start_line: int) -> tuple[int, int] | None:
118 """!
119 @brief Finds the Doxygen block immediately attached to a declaration or definition.
120 @param[in] lines File content lines.
121 @param[in] start_line 0-based line index where the symbol begins.
122 @return `(start, end)` line indices for the attached block, or `None`.
123 """
124
125 probe = start_line - 1
126 while probe >= 0 and lines[probe].strip() == "":
127 probe -= 1
128
129 if probe < 0 or "*/" not in lines[probe]:
130 return None
131
132 end = probe
133 while probe >= 0 and "/**" not in lines[probe]:
134 probe -= 1
135
136 if probe < 0:
137 return None
138
139 return probe, end
140
141
Here is the caller graph for this function:

◆ _split_c_parameters()

list[str] audit_function_docs._split_c_parameters ( str  signature)
protected

Splits a C signature parameter list into parameter names.

Parameters
[in]signatureFull function signature text.
Returns
Ordered list of parameter names excluding void and variadics.

Definition at line 142 of file audit_function_docs.py.

142def _split_c_parameters(signature: str) -> list[str]:
143 """!
144 @brief Splits a C signature parameter list into parameter names.
145 @param[in] signature Full function signature text.
146 @return Ordered list of parameter names excluding `void` and variadics.
147 """
148
149 start = signature.find("(")
150 end = signature.rfind(")")
151 if start < 0 or end < 0 or end <= start:
152 return []
153
154 raw = signature[start + 1:end]
155 params: list[str] = []
156 depth = 0
157 current: list[str] = []
158 for char in raw:
159 if char == "," and depth == 0:
160 params.append("".join(current).strip())
161 current = []
162 continue
163 current.append(char)
164 if char in "([{":
165 depth += 1
166 elif char in ")]}":
167 depth -= 1
168 if current:
169 params.append("".join(current).strip())
170
171 names: list[str] = []
172 for param in params:
173 if not param or param == "void" or param == "...":
174 continue
175
176 clean = re.sub(r"\b(const|volatile|restrict|extern|static|register|inline)\b", "", param)
177 clean = clean.strip()
178 match = re.search(r"([A-Za-z_][A-Za-z0-9_]*)\s*(?:\[[^\]]*\]\s*)*$", clean)
179 if match:
180 names.append(match.group(1))
181
182 return names
183
184
Here is the caller graph for this function:

◆ _c_return_type()

str audit_function_docs._c_return_type ( str  signature,
str  symbol 
)
protected

Extracts the declared C return type prefix for one signature.

Parameters
[in]signatureFull function signature text.
[in]symbolFunction name contained in the signature.
Returns
Normalized return-type prefix.

Definition at line 185 of file audit_function_docs.py.

185def _c_return_type(signature: str, symbol: str) -> str:
186 """!
187 @brief Extracts the declared C return type prefix for one signature.
188 @param[in] signature Full function signature text.
189 @param[in] symbol Function name contained in the signature.
190 @return Normalized return-type prefix.
191 """
192
193 prefix = signature.split(symbol, 1)[0]
194 return " ".join(prefix.split())
195
196
Here is the caller graph for this function:

◆ _return_tag_required()

bool audit_function_docs._return_tag_required ( str  return_type)
protected

Reports whether a Doxygen @return tag is required for a C symbol.

Parameters
[in]return_typeNormalized return-type prefix.
Returns
True when the symbol does not return void.

Definition at line 197 of file audit_function_docs.py.

197def _return_tag_required(return_type: str) -> bool:
198 """!
199 @brief Reports whether a Doxygen `@return` tag is required for a C symbol.
200 @param[in] return_type Normalized return-type prefix.
201 @return `True` when the symbol does not return `void`.
202 """
203
204 stripped = return_type.replace("extern ", "").replace("static ", "").replace("inline ", "").strip()
205 return not stripped.startswith("void")
206
207
Here is the caller graph for this function:

◆ _collect_c_signatures()

list[tuple[int, str, str]] audit_function_docs._collect_c_signatures ( Path  path,
str  require_terminator 
)
protected

Collects C signatures from a header or source file.

Parameters
[in]pathFile to scan.
[in]require_terminatorExpected signature terminator, either ; or {.
Returns
List of (start_line, symbol, signature_text) tuples.

Definition at line 208 of file audit_function_docs.py.

208def _collect_c_signatures(path: Path, require_terminator: str) -> list[tuple[int, str, str]]:
209 """!
210 @brief Collects C signatures from a header or source file.
211 @param[in] path File to scan.
212 @param[in] require_terminator Expected signature terminator, either `;` or `{`.
213 @return List of `(start_line, symbol, signature_text)` tuples.
214 """
215
216 lines = _read_lines(path)
217 signatures: list[tuple[int, str, str]] = []
218 line_index = 0
219 in_block_comment = False
220
221 while line_index < len(lines):
222 stripped = lines[line_index].lstrip()
223 if in_block_comment:
224 if "*/" in lines[line_index]:
225 in_block_comment = False
226 line_index += 1
227 continue
228
229 if "/*" in lines[line_index]:
230 if "*/" not in lines[line_index]:
231 in_block_comment = True
232 line_index += 1
233 continue
234
235 if stripped.startswith(("#", "/*", "*", "//")) or "(" not in lines[line_index]:
236 line_index += 1
237 continue
238
239 match = C_DECL_START_RE.match(lines[line_index])
240 if not match:
241 line_index += 1
242 continue
243
244 symbol = match.group(1)
245 start_line = line_index
246 signature = lines[line_index].rstrip()
247 while line_index + 1 < len(lines) and require_terminator not in signature and ";" not in signature:
248 line_index += 1
249 signature += " " + lines[line_index].strip()
250
251 if require_terminator == ";" and ";" in signature:
252 signatures.append((start_line, symbol, signature))
253 elif require_terminator == "{" and "{" in signature and ";" not in signature.split("{", 1)[0]:
254 signatures.append((start_line, symbol, signature))
255
256 line_index += 1
257
258 return signatures
259
260
Here is the call graph for this function:
Here is the caller graph for this function:

◆ _audit_c_header()

list[AuditFinding] audit_function_docs._audit_c_header ( Path  path)
protected

Audits public C declarations in one header file.

Parameters
[in]pathHeader file to scan.
Returns
Findings emitted for the header.

Definition at line 261 of file audit_function_docs.py.

261def _audit_c_header(path: Path) -> list[AuditFinding]:
262 """!
263 @brief Audits public C declarations in one header file.
264 @param[in] path Header file to scan.
265 @return Findings emitted for the header.
266 """
267
268 findings: list[AuditFinding] = []
269 lines = _read_lines(path)
270 for start_line, symbol, signature in _collect_c_signatures(path, ";"):
271 block_range = _find_attached_doxygen_block(lines, start_line)
272 if block_range is None:
273 findings.append(AuditFinding(_relative_path(path), start_line + 1, symbol, "missing attached Doxygen block"))
274 continue
275
276 block = "\n".join(lines[block_range[0]:block_range[1] + 1])
277 if "@brief" not in block:
278 findings.append(AuditFinding(_relative_path(path), start_line + 1, symbol, "missing @brief tag"))
279
280 declared_params = _split_c_parameters(signature)
281 documented_params = set(C_PARAM_RE.findall(block))
282 if set(declared_params) != documented_params:
283 findings.append(
284 AuditFinding(
285 _relative_path(path),
286 start_line + 1,
287 symbol,
288 f"documented @param names {sorted(documented_params)} do not match declaration {declared_params}",
289 )
290 )
291
292 if _return_tag_required(_c_return_type(signature, symbol)) and "@return" not in block:
293 findings.append(AuditFinding(_relative_path(path), start_line + 1, symbol, "missing @return tag"))
294
295 return findings
296
297
Here is the call graph for this function:
Here is the caller graph for this function:

◆ _audit_c_source()

list[AuditFinding] audit_function_docs._audit_c_source ( Path  path)
protected

Audits function definitions in one C source file.

Parameters
[in]pathSource file to scan.
Returns
Findings emitted for the source file.

Definition at line 298 of file audit_function_docs.py.

298def _audit_c_source(path: Path) -> list[AuditFinding]:
299 """!
300 @brief Audits function definitions in one C source file.
301 @param[in] path Source file to scan.
302 @return Findings emitted for the source file.
303 """
304
305 findings: list[AuditFinding] = []
306 lines = _read_lines(path)
307 for start_line, symbol, _signature in _collect_c_signatures(path, "{"):
308 block_range = _find_attached_doxygen_block(lines, start_line)
309 if block_range is None:
310 findings.append(AuditFinding(_relative_path(path), start_line + 1, symbol, "missing attached Doxygen block"))
311 continue
312
313 block = "\n".join(lines[block_range[0]:block_range[1] + 1])
314 if "@brief" not in block:
315 findings.append(AuditFinding(_relative_path(path), start_line + 1, symbol, "missing @brief tag"))
316
317 return findings
318
319
Here is the call graph for this function:
Here is the caller graph for this function:

◆ _python_parameter_names()

list[str] audit_function_docs._python_parameter_names ( ast.FunctionDef | ast.AsyncFunctionDef  node)
protected

Returns the meaningful Python parameter names for one function node.

Parameters
[in]nodeFunction AST node.
Returns
Ordered list of parameters expected in @param tags.

Definition at line 320 of file audit_function_docs.py.

320def _python_parameter_names(node: ast.FunctionDef | ast.AsyncFunctionDef) -> list[str]:
321 """!
322 @brief Returns the meaningful Python parameter names for one function node.
323 @param[in] node Function AST node.
324 @return Ordered list of parameters expected in `@param` tags.
325 """
326
327 names = [arg.arg for arg in node.args.posonlyargs + node.args.args + node.args.kwonlyargs]
328 names = [name for name in names if name not in {"self", "cls"}]
329 if node.args.vararg is not None:
330 names.append(node.args.vararg.arg)
331 if node.args.kwarg is not None:
332 names.append(node.args.kwarg.arg)
333 return names
334
335
Here is the caller graph for this function:

◆ _python_requires_return()

bool audit_function_docs._python_requires_return ( ast.FunctionDef | ast.AsyncFunctionDef  node)
protected

Reports whether one Python function should document a return value.

Parameters
[in]nodeFunction AST node.
Returns
True when the function returns a non-None value.

Definition at line 336 of file audit_function_docs.py.

336def _python_requires_return(node: ast.FunctionDef | ast.AsyncFunctionDef) -> bool:
337 """!
338 @brief Reports whether one Python function should document a return value.
339 @param[in] node Function AST node.
340 @return `True` when the function returns a non-`None` value.
341 """
342
343 for child in ast.walk(node):
344 if isinstance(child, ast.Return) and child.value is not None:
345 if isinstance(child.value, ast.Constant) and child.value.value is None:
346 continue
347 return True
348 return False
349
350
Here is the caller graph for this function:

◆ _audit_python_file()

list[AuditFinding] audit_function_docs._audit_python_file ( Path  path)
protected

Audits Python function docstrings in one file.

Parameters
[in]pathPython source file to scan.
Returns
Findings emitted for the Python file.

Definition at line 351 of file audit_function_docs.py.

351def _audit_python_file(path: Path) -> list[AuditFinding]:
352 """!
353 @brief Audits Python function docstrings in one file.
354 @param[in] path Python source file to scan.
355 @return Findings emitted for the Python file.
356 """
357
358 findings: list[AuditFinding] = []
359 source = path.read_text(encoding="utf-8")
360 tree = ast.parse(source, filename=str(path))
361
362 for node in ast.walk(tree):
363 if not isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
364 continue
365
366 docstring = ast.get_docstring(node)
367 if docstring is None:
368 findings.append(AuditFinding(_relative_path(path), node.lineno, node.name, "missing Python docstring"))
369 continue
370
371 if "@brief" not in docstring:
372 findings.append(AuditFinding(_relative_path(path), node.lineno, node.name, "missing @brief tag"))
373
374 declared_params = _python_parameter_names(node)
375 documented_params = set(re.findall(r"@param(?:\[[^\]]+\])?\s+([A-Za-z_][A-Za-z0-9_]*)", docstring))
376 if set(declared_params) != documented_params:
377 findings.append(
378 AuditFinding(
379 _relative_path(path),
380 node.lineno,
381 node.name,
382 f"documented @param names {sorted(documented_params)} do not match declaration {declared_params}",
383 )
384 )
385
386 if _python_requires_return(node) and "@return" not in docstring:
387 findings.append(AuditFinding(_relative_path(path), node.lineno, node.name, "missing @return tag"))
388
389 return findings
390
391
Here is the call graph for this function:
Here is the caller graph for this function:

◆ _collect_findings()

list[AuditFinding] audit_function_docs._collect_findings ( )
protected

Runs the full repository documentation audit.

Returns
Sorted list of all findings emitted by the audit.

Definition at line 392 of file audit_function_docs.py.

392def _collect_findings() -> list[AuditFinding]:
393 """!
394 @brief Runs the full repository documentation audit.
395 @return Sorted list of all findings emitted by the audit.
396 """
397
398 findings: list[AuditFinding] = []
399 for path in _iter_c_files(C_HEADER_DIRS):
400 findings.extend(_audit_c_header(path))
401 for path in _iter_c_files(C_SOURCE_DIRS):
402 if path.suffix == ".c":
403 findings.extend(_audit_c_source(path))
404 for path in _iter_python_files():
405 findings.extend(_audit_python_file(path))
406
407 return sorted(findings, key=lambda item: (item.path, item.line, item.symbol, item.message))
408
409
Here is the call graph for this function:
Here is the caller graph for this function:

◆ _print_findings()

None audit_function_docs._print_findings ( list[AuditFinding findings)
protected

Prints findings in a grep-friendly format.

Parameters
[in]findingsFindings to render.

Definition at line 410 of file audit_function_docs.py.

410def _print_findings(findings: list[AuditFinding]) -> None:
411 """!
412 @brief Prints findings in a grep-friendly format.
413 @param[in] findings Findings to render.
414 """
415
416 for finding in findings:
417 print(f"{finding.path}:{finding.line}: {finding.symbol}: {finding.message}")
418
419
Here is the caller graph for this function:

◆ main()

int audit_function_docs.main ( )

Runs the repository function documentation audit from the command line.

Returns
Process exit status.

Definition at line 420 of file audit_function_docs.py.

420def main() -> int:
421 """!
422 @brief Runs the repository function documentation audit from the command line.
423 @return Process exit status.
424 """
425
426 findings = _collect_findings()
427 if findings:
428 _print_findings(findings)
429 print(f"\nFound {len(findings)} documentation issue(s).", file=sys.stderr)
430 return 1
431
432 print("Function documentation audit passed.")
433 return 0
434
435
int main(int argc, char **argv)
Entry point for the postprocessor executable.
Here is the call graph for this function:
Here is the caller graph for this function:

Variable Documentation

◆ REPO_ROOT

audit_function_docs.REPO_ROOT = Path(__file__).resolve().parents[2]

Definition at line 25 of file audit_function_docs.py.

◆ C_HEADER_DIRS

tuple audit_function_docs.C_HEADER_DIRS = (REPO_ROOT / "include",)

Definition at line 27 of file audit_function_docs.py.

◆ C_SOURCE_DIRS

tuple audit_function_docs.C_SOURCE_DIRS = (REPO_ROOT / "src", REPO_ROOT / "tests" / "c")

Definition at line 28 of file audit_function_docs.py.

◆ PYTHON_DIRS

tuple audit_function_docs.PYTHON_DIRS = (REPO_ROOT / "picurv_cli", REPO_ROOT / "tests")

Definition at line 29 of file audit_function_docs.py.

◆ PYTHON_EXTRA_FILES

tuple audit_function_docs.PYTHON_EXTRA_FILES
Initial value:
1= (
2 REPO_ROOT / "picurv_cli" / "picurv",
3 REPO_ROOT / "generators" / "grid.gen",
4 REPO_ROOT / "generators" / "profile.gen",
5 REPO_ROOT / "generators" / "ic.gen",
6 REPO_ROOT / "generators" / "plot.gen",
7)

Definition at line 30 of file audit_function_docs.py.

◆ C_DECL_START_RE

audit_function_docs.C_DECL_START_RE
Initial value:
1= re.compile(
2 r"^\s*(?!typedef\b)(?!if\b)(?!for\b)(?!while\b)(?!switch\b)(?!return\b)(?!else\b)"
3 r"(?:extern\s+)?(?:static\s+)?(?:inline\s+)?(?:const\s+)?(?:unsigned\s+|signed\s+)?"
4 r"(?:[A-Za-z_][A-Za-z0-9_]*\s+)+(?:\*\s*)*"
5 r"([A-Za-z_][A-Za-z0-9_]*)\s*\‍("
6)

Definition at line 38 of file audit_function_docs.py.

◆ C_PARAM_RE

audit_function_docs.C_PARAM_RE = re.compile(r"@param(?:\[[^\]]+\])?\s+([A-Za-z_][A-Za-z0-9_]*)")

Definition at line 44 of file audit_function_docs.py.