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.
Files changed (70) hide show
  1. {code2docs-3.0.33 → code2docs-3.0.35}/PKG-INFO +23 -12
  2. {code2docs-3.0.33 → code2docs-3.0.35}/README.md +21 -7
  3. {code2docs-3.0.33 → code2docs-3.0.35}/code2docs/__init__.py +10 -7
  4. {code2docs-3.0.33 → code2docs-3.0.35}/code2docs/analyzers/dependency_scanner.py +111 -89
  5. {code2docs-3.0.33 → code2docs-3.0.35}/code2docs/analyzers/docstring_extractor.py +5 -4
  6. {code2docs-3.0.33 → code2docs-3.0.35}/code2docs/analyzers/endpoint_detector.py +45 -15
  7. {code2docs-3.0.33 → code2docs-3.0.35}/code2docs/analyzers/markdown_validator.py +60 -36
  8. {code2docs-3.0.33 → code2docs-3.0.35}/code2docs/analyzers/project_scanner.py +3 -1
  9. {code2docs-3.0.33 → code2docs-3.0.35}/code2docs/base.py +1 -0
  10. {code2docs-3.0.33 → code2docs-3.0.35}/code2docs/cli.py +127 -87
  11. code2docs-3.0.35/code2docs/config.py +331 -0
  12. {code2docs-3.0.33 → code2docs-3.0.35}/code2docs/examples/quickstart.py +0 -1
  13. {code2docs-3.0.33 → code2docs-3.0.35}/code2docs/formatters/badges.py +8 -8
  14. {code2docs-3.0.33 → code2docs-3.0.35}/code2docs/formatters/toc.py +4 -4
  15. {code2docs-3.0.33 → code2docs-3.0.35}/code2docs/generators/_registry_adapters.py +147 -96
  16. {code2docs-3.0.33 → code2docs-3.0.35}/code2docs/generators/_source_links.py +3 -1
  17. {code2docs-3.0.33 → code2docs-3.0.35}/code2docs/generators/api_changelog_gen.py +56 -27
  18. {code2docs-3.0.33 → code2docs-3.0.35}/code2docs/generators/api_reference_gen.py +17 -11
  19. {code2docs-3.0.33 → code2docs-3.0.35}/code2docs/generators/architecture_gen.py +10 -9
  20. {code2docs-3.0.33 → code2docs-3.0.35}/code2docs/generators/changelog_gen.py +12 -6
  21. {code2docs-3.0.33 → code2docs-3.0.35}/code2docs/generators/code2llm_gen.py +33 -24
  22. {code2docs-3.0.33 → code2docs-3.0.35}/code2docs/generators/contributing_gen.py +79 -61
  23. {code2docs-3.0.33 → code2docs-3.0.35}/code2docs/generators/coverage_gen.py +16 -10
  24. {code2docs-3.0.33 → code2docs-3.0.35}/code2docs/generators/depgraph_gen.py +1 -2
  25. {code2docs-3.0.33 → code2docs-3.0.35}/code2docs/generators/examples_gen.py +48 -39
  26. {code2docs-3.0.33 → code2docs-3.0.35}/code2docs/generators/getting_started_gen.py +11 -12
  27. {code2docs-3.0.33 → code2docs-3.0.35}/code2docs/generators/mkdocs_gen.py +20 -12
  28. {code2docs-3.0.33 → code2docs-3.0.35}/code2docs/generators/module_docs_gen.py +13 -19
  29. {code2docs-3.0.33 → code2docs-3.0.35}/code2docs/generators/org_readme_gen.py +28 -22
  30. code2docs-3.0.35/code2docs/generators/readme_gen.py +492 -0
  31. {code2docs-3.0.33 → code2docs-3.0.35}/code2docs/llm_helper.py +33 -19
  32. {code2docs-3.0.33 → code2docs-3.0.35}/code2docs/registry.py +6 -4
  33. {code2docs-3.0.33 → code2docs-3.0.35}/code2docs/sync/differ.py +26 -19
  34. {code2docs-3.0.33 → code2docs-3.0.35}/code2docs/sync/watcher.py +1 -2
  35. {code2docs-3.0.33 → code2docs-3.0.35}/code2docs.egg-info/PKG-INFO +23 -12
  36. {code2docs-3.0.33 → code2docs-3.0.35}/code2docs.egg-info/requires.txt +0 -2
  37. {code2docs-3.0.33 → code2docs-3.0.35}/pyproject.toml +8 -21
  38. {code2docs-3.0.33 → code2docs-3.0.35}/tests/test_analyzers.py +14 -0
  39. {code2docs-3.0.33 → code2docs-3.0.35}/tests/test_llm_helper.py +24 -0
  40. code2docs-3.0.33/code2docs/config.py +0 -155
  41. code2docs-3.0.33/code2docs/generators/readme_gen.py +0 -375
  42. {code2docs-3.0.33 → code2docs-3.0.35}/LICENSE +0 -0
  43. {code2docs-3.0.33 → code2docs-3.0.35}/code2docs/__main__.py +0 -0
  44. {code2docs-3.0.33 → code2docs-3.0.35}/code2docs/analyzers/__init__.py +0 -0
  45. {code2docs-3.0.33 → code2docs-3.0.35}/code2docs/examples/advanced_usage.py +0 -0
  46. {code2docs-3.0.33 → code2docs-3.0.35}/code2docs/formatters/__init__.py +0 -0
  47. {code2docs-3.0.33 → code2docs-3.0.35}/code2docs/formatters/markdown.py +0 -0
  48. {code2docs-3.0.33 → code2docs-3.0.35}/code2docs/generators/__init__.py +0 -0
  49. {code2docs-3.0.33 → code2docs-3.0.35}/code2docs/generators/config_docs_gen.py +0 -0
  50. {code2docs-3.0.33 → code2docs-3.0.35}/code2docs/sync/__init__.py +0 -0
  51. {code2docs-3.0.33 → code2docs-3.0.35}/code2docs/sync/updater.py +0 -0
  52. {code2docs-3.0.33 → code2docs-3.0.35}/code2docs/templates/api_module.md.j2 +0 -0
  53. {code2docs-3.0.33 → code2docs-3.0.35}/code2docs/templates/architecture.md.j2 +0 -0
  54. {code2docs-3.0.33 → code2docs-3.0.35}/code2docs/templates/example_usage.py.j2 +0 -0
  55. {code2docs-3.0.33 → code2docs-3.0.35}/code2docs/templates/index.md.j2 +0 -0
  56. {code2docs-3.0.33 → code2docs-3.0.35}/code2docs/templates/module_doc.md.j2 +0 -0
  57. {code2docs-3.0.33 → code2docs-3.0.35}/code2docs/templates/readme.md.j2 +0 -0
  58. {code2docs-3.0.33 → code2docs-3.0.35}/code2docs.egg-info/SOURCES.txt +0 -0
  59. {code2docs-3.0.33 → code2docs-3.0.35}/code2docs.egg-info/dependency_links.txt +0 -0
  60. {code2docs-3.0.33 → code2docs-3.0.35}/code2docs.egg-info/entry_points.txt +0 -0
  61. {code2docs-3.0.33 → code2docs-3.0.35}/code2docs.egg-info/top_level.txt +0 -0
  62. {code2docs-3.0.33 → code2docs-3.0.35}/setup.cfg +0 -0
  63. {code2docs-3.0.33 → code2docs-3.0.35}/tests/test_cli.py +0 -0
  64. {code2docs-3.0.33 → code2docs-3.0.35}/tests/test_code2docs.py +0 -0
  65. {code2docs-3.0.33 → code2docs-3.0.35}/tests/test_config.py +0 -0
  66. {code2docs-3.0.33 → code2docs-3.0.35}/tests/test_formatters.py +0 -0
  67. {code2docs-3.0.33 → code2docs-3.0.35}/tests/test_generators.py +0 -0
  68. {code2docs-3.0.33 → code2docs-3.0.35}/tests/test_markdown_validator.py +0 -0
  69. {code2docs-3.0.33 → code2docs-3.0.35}/tests/test_registry.py +0 -0
  70. {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.33
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.9
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
- ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-3.0.33-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
57
- ![AI Cost](https://img.shields.io/badge/AI%20Cost-$7.50-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-25.3h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
53
+ ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-3.0.35-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
54
+ ![AI Cost](https://img.shields.io/badge/AI%20Cost-$5.36-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-45.9h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
58
55
 
59
- - 🤖 **LLM usage:** $7.5000 (73 commits)
60
- - 👤 **Human dev:** ~$2534 (25.3h @ $100/h, 30min dedup)
56
+ - 🤖 **LLM usage:** $5.3635 (99 commits)
57
+ - 👤 **Human dev:** ~$4589 (45.9h @ $100/h, 30min dedup)
61
58
 
62
- Generated on 2026-04-20 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
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
- ![version](https://img.shields.io/badge/version-3.0.33-blue) ![python](https://img.shields.io/badge/python-%3E%3D3.9-blue) ![docs](https://img.shields.io/badge/docs-auto--generated-blueviolet)
63
+ ![version](https://img.shields.io/badge/version-3.0.35-blue) ![python](https://img.shields.io/badge/python-%3E%3D3.9-blue) ![docs](https://img.shields.io/badge/docs-auto--generated-blueviolet)
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
- ![version](https://img.shields.io/badge/version-3.0.33-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)
200
+ ![version](https://img.shields.io/badge/version-3.0.35-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)
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
- ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-3.0.33-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
4
- ![AI Cost](https://img.shields.io/badge/AI%20Cost-$7.50-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-25.3h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
3
+ ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-3.0.35-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
4
+ ![AI Cost](https://img.shields.io/badge/AI%20Cost-$5.36-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-45.9h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
5
5
 
6
- - 🤖 **LLM usage:** $7.5000 (73 commits)
7
- - 👤 **Human dev:** ~$2534 (25.3h @ $100/h, 30min dedup)
6
+ - 🤖 **LLM usage:** $5.3635 (99 commits)
7
+ - 👤 **Human dev:** ~$4589 (45.9h @ $100/h, 30min dedup)
8
8
 
9
- Generated on 2026-04-20 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
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
- ![version](https://img.shields.io/badge/version-3.0.33-blue) ![python](https://img.shields.io/badge/python-%3E%3D3.9-blue) ![docs](https://img.shields.io/badge/docs-auto--generated-blueviolet)
13
+ ![version](https://img.shields.io/badge/version-3.0.35-blue) ![python](https://img.shields.io/badge/python-%3E%3D3.9-blue) ![docs](https://img.shields.io/badge/docs-auto--generated-blueviolet)
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
- ![version](https://img.shields.io/badge/version-3.0.33-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)
150
+ ![version](https://img.shields.io/badge/version-3.0.35-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)
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__ = '3.0.33'
9
- __author__ = 'Tom Sapletta'
10
- __all__ = ['Code2DocsConfig', 'generate_readme', 'generate_docs', 'analyze_and_document']
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 == 'generate_readme':
15
+ if name == "generate_readme":
16
16
  from .generators.readme_gen import generate_readme
17
+
17
18
  return generate_readme
18
- if name == 'generate_docs':
19
+ if name == "generate_docs":
19
20
  from .generators import generate_docs
21
+
20
22
  return generate_docs
21
- if name == 'analyze_and_document':
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'module {__name__!r} has no attribute {name!r}')
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 = 'main'
24
+ group: str = "main"
25
+
21
26
 
22
27
  @dataclass
23
28
  class ProjectDependencies:
24
29
  """All detected project dependencies."""
25
- language: str = 'python'
26
- python_version: str = ''
27
- runtime_version: str = ''
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 = 'pip install .'
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 / 'package.json'
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 = 'package.json'
49
- tsconfig = project / 'tsconfig.json'
55
+ deps.source_file = "package.json"
56
+ tsconfig = project / "tsconfig.json"
50
57
  if tsconfig.exists():
51
- deps.language = 'typescript'
58
+ deps.language = "typescript"
52
59
  return deps
53
- composer_json = project / 'composer.json'
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 = 'composer.json'
63
+ deps.source_file = "composer.json"
57
64
  return deps
58
- cargo_toml = project / 'Cargo.toml'
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 = 'Cargo.toml'
68
+ deps.source_file = "Cargo.toml"
62
69
  return deps
63
- go_mod = project / 'go.mod'
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 = 'go.mod'
73
+ deps.source_file = "go.mod"
67
74
  return deps
68
- pyproject = project / 'pyproject.toml'
75
+ pyproject = project / "pyproject.toml"
69
76
  if pyproject.exists():
70
77
  deps = self._parse_pyproject(pyproject)
71
- deps.source_file = 'pyproject.toml'
78
+ deps.source_file = "pyproject.toml"
72
79
  return deps
73
- setup_py = project / 'setup.py'
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 = 'setup.py'
83
+ deps.source_file = "setup.py"
77
84
  return deps
78
- req_txt = project / 'requirements.txt'
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 = 'requirements.txt'
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, 'rb') as f:
97
+ with open(path, "rb") as f:
91
98
  data = tomllib.load(f)
92
- project = data.get('project', {})
93
- deps.python_version = project.get('requires-python', '')
94
- deps.version = project.get('version', '')
95
- deps.keywords = project.get('keywords', [])
96
- deps.classifiers = project.get('classifiers', [])
97
- deps.urls = project.get('urls', {})
98
- for dep_str in project.get('dependencies', []):
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('optional-dependencies', {}).items():
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 == 'dev':
113
+ if group == "dev":
107
114
  deps.dev_dependencies = group_deps
108
- name = project.get('name', '')
115
+ name = project.get("name", "")
109
116
  if name:
110
- deps.install_command = f'pip install {name}'
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='utf-8')
118
- dep_match = re.search('dependencies\\s*=\\s*\\[(.*?)\\]', content, re.DOTALL)
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='utf-8')
131
- match = re.search('install_requires\\s*=\\s*\\[(.*?)\\]', content, re.DOTALL)
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='utf-8').splitlines():
150
+ for line in path.read_text(encoding="utf-8").splitlines():
144
151
  line = line.strip()
145
- if not line or line.startswith('#') 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 = 'pip install -r requirements.txt'
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
- deps = ProjectDependencies(language='javascript')
161
+
162
+ deps = ProjectDependencies(language="javascript")
155
163
  try:
156
- data = json.loads(path.read_text(encoding='utf-8'))
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('version', '')
160
- name = data.get('name', '')
161
- engines = data.get('engines', {})
162
- deps.runtime_version = engines.get('node', '')
163
- for dep_name, ver in data.get('dependencies', {}).items():
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('devDependencies', {}).items():
166
- deps.dev_dependencies.append(DependencyInfo(name=dep_name, version_spec=ver, group='dev'))
167
- lock_yarn = path.parent / 'yarn.lock'
168
- lock_pnpm = path.parent / 'pnpm-lock.yaml'
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 = 'pnpm install'
180
+ deps.install_command = "pnpm install"
171
181
  elif lock_yarn.exists():
172
- deps.install_command = 'yarn install'
182
+ deps.install_command = "yarn install"
173
183
  else:
174
- deps.install_command = 'npm install'
184
+ deps.install_command = "npm install"
175
185
  if name:
176
- deps.keywords = data.get('keywords', [])
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
- deps = ProjectDependencies(language='php')
192
+
193
+ deps = ProjectDependencies(language="php")
183
194
  try:
184
- data = json.loads(path.read_text(encoding='utf-8'))
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('version', '')
188
- deps.keywords = data.get('keywords', []) or []
189
- for dep_name, ver in (data.get('require', {}) or {}).items():
190
- if dep_name.lower() == 'php':
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('require-dev', {}) or {}).items():
195
- deps.dev_dependencies.append(DependencyInfo(name=dep_name, version_spec=ver, group='dev'))
196
- deps.install_command = 'composer install'
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='rust')
202
- content = path.read_text(encoding='utf-8')
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 == '[dependencies]':
223
+ if stripped == "[dependencies]":
211
224
  in_deps, in_dev_deps = (True, False)
212
225
  continue
213
- elif stripped == '[dev-dependencies]':
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 = 'dev'
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 = 'cargo build'
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='go')
233
- content = path.read_text(encoding='utf-8')
234
- go_ver = re.search('^go\\s+(\\S+)', content, re.MULTILINE)
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('require\\s*\\((.*?)\\)', content, re.DOTALL)
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('^require\\s+(\\S+)\\s+(\\S+)', content, re.MULTILINE):
247
- deps.dependencies.append(DependencyInfo(name=match.group(1), version_spec=match.group(2)))
248
- deps.install_command = 'go mod download'
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('^([a-zA-Z0-9_-]+)\\s*(.*)', dep_str.strip())
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='') -> 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 / 'VERSION'
278
+ version_file = project_path / "VERSION"
264
279
  if version_file.exists():
265
- return version_file.read_text(encoding='utf-8').strip()
280
+ return version_file.read_text(encoding="utf-8").strip()
266
281
  try:
267
282
  import subprocess
268
- result = subprocess.run(['git', 'describe', '--tags', '--abbrev=0'], cwd=str(project_path), capture_output=True, text=True, timeout=5)
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('v')
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
- / (total_funcs + total_classes)
136
- * 100
137
- ) if (total_funcs + total_classes) else 0,
135
+ (documented_funcs + documented_classes) / (total_funcs + total_classes) * 100
136
+ )
137
+ if (total_funcs + total_classes)
138
+ else 0,
138
139
  }