Skip to content

Commit fdabe03

Browse files
refactor(docs): code analysis engine
changes: - file: dependency_scanner.py area: analyzer added: [_parse_package_json, _parse_go_mod, _parse_cargo_toml] modified: [DependencyScanner, scan, ProjectDependencies] - file: contributing_gen.py area: docs modified: [_render_code_style, _render_setup, _detect_dev_tools, ContributingGenerator, _render_testing] - file: getting_started_gen.py area: docs modified: [_render_prerequisites, _render_installation, GettingStartedGenerator] - file: readme_gen.py area: docs modified: [ReadmeGenerator, _build_install_section, _build_quickstart_section] stats: lines: "+9859/-9589 (net +270)" files: 15 complexity: "Large structural change (normalized)"
1 parent bb07d16 commit fdabe03

22 files changed

+9886
-9594
lines changed

CHANGELOG.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,28 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
## [3.0.13] - 2026-03-09
11+
12+
### Docs
13+
- Update project/context.md
14+
15+
### Test
16+
- Update tests/project/dashboard.html
17+
- Update tests/project/project.yaml
18+
19+
### Other
20+
- Update code2docs/analyzers/dependency_scanner.py
21+
- Update code2docs/generators/contributing_gen.py
22+
- Update code2docs/generators/getting_started_gen.py
23+
- Update code2docs/generators/readme_gen.py
24+
- Update project/analysis.json
25+
- Update project/analysis.toon
26+
- Update project/analysis.yaml
27+
- Update project/calls.mmd
28+
- Update project/dashboard.html
29+
- Update project/flow.mmd
30+
- ... and 4 more files
31+
1032
## [3.0.12] - 2026-03-09
1133

1234
### Docs

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# code2docs
22

3-
![version](https://img.shields.io/badge/version-3.0.12-blue) ![python](https://img.shields.io/badge/python-%3E%3D3.9-blue) ![docs](https://img.shields.io/badge/docs-auto--generated-blueviolet)
3+
![version](https://img.shields.io/badge/version-3.0.13-blue) ![python](https://img.shields.io/badge/python-%3E%3D3.9-blue) ![docs](https://img.shields.io/badge/docs-auto--generated-blueviolet)
44

55
> Auto-generate and sync project documentation from source code analysis.
66
@@ -140,7 +140,7 @@ code2docs can update only specific sections of an existing README using markers:
140140
```markdown
141141
<!-- code2docs:start --># code2docs
142142

143-
![version](https://img.shields.io/badge/version-3.0.12-blue) ![python](https://img.shields.io/badge/python-%3E%3D3.9-blue) ![coverage](https://img.shields.io/badge/coverage-unknown-lightgrey) ![functions](https://img.shields.io/badge/functions-276-green)
143+
![version](https://img.shields.io/badge/version-3.0.13-blue) ![python](https://img.shields.io/badge/python-%3E%3D3.9-blue) ![coverage](https://img.shields.io/badge/coverage-unknown-lightgrey) ![functions](https://img.shields.io/badge/functions-276-green)
144144
> **276** functions | **57** classes | **51** files | CC̄ = 3.8
145145

146146
> Auto-generated project documentation from source code analysis.

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
3.0.12
1+
3.0.13

code2docs/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
README.md, API references, module docs, examples, and architecture diagrams.
66
"""
77

8-
__version__ = "3.0.12"
8+
__version__ = "3.0.13"
99
__author__ = "Tom Sapletta"
1010

1111
from .config import Code2DocsConfig

code2docs/analyzers/dependency_scanner.py

Lines changed: 130 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
"""Scan project dependencies from requirements.txt, pyproject.toml, setup.py."""
1+
"""Scan project dependencies from requirements.txt, pyproject.toml, setup.py, package.json, Cargo.toml, go.mod."""
22

33
import re
44
from dataclasses import dataclass, field
@@ -26,7 +26,9 @@ class DependencyInfo:
2626
@dataclass
2727
class ProjectDependencies:
2828
"""All detected project dependencies."""
29+
language: str = "python"
2930
python_version: str = ""
31+
runtime_version: str = ""
3032
dependencies: List[DependencyInfo] = field(default_factory=list)
3133
dev_dependencies: List[DependencyInfo] = field(default_factory=list)
3234
optional_groups: Dict[str, List[DependencyInfo]] = field(default_factory=dict)
@@ -47,7 +49,31 @@ def scan(self, project_path: str) -> ProjectDependencies:
4749
project = Path(project_path)
4850
deps = ProjectDependencies()
4951

50-
# Priority: pyproject.toml > setup.py > requirements.txt
52+
# Detect language from dependency files
53+
# Priority: pyproject.toml > setup.py > requirements.txt (Python)
54+
# package.json (JS/TS) > Cargo.toml (Rust) > go.mod (Go)
55+
package_json = project / "package.json"
56+
if package_json.exists():
57+
deps = self._parse_package_json(package_json)
58+
deps.source_file = "package.json"
59+
# Detect TypeScript
60+
tsconfig = project / "tsconfig.json"
61+
if tsconfig.exists():
62+
deps.language = "typescript"
63+
return deps
64+
65+
cargo_toml = project / "Cargo.toml"
66+
if cargo_toml.exists():
67+
deps = self._parse_cargo_toml(cargo_toml)
68+
deps.source_file = "Cargo.toml"
69+
return deps
70+
71+
go_mod = project / "go.mod"
72+
if go_mod.exists():
73+
deps = self._parse_go_mod(go_mod)
74+
deps.source_file = "go.mod"
75+
return deps
76+
5177
pyproject = project / "pyproject.toml"
5278
if pyproject.exists():
5379
deps = self._parse_pyproject(pyproject)
@@ -159,6 +185,108 @@ def _parse_requirements_txt(self, path: Path) -> ProjectDependencies:
159185
deps.install_command = "pip install -r requirements.txt"
160186
return deps
161187

188+
def _parse_package_json(self, path: Path) -> ProjectDependencies:
189+
"""Parse package.json for dependencies."""
190+
import json
191+
deps = ProjectDependencies(language="javascript")
192+
try:
193+
data = json.loads(path.read_text(encoding="utf-8"))
194+
except (json.JSONDecodeError, OSError):
195+
return deps
196+
197+
deps.version = data.get("version", "")
198+
name = data.get("name", "")
199+
200+
# Node engine version
201+
engines = data.get("engines", {})
202+
deps.runtime_version = engines.get("node", "")
203+
204+
for dep_name, ver in data.get("dependencies", {}).items():
205+
deps.dependencies.append(DependencyInfo(name=dep_name, version_spec=ver))
206+
207+
for dep_name, ver in data.get("devDependencies", {}).items():
208+
deps.dev_dependencies.append(DependencyInfo(name=dep_name, version_spec=ver, group="dev"))
209+
210+
# Detect install command
211+
lock_yarn = path.parent / "yarn.lock"
212+
lock_pnpm = path.parent / "pnpm-lock.yaml"
213+
if lock_pnpm.exists():
214+
deps.install_command = "pnpm install"
215+
elif lock_yarn.exists():
216+
deps.install_command = "yarn install"
217+
else:
218+
deps.install_command = "npm install"
219+
220+
if name:
221+
deps.keywords = data.get("keywords", [])
222+
223+
return deps
224+
225+
def _parse_cargo_toml(self, path: Path) -> ProjectDependencies:
226+
"""Parse Cargo.toml for Rust dependencies."""
227+
deps = ProjectDependencies(language="rust")
228+
content = path.read_text(encoding="utf-8")
229+
230+
# Version
231+
ver_match = re.search(r'^version\s*=\s*"([^"]+)"', content, re.MULTILINE)
232+
if ver_match:
233+
deps.version = ver_match.group(1)
234+
235+
# Dependencies section
236+
in_deps = False
237+
in_dev_deps = False
238+
for line in content.splitlines():
239+
stripped = line.strip()
240+
if stripped == "[dependencies]":
241+
in_deps, in_dev_deps = True, False
242+
continue
243+
elif stripped == "[dev-dependencies]":
244+
in_deps, in_dev_deps = False, True
245+
continue
246+
elif stripped.startswith("["):
247+
in_deps, in_dev_deps = False, False
248+
continue
249+
250+
dep_match = re.match(r'^([a-zA-Z0-9_-]+)\s*=\s*"?([^"\s]+)"?', stripped)
251+
if dep_match:
252+
info = DependencyInfo(name=dep_match.group(1), version_spec=dep_match.group(2))
253+
if in_dev_deps:
254+
info.group = "dev"
255+
deps.dev_dependencies.append(info)
256+
elif in_deps:
257+
deps.dependencies.append(info)
258+
259+
deps.install_command = "cargo build"
260+
return deps
261+
262+
def _parse_go_mod(self, path: Path) -> ProjectDependencies:
263+
"""Parse go.mod for Go dependencies."""
264+
deps = ProjectDependencies(language="go")
265+
content = path.read_text(encoding="utf-8")
266+
267+
# Go version
268+
go_ver = re.search(r'^go\s+(\S+)', content, re.MULTILINE)
269+
if go_ver:
270+
deps.runtime_version = go_ver.group(1)
271+
272+
# Require block
273+
require_block = re.search(r'require\s*\((.*?)\)', content, re.DOTALL)
274+
if require_block:
275+
for line in require_block.group(1).splitlines():
276+
line = line.strip()
277+
if not line or line.startswith("//"):
278+
continue
279+
parts = line.split()
280+
if len(parts) >= 2:
281+
deps.dependencies.append(DependencyInfo(name=parts[0], version_spec=parts[1]))
282+
283+
# Single-line requires
284+
for match in re.finditer(r'^require\s+(\S+)\s+(\S+)', content, re.MULTILINE):
285+
deps.dependencies.append(DependencyInfo(name=match.group(1), version_spec=match.group(2)))
286+
287+
deps.install_command = "go mod download"
288+
return deps
289+
162290
@staticmethod
163291
def _parse_dep_string(dep_str: str) -> DependencyInfo:
164292
"""Parse a dependency string like 'package>=1.0'."""

code2docs/generators/contributing_gen.py

Lines changed: 111 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ def _detect_dev_tools(self) -> Dict[str, bool]:
4141
all_deps = {d.name.lower() for d in deps.dependencies}
4242
all_deps |= {d.name.lower() for d in deps.dev_dependencies}
4343
return {
44+
"language": deps.language,
45+
"install_command": deps.install_command,
46+
# Python tools
4447
"pytest": "pytest" in all_deps,
4548
"black": "black" in all_deps,
4649
"ruff": "ruff" in all_deps,
@@ -49,22 +52,43 @@ def _detect_dev_tools(self) -> Dict[str, bool]:
4952
"isort": "isort" in all_deps,
5053
"pre-commit": "pre-commit" in all_deps,
5154
"tox": "tox" in all_deps,
55+
# JS/TS tools
56+
"eslint": "eslint" in all_deps,
57+
"prettier": "prettier" in all_deps,
58+
"jest": "jest" in all_deps,
59+
"vitest": "vitest" in all_deps,
60+
"typescript": "typescript" in all_deps,
5261
}
5362

5463
def _render_setup(self, tools: Dict[str, bool]) -> str:
5564
"""Render development setup instructions."""
5665
project = self.config.project_name or "project"
5766
repo_url = self.config.repo_url or "<repository-url>"
67+
lang = tools.get("language", "python")
68+
install_cmd = tools.get("install_command", "pip install -e .")
69+
5870
lines = [
5971
"## Development Setup\n",
6072
"```bash",
6173
f"git clone {repo_url}",
6274
f"cd {project}",
63-
"python -m venv .venv",
64-
"source .venv/bin/activate # or .venv\\Scripts\\activate on Windows",
65-
"pip install -e \".[dev]\"",
66-
"```",
6775
]
76+
77+
if lang in ("javascript", "typescript"):
78+
lines.append(install_cmd)
79+
elif lang == "rust":
80+
lines.append("cargo build")
81+
elif lang == "go":
82+
lines.append("go mod download")
83+
lines.append("go build ./...")
84+
else:
85+
lines.extend([
86+
"python -m venv .venv",
87+
"source .venv/bin/activate # or .venv\\Scripts\\activate on Windows",
88+
'pip install -e ".[dev]"',
89+
])
90+
91+
lines.append("```")
6892
return "\n".join(lines)
6993

7094
@staticmethod
@@ -83,8 +107,60 @@ def _render_development(tools: Dict[str, bool]) -> str:
83107
@staticmethod
84108
def _render_testing(tools: Dict[str, bool]) -> str:
85109
"""Render testing instructions."""
110+
lang = tools.get("language", "python")
86111
lines = ["## Testing\n"]
87-
if tools.get("pytest"):
112+
113+
if lang in ("javascript", "typescript"):
114+
if tools.get("vitest"):
115+
lines.extend([
116+
"```bash",
117+
"# Run all tests",
118+
"npx vitest",
119+
"",
120+
"# Run with coverage",
121+
"npx vitest --coverage",
122+
"",
123+
"# Watch mode",
124+
"npx vitest --watch",
125+
"```",
126+
])
127+
elif tools.get("jest"):
128+
lines.extend([
129+
"```bash",
130+
"# Run all tests",
131+
"npx jest",
132+
"",
133+
"# Run with coverage",
134+
"npx jest --coverage",
135+
"```",
136+
])
137+
else:
138+
lines.extend([
139+
"```bash",
140+
"npm test",
141+
"```",
142+
])
143+
elif lang == "rust":
144+
lines.extend([
145+
"```bash",
146+
"# Run all tests",
147+
"cargo test",
148+
"",
149+
"# Run a specific test",
150+
"cargo test test_name",
151+
"```",
152+
])
153+
elif lang == "go":
154+
lines.extend([
155+
"```bash",
156+
"# Run all tests",
157+
"go test ./...",
158+
"",
159+
"# Run with coverage",
160+
"go test -cover ./...",
161+
"```",
162+
])
163+
elif tools.get("pytest"):
88164
lines.extend([
89165
"```bash",
90166
"# Run all tests",
@@ -108,19 +184,37 @@ def _render_testing(tools: Dict[str, bool]) -> str:
108184
@staticmethod
109185
def _render_code_style(tools: Dict[str, bool]) -> str:
110186
"""Render code style guidelines."""
187+
lang = tools.get("language", "python")
111188
lines = ["## Code Style\n"]
112-
if tools.get("black"):
113-
lines.append("- **Formatting:** [Black](https://black.readthedocs.io/) — `black .`")
114-
if tools.get("ruff"):
115-
lines.append("- **Linting:** [Ruff](https://docs.astral.sh/ruff/) — `ruff check .`")
116-
if tools.get("mypy"):
117-
lines.append("- **Type checking:** [mypy](https://mypy.readthedocs.io/) — `mypy .`")
118-
if tools.get("flake8"):
119-
lines.append("- **Linting:** [flake8](https://flake8.pycqa.org/) — `flake8 .`")
120-
if tools.get("isort"):
121-
lines.append("- **Imports:** [isort](https://pycqa.github.io/isort/) — `isort .`")
122-
if not any(tools.get(t) for t in ("black", "ruff", "mypy", "flake8", "isort")):
123-
lines.append("Follow PEP 8 conventions.")
189+
190+
if lang in ("javascript", "typescript"):
191+
if tools.get("eslint"):
192+
lines.append("- **Linting:** [ESLint](https://eslint.org/) — `npx eslint .`")
193+
if tools.get("prettier"):
194+
lines.append("- **Formatting:** [Prettier](https://prettier.io/) — `npx prettier --write .`")
195+
if tools.get("typescript"):
196+
lines.append("- **Type checking:** TypeScript — `npx tsc --noEmit`")
197+
if not any(tools.get(t) for t in ("eslint", "prettier")):
198+
lines.append("Follow the project's ESLint/Prettier configuration.")
199+
elif lang == "rust":
200+
lines.append("- **Formatting:** `cargo fmt`")
201+
lines.append("- **Linting:** `cargo clippy`")
202+
elif lang == "go":
203+
lines.append("- **Formatting:** `gofmt -w .`")
204+
lines.append("- **Linting:** `go vet ./...`")
205+
else:
206+
if tools.get("black"):
207+
lines.append("- **Formatting:** [Black](https://black.readthedocs.io/) — `black .`")
208+
if tools.get("ruff"):
209+
lines.append("- **Linting:** [Ruff](https://docs.astral.sh/ruff/) — `ruff check .`")
210+
if tools.get("mypy"):
211+
lines.append("- **Type checking:** [mypy](https://mypy.readthedocs.io/) — `mypy .`")
212+
if tools.get("flake8"):
213+
lines.append("- **Linting:** [flake8](https://flake8.pycqa.org/) — `flake8 .`")
214+
if tools.get("isort"):
215+
lines.append("- **Imports:** [isort](https://pycqa.github.io/isort/) — `isort .`")
216+
if not any(tools.get(t) for t in ("black", "ruff", "mypy", "flake8", "isort")):
217+
lines.append("Follow PEP 8 conventions.")
124218
return "\n".join(lines)
125219

126220
@staticmethod

0 commit comments

Comments
 (0)