code2docs 3.0.25__py3-none-any.whl → 3.0.26__py3-none-any.whl

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/__init__.py CHANGED
@@ -5,28 +5,20 @@ 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.25"
9
- __author__ = "Tom Sapletta"
10
-
11
- from .config import Code2DocsConfig
12
-
13
- __all__ = [
14
- "Code2DocsConfig",
15
- "generate_readme",
16
- "generate_docs",
17
- "analyze_and_document",
18
- ]
8
+ __version__ = '3.0.26'
9
+ __author__ = 'Tom Sapletta'
10
+ __all__ = ['Code2DocsConfig', 'generate_readme', 'generate_docs', 'analyze_and_document']
19
11
 
20
12
 
21
13
  def __getattr__(name):
22
14
  """Lazy import heavy modules on first access."""
23
- if name == "generate_readme":
15
+ if name == 'generate_readme':
24
16
  from .generators.readme_gen import generate_readme
25
17
  return generate_readme
26
- if name == "generate_docs":
18
+ if name == 'generate_docs':
27
19
  from .generators import generate_docs
28
20
  return generate_docs
29
- if name == "analyze_and_document":
21
+ if name == 'analyze_and_document':
30
22
  from .analyzers.project_scanner import analyze_and_document
31
23
  return analyze_and_document
32
- raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
24
+ raise AttributeError(f'module {__name__!r} has no attribute {name!r}')
@@ -1,45 +1,39 @@
1
1
  """Scan project dependencies from requirements.txt, pyproject.toml, setup.py, package.json, Cargo.toml, go.mod."""
2
-
3
2
  import re
4
3
  from dataclasses import dataclass, field
5
4
  from pathlib import Path
6
- from typing import Dict, List, Optional
7
-
5
+ from typing import Dict, List
8
6
  try:
9
7
  import tomllib
10
8
  except ImportError:
11
9
  try:
12
10
  import tomli as tomllib
13
11
  except ImportError:
14
- tomllib = None # type: ignore
15
-
12
+ tomllib = None
16
13
 
17
14
  @dataclass
18
15
  class DependencyInfo:
19
16
  """Information about a project dependency."""
20
17
  name: str
21
- version_spec: str = ""
18
+ version_spec: str = ''
22
19
  optional: bool = False
23
- group: str = "main"
24
-
20
+ group: str = 'main'
25
21
 
26
22
  @dataclass
27
23
  class ProjectDependencies:
28
24
  """All detected project dependencies."""
29
- language: str = "python"
30
- python_version: str = ""
31
- runtime_version: str = ""
25
+ language: str = 'python'
26
+ python_version: str = ''
27
+ runtime_version: str = ''
32
28
  dependencies: List[DependencyInfo] = field(default_factory=list)
33
29
  dev_dependencies: List[DependencyInfo] = field(default_factory=list)
34
30
  optional_groups: Dict[str, List[DependencyInfo]] = field(default_factory=dict)
35
- install_command: str = "pip install ."
36
- source_file: str = ""
37
- # Additional metadata from pyproject.toml
31
+ install_command: str = 'pip install .'
32
+ source_file: str = ''
38
33
  keywords: List[str] = field(default_factory=list)
39
34
  classifiers: List[str] = field(default_factory=list)
40
35
  urls: Dict[str, str] = field(default_factory=dict)
41
- version: str = ""
42
-
36
+ version: str = ''
43
37
 
44
38
  class DependencyScanner:
45
39
  """Scan and parse project dependency files."""
@@ -48,278 +42,207 @@ class DependencyScanner:
48
42
  """Scan project for dependency information."""
49
43
  project = Path(project_path)
50
44
  deps = ProjectDependencies()
51
-
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"
45
+ package_json = project / 'package.json'
56
46
  if package_json.exists():
57
47
  deps = self._parse_package_json(package_json)
58
- deps.source_file = "package.json"
59
- # Detect TypeScript
60
- tsconfig = project / "tsconfig.json"
48
+ deps.source_file = 'package.json'
49
+ tsconfig = project / 'tsconfig.json'
61
50
  if tsconfig.exists():
62
- deps.language = "typescript"
51
+ deps.language = 'typescript'
63
52
  return deps
64
-
65
- cargo_toml = project / "Cargo.toml"
53
+ cargo_toml = project / 'Cargo.toml'
66
54
  if cargo_toml.exists():
67
55
  deps = self._parse_cargo_toml(cargo_toml)
68
- deps.source_file = "Cargo.toml"
56
+ deps.source_file = 'Cargo.toml'
69
57
  return deps
70
-
71
- go_mod = project / "go.mod"
58
+ go_mod = project / 'go.mod'
72
59
  if go_mod.exists():
73
60
  deps = self._parse_go_mod(go_mod)
74
- deps.source_file = "go.mod"
61
+ deps.source_file = 'go.mod'
75
62
  return deps
76
-
77
- pyproject = project / "pyproject.toml"
63
+ pyproject = project / 'pyproject.toml'
78
64
  if pyproject.exists():
79
65
  deps = self._parse_pyproject(pyproject)
80
- deps.source_file = "pyproject.toml"
66
+ deps.source_file = 'pyproject.toml'
81
67
  return deps
82
-
83
- setup_py = project / "setup.py"
68
+ setup_py = project / 'setup.py'
84
69
  if setup_py.exists():
85
70
  deps = self._parse_setup_py(setup_py)
86
- deps.source_file = "setup.py"
71
+ deps.source_file = 'setup.py'
87
72
  return deps
88
-
89
- req_txt = project / "requirements.txt"
73
+ req_txt = project / 'requirements.txt'
90
74
  if req_txt.exists():
91
75
  deps = self._parse_requirements_txt(req_txt)
92
- deps.source_file = "requirements.txt"
76
+ deps.source_file = 'requirements.txt'
93
77
  return deps
94
-
95
78
  return deps
96
79
 
97
80
  def _parse_pyproject(self, path: Path) -> ProjectDependencies:
98
81
  """Parse pyproject.toml for dependencies."""
99
82
  deps = ProjectDependencies()
100
-
101
83
  if tomllib is None:
102
- # Fallback: regex-based parsing
103
84
  return self._parse_pyproject_regex(path)
104
-
105
- with open(path, "rb") as f:
85
+ with open(path, 'rb') as f:
106
86
  data = tomllib.load(f)
107
-
108
- project = data.get("project", {})
109
- deps.python_version = project.get("requires-python", "")
110
- deps.version = project.get("version", "")
111
- deps.keywords = project.get("keywords", [])
112
- deps.classifiers = project.get("classifiers", [])
113
- deps.urls = project.get("urls", {})
114
-
115
- # Main dependencies
116
- for dep_str in project.get("dependencies", []):
87
+ project = data.get('project', {})
88
+ deps.python_version = project.get('requires-python', '')
89
+ deps.version = project.get('version', '')
90
+ deps.keywords = project.get('keywords', [])
91
+ deps.classifiers = project.get('classifiers', [])
92
+ deps.urls = project.get('urls', {})
93
+ for dep_str in project.get('dependencies', []):
117
94
  deps.dependencies.append(self._parse_dep_string(dep_str))
118
-
119
- # Optional dependencies
120
- for group, dep_list in project.get("optional-dependencies", {}).items():
95
+ for group, dep_list in project.get('optional-dependencies', {}).items():
121
96
  group_deps = [self._parse_dep_string(d) for d in dep_list]
122
97
  for d in group_deps:
123
98
  d.optional = True
124
99
  d.group = group
125
100
  deps.optional_groups[group] = group_deps
126
- if group == "dev":
101
+ if group == 'dev':
127
102
  deps.dev_dependencies = group_deps
128
-
129
- # Install command
130
- name = project.get("name", "")
103
+ name = project.get('name', '')
131
104
  if name:
132
- deps.install_command = f"pip install {name}"
133
-
134
- # Detect version with fallback to git tags or VERSION file
105
+ deps.install_command = f'pip install {name}'
135
106
  deps.version = self._detect_version(path.parent, deps.version)
136
-
137
107
  return deps
138
108
 
139
109
  def _parse_pyproject_regex(self, path: Path) -> ProjectDependencies:
140
110
  """Fallback regex-based pyproject.toml parser."""
141
111
  deps = ProjectDependencies()
142
- content = path.read_text(encoding="utf-8")
143
-
144
- # Extract dependencies array
145
- dep_match = re.search(r'dependencies\s*=\s*\[(.*?)\]', content, re.DOTALL)
112
+ content = path.read_text(encoding='utf-8')
113
+ dep_match = re.search('dependencies\\s*=\\s*\\[(.*?)\\]', content, re.DOTALL)
146
114
  if dep_match:
147
- for dep_str in re.findall(r'"([^"]+)"', dep_match.group(1)):
115
+ for dep_str in re.findall('"([^"]+)"', dep_match.group(1)):
148
116
  deps.dependencies.append(self._parse_dep_string(dep_str))
149
-
150
- # Extract python version
151
- py_match = re.search(r'requires-python\s*=\s*"([^"]+)"', content)
117
+ py_match = re.search('requires-python\\s*=\\s*"([^"]+)"', content)
152
118
  if py_match:
153
119
  deps.python_version = py_match.group(1)
154
-
155
120
  return deps
156
121
 
157
122
  def _parse_setup_py(self, path: Path) -> ProjectDependencies:
158
123
  """Parse setup.py for dependencies (regex-based, no exec)."""
159
124
  deps = ProjectDependencies()
160
- content = path.read_text(encoding="utf-8")
161
-
162
- # install_requires
163
- match = re.search(r'install_requires\s*=\s*\[(.*?)\]', content, re.DOTALL)
125
+ content = path.read_text(encoding='utf-8')
126
+ match = re.search('install_requires\\s*=\\s*\\[(.*?)\\]', content, re.DOTALL)
164
127
  if match:
165
- for dep_str in re.findall(r'"([^"]+)"', match.group(1)):
128
+ for dep_str in re.findall('"([^"]+)"', match.group(1)):
166
129
  deps.dependencies.append(self._parse_dep_string(dep_str))
167
-
168
- # python_requires
169
- py_match = re.search(r'python_requires\s*=\s*"([^"]+)"', content)
130
+ py_match = re.search('python_requires\\s*=\\s*"([^"]+)"', content)
170
131
  if py_match:
171
132
  deps.python_version = py_match.group(1)
172
-
173
133
  return deps
174
134
 
175
135
  def _parse_requirements_txt(self, path: Path) -> ProjectDependencies:
176
136
  """Parse requirements.txt."""
177
137
  deps = ProjectDependencies()
178
-
179
- for line in path.read_text(encoding="utf-8").splitlines():
138
+ for line in path.read_text(encoding='utf-8').splitlines():
180
139
  line = line.strip()
181
- if not line or line.startswith("#") or line.startswith("-"):
140
+ if not line or line.startswith('#') or line.startswith('-'):
182
141
  continue
183
142
  deps.dependencies.append(self._parse_dep_string(line))
184
-
185
- deps.install_command = "pip install -r requirements.txt"
143
+ deps.install_command = 'pip install -r requirements.txt'
186
144
  return deps
187
145
 
188
146
  def _parse_package_json(self, path: Path) -> ProjectDependencies:
189
147
  """Parse package.json for dependencies."""
190
148
  import json
191
- deps = ProjectDependencies(language="javascript")
149
+ deps = ProjectDependencies(language='javascript')
192
150
  try:
193
- data = json.loads(path.read_text(encoding="utf-8"))
151
+ data = json.loads(path.read_text(encoding='utf-8'))
194
152
  except (json.JSONDecodeError, OSError):
195
153
  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():
154
+ deps.version = data.get('version', '')
155
+ name = data.get('name', '')
156
+ engines = data.get('engines', {})
157
+ deps.runtime_version = engines.get('node', '')
158
+ for dep_name, ver in data.get('dependencies', {}).items():
205
159
  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"
160
+ for dep_name, ver in data.get('devDependencies', {}).items():
161
+ deps.dev_dependencies.append(DependencyInfo(name=dep_name, version_spec=ver, group='dev'))
162
+ lock_yarn = path.parent / 'yarn.lock'
163
+ lock_pnpm = path.parent / 'pnpm-lock.yaml'
213
164
  if lock_pnpm.exists():
214
- deps.install_command = "pnpm install"
165
+ deps.install_command = 'pnpm install'
215
166
  elif lock_yarn.exists():
216
- deps.install_command = "yarn install"
167
+ deps.install_command = 'yarn install'
217
168
  else:
218
- deps.install_command = "npm install"
219
-
169
+ deps.install_command = 'npm install'
220
170
  if name:
221
- deps.keywords = data.get("keywords", [])
222
-
171
+ deps.keywords = data.get('keywords', [])
223
172
  return deps
224
173
 
225
174
  def _parse_cargo_toml(self, path: Path) -> ProjectDependencies:
226
175
  """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)
176
+ deps = ProjectDependencies(language='rust')
177
+ content = path.read_text(encoding='utf-8')
178
+ ver_match = re.search('^version\\s*=\\s*"([^"]+)"', content, re.MULTILINE)
232
179
  if ver_match:
233
180
  deps.version = ver_match.group(1)
234
-
235
- # Dependencies section
236
181
  in_deps = False
237
182
  in_dev_deps = False
238
183
  for line in content.splitlines():
239
184
  stripped = line.strip()
240
- if stripped == "[dependencies]":
241
- in_deps, in_dev_deps = True, False
185
+ if stripped == '[dependencies]':
186
+ in_deps, in_dev_deps = (True, False)
242
187
  continue
243
- elif stripped == "[dev-dependencies]":
244
- in_deps, in_dev_deps = False, True
188
+ elif stripped == '[dev-dependencies]':
189
+ in_deps, in_dev_deps = (False, True)
245
190
  continue
246
- elif stripped.startswith("["):
247
- in_deps, in_dev_deps = False, False
191
+ elif stripped.startswith('['):
192
+ in_deps, in_dev_deps = (False, False)
248
193
  continue
249
-
250
- dep_match = re.match(r'^([a-zA-Z0-9_-]+)\s*=\s*"?([^"\s]+)"?', stripped)
194
+ dep_match = re.match('^([a-zA-Z0-9_-]+)\\s*=\\s*"?([^"\\s]+)"?', stripped)
251
195
  if dep_match:
252
196
  info = DependencyInfo(name=dep_match.group(1), version_spec=dep_match.group(2))
253
197
  if in_dev_deps:
254
- info.group = "dev"
198
+ info.group = 'dev'
255
199
  deps.dev_dependencies.append(info)
256
200
  elif in_deps:
257
201
  deps.dependencies.append(info)
258
-
259
- deps.install_command = "cargo build"
202
+ deps.install_command = 'cargo build'
260
203
  return deps
261
204
 
262
205
  def _parse_go_mod(self, path: Path) -> ProjectDependencies:
263
206
  """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)
207
+ deps = ProjectDependencies(language='go')
208
+ content = path.read_text(encoding='utf-8')
209
+ go_ver = re.search('^go\\s+(\\S+)', content, re.MULTILINE)
269
210
  if go_ver:
270
211
  deps.runtime_version = go_ver.group(1)
271
-
272
- # Require block
273
- require_block = re.search(r'require\s*\((.*?)\)', content, re.DOTALL)
212
+ require_block = re.search('require\\s*\\((.*?)\\)', content, re.DOTALL)
274
213
  if require_block:
275
214
  for line in require_block.group(1).splitlines():
276
215
  line = line.strip()
277
- if not line or line.startswith("//"):
216
+ if not line or line.startswith('//'):
278
217
  continue
279
218
  parts = line.split()
280
219
  if len(parts) >= 2:
281
220
  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):
221
+ for match in re.finditer('^require\\s+(\\S+)\\s+(\\S+)', content, re.MULTILINE):
285
222
  deps.dependencies.append(DependencyInfo(name=match.group(1), version_spec=match.group(2)))
286
-
287
- deps.install_command = "go mod download"
223
+ deps.install_command = 'go mod download'
288
224
  return deps
289
225
 
290
226
  @staticmethod
291
227
  def _parse_dep_string(dep_str: str) -> DependencyInfo:
292
228
  """Parse a dependency string like 'package>=1.0'."""
293
- match = re.match(r'^([a-zA-Z0-9_-]+)\s*(.*)', dep_str.strip())
229
+ match = re.match('^([a-zA-Z0-9_-]+)\\s*(.*)', dep_str.strip())
294
230
  if match:
295
- return DependencyInfo(
296
- name=match.group(1),
297
- version_spec=match.group(2).strip(),
298
- )
231
+ return DependencyInfo(name=match.group(1), version_spec=match.group(2).strip())
299
232
  return DependencyInfo(name=dep_str.strip())
300
233
 
301
- def _detect_version(self, project_path: Path, pyproject_version: str = "") -> str:
234
+ def _detect_version(self, project_path: Path, pyproject_version: str='') -> str:
302
235
  """Detect version from pyproject.toml, git tags, or VERSION file."""
303
- # Priority 1: pyproject.toml version
304
236
  if pyproject_version:
305
237
  return pyproject_version
306
-
307
- # Priority 2: VERSION file
308
- version_file = project_path / "VERSION"
238
+ version_file = project_path / 'VERSION'
309
239
  if version_file.exists():
310
- return version_file.read_text(encoding="utf-8").strip()
311
-
312
- # Priority 3: git tags (latest tag)
240
+ return version_file.read_text(encoding='utf-8').strip()
313
241
  try:
314
242
  import subprocess
315
- result = subprocess.run(
316
- ["git", "describe", "--tags", "--abbrev=0"],
317
- cwd=str(project_path),
318
- capture_output=True, text=True, timeout=5,
319
- )
243
+ result = subprocess.run(['git', 'describe', '--tags', '--abbrev=0'], cwd=str(project_path), capture_output=True, text=True, timeout=5)
320
244
  if result.returncode == 0:
321
- return result.stdout.strip().lstrip("v")
245
+ return result.stdout.strip().lstrip('v')
322
246
  except (subprocess.TimeoutExpired, FileNotFoundError):
323
247
  pass
324
-
325
- return ""
248
+ return ''