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 +7 -15
- code2docs/analyzers/dependency_scanner.py +89 -166
- code2docs/cli.py +84 -158
- code2docs/config.py +63 -205
- code2docs/generators/_registry_adapters.py +108 -259
- code2docs/generators/examples_gen.py +20 -8
- code2docs/generators/readme_gen.py +114 -269
- code2docs/registry.py +5 -9
- {code2docs-3.0.25.dist-info → code2docs-3.0.26.dist-info}/METADATA +8 -10
- {code2docs-3.0.25.dist-info → code2docs-3.0.26.dist-info}/RECORD +14 -14
- {code2docs-3.0.25.dist-info → code2docs-3.0.26.dist-info}/WHEEL +0 -0
- {code2docs-3.0.25.dist-info → code2docs-3.0.26.dist-info}/entry_points.txt +0 -0
- {code2docs-3.0.25.dist-info → code2docs-3.0.26.dist-info}/licenses/LICENSE +0 -0
- {code2docs-3.0.25.dist-info → code2docs-3.0.26.dist-info}/top_level.txt +0 -0
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__ =
|
|
9
|
-
__author__ =
|
|
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 ==
|
|
15
|
+
if name == 'generate_readme':
|
|
24
16
|
from .generators.readme_gen import generate_readme
|
|
25
17
|
return generate_readme
|
|
26
|
-
if name ==
|
|
18
|
+
if name == 'generate_docs':
|
|
27
19
|
from .generators import generate_docs
|
|
28
20
|
return generate_docs
|
|
29
|
-
if name ==
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
24
|
-
|
|
20
|
+
group: str = 'main'
|
|
25
21
|
|
|
26
22
|
@dataclass
|
|
27
23
|
class ProjectDependencies:
|
|
28
24
|
"""All detected project dependencies."""
|
|
29
|
-
language: str =
|
|
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 =
|
|
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 =
|
|
59
|
-
|
|
60
|
-
tsconfig = project / "tsconfig.json"
|
|
48
|
+
deps.source_file = 'package.json'
|
|
49
|
+
tsconfig = project / 'tsconfig.json'
|
|
61
50
|
if tsconfig.exists():
|
|
62
|
-
deps.language =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
109
|
-
deps.
|
|
110
|
-
deps.
|
|
111
|
-
deps.
|
|
112
|
-
deps.
|
|
113
|
-
|
|
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 ==
|
|
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
|
|
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=
|
|
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(
|
|
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=
|
|
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(
|
|
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(
|
|
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=
|
|
149
|
+
deps = ProjectDependencies(language='javascript')
|
|
192
150
|
try:
|
|
193
|
-
data = json.loads(path.read_text(encoding=
|
|
151
|
+
data = json.loads(path.read_text(encoding='utf-8'))
|
|
194
152
|
except (json.JSONDecodeError, OSError):
|
|
195
153
|
return deps
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
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
|
-
|
|
208
|
-
|
|
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 =
|
|
165
|
+
deps.install_command = 'pnpm install'
|
|
215
166
|
elif lock_yarn.exists():
|
|
216
|
-
deps.install_command =
|
|
167
|
+
deps.install_command = 'yarn install'
|
|
217
168
|
else:
|
|
218
|
-
deps.install_command =
|
|
219
|
-
|
|
169
|
+
deps.install_command = 'npm install'
|
|
220
170
|
if name:
|
|
221
|
-
deps.keywords = data.get(
|
|
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=
|
|
228
|
-
content = path.read_text(encoding=
|
|
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 ==
|
|
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 ==
|
|
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 =
|
|
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=
|
|
265
|
-
content = path.read_text(encoding=
|
|
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(
|
|
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
|
|
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=
|
|
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(
|
|
245
|
+
return result.stdout.strip().lstrip('v')
|
|
322
246
|
except (subprocess.TimeoutExpired, FileNotFoundError):
|
|
323
247
|
pass
|
|
324
|
-
|
|
325
|
-
return ""
|
|
248
|
+
return ''
|