code2docs 3.0.33__tar.gz → 3.0.35__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.
- {code2docs-3.0.33 → code2docs-3.0.35}/PKG-INFO +23 -12
- {code2docs-3.0.33 → code2docs-3.0.35}/README.md +21 -7
- {code2docs-3.0.33 → code2docs-3.0.35}/code2docs/__init__.py +10 -7
- {code2docs-3.0.33 → code2docs-3.0.35}/code2docs/analyzers/dependency_scanner.py +111 -89
- {code2docs-3.0.33 → code2docs-3.0.35}/code2docs/analyzers/docstring_extractor.py +5 -4
- {code2docs-3.0.33 → code2docs-3.0.35}/code2docs/analyzers/endpoint_detector.py +45 -15
- {code2docs-3.0.33 → code2docs-3.0.35}/code2docs/analyzers/markdown_validator.py +60 -36
- {code2docs-3.0.33 → code2docs-3.0.35}/code2docs/analyzers/project_scanner.py +3 -1
- {code2docs-3.0.33 → code2docs-3.0.35}/code2docs/base.py +1 -0
- {code2docs-3.0.33 → code2docs-3.0.35}/code2docs/cli.py +127 -87
- code2docs-3.0.35/code2docs/config.py +331 -0
- {code2docs-3.0.33 → code2docs-3.0.35}/code2docs/examples/quickstart.py +0 -1
- {code2docs-3.0.33 → code2docs-3.0.35}/code2docs/formatters/badges.py +8 -8
- {code2docs-3.0.33 → code2docs-3.0.35}/code2docs/formatters/toc.py +4 -4
- {code2docs-3.0.33 → code2docs-3.0.35}/code2docs/generators/_registry_adapters.py +147 -96
- {code2docs-3.0.33 → code2docs-3.0.35}/code2docs/generators/_source_links.py +3 -1
- {code2docs-3.0.33 → code2docs-3.0.35}/code2docs/generators/api_changelog_gen.py +56 -27
- {code2docs-3.0.33 → code2docs-3.0.35}/code2docs/generators/api_reference_gen.py +17 -11
- {code2docs-3.0.33 → code2docs-3.0.35}/code2docs/generators/architecture_gen.py +10 -9
- {code2docs-3.0.33 → code2docs-3.0.35}/code2docs/generators/changelog_gen.py +12 -6
- {code2docs-3.0.33 → code2docs-3.0.35}/code2docs/generators/code2llm_gen.py +33 -24
- {code2docs-3.0.33 → code2docs-3.0.35}/code2docs/generators/contributing_gen.py +79 -61
- {code2docs-3.0.33 → code2docs-3.0.35}/code2docs/generators/coverage_gen.py +16 -10
- {code2docs-3.0.33 → code2docs-3.0.35}/code2docs/generators/depgraph_gen.py +1 -2
- {code2docs-3.0.33 → code2docs-3.0.35}/code2docs/generators/examples_gen.py +48 -39
- {code2docs-3.0.33 → code2docs-3.0.35}/code2docs/generators/getting_started_gen.py +11 -12
- {code2docs-3.0.33 → code2docs-3.0.35}/code2docs/generators/mkdocs_gen.py +20 -12
- {code2docs-3.0.33 → code2docs-3.0.35}/code2docs/generators/module_docs_gen.py +13 -19
- {code2docs-3.0.33 → code2docs-3.0.35}/code2docs/generators/org_readme_gen.py +28 -22
- code2docs-3.0.35/code2docs/generators/readme_gen.py +492 -0
- {code2docs-3.0.33 → code2docs-3.0.35}/code2docs/llm_helper.py +33 -19
- {code2docs-3.0.33 → code2docs-3.0.35}/code2docs/registry.py +6 -4
- {code2docs-3.0.33 → code2docs-3.0.35}/code2docs/sync/differ.py +26 -19
- {code2docs-3.0.33 → code2docs-3.0.35}/code2docs/sync/watcher.py +1 -2
- {code2docs-3.0.33 → code2docs-3.0.35}/code2docs.egg-info/PKG-INFO +23 -12
- {code2docs-3.0.33 → code2docs-3.0.35}/code2docs.egg-info/requires.txt +0 -2
- {code2docs-3.0.33 → code2docs-3.0.35}/pyproject.toml +8 -21
- {code2docs-3.0.33 → code2docs-3.0.35}/tests/test_analyzers.py +14 -0
- {code2docs-3.0.33 → code2docs-3.0.35}/tests/test_llm_helper.py +24 -0
- code2docs-3.0.33/code2docs/config.py +0 -155
- code2docs-3.0.33/code2docs/generators/readme_gen.py +0 -375
- {code2docs-3.0.33 → code2docs-3.0.35}/LICENSE +0 -0
- {code2docs-3.0.33 → code2docs-3.0.35}/code2docs/__main__.py +0 -0
- {code2docs-3.0.33 → code2docs-3.0.35}/code2docs/analyzers/__init__.py +0 -0
- {code2docs-3.0.33 → code2docs-3.0.35}/code2docs/examples/advanced_usage.py +0 -0
- {code2docs-3.0.33 → code2docs-3.0.35}/code2docs/formatters/__init__.py +0 -0
- {code2docs-3.0.33 → code2docs-3.0.35}/code2docs/formatters/markdown.py +0 -0
- {code2docs-3.0.33 → code2docs-3.0.35}/code2docs/generators/__init__.py +0 -0
- {code2docs-3.0.33 → code2docs-3.0.35}/code2docs/generators/config_docs_gen.py +0 -0
- {code2docs-3.0.33 → code2docs-3.0.35}/code2docs/sync/__init__.py +0 -0
- {code2docs-3.0.33 → code2docs-3.0.35}/code2docs/sync/updater.py +0 -0
- {code2docs-3.0.33 → code2docs-3.0.35}/code2docs/templates/api_module.md.j2 +0 -0
- {code2docs-3.0.33 → code2docs-3.0.35}/code2docs/templates/architecture.md.j2 +0 -0
- {code2docs-3.0.33 → code2docs-3.0.35}/code2docs/templates/example_usage.py.j2 +0 -0
- {code2docs-3.0.33 → code2docs-3.0.35}/code2docs/templates/index.md.j2 +0 -0
- {code2docs-3.0.33 → code2docs-3.0.35}/code2docs/templates/module_doc.md.j2 +0 -0
- {code2docs-3.0.33 → code2docs-3.0.35}/code2docs/templates/readme.md.j2 +0 -0
- {code2docs-3.0.33 → code2docs-3.0.35}/code2docs.egg-info/SOURCES.txt +0 -0
- {code2docs-3.0.33 → code2docs-3.0.35}/code2docs.egg-info/dependency_links.txt +0 -0
- {code2docs-3.0.33 → code2docs-3.0.35}/code2docs.egg-info/entry_points.txt +0 -0
- {code2docs-3.0.33 → code2docs-3.0.35}/code2docs.egg-info/top_level.txt +0 -0
- {code2docs-3.0.33 → code2docs-3.0.35}/setup.cfg +0 -0
- {code2docs-3.0.33 → code2docs-3.0.35}/tests/test_cli.py +0 -0
- {code2docs-3.0.33 → code2docs-3.0.35}/tests/test_code2docs.py +0 -0
- {code2docs-3.0.33 → code2docs-3.0.35}/tests/test_config.py +0 -0
- {code2docs-3.0.33 → code2docs-3.0.35}/tests/test_formatters.py +0 -0
- {code2docs-3.0.33 → code2docs-3.0.35}/tests/test_generators.py +0 -0
- {code2docs-3.0.33 → code2docs-3.0.35}/tests/test_markdown_validator.py +0 -0
- {code2docs-3.0.33 → code2docs-3.0.35}/tests/test_registry.py +0 -0
- {code2docs-3.0.33 → code2docs-3.0.35}/tests/test_sync.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: code2docs
|
|
3
|
-
Version: 3.0.
|
|
3
|
+
Version: 3.0.35
|
|
4
4
|
Summary: Auto-generate and sync project documentation from source code analysis
|
|
5
5
|
Author-email: Tom Sapletta <tom@sapletta.com>
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -11,14 +11,13 @@ Keywords: documentation,auto-docs,readme-generator,api-reference,code-analysis,s
|
|
|
11
11
|
Classifier: Development Status :: 3 - Alpha
|
|
12
12
|
Classifier: Intended Audience :: Developers
|
|
13
13
|
Classifier: Programming Language :: Python :: 3
|
|
14
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
15
14
|
Classifier: Programming Language :: Python :: 3.10
|
|
16
15
|
Classifier: Programming Language :: Python :: 3.11
|
|
17
16
|
Classifier: Programming Language :: Python :: 3.12
|
|
18
17
|
Classifier: Programming Language :: Python :: 3.13
|
|
19
18
|
Classifier: Topic :: Software Development :: Documentation
|
|
20
19
|
Classifier: Topic :: Software Development :: Code Generators
|
|
21
|
-
Requires-Python: >=3.
|
|
20
|
+
Requires-Python: >=3.10
|
|
22
21
|
Description-Content-Type: text/markdown
|
|
23
22
|
License-File: LICENSE
|
|
24
23
|
Requires-Dist: code2llm>=0.5.0
|
|
@@ -44,26 +43,24 @@ Requires-Dist: ruff>=0.1; extra == "dev"
|
|
|
44
43
|
Requires-Dist: mypy>=1.0; extra == "dev"
|
|
45
44
|
Requires-Dist: build>=1.0; extra == "dev"
|
|
46
45
|
Requires-Dist: twine>=5.0; extra == "dev"
|
|
47
|
-
Requires-Dist: goal>=2.1.0; extra == "dev"
|
|
48
46
|
Requires-Dist: costs>=0.1.20; extra == "dev"
|
|
49
|
-
Requires-Dist: pfix>=0.1.60; extra == "dev"
|
|
50
47
|
Provides-Extra: all
|
|
51
48
|
Requires-Dist: code2docs[git,llm,mkdocs,watch]; extra == "all"
|
|
52
49
|
Dynamic: license-file
|
|
53
50
|
|
|
54
51
|
## AI Cost Tracking
|
|
55
52
|
|
|
56
|
-
    
|
|
54
|
+
  
|
|
58
55
|
|
|
59
|
-
- 🤖 **LLM usage:** $
|
|
60
|
-
- 👤 **Human dev:** ~$
|
|
56
|
+
- 🤖 **LLM usage:** $5.3635 (99 commits)
|
|
57
|
+
- 👤 **Human dev:** ~$4589 (45.9h @ $100/h, 30min dedup)
|
|
61
58
|
|
|
62
|
-
Generated on 2026-04
|
|
59
|
+
Generated on 2026-10-04 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
|
|
63
60
|
|
|
64
61
|
---
|
|
65
62
|
|
|
66
|
-
  
|
|
67
64
|
|
|
68
65
|
> Auto-generate and sync project documentation from source code analysis.
|
|
69
66
|
|
|
@@ -200,7 +197,7 @@ code2docs can update only specific sections of an existing README using markers:
|
|
|
200
197
|
```markdown
|
|
201
198
|
<!-- code2docs:start --># code2docs
|
|
202
199
|
|
|
203
|
-
   
|
|
204
201
|
> **276** functions | **57** classes | **51** files | CC̄ = 3.8
|
|
205
202
|
|
|
206
203
|
> Auto-generated project documentation from source code analysis.
|
|
@@ -579,3 +576,17 @@ code2docs/
|
|
|
579
576
|
## License
|
|
580
577
|
|
|
581
578
|
Licensed under Apache-2.0.
|
|
579
|
+
## Status
|
|
580
|
+
|
|
581
|
+
_Last updated by [taskill](https://github.com/oqlos/taskill) at 2026-04-25 18:20 UTC_
|
|
582
|
+
|
|
583
|
+
| Metric | Value |
|
|
584
|
+
|---|---|
|
|
585
|
+
| HEAD | `42f2ec4` |
|
|
586
|
+
| Coverage | — |
|
|
587
|
+
| Failing tests | — |
|
|
588
|
+
| Commits in last cycle | 0 |
|
|
589
|
+
|
|
590
|
+
> No commits or file changes since the last run. The project state is unchanged; TODO.md shows a number of completed lints/tasks but there are still active issues to address.
|
|
591
|
+
|
|
592
|
+
<!-- taskill:status:end -->
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
## AI Cost Tracking
|
|
2
2
|
|
|
3
|
-
    
|
|
4
|
+
  
|
|
5
5
|
|
|
6
|
-
- 🤖 **LLM usage:** $
|
|
7
|
-
- 👤 **Human dev:** ~$
|
|
6
|
+
- 🤖 **LLM usage:** $5.3635 (99 commits)
|
|
7
|
+
- 👤 **Human dev:** ~$4589 (45.9h @ $100/h, 30min dedup)
|
|
8
8
|
|
|
9
|
-
Generated on 2026-04
|
|
9
|
+
Generated on 2026-10-04 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
|
|
10
10
|
|
|
11
11
|
---
|
|
12
12
|
|
|
13
|
-
  
|
|
14
14
|
|
|
15
15
|
> Auto-generate and sync project documentation from source code analysis.
|
|
16
16
|
|
|
@@ -147,7 +147,7 @@ code2docs can update only specific sections of an existing README using markers:
|
|
|
147
147
|
```markdown
|
|
148
148
|
<!-- code2docs:start --># code2docs
|
|
149
149
|
|
|
150
|
-
   
|
|
151
151
|
> **276** functions | **57** classes | **51** files | CC̄ = 3.8
|
|
152
152
|
|
|
153
153
|
> Auto-generated project documentation from source code analysis.
|
|
@@ -526,3 +526,17 @@ code2docs/
|
|
|
526
526
|
## License
|
|
527
527
|
|
|
528
528
|
Licensed under Apache-2.0.
|
|
529
|
+
## Status
|
|
530
|
+
|
|
531
|
+
_Last updated by [taskill](https://github.com/oqlos/taskill) at 2026-04-25 18:20 UTC_
|
|
532
|
+
|
|
533
|
+
| Metric | Value |
|
|
534
|
+
|---|---|
|
|
535
|
+
| HEAD | `42f2ec4` |
|
|
536
|
+
| Coverage | — |
|
|
537
|
+
| Failing tests | — |
|
|
538
|
+
| Commits in last cycle | 0 |
|
|
539
|
+
|
|
540
|
+
> No commits or file changes since the last run. The project state is unchanged; TODO.md shows a number of completed lints/tasks but there are still active issues to address.
|
|
541
|
+
|
|
542
|
+
<!-- taskill:status:end -->
|
|
@@ -5,20 +5,23 @@ Uses code2llm's AnalysisResult to produce human-readable documentation:
|
|
|
5
5
|
README.md, API references, module docs, examples, and architecture diagrams.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
-
__version__ =
|
|
9
|
-
__author__ =
|
|
10
|
-
__all__ = [
|
|
8
|
+
__version__ = "3.0.35"
|
|
9
|
+
__author__ = "Tom Sapletta"
|
|
10
|
+
__all__ = ["Code2DocsConfig", "generate_readme", "generate_docs", "analyze_and_document"]
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
def __getattr__(name):
|
|
14
14
|
"""Lazy import heavy modules on first access."""
|
|
15
|
-
if name ==
|
|
15
|
+
if name == "generate_readme":
|
|
16
16
|
from .generators.readme_gen import generate_readme
|
|
17
|
+
|
|
17
18
|
return generate_readme
|
|
18
|
-
if name ==
|
|
19
|
+
if name == "generate_docs":
|
|
19
20
|
from .generators import generate_docs
|
|
21
|
+
|
|
20
22
|
return generate_docs
|
|
21
|
-
if name ==
|
|
23
|
+
if name == "analyze_and_document":
|
|
22
24
|
from .analyzers.project_scanner import analyze_and_document
|
|
25
|
+
|
|
23
26
|
return analyze_and_document
|
|
24
|
-
raise AttributeError(f
|
|
27
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
"""Scan project dependencies from requirements.txt, pyproject.toml, setup.py, package.json, composer.json, Cargo.toml, go.mod."""
|
|
2
|
+
|
|
2
3
|
import re
|
|
3
4
|
from dataclasses import dataclass, field
|
|
4
5
|
from pathlib import Path
|
|
5
6
|
from typing import Dict, List
|
|
7
|
+
|
|
6
8
|
try:
|
|
7
9
|
import tomllib
|
|
8
10
|
except ImportError:
|
|
@@ -11,29 +13,34 @@ except ImportError:
|
|
|
11
13
|
except ImportError:
|
|
12
14
|
tomllib = None
|
|
13
15
|
|
|
16
|
+
|
|
14
17
|
@dataclass
|
|
15
18
|
class DependencyInfo:
|
|
16
19
|
"""Information about a project dependency."""
|
|
20
|
+
|
|
17
21
|
name: str
|
|
18
|
-
version_spec: str =
|
|
22
|
+
version_spec: str = ""
|
|
19
23
|
optional: bool = False
|
|
20
|
-
group: str =
|
|
24
|
+
group: str = "main"
|
|
25
|
+
|
|
21
26
|
|
|
22
27
|
@dataclass
|
|
23
28
|
class ProjectDependencies:
|
|
24
29
|
"""All detected project dependencies."""
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
30
|
+
|
|
31
|
+
language: str = "python"
|
|
32
|
+
python_version: str = ""
|
|
33
|
+
runtime_version: str = ""
|
|
28
34
|
dependencies: List[DependencyInfo] = field(default_factory=list)
|
|
29
35
|
dev_dependencies: List[DependencyInfo] = field(default_factory=list)
|
|
30
36
|
optional_groups: Dict[str, List[DependencyInfo]] = field(default_factory=dict)
|
|
31
|
-
install_command: str =
|
|
32
|
-
source_file: str =
|
|
37
|
+
install_command: str = "pip install ."
|
|
38
|
+
source_file: str = ""
|
|
33
39
|
keywords: List[str] = field(default_factory=list)
|
|
34
40
|
classifiers: List[str] = field(default_factory=list)
|
|
35
41
|
urls: Dict[str, str] = field(default_factory=dict)
|
|
36
|
-
version: str =
|
|
42
|
+
version: str = ""
|
|
43
|
+
|
|
37
44
|
|
|
38
45
|
class DependencyScanner:
|
|
39
46
|
"""Scan and parse project dependency files."""
|
|
@@ -42,43 +49,43 @@ class DependencyScanner:
|
|
|
42
49
|
"""Scan project for dependency information."""
|
|
43
50
|
project = Path(project_path)
|
|
44
51
|
deps = ProjectDependencies()
|
|
45
|
-
package_json = project /
|
|
52
|
+
package_json = project / "package.json"
|
|
46
53
|
if package_json.exists():
|
|
47
54
|
deps = self._parse_package_json(package_json)
|
|
48
|
-
deps.source_file =
|
|
49
|
-
tsconfig = project /
|
|
55
|
+
deps.source_file = "package.json"
|
|
56
|
+
tsconfig = project / "tsconfig.json"
|
|
50
57
|
if tsconfig.exists():
|
|
51
|
-
deps.language =
|
|
58
|
+
deps.language = "typescript"
|
|
52
59
|
return deps
|
|
53
|
-
composer_json = project /
|
|
60
|
+
composer_json = project / "composer.json"
|
|
54
61
|
if composer_json.exists():
|
|
55
62
|
deps = self._parse_composer_json(composer_json)
|
|
56
|
-
deps.source_file =
|
|
63
|
+
deps.source_file = "composer.json"
|
|
57
64
|
return deps
|
|
58
|
-
cargo_toml = project /
|
|
65
|
+
cargo_toml = project / "Cargo.toml"
|
|
59
66
|
if cargo_toml.exists():
|
|
60
67
|
deps = self._parse_cargo_toml(cargo_toml)
|
|
61
|
-
deps.source_file =
|
|
68
|
+
deps.source_file = "Cargo.toml"
|
|
62
69
|
return deps
|
|
63
|
-
go_mod = project /
|
|
70
|
+
go_mod = project / "go.mod"
|
|
64
71
|
if go_mod.exists():
|
|
65
72
|
deps = self._parse_go_mod(go_mod)
|
|
66
|
-
deps.source_file =
|
|
73
|
+
deps.source_file = "go.mod"
|
|
67
74
|
return deps
|
|
68
|
-
pyproject = project /
|
|
75
|
+
pyproject = project / "pyproject.toml"
|
|
69
76
|
if pyproject.exists():
|
|
70
77
|
deps = self._parse_pyproject(pyproject)
|
|
71
|
-
deps.source_file =
|
|
78
|
+
deps.source_file = "pyproject.toml"
|
|
72
79
|
return deps
|
|
73
|
-
setup_py = project /
|
|
80
|
+
setup_py = project / "setup.py"
|
|
74
81
|
if setup_py.exists():
|
|
75
82
|
deps = self._parse_setup_py(setup_py)
|
|
76
|
-
deps.source_file =
|
|
83
|
+
deps.source_file = "setup.py"
|
|
77
84
|
return deps
|
|
78
|
-
req_txt = project /
|
|
85
|
+
req_txt = project / "requirements.txt"
|
|
79
86
|
if req_txt.exists():
|
|
80
87
|
deps = self._parse_requirements_txt(req_txt)
|
|
81
|
-
deps.source_file =
|
|
88
|
+
deps.source_file = "requirements.txt"
|
|
82
89
|
return deps
|
|
83
90
|
return deps
|
|
84
91
|
|
|
@@ -87,35 +94,35 @@ class DependencyScanner:
|
|
|
87
94
|
deps = ProjectDependencies()
|
|
88
95
|
if tomllib is None:
|
|
89
96
|
return self._parse_pyproject_regex(path)
|
|
90
|
-
with open(path,
|
|
97
|
+
with open(path, "rb") as f:
|
|
91
98
|
data = tomllib.load(f)
|
|
92
|
-
project = data.get(
|
|
93
|
-
deps.python_version = project.get(
|
|
94
|
-
deps.version = project.get(
|
|
95
|
-
deps.keywords = project.get(
|
|
96
|
-
deps.classifiers = project.get(
|
|
97
|
-
deps.urls = project.get(
|
|
98
|
-
for dep_str in project.get(
|
|
99
|
+
project = data.get("project", {})
|
|
100
|
+
deps.python_version = project.get("requires-python", "")
|
|
101
|
+
deps.version = project.get("version", "")
|
|
102
|
+
deps.keywords = project.get("keywords", [])
|
|
103
|
+
deps.classifiers = project.get("classifiers", [])
|
|
104
|
+
deps.urls = project.get("urls", {})
|
|
105
|
+
for dep_str in project.get("dependencies", []):
|
|
99
106
|
deps.dependencies.append(self._parse_dep_string(dep_str))
|
|
100
|
-
for group, dep_list in project.get(
|
|
107
|
+
for group, dep_list in project.get("optional-dependencies", {}).items():
|
|
101
108
|
group_deps = [self._parse_dep_string(d) for d in dep_list]
|
|
102
109
|
for d in group_deps:
|
|
103
110
|
d.optional = True
|
|
104
111
|
d.group = group
|
|
105
112
|
deps.optional_groups[group] = group_deps
|
|
106
|
-
if group ==
|
|
113
|
+
if group == "dev":
|
|
107
114
|
deps.dev_dependencies = group_deps
|
|
108
|
-
name = project.get(
|
|
115
|
+
name = project.get("name", "")
|
|
109
116
|
if name:
|
|
110
|
-
deps.install_command = f
|
|
117
|
+
deps.install_command = f"pip install {name}"
|
|
111
118
|
deps.version = self._detect_version(path.parent, deps.version)
|
|
112
119
|
return deps
|
|
113
120
|
|
|
114
121
|
def _parse_pyproject_regex(self, path: Path) -> ProjectDependencies:
|
|
115
122
|
"""Fallback regex-based pyproject.toml parser."""
|
|
116
123
|
deps = ProjectDependencies()
|
|
117
|
-
content = path.read_text(encoding=
|
|
118
|
-
dep_match = re.search(
|
|
124
|
+
content = path.read_text(encoding="utf-8")
|
|
125
|
+
dep_match = re.search("dependencies\\s*=\\s*\\[(.*?)\\]", content, re.DOTALL)
|
|
119
126
|
if dep_match:
|
|
120
127
|
for dep_str in re.findall('"([^"]+)"', dep_match.group(1)):
|
|
121
128
|
deps.dependencies.append(self._parse_dep_string(dep_str))
|
|
@@ -127,8 +134,8 @@ class DependencyScanner:
|
|
|
127
134
|
def _parse_setup_py(self, path: Path) -> ProjectDependencies:
|
|
128
135
|
"""Parse setup.py for dependencies (regex-based, no exec)."""
|
|
129
136
|
deps = ProjectDependencies()
|
|
130
|
-
content = path.read_text(encoding=
|
|
131
|
-
match = re.search(
|
|
137
|
+
content = path.read_text(encoding="utf-8")
|
|
138
|
+
match = re.search("install_requires\\s*=\\s*\\[(.*?)\\]", content, re.DOTALL)
|
|
132
139
|
if match:
|
|
133
140
|
for dep_str in re.findall('"([^"]+)"', match.group(1)):
|
|
134
141
|
deps.dependencies.append(self._parse_dep_string(dep_str))
|
|
@@ -140,66 +147,72 @@ class DependencyScanner:
|
|
|
140
147
|
def _parse_requirements_txt(self, path: Path) -> ProjectDependencies:
|
|
141
148
|
"""Parse requirements.txt."""
|
|
142
149
|
deps = ProjectDependencies()
|
|
143
|
-
for line in path.read_text(encoding=
|
|
150
|
+
for line in path.read_text(encoding="utf-8").splitlines():
|
|
144
151
|
line = line.strip()
|
|
145
|
-
if not line or line.startswith(
|
|
152
|
+
if not line or line.startswith("#") or line.startswith("-"):
|
|
146
153
|
continue
|
|
147
154
|
deps.dependencies.append(self._parse_dep_string(line))
|
|
148
|
-
deps.install_command =
|
|
155
|
+
deps.install_command = "pip install -r requirements.txt"
|
|
149
156
|
return deps
|
|
150
157
|
|
|
151
158
|
def _parse_package_json(self, path: Path) -> ProjectDependencies:
|
|
152
159
|
"""Parse package.json for dependencies."""
|
|
153
160
|
import json
|
|
154
|
-
|
|
161
|
+
|
|
162
|
+
deps = ProjectDependencies(language="javascript")
|
|
155
163
|
try:
|
|
156
|
-
data = json.loads(path.read_text(encoding=
|
|
164
|
+
data = json.loads(path.read_text(encoding="utf-8"))
|
|
157
165
|
except (json.JSONDecodeError, OSError):
|
|
158
166
|
return deps
|
|
159
|
-
deps.version = data.get(
|
|
160
|
-
name = data.get(
|
|
161
|
-
engines = data.get(
|
|
162
|
-
deps.runtime_version = engines.get(
|
|
163
|
-
for dep_name, ver in data.get(
|
|
167
|
+
deps.version = data.get("version", "")
|
|
168
|
+
name = data.get("name", "")
|
|
169
|
+
engines = data.get("engines", {})
|
|
170
|
+
deps.runtime_version = engines.get("node", "")
|
|
171
|
+
for dep_name, ver in data.get("dependencies", {}).items():
|
|
164
172
|
deps.dependencies.append(DependencyInfo(name=dep_name, version_spec=ver))
|
|
165
|
-
for dep_name, ver in data.get(
|
|
166
|
-
deps.dev_dependencies.append(
|
|
167
|
-
|
|
168
|
-
|
|
173
|
+
for dep_name, ver in data.get("devDependencies", {}).items():
|
|
174
|
+
deps.dev_dependencies.append(
|
|
175
|
+
DependencyInfo(name=dep_name, version_spec=ver, group="dev")
|
|
176
|
+
)
|
|
177
|
+
lock_yarn = path.parent / "yarn.lock"
|
|
178
|
+
lock_pnpm = path.parent / "pnpm-lock.yaml"
|
|
169
179
|
if lock_pnpm.exists():
|
|
170
|
-
deps.install_command =
|
|
180
|
+
deps.install_command = "pnpm install"
|
|
171
181
|
elif lock_yarn.exists():
|
|
172
|
-
deps.install_command =
|
|
182
|
+
deps.install_command = "yarn install"
|
|
173
183
|
else:
|
|
174
|
-
deps.install_command =
|
|
184
|
+
deps.install_command = "npm install"
|
|
175
185
|
if name:
|
|
176
|
-
deps.keywords = data.get(
|
|
186
|
+
deps.keywords = data.get("keywords", [])
|
|
177
187
|
return deps
|
|
178
188
|
|
|
179
189
|
def _parse_composer_json(self, path: Path) -> ProjectDependencies:
|
|
180
190
|
"""Parse composer.json for PHP dependencies."""
|
|
181
191
|
import json
|
|
182
|
-
|
|
192
|
+
|
|
193
|
+
deps = ProjectDependencies(language="php")
|
|
183
194
|
try:
|
|
184
|
-
data = json.loads(path.read_text(encoding=
|
|
195
|
+
data = json.loads(path.read_text(encoding="utf-8"))
|
|
185
196
|
except (json.JSONDecodeError, OSError):
|
|
186
197
|
return deps
|
|
187
|
-
deps.version = data.get(
|
|
188
|
-
deps.keywords = data.get(
|
|
189
|
-
for dep_name, ver in (data.get(
|
|
190
|
-
if dep_name.lower() ==
|
|
198
|
+
deps.version = data.get("version", "")
|
|
199
|
+
deps.keywords = data.get("keywords", []) or []
|
|
200
|
+
for dep_name, ver in (data.get("require", {}) or {}).items():
|
|
201
|
+
if dep_name.lower() == "php":
|
|
191
202
|
deps.runtime_version = ver
|
|
192
203
|
continue
|
|
193
204
|
deps.dependencies.append(DependencyInfo(name=dep_name, version_spec=ver))
|
|
194
|
-
for dep_name, ver in (data.get(
|
|
195
|
-
deps.dev_dependencies.append(
|
|
196
|
-
|
|
205
|
+
for dep_name, ver in (data.get("require-dev", {}) or {}).items():
|
|
206
|
+
deps.dev_dependencies.append(
|
|
207
|
+
DependencyInfo(name=dep_name, version_spec=ver, group="dev")
|
|
208
|
+
)
|
|
209
|
+
deps.install_command = "composer install"
|
|
197
210
|
return deps
|
|
198
211
|
|
|
199
212
|
def _parse_cargo_toml(self, path: Path) -> ProjectDependencies:
|
|
200
213
|
"""Parse Cargo.toml for Rust dependencies."""
|
|
201
|
-
deps = ProjectDependencies(language=
|
|
202
|
-
content = path.read_text(encoding=
|
|
214
|
+
deps = ProjectDependencies(language="rust")
|
|
215
|
+
content = path.read_text(encoding="utf-8")
|
|
203
216
|
ver_match = re.search('^version\\s*=\\s*"([^"]+)"', content, re.MULTILINE)
|
|
204
217
|
if ver_match:
|
|
205
218
|
deps.version = ver_match.group(1)
|
|
@@ -207,67 +220,76 @@ class DependencyScanner:
|
|
|
207
220
|
in_dev_deps = False
|
|
208
221
|
for line in content.splitlines():
|
|
209
222
|
stripped = line.strip()
|
|
210
|
-
if stripped ==
|
|
223
|
+
if stripped == "[dependencies]":
|
|
211
224
|
in_deps, in_dev_deps = (True, False)
|
|
212
225
|
continue
|
|
213
|
-
elif stripped ==
|
|
226
|
+
elif stripped == "[dev-dependencies]":
|
|
214
227
|
in_deps, in_dev_deps = (False, True)
|
|
215
228
|
continue
|
|
216
|
-
elif stripped.startswith(
|
|
229
|
+
elif stripped.startswith("["):
|
|
217
230
|
in_deps, in_dev_deps = (False, False)
|
|
218
231
|
continue
|
|
219
232
|
dep_match = re.match('^([a-zA-Z0-9_-]+)\\s*=\\s*"?([^"\\s]+)"?', stripped)
|
|
220
233
|
if dep_match:
|
|
221
234
|
info = DependencyInfo(name=dep_match.group(1), version_spec=dep_match.group(2))
|
|
222
235
|
if in_dev_deps:
|
|
223
|
-
info.group =
|
|
236
|
+
info.group = "dev"
|
|
224
237
|
deps.dev_dependencies.append(info)
|
|
225
238
|
elif in_deps:
|
|
226
239
|
deps.dependencies.append(info)
|
|
227
|
-
deps.install_command =
|
|
240
|
+
deps.install_command = "cargo build"
|
|
228
241
|
return deps
|
|
229
242
|
|
|
230
243
|
def _parse_go_mod(self, path: Path) -> ProjectDependencies:
|
|
231
244
|
"""Parse go.mod for Go dependencies."""
|
|
232
|
-
deps = ProjectDependencies(language=
|
|
233
|
-
content = path.read_text(encoding=
|
|
234
|
-
go_ver = re.search(
|
|
245
|
+
deps = ProjectDependencies(language="go")
|
|
246
|
+
content = path.read_text(encoding="utf-8")
|
|
247
|
+
go_ver = re.search("^go\\s+(\\S+)", content, re.MULTILINE)
|
|
235
248
|
if go_ver:
|
|
236
249
|
deps.runtime_version = go_ver.group(1)
|
|
237
|
-
require_block = re.search(
|
|
250
|
+
require_block = re.search("require\\s*\\((.*?)\\)", content, re.DOTALL)
|
|
238
251
|
if require_block:
|
|
239
252
|
for line in require_block.group(1).splitlines():
|
|
240
253
|
line = line.strip()
|
|
241
|
-
if not line or line.startswith(
|
|
254
|
+
if not line or line.startswith("//"):
|
|
242
255
|
continue
|
|
243
256
|
parts = line.split()
|
|
244
257
|
if len(parts) >= 2:
|
|
245
258
|
deps.dependencies.append(DependencyInfo(name=parts[0], version_spec=parts[1]))
|
|
246
|
-
for match in re.finditer(
|
|
247
|
-
deps.dependencies.append(
|
|
248
|
-
|
|
259
|
+
for match in re.finditer("^require\\s+(\\S+)\\s+(\\S+)", content, re.MULTILINE):
|
|
260
|
+
deps.dependencies.append(
|
|
261
|
+
DependencyInfo(name=match.group(1), version_spec=match.group(2))
|
|
262
|
+
)
|
|
263
|
+
deps.install_command = "go mod download"
|
|
249
264
|
return deps
|
|
250
265
|
|
|
251
266
|
@staticmethod
|
|
252
267
|
def _parse_dep_string(dep_str: str) -> DependencyInfo:
|
|
253
268
|
"""Parse a dependency string like 'package>=1.0'."""
|
|
254
|
-
match = re.match(
|
|
269
|
+
match = re.match("^([a-zA-Z0-9_-]+)\\s*(.*)", dep_str.strip())
|
|
255
270
|
if match:
|
|
256
271
|
return DependencyInfo(name=match.group(1), version_spec=match.group(2).strip())
|
|
257
272
|
return DependencyInfo(name=dep_str.strip())
|
|
258
273
|
|
|
259
|
-
def _detect_version(self, project_path: Path, pyproject_version: str=
|
|
274
|
+
def _detect_version(self, project_path: Path, pyproject_version: str = "") -> str:
|
|
260
275
|
"""Detect version from pyproject.toml, git tags, or VERSION file."""
|
|
261
276
|
if pyproject_version:
|
|
262
277
|
return pyproject_version
|
|
263
|
-
version_file = project_path /
|
|
278
|
+
version_file = project_path / "VERSION"
|
|
264
279
|
if version_file.exists():
|
|
265
|
-
return version_file.read_text(encoding=
|
|
280
|
+
return version_file.read_text(encoding="utf-8").strip()
|
|
266
281
|
try:
|
|
267
282
|
import subprocess
|
|
268
|
-
|
|
283
|
+
|
|
284
|
+
result = subprocess.run(
|
|
285
|
+
["git", "describe", "--tags", "--abbrev=0"],
|
|
286
|
+
cwd=str(project_path),
|
|
287
|
+
capture_output=True,
|
|
288
|
+
text=True,
|
|
289
|
+
timeout=5,
|
|
290
|
+
)
|
|
269
291
|
if result.returncode == 0:
|
|
270
|
-
return result.stdout.strip().lstrip(
|
|
292
|
+
return result.stdout.strip().lstrip("v")
|
|
271
293
|
except (subprocess.TimeoutExpired, FileNotFoundError):
|
|
272
294
|
pass
|
|
273
|
-
return
|
|
295
|
+
return ""
|
|
@@ -9,6 +9,7 @@ from code2llm.api import AnalysisResult
|
|
|
9
9
|
@dataclass
|
|
10
10
|
class DocstringInfo:
|
|
11
11
|
"""Parsed docstring with sections."""
|
|
12
|
+
|
|
12
13
|
raw: str
|
|
13
14
|
summary: str = ""
|
|
14
15
|
description: str = ""
|
|
@@ -131,8 +132,8 @@ class DocstringExtractor:
|
|
|
131
132
|
"classes_documented": documented_classes,
|
|
132
133
|
"classes_coverage": (documented_classes / total_classes * 100) if total_classes else 0,
|
|
133
134
|
"overall_coverage": (
|
|
134
|
-
(documented_funcs + documented_classes)
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
135
|
+
(documented_funcs + documented_classes) / (total_funcs + total_classes) * 100
|
|
136
|
+
)
|
|
137
|
+
if (total_funcs + total_classes)
|
|
138
|
+
else 0,
|
|
138
139
|
}
|