svdoc 0.1.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.
- svdoc-0.1.0/LICENSE +21 -0
- svdoc-0.1.0/PKG-INFO +119 -0
- svdoc-0.1.0/README.md +74 -0
- svdoc-0.1.0/pyproject.toml +31 -0
- svdoc-0.1.0/setup.cfg +4 -0
- svdoc-0.1.0/svdoc/__init__.py +0 -0
- svdoc-0.1.0/svdoc/build.py +71 -0
- svdoc-0.1.0/svdoc/cli.py +123 -0
- svdoc-0.1.0/svdoc/fixer.py +91 -0
- svdoc-0.1.0/svdoc/ir.py +166 -0
- svdoc-0.1.0/svdoc/parser.py +455 -0
- svdoc-0.1.0/svdoc/render_html.py +219 -0
- svdoc-0.1.0/svdoc/render_md.py +127 -0
- svdoc-0.1.0/svdoc.egg-info/PKG-INFO +119 -0
- svdoc-0.1.0/svdoc.egg-info/SOURCES.txt +17 -0
- svdoc-0.1.0/svdoc.egg-info/dependency_links.txt +1 -0
- svdoc-0.1.0/svdoc.egg-info/entry_points.txt +2 -0
- svdoc-0.1.0/svdoc.egg-info/requires.txt +9 -0
- svdoc-0.1.0/svdoc.egg-info/top_level.txt +1 -0
svdoc-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 svdoc contributors
|
|
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.
|
svdoc-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: svdoc
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: pydoc-style documentation generator for SystemVerilog RTL
|
|
5
|
+
License: MIT License
|
|
6
|
+
|
|
7
|
+
Copyright (c) 2026 svdoc contributors
|
|
8
|
+
|
|
9
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
10
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
11
|
+
in the Software without restriction, including without limitation the rights
|
|
12
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
13
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
14
|
+
furnished to do so, subject to the following conditions:
|
|
15
|
+
|
|
16
|
+
The above copyright notice and this permission notice shall be included in all
|
|
17
|
+
copies or substantial portions of the Software.
|
|
18
|
+
|
|
19
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
20
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
21
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
22
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
23
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
24
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
25
|
+
SOFTWARE.
|
|
26
|
+
|
|
27
|
+
Classifier: Development Status :: 3 - Alpha
|
|
28
|
+
Classifier: Environment :: Console
|
|
29
|
+
Classifier: Intended Audience :: Developers
|
|
30
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
31
|
+
Classifier: Programming Language :: Python :: 3
|
|
32
|
+
Classifier: Topic :: Software Development :: Documentation
|
|
33
|
+
Classifier: Topic :: Utilities
|
|
34
|
+
Requires-Python: >=3.9
|
|
35
|
+
Description-Content-Type: text/markdown
|
|
36
|
+
License-File: LICENSE
|
|
37
|
+
Requires-Dist: pyslang>=11.0.0
|
|
38
|
+
Provides-Extra: docs
|
|
39
|
+
Requires-Dist: sphinx; extra == "docs"
|
|
40
|
+
Requires-Dist: sphinx-rtd-theme; extra == "docs"
|
|
41
|
+
Provides-Extra: dev
|
|
42
|
+
Requires-Dist: ruff; extra == "dev"
|
|
43
|
+
Requires-Dist: pytest; extra == "dev"
|
|
44
|
+
Dynamic: license-file
|
|
45
|
+
|
|
46
|
+
# svdoc
|
|
47
|
+
|
|
48
|
+
A pydoc-style documentation generator for SystemVerilog RTL, built on
|
|
49
|
+
[`pyslang`](https://pypi.org/project/pyslang/) (Python bindings for the
|
|
50
|
+
`slang` SystemVerilog compiler).
|
|
51
|
+
|
|
52
|
+
Extracts Doxygen-style doc comments (`/** @brief ... */`, `///< ...`) from
|
|
53
|
+
modules, interfaces, and packages, and renders clean Markdown or HTML
|
|
54
|
+
documentation — no manual editing required.
|
|
55
|
+
|
|
56
|
+
## What it documents today
|
|
57
|
+
|
|
58
|
+
- **Modules** — name, parameters, ports, doc comments
|
|
59
|
+
- **Interfaces** — parameters, ports, signals, modports
|
|
60
|
+
- **Packages** — typedefs (enums, packed structs, aliases), functions, tasks
|
|
61
|
+
- **Cross-file type resolution** — a module's port typed from a package in
|
|
62
|
+
another file resolves to its fully-qualified `package::type` name
|
|
63
|
+
|
|
64
|
+
Not yet supported: classes/UVM, instantiation hierarchy, block diagrams, full
|
|
65
|
+
multi-file `` `include`` resolution.
|
|
66
|
+
|
|
67
|
+
## Install
|
|
68
|
+
|
|
69
|
+
```
|
|
70
|
+
pip install svdoc
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Usage
|
|
74
|
+
|
|
75
|
+
```
|
|
76
|
+
# print Markdown to stdout
|
|
77
|
+
svdoc my_module.sv
|
|
78
|
+
|
|
79
|
+
# write a .md or .html file instead
|
|
80
|
+
svdoc my_module.sv --out md
|
|
81
|
+
svdoc my_module.sv --out html
|
|
82
|
+
|
|
83
|
+
# resolve cross-file types (e.g. a module using a package's enum/struct)
|
|
84
|
+
svdoc my_module.sv my_package.sv --out html
|
|
85
|
+
|
|
86
|
+
# auto-scaffold missing doc comments in place (like `ruff --fix`)
|
|
87
|
+
svdoc my_module.sv --fix
|
|
88
|
+
|
|
89
|
+
# build a full linked HTML site from many files (any directory layout)
|
|
90
|
+
svdoc build my_module.sv my_interface.sv my_package.sv --out-dir site/
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Doc comment convention
|
|
94
|
+
|
|
95
|
+
Doxygen-style, both forms supported:
|
|
96
|
+
|
|
97
|
+
```systemverilog
|
|
98
|
+
/**
|
|
99
|
+
* @brief A simple synchronous FIFO.
|
|
100
|
+
*/
|
|
101
|
+
module fifo #(
|
|
102
|
+
parameter int DEPTH = 16 ///< Number of entries in the FIFO
|
|
103
|
+
) (
|
|
104
|
+
input logic clk ///< Clock
|
|
105
|
+
);
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Development
|
|
109
|
+
|
|
110
|
+
```
|
|
111
|
+
python3 -m venv .venv
|
|
112
|
+
source .venv/bin/activate
|
|
113
|
+
pip install -e ".[dev,docs]"
|
|
114
|
+
python -m pytest
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## License
|
|
118
|
+
|
|
119
|
+
MIT — see `LICENSE`.
|
svdoc-0.1.0/README.md
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# svdoc
|
|
2
|
+
|
|
3
|
+
A pydoc-style documentation generator for SystemVerilog RTL, built on
|
|
4
|
+
[`pyslang`](https://pypi.org/project/pyslang/) (Python bindings for the
|
|
5
|
+
`slang` SystemVerilog compiler).
|
|
6
|
+
|
|
7
|
+
Extracts Doxygen-style doc comments (`/** @brief ... */`, `///< ...`) from
|
|
8
|
+
modules, interfaces, and packages, and renders clean Markdown or HTML
|
|
9
|
+
documentation — no manual editing required.
|
|
10
|
+
|
|
11
|
+
## What it documents today
|
|
12
|
+
|
|
13
|
+
- **Modules** — name, parameters, ports, doc comments
|
|
14
|
+
- **Interfaces** — parameters, ports, signals, modports
|
|
15
|
+
- **Packages** — typedefs (enums, packed structs, aliases), functions, tasks
|
|
16
|
+
- **Cross-file type resolution** — a module's port typed from a package in
|
|
17
|
+
another file resolves to its fully-qualified `package::type` name
|
|
18
|
+
|
|
19
|
+
Not yet supported: classes/UVM, instantiation hierarchy, block diagrams, full
|
|
20
|
+
multi-file `` `include`` resolution.
|
|
21
|
+
|
|
22
|
+
## Install
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
pip install svdoc
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Usage
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
# print Markdown to stdout
|
|
32
|
+
svdoc my_module.sv
|
|
33
|
+
|
|
34
|
+
# write a .md or .html file instead
|
|
35
|
+
svdoc my_module.sv --out md
|
|
36
|
+
svdoc my_module.sv --out html
|
|
37
|
+
|
|
38
|
+
# resolve cross-file types (e.g. a module using a package's enum/struct)
|
|
39
|
+
svdoc my_module.sv my_package.sv --out html
|
|
40
|
+
|
|
41
|
+
# auto-scaffold missing doc comments in place (like `ruff --fix`)
|
|
42
|
+
svdoc my_module.sv --fix
|
|
43
|
+
|
|
44
|
+
# build a full linked HTML site from many files (any directory layout)
|
|
45
|
+
svdoc build my_module.sv my_interface.sv my_package.sv --out-dir site/
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Doc comment convention
|
|
49
|
+
|
|
50
|
+
Doxygen-style, both forms supported:
|
|
51
|
+
|
|
52
|
+
```systemverilog
|
|
53
|
+
/**
|
|
54
|
+
* @brief A simple synchronous FIFO.
|
|
55
|
+
*/
|
|
56
|
+
module fifo #(
|
|
57
|
+
parameter int DEPTH = 16 ///< Number of entries in the FIFO
|
|
58
|
+
) (
|
|
59
|
+
input logic clk ///< Clock
|
|
60
|
+
);
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Development
|
|
64
|
+
|
|
65
|
+
```
|
|
66
|
+
python3 -m venv .venv
|
|
67
|
+
source .venv/bin/activate
|
|
68
|
+
pip install -e ".[dev,docs]"
|
|
69
|
+
python -m pytest
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## License
|
|
73
|
+
|
|
74
|
+
MIT — see `LICENSE`.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=64"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "svdoc"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "pydoc-style documentation generator for SystemVerilog RTL"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = { file = "LICENSE" }
|
|
11
|
+
requires-python = ">=3.9"
|
|
12
|
+
dependencies = ["pyslang>=11.0.0"]
|
|
13
|
+
classifiers = [
|
|
14
|
+
"Development Status :: 3 - Alpha",
|
|
15
|
+
"Environment :: Console",
|
|
16
|
+
"Intended Audience :: Developers",
|
|
17
|
+
"License :: OSI Approved :: MIT License",
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
"Topic :: Software Development :: Documentation",
|
|
20
|
+
"Topic :: Utilities",
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
[project.optional-dependencies]
|
|
24
|
+
docs = ["sphinx", "sphinx-rtd-theme"]
|
|
25
|
+
dev = ["ruff", "pytest"]
|
|
26
|
+
|
|
27
|
+
[project.scripts]
|
|
28
|
+
svdoc = "svdoc.cli:main"
|
|
29
|
+
|
|
30
|
+
[tool.setuptools]
|
|
31
|
+
packages = ["svdoc"]
|
svdoc-0.1.0/setup.cfg
ADDED
|
File without changes
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""svdoc build: a flat multi-page HTML site (one page per construct) with a
|
|
2
|
+
navigable index, so cross-links between pages always resolve correctly --
|
|
3
|
+
avoids the relative-path problem of generating standalone pages into
|
|
4
|
+
whatever directories the source files happen to live in.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import pathlib
|
|
8
|
+
import re
|
|
9
|
+
from html import escape
|
|
10
|
+
|
|
11
|
+
from . import render_html
|
|
12
|
+
from .ir import InterfaceDoc, ModuleDoc, PackageDoc
|
|
13
|
+
from .parser import parse_file, resolve_types
|
|
14
|
+
|
|
15
|
+
_UNSAFE_NAME_CHARS = re.compile(r"[^\w.-]")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _safe_page_name(name: str) -> str:
|
|
19
|
+
"""Sanitize a construct name for use as an output filename. SV escaped
|
|
20
|
+
identifiers (``\\../../foo``) parse without error and can contain path
|
|
21
|
+
separators, so page filenames can't trust ``doc.name`` directly."""
|
|
22
|
+
return _UNSAFE_NAME_CHARS.sub("_", name)
|
|
23
|
+
|
|
24
|
+
_RENDERERS = {
|
|
25
|
+
ModuleDoc: render_html.render,
|
|
26
|
+
InterfaceDoc: render_html.render_interface,
|
|
27
|
+
PackageDoc: render_html.render_package,
|
|
28
|
+
}
|
|
29
|
+
_KIND_LABELS = {
|
|
30
|
+
ModuleDoc: "Module",
|
|
31
|
+
InterfaceDoc: "Interface",
|
|
32
|
+
PackageDoc: "Package",
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def build_site(paths: list, out_dir: str) -> str:
|
|
37
|
+
"""Parse every file in ``paths`` and write one HTML page per construct
|
|
38
|
+
into a single flat ``out_dir``, plus an ``index.html`` linking to all of
|
|
39
|
+
them. Because every page lands in the same directory, the convention-based
|
|
40
|
+
cross-links in :mod:`svdoc.render_html` (``pkg_name.html#member``) always
|
|
41
|
+
resolve correctly, regardless of where the source ``.sv`` files live.
|
|
42
|
+
|
|
43
|
+
:param paths: All ``.sv`` files to document. Each is parsed independently
|
|
44
|
+
(one construct per file, same as :func:`svdoc.parser.parse_file`);
|
|
45
|
+
cross-file type resolution runs against the full set for every module.
|
|
46
|
+
:param out_dir: Directory to write the site into. Created if missing.
|
|
47
|
+
:returns: Path to the written ``index.html``.
|
|
48
|
+
:raises ValueError: If any file fails to parse cleanly.
|
|
49
|
+
"""
|
|
50
|
+
out = pathlib.Path(out_dir)
|
|
51
|
+
out.mkdir(parents=True, exist_ok=True)
|
|
52
|
+
|
|
53
|
+
docs = [parse_file(p) for p in paths]
|
|
54
|
+
for doc in docs:
|
|
55
|
+
if isinstance(doc, ModuleDoc):
|
|
56
|
+
resolve_types(doc, paths)
|
|
57
|
+
|
|
58
|
+
index_lines = ["<h1>svdoc site</h1>", "<ul>"]
|
|
59
|
+
for doc in sorted(docs, key=lambda d: (_KIND_LABELS[type(d)], d.name)):
|
|
60
|
+
page_name = f"{_safe_page_name(doc.name)}.html"
|
|
61
|
+
(out / page_name).write_text(_RENDERERS[type(doc)](doc))
|
|
62
|
+
label = _KIND_LABELS[type(doc)]
|
|
63
|
+
index_lines.append(
|
|
64
|
+
f'<li>{label}: <a href="{escape(page_name)}">{escape(doc.name)}</a></li>'
|
|
65
|
+
)
|
|
66
|
+
index_lines.append("</ul>")
|
|
67
|
+
|
|
68
|
+
index_html = render_html.page("svdoc site", index_lines)
|
|
69
|
+
index_path = out / "index.html"
|
|
70
|
+
index_path.write_text(index_html)
|
|
71
|
+
return str(index_path)
|
svdoc-0.1.0/svdoc/cli.py
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"""svdoc CLI (v1): `svdoc <file.sv>` prints to terminal; `--out md`/`--out html`
|
|
2
|
+
writes a file; `svdoc build <files...>` writes a multi-page linked HTML site.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import pathlib
|
|
7
|
+
import sys
|
|
8
|
+
|
|
9
|
+
from . import render_html, render_md
|
|
10
|
+
from .build import build_site
|
|
11
|
+
from .fixer import fix_file
|
|
12
|
+
from .ir import InterfaceDoc, ModuleDoc, PackageDoc
|
|
13
|
+
from .parser import parse_file, resolve_types
|
|
14
|
+
|
|
15
|
+
_RENDERERS = {
|
|
16
|
+
"md": {
|
|
17
|
+
ModuleDoc: render_md.render,
|
|
18
|
+
InterfaceDoc: render_md.render_interface,
|
|
19
|
+
PackageDoc: render_md.render_package,
|
|
20
|
+
},
|
|
21
|
+
"html": {
|
|
22
|
+
ModuleDoc: render_html.render,
|
|
23
|
+
InterfaceDoc: render_html.render_interface,
|
|
24
|
+
PackageDoc: render_html.render_package,
|
|
25
|
+
},
|
|
26
|
+
}
|
|
27
|
+
_EXTENSIONS = {"md": ".md", "html": ".html"}
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _run_single(args):
|
|
31
|
+
if args.fix:
|
|
32
|
+
changed = fix_file(args.file)
|
|
33
|
+
print(f"{'fixed' if changed else 'no changes needed for'} {args.file}")
|
|
34
|
+
return
|
|
35
|
+
|
|
36
|
+
doc = parse_file(args.file)
|
|
37
|
+
if args.more_files and isinstance(doc, ModuleDoc):
|
|
38
|
+
resolve_types(doc, [args.file] + args.more_files)
|
|
39
|
+
|
|
40
|
+
if args.out:
|
|
41
|
+
text = _RENDERERS[args.out][type(doc)](doc)
|
|
42
|
+
out_path = pathlib.Path(args.file).with_suffix(_EXTENSIONS[args.out])
|
|
43
|
+
out_path.write_text(text)
|
|
44
|
+
print(f"wrote {out_path}")
|
|
45
|
+
else:
|
|
46
|
+
print(_RENDERERS["md"][type(doc)](doc))
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _run_build(args):
|
|
50
|
+
index_path = build_site(args.files, args.out_dir)
|
|
51
|
+
print(f"wrote {len(args.files)} file(s) to a site at {index_path}")
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _build_parser():
|
|
55
|
+
ap = argparse.ArgumentParser(prog="svdoc build")
|
|
56
|
+
ap.add_argument("files", nargs="+", help=".sv files to document")
|
|
57
|
+
ap.add_argument("--out-dir", required=True, help="directory to write the site into")
|
|
58
|
+
return ap
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _single_parser():
|
|
62
|
+
ap = argparse.ArgumentParser(
|
|
63
|
+
prog="svdoc",
|
|
64
|
+
epilog="Also available: `svdoc build <files...> --out-dir <dir>` to "
|
|
65
|
+
"generate a multi-page linked HTML site.",
|
|
66
|
+
)
|
|
67
|
+
ap.add_argument(
|
|
68
|
+
"file",
|
|
69
|
+
help="path to the .sv file containing the module/interface/package to document",
|
|
70
|
+
)
|
|
71
|
+
ap.add_argument(
|
|
72
|
+
"more_files",
|
|
73
|
+
nargs="*",
|
|
74
|
+
help="additional .sv files (e.g. packages it depends on) used only to "
|
|
75
|
+
"resolve cross-file types -- not documented themselves",
|
|
76
|
+
)
|
|
77
|
+
ap.add_argument(
|
|
78
|
+
"--out",
|
|
79
|
+
choices=["md", "html"],
|
|
80
|
+
help="write rendered doc to a file in this format instead of printing to stdout",
|
|
81
|
+
)
|
|
82
|
+
ap.add_argument(
|
|
83
|
+
"--fix",
|
|
84
|
+
action="store_true",
|
|
85
|
+
help="insert ///< TODO stubs next to undocumented ports/params in place",
|
|
86
|
+
)
|
|
87
|
+
return ap
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def main(argv=None):
|
|
91
|
+
"""Entry point for the ``svdoc`` command (registered via ``pyproject.toml``).
|
|
92
|
+
|
|
93
|
+
Two modes, dispatched on whether the first argument is the literal
|
|
94
|
+
``build`` subcommand (argparse subparsers don't mix cleanly with a
|
|
95
|
+
same-position positional argument, so this is checked manually rather
|
|
96
|
+
than via ``add_subparsers``):
|
|
97
|
+
|
|
98
|
+
- ``svdoc <file.sv> [more_files...]`` — document a single module/interface/
|
|
99
|
+
package, optionally resolving cross-file types against ``more_files``,
|
|
100
|
+
printing Markdown to stdout or writing a single Markdown/HTML file
|
|
101
|
+
(``--out``), or scaffolding missing doc comments in place (``--fix``).
|
|
102
|
+
- ``svdoc build <files...> --out-dir <dir>`` — document every given file
|
|
103
|
+
into a single flat multi-page HTML site (one page per construct plus
|
|
104
|
+
an ``index.html``), so cross-links between pages always resolve
|
|
105
|
+
correctly regardless of where the source files live on disk.
|
|
106
|
+
|
|
107
|
+
:param argv: Argument list to parse (as :func:`sys.argv`\\ ``[1:]`` would
|
|
108
|
+
appear). Defaults to ``sys.argv[1:]`` when ``None``.
|
|
109
|
+
"""
|
|
110
|
+
if argv is None:
|
|
111
|
+
argv = sys.argv[1:]
|
|
112
|
+
|
|
113
|
+
if argv and argv[0] == "build":
|
|
114
|
+
args = _build_parser().parse_args(argv[1:])
|
|
115
|
+
_run_build(args)
|
|
116
|
+
return
|
|
117
|
+
|
|
118
|
+
args = _single_parser().parse_args(argv)
|
|
119
|
+
_run_single(args)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
if __name__ == "__main__":
|
|
123
|
+
sys.exit(main())
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"""svdoc --fix: insert `///< TODO` next to undocumented ports/params, and a
|
|
2
|
+
`/** @brief TODO */` stub above an undocumented module, in place. Mirrors
|
|
3
|
+
`ruff --fix` — scaffolds missing docs rather than guessing their content.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import pyslang
|
|
7
|
+
|
|
8
|
+
from .parser import _leading_doc, _trailing_doc
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _end_offset(node) -> int:
|
|
12
|
+
"""node may be a raw Token (comma, closing paren) or a syntax node."""
|
|
13
|
+
tok = node.getLastToken() if hasattr(node, "getLastToken") else node
|
|
14
|
+
return tok.range.end.offset
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def fix_file(path: str) -> bool:
|
|
18
|
+
"""Scaffold missing doc comments in place, in ``path``, and save the result.
|
|
19
|
+
|
|
20
|
+
Inserts ``///< TODO`` next to any undocumented port/parameter, and a
|
|
21
|
+
``/** @brief TODO */`` stub above the module/interface/package itself if
|
|
22
|
+
it has no doc comment. Idempotent: re-running on an already-documented
|
|
23
|
+
file makes no changes.
|
|
24
|
+
|
|
25
|
+
:param path: Path to the ``.sv`` file to fix in place.
|
|
26
|
+
:returns: ``True`` if the file was modified, ``False`` if it was already
|
|
27
|
+
fully documented.
|
|
28
|
+
:raises ValueError: If the file fails to parse cleanly.
|
|
29
|
+
"""
|
|
30
|
+
tree = pyslang.syntax.SyntaxTree.fromFile(path, pyslang.SourceManager())
|
|
31
|
+
if tree.diagnostics:
|
|
32
|
+
raise ValueError(f"parse errors in {path}: {list(tree.diagnostics)}")
|
|
33
|
+
|
|
34
|
+
# module and interface declarations share the same syntax shape
|
|
35
|
+
# (ModuleDeclarationSyntax, differentiated only by .kind) -- --fix scaffolds
|
|
36
|
+
# their header (params/ports) doc comments identically. Body constructs
|
|
37
|
+
# (interface signals/modports) aren't scaffolded here yet.
|
|
38
|
+
mod = next(m for m in tree.root.members if hasattr(m, "header"))
|
|
39
|
+
header = mod.header
|
|
40
|
+
|
|
41
|
+
# (offset, text_to_insert), applied back-to-front so earlier offsets stay valid.
|
|
42
|
+
insertions = []
|
|
43
|
+
|
|
44
|
+
if _leading_doc(mod) is None:
|
|
45
|
+
offset = mod.getFirstToken().range.start.offset
|
|
46
|
+
insertions.append((offset, "/**\n * @brief TODO\n */\n"))
|
|
47
|
+
|
|
48
|
+
if header.parameters:
|
|
49
|
+
raw = list(header.parameters.declarations)
|
|
50
|
+
for i, node in enumerate(raw):
|
|
51
|
+
if node.kind != pyslang.syntax.SyntaxKind.ParameterDeclaration:
|
|
52
|
+
continue
|
|
53
|
+
has_comma = i + 1 < len(raw)
|
|
54
|
+
# trailing ///< doc for this item is leading trivia on the node
|
|
55
|
+
# AFTER its separator (see parser._trailing_doc) -- for the last
|
|
56
|
+
# item there's no comma, so the lookup and insertion point are
|
|
57
|
+
# both the item itself, right before the closing paren.
|
|
58
|
+
lookup = (
|
|
59
|
+
raw[i + 2]
|
|
60
|
+
if has_comma and i + 2 < len(raw)
|
|
61
|
+
else header.parameters.closeParen
|
|
62
|
+
)
|
|
63
|
+
if _trailing_doc(lookup) is None:
|
|
64
|
+
offset = _end_offset(raw[i + 1]) if has_comma else _end_offset(node)
|
|
65
|
+
insertions.append((offset, " ///< TODO"))
|
|
66
|
+
|
|
67
|
+
if header.ports:
|
|
68
|
+
raw = list(header.ports.ports)
|
|
69
|
+
for i, node in enumerate(raw):
|
|
70
|
+
if not hasattr(node, "declarator"):
|
|
71
|
+
continue
|
|
72
|
+
has_comma = i + 1 < len(raw)
|
|
73
|
+
lookup = (
|
|
74
|
+
raw[i + 2]
|
|
75
|
+
if has_comma and i + 2 < len(raw)
|
|
76
|
+
else header.ports.closeParen
|
|
77
|
+
)
|
|
78
|
+
if _trailing_doc(lookup) is None:
|
|
79
|
+
offset = _end_offset(raw[i + 1]) if has_comma else _end_offset(node)
|
|
80
|
+
insertions.append((offset, " ///< TODO"))
|
|
81
|
+
|
|
82
|
+
if not insertions:
|
|
83
|
+
return False
|
|
84
|
+
|
|
85
|
+
with open(path) as f:
|
|
86
|
+
text = f.read()
|
|
87
|
+
for offset, snippet in sorted(insertions, reverse=True):
|
|
88
|
+
text = text[:offset] + snippet + text[offset:]
|
|
89
|
+
with open(path, "w") as f:
|
|
90
|
+
f.write(text)
|
|
91
|
+
return True
|