gd-tools-cli 0.1.0__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.
- gd_tools/__init__.py +3 -0
- gd_tools/__main__.py +31 -0
- gd_tools/addons/__init__.py +0 -0
- gd_tools/addons/gd-tools-coverage/coverage.gd +43 -0
- gd_tools/addons/gd-tools-coverage/post_run_hook.gd +113 -0
- gd_tools/addons/gd-tools-coverage/pre_run_hook.gd +229 -0
- gd_tools/cli.py +329 -0
- gd_tools/config.py +289 -0
- gd_tools/coverage/__init__.py +23 -0
- gd_tools/coverage/cobertura_reporter.py +136 -0
- gd_tools/coverage/html_reporter.py +95 -0
- gd_tools/coverage/lcov_reporter.py +90 -0
- gd_tools/coverage/orchestrator.py +282 -0
- gd_tools/coverage/plan_generator.py +377 -0
- gd_tools/coverage/reporter.py +518 -0
- gd_tools/coverage/templates/file.html +72 -0
- gd_tools/coverage/templates/index.html +90 -0
- gd_tools/coverage/terminal_reporter.py +107 -0
- gd_tools/doctor.py +495 -0
- gd_tools/errors.py +79 -0
- gd_tools/file_discovery.py +41 -0
- gd_tools/format_runner.py +118 -0
- gd_tools/godot.py +346 -0
- gd_tools/init.py +619 -0
- gd_tools/lint_runner.py +223 -0
- gd_tools/test_runner.py +474 -0
- gd_tools_cli-0.1.0.dist-info/METADATA +191 -0
- gd_tools_cli-0.1.0.dist-info/RECORD +31 -0
- gd_tools_cli-0.1.0.dist-info/WHEEL +5 -0
- gd_tools_cli-0.1.0.dist-info/entry_points.txt +2 -0
- gd_tools_cli-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""Shared file discovery module for gd-tools.
|
|
2
|
+
|
|
3
|
+
Discovers ``.gd`` files under a given path, applying exclude
|
|
4
|
+
patterns from the configuration. Used by both the lint runner
|
|
5
|
+
and the format runner.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
|
|
10
|
+
from gd_tools.config import DEFAULT_EXCLUDES
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def discover_gd_files(
|
|
14
|
+
path: str, excludes: list[str] | None = None
|
|
15
|
+
) -> list[str]:
|
|
16
|
+
"""Discover ``.gd`` files in ``path``, skipping excluded directories.
|
|
17
|
+
|
|
18
|
+
Recursively walks ``path`` and collects all files with a ``.gd``
|
|
19
|
+
extension (case-insensitive). Directories whose names appear in
|
|
20
|
+
``excludes`` are pruned from the walk so their contents are never
|
|
21
|
+
visited.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
path: Root directory to search.
|
|
25
|
+
excludes: Directory names to skip. Defaults to
|
|
26
|
+
:data:`DEFAULT_EXCLUDES` from ``config.py``.
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
List of file paths (as strings) to ``.gd`` files.
|
|
30
|
+
"""
|
|
31
|
+
if excludes is None:
|
|
32
|
+
excludes = DEFAULT_EXCLUDES
|
|
33
|
+
|
|
34
|
+
gd_files: list[str] = []
|
|
35
|
+
for root, dirs, files in os.walk(path):
|
|
36
|
+
# Prune excluded dirs in-place so os.walk doesn't descend into them
|
|
37
|
+
dirs[:] = [d for d in dirs if d not in excludes]
|
|
38
|
+
for file in files:
|
|
39
|
+
if file.lower().endswith(".gd"):
|
|
40
|
+
gd_files.append(os.path.join(root, file))
|
|
41
|
+
return gd_files
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"""Format runner module for gd-tools.
|
|
2
|
+
|
|
3
|
+
Wraps ``gdformat`` (via the gdtoolkit Python API) with config-driven
|
|
4
|
+
excludes and clean, formatted output. Discovers ``.gd`` files,
|
|
5
|
+
invokes the formatter programmatically, and returns structured results.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import difflib
|
|
9
|
+
import sys
|
|
10
|
+
from dataclasses import dataclass, field
|
|
11
|
+
|
|
12
|
+
from gdtoolkit.formatter import format_code
|
|
13
|
+
from lark.exceptions import LarkError
|
|
14
|
+
|
|
15
|
+
from gd_tools.config import GdToolsConfig
|
|
16
|
+
from gd_tools.errors import FormatError
|
|
17
|
+
from gd_tools.file_discovery import discover_gd_files
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass
|
|
21
|
+
class FormatResult:
|
|
22
|
+
"""Result of a format run.
|
|
23
|
+
|
|
24
|
+
Attributes:
|
|
25
|
+
files_checked: Total number of .gd files examined.
|
|
26
|
+
files_formatted: Number of files that were reformatted
|
|
27
|
+
(written with changes). Only non-zero in default mode.
|
|
28
|
+
files_needing_format: Number of files whose formatted
|
|
29
|
+
version differs from the original. Non-zero in --check
|
|
30
|
+
and --diff modes.
|
|
31
|
+
files_needing_format_paths: List of file paths that need
|
|
32
|
+
formatting. Only populated in --check mode.
|
|
33
|
+
diffs: List of unified diff strings for files that differ.
|
|
34
|
+
Only populated in --diff mode.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
files_checked: int = 0
|
|
38
|
+
files_formatted: int = 0
|
|
39
|
+
files_needing_format: int = 0
|
|
40
|
+
files_needing_format_paths: list[str] = field(default_factory=list)
|
|
41
|
+
diffs: list[str] = field(default_factory=list)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def run_format(
|
|
45
|
+
config: GdToolsConfig,
|
|
46
|
+
path: str = ".",
|
|
47
|
+
check: bool = False,
|
|
48
|
+
diff: bool = False,
|
|
49
|
+
) -> FormatResult:
|
|
50
|
+
"""Format ``.gd`` files in ``path`` using the gdtoolkit formatter.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
config: Project configuration with format excludes.
|
|
54
|
+
path: Root directory to search for .gd files.
|
|
55
|
+
check: If True, report files needing format without modifying.
|
|
56
|
+
diff: If True, show unified diffs without modifying.
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
FormatResult with counts and diffs.
|
|
60
|
+
|
|
61
|
+
Raises:
|
|
62
|
+
FormatError: If both check and diff are True (mutually exclusive).
|
|
63
|
+
"""
|
|
64
|
+
if check and diff:
|
|
65
|
+
raise FormatError(
|
|
66
|
+
"--check and --diff are mutually exclusive",
|
|
67
|
+
exit_code=2,
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
excludes = config.format.exclude
|
|
71
|
+
gd_files = discover_gd_files(path, excludes)
|
|
72
|
+
|
|
73
|
+
if not gd_files:
|
|
74
|
+
return FormatResult()
|
|
75
|
+
|
|
76
|
+
files_checked = len(gd_files)
|
|
77
|
+
files_formatted = 0
|
|
78
|
+
files_needing_format = 0
|
|
79
|
+
files_needing_format_paths: list[str] = []
|
|
80
|
+
diffs_list: list[str] = []
|
|
81
|
+
|
|
82
|
+
for file_path in gd_files:
|
|
83
|
+
try:
|
|
84
|
+
with open(file_path, "r", encoding="utf-8") as f:
|
|
85
|
+
original_code = f.read()
|
|
86
|
+
|
|
87
|
+
formatted_code = format_code(original_code, max_line_length=100)
|
|
88
|
+
|
|
89
|
+
if formatted_code != original_code:
|
|
90
|
+
if check:
|
|
91
|
+
files_needing_format += 1
|
|
92
|
+
files_needing_format_paths.append(file_path)
|
|
93
|
+
elif diff:
|
|
94
|
+
diff_str = "".join(
|
|
95
|
+
difflib.unified_diff(
|
|
96
|
+
original_code.splitlines(keepends=True),
|
|
97
|
+
formatted_code.splitlines(keepends=True),
|
|
98
|
+
fromfile=file_path,
|
|
99
|
+
tofile=file_path,
|
|
100
|
+
)
|
|
101
|
+
)
|
|
102
|
+
diffs_list.append(diff_str)
|
|
103
|
+
else:
|
|
104
|
+
with open(file_path, "w", encoding="utf-8") as f:
|
|
105
|
+
f.write(formatted_code)
|
|
106
|
+
files_formatted += 1
|
|
107
|
+
except LarkError as e:
|
|
108
|
+
# Syntax error: report file path and description, then skip
|
|
109
|
+
print(f"Warning: Skipping {file_path}: {e}", file=sys.stderr)
|
|
110
|
+
continue
|
|
111
|
+
|
|
112
|
+
return FormatResult(
|
|
113
|
+
files_checked=files_checked,
|
|
114
|
+
files_formatted=files_formatted,
|
|
115
|
+
files_needing_format=files_needing_format,
|
|
116
|
+
files_needing_format_paths=files_needing_format_paths,
|
|
117
|
+
diffs=diffs_list,
|
|
118
|
+
)
|
gd_tools/godot.py
ADDED
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
"""Godot binary detection and invocation module.
|
|
2
|
+
|
|
3
|
+
Resolves the Godot binary path via a 5-level priority chain, detects
|
|
4
|
+
and validates the Godot version, maps Godot versions to compatible
|
|
5
|
+
GUT versions, and provides a subprocess wrapper for invoking Godot.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
import re
|
|
10
|
+
import shutil
|
|
11
|
+
import subprocess
|
|
12
|
+
import sys
|
|
13
|
+
from dataclasses import dataclass
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
|
|
16
|
+
from gd_tools.config import GodotConfig
|
|
17
|
+
from gd_tools.errors import ConfigError, GodotNotFoundError
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass
|
|
21
|
+
class GodotInfo:
|
|
22
|
+
"""Information about a detected Godot binary.
|
|
23
|
+
|
|
24
|
+
Attributes:
|
|
25
|
+
path: Resolved binary path.
|
|
26
|
+
version: Parsed version string (e.g., "4.5.1").
|
|
27
|
+
is_valid: True if version >= 4.5.0.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
path: str
|
|
31
|
+
version: str
|
|
32
|
+
is_valid: bool
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
# --- Version Detection ---
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def get_godot_version(binary: str) -> str:
|
|
39
|
+
"""Get the Godot version string from the binary.
|
|
40
|
+
|
|
41
|
+
Runs ``godot --version`` and parses the output into a normalized
|
|
42
|
+
``major.minor.patch`` string.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
binary: Path to the Godot binary.
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
Normalized version string (e.g., "4.5.1").
|
|
49
|
+
|
|
50
|
+
Raises:
|
|
51
|
+
GodotNotFoundError: If the binary fails to run or produces
|
|
52
|
+
unparseable output.
|
|
53
|
+
"""
|
|
54
|
+
try:
|
|
55
|
+
result = subprocess.run(
|
|
56
|
+
[binary, "--version"],
|
|
57
|
+
capture_output=True,
|
|
58
|
+
text=True,
|
|
59
|
+
)
|
|
60
|
+
except (OSError, subprocess.SubprocessError) as exc:
|
|
61
|
+
raise GodotNotFoundError(
|
|
62
|
+
f"Failed to execute Godot binary at {binary}: {exc}"
|
|
63
|
+
) from exc
|
|
64
|
+
|
|
65
|
+
if result.returncode != 0:
|
|
66
|
+
raise GodotNotFoundError(
|
|
67
|
+
f"Godot binary at {binary} exited with code "
|
|
68
|
+
f"{result.returncode}: {result.stderr.strip()}"
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
output = result.stdout.strip()
|
|
72
|
+
match = re.match(r"(\d+)\.(\d+)(?:\.(\d+))?", output)
|
|
73
|
+
if not match:
|
|
74
|
+
raise GodotNotFoundError(
|
|
75
|
+
f"Could not parse Godot version from output: {output!r}"
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
major, minor = match.group(1), match.group(2)
|
|
79
|
+
patch = match.group(3) or "0"
|
|
80
|
+
return f"{major}.{minor}.{patch}"
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def check_version_compatible(version: str) -> bool:
|
|
84
|
+
"""Check if a Godot version is compatible (>= 4.5.0).
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
version: Normalized version string (e.g., "4.5.1").
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
True if version >= 4.5.0, False otherwise.
|
|
91
|
+
"""
|
|
92
|
+
match = re.match(r"(\d+)\.(\d+)\.(\d+)", version)
|
|
93
|
+
if not match:
|
|
94
|
+
return False
|
|
95
|
+
major, minor, patch = (
|
|
96
|
+
int(match.group(1)),
|
|
97
|
+
int(match.group(2)),
|
|
98
|
+
int(match.group(3)),
|
|
99
|
+
)
|
|
100
|
+
return (major, minor, patch) >= (4, 5, 0)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
GUT_VERSION_MAP = {
|
|
104
|
+
"4.5": "9.5.0",
|
|
105
|
+
"4.6": "9.6.0",
|
|
106
|
+
"4.7": "9.7.0",
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def get_gut_version_for_godot(godot_version: str) -> str:
|
|
111
|
+
"""Map a Godot version to its compatible GUT version.
|
|
112
|
+
|
|
113
|
+
Uses the ``major.minor`` prefix to look up the GUT version.
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
godot_version: Normalized Godot version (e.g., "4.5.1").
|
|
117
|
+
|
|
118
|
+
Returns:
|
|
119
|
+
Compatible GUT version string (e.g., "9.5.0").
|
|
120
|
+
|
|
121
|
+
Raises:
|
|
122
|
+
ConfigError: If the Godot version is not in the GUT_VERSION_MAP.
|
|
123
|
+
"""
|
|
124
|
+
parts = godot_version.split(".")
|
|
125
|
+
if len(parts) < 2:
|
|
126
|
+
raise ConfigError(
|
|
127
|
+
f"Invalid Godot version '{godot_version}': "
|
|
128
|
+
f"expected format 'major.minor.patch'"
|
|
129
|
+
)
|
|
130
|
+
key = f"{parts[0]}.{parts[1]}"
|
|
131
|
+
if key not in GUT_VERSION_MAP:
|
|
132
|
+
raise ConfigError(
|
|
133
|
+
f"No GUT version mapping for Godot {key}. "
|
|
134
|
+
f"Supported versions: {', '.join(sorted(GUT_VERSION_MAP))}"
|
|
135
|
+
)
|
|
136
|
+
return GUT_VERSION_MAP[key]
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
# --- Binary Resolution Chain ---
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def _is_executable(path: str) -> bool:
|
|
143
|
+
"""Check if a path exists and is executable.
|
|
144
|
+
|
|
145
|
+
Args:
|
|
146
|
+
path: File path to check.
|
|
147
|
+
|
|
148
|
+
Returns:
|
|
149
|
+
True if the file exists and is executable.
|
|
150
|
+
"""
|
|
151
|
+
return Path(path).is_file() and os.access(path, os.X_OK)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def _check_config(config: GodotConfig) -> str | None:
|
|
155
|
+
"""Check the user-specified binary path from config.
|
|
156
|
+
|
|
157
|
+
Args:
|
|
158
|
+
config: The Godot configuration.
|
|
159
|
+
|
|
160
|
+
Returns:
|
|
161
|
+
Binary path if config has a valid binary, None otherwise.
|
|
162
|
+
"""
|
|
163
|
+
if config.binary is None:
|
|
164
|
+
return None
|
|
165
|
+
if _is_executable(config.binary):
|
|
166
|
+
return config.binary
|
|
167
|
+
return None
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def _check_env_vars() -> str | None:
|
|
171
|
+
"""Check environment variables for the Godot binary.
|
|
172
|
+
|
|
173
|
+
Checks ``GODOT_BIN``, ``GODOT4_BIN``, and ``GODOT_PATH`` in order.
|
|
174
|
+
|
|
175
|
+
Returns:
|
|
176
|
+
Binary path if found in an env var, None otherwise.
|
|
177
|
+
"""
|
|
178
|
+
for var_name in ("GODOT_BIN", "GODOT4_BIN", "GODOT_PATH"):
|
|
179
|
+
path = os.environ.get(var_name)
|
|
180
|
+
if path and _is_executable(path):
|
|
181
|
+
return path
|
|
182
|
+
return None
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def _check_path() -> str | None:
|
|
186
|
+
"""Check PATH for the Godot binary via ``shutil.which``.
|
|
187
|
+
|
|
188
|
+
Returns:
|
|
189
|
+
Binary path if found on PATH, None otherwise.
|
|
190
|
+
"""
|
|
191
|
+
for name in ("godot", "godot4"):
|
|
192
|
+
found = shutil.which(name)
|
|
193
|
+
if found:
|
|
194
|
+
return found
|
|
195
|
+
return None
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def _check_common_locations() -> str | None:
|
|
199
|
+
"""Check common install locations for the Godot binary.
|
|
200
|
+
|
|
201
|
+
Checks platform-specific standard install paths.
|
|
202
|
+
|
|
203
|
+
Returns:
|
|
204
|
+
Binary path if found at a common location, None otherwise.
|
|
205
|
+
"""
|
|
206
|
+
if sys.platform == "win32":
|
|
207
|
+
localappdata = os.environ.get("LOCALAPPDATA")
|
|
208
|
+
candidates = [
|
|
209
|
+
r"C:\Program Files\Godot\godot.exe",
|
|
210
|
+
(
|
|
211
|
+
os.path.join(localappdata, "Godot", "godot.exe")
|
|
212
|
+
if localappdata
|
|
213
|
+
else None
|
|
214
|
+
),
|
|
215
|
+
]
|
|
216
|
+
elif sys.platform == "darwin":
|
|
217
|
+
candidates = [
|
|
218
|
+
"/Applications/Godot.app/Contents/MacOS/Godot",
|
|
219
|
+
"/opt/homebrew/bin/godot",
|
|
220
|
+
]
|
|
221
|
+
else:
|
|
222
|
+
candidates = [
|
|
223
|
+
os.path.expanduser("~/.local/bin/godot"),
|
|
224
|
+
"/usr/bin/godot",
|
|
225
|
+
"/usr/local/bin/godot",
|
|
226
|
+
]
|
|
227
|
+
|
|
228
|
+
for candidate in candidates:
|
|
229
|
+
if candidate and _is_executable(candidate):
|
|
230
|
+
return candidate
|
|
231
|
+
return None
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def _build_not_found_message() -> str:
|
|
235
|
+
"""Build a comprehensive 'Godot not found' error message.
|
|
236
|
+
|
|
237
|
+
Returns:
|
|
238
|
+
Error message with resolution methods and install instructions.
|
|
239
|
+
"""
|
|
240
|
+
lines = ["Godot binary not found."]
|
|
241
|
+
lines.append("")
|
|
242
|
+
lines.append("Tried the following resolution methods:")
|
|
243
|
+
lines.append(" 1. Config: [godot].binary in gd-tools.toml")
|
|
244
|
+
lines.append(" 2. Environment: GODOT_BIN, GODOT4_BIN, GODOT_PATH")
|
|
245
|
+
lines.append(" 3. PATH: shutil.which('godot'), shutil.which('godot4')")
|
|
246
|
+
lines.append(" 4. Common install locations")
|
|
247
|
+
lines.append("")
|
|
248
|
+
if sys.platform == "win32":
|
|
249
|
+
lines.append("Install Godot on Windows:")
|
|
250
|
+
lines.append(
|
|
251
|
+
" Download from https://godotengine.org/download/windows/"
|
|
252
|
+
)
|
|
253
|
+
lines.append(" Or: winget install GodotEngine.GodotEngine")
|
|
254
|
+
elif sys.platform == "darwin":
|
|
255
|
+
lines.append("Install Godot on macOS:")
|
|
256
|
+
lines.append(" Download from https://godotengine.org/download/macos/")
|
|
257
|
+
lines.append(" Or: brew install --cask godot")
|
|
258
|
+
else:
|
|
259
|
+
lines.append("Install Godot on Linux:")
|
|
260
|
+
lines.append(" Download from https://godotengine.org/download/linux/")
|
|
261
|
+
lines.append(" Or: flatpak install org.godotengine.Godot")
|
|
262
|
+
lines.append("")
|
|
263
|
+
lines.append("To configure manually:")
|
|
264
|
+
lines.append(" - Run 'gd-tools init' to set up interactively")
|
|
265
|
+
lines.append(" - Set the GODOT_BIN environment variable")
|
|
266
|
+
lines.append(" - Add [godot].binary = '/path/to/godot' to gd-tools.toml")
|
|
267
|
+
return "\n".join(lines)
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def find_godot(config: GodotConfig) -> GodotInfo:
|
|
271
|
+
"""Find the Godot binary using a 5-level priority chain.
|
|
272
|
+
|
|
273
|
+
Resolution order (first match wins):
|
|
274
|
+
1. ``config.binary``
|
|
275
|
+
2. Environment variables (``GODOT_BIN``, ``GODOT4_BIN``,
|
|
276
|
+
``GODOT_PATH``)
|
|
277
|
+
3. PATH lookup (``shutil.which``)
|
|
278
|
+
4. Common install locations
|
|
279
|
+
5. Raise ``GodotNotFoundError``
|
|
280
|
+
|
|
281
|
+
Args:
|
|
282
|
+
config: The Godot configuration.
|
|
283
|
+
|
|
284
|
+
Returns:
|
|
285
|
+
GodotInfo with resolved path, version, and validity.
|
|
286
|
+
|
|
287
|
+
Raises:
|
|
288
|
+
GodotNotFoundError: If no Godot binary is found.
|
|
289
|
+
"""
|
|
290
|
+
binary = (
|
|
291
|
+
_check_config(config)
|
|
292
|
+
or _check_env_vars()
|
|
293
|
+
or _check_path()
|
|
294
|
+
or _check_common_locations()
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
if binary is None:
|
|
298
|
+
raise GodotNotFoundError(_build_not_found_message())
|
|
299
|
+
|
|
300
|
+
try:
|
|
301
|
+
version = get_godot_version(binary)
|
|
302
|
+
is_valid = check_version_compatible(version)
|
|
303
|
+
except GodotNotFoundError:
|
|
304
|
+
version = "unknown"
|
|
305
|
+
is_valid = False
|
|
306
|
+
|
|
307
|
+
return GodotInfo(path=binary, version=version, is_valid=is_valid)
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
# --- Godot Invocation Wrapper ---
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
def run_godot(
|
|
314
|
+
binary: str,
|
|
315
|
+
project_path: Path,
|
|
316
|
+
args: list[str],
|
|
317
|
+
env: dict[str, str] | None = None,
|
|
318
|
+
timeout: int | None = None,
|
|
319
|
+
) -> subprocess.CompletedProcess:
|
|
320
|
+
"""Invoke Godot with the given project path and arguments.
|
|
321
|
+
|
|
322
|
+
Sets ``--path`` to ``project_path`` and merges the provided ``env``
|
|
323
|
+
with the current ``os.environ`` (caller values take precedence).
|
|
324
|
+
|
|
325
|
+
Args:
|
|
326
|
+
binary: Path to the Godot binary.
|
|
327
|
+
project_path: Path to the Godot project directory.
|
|
328
|
+
args: Additional arguments to pass to Godot.
|
|
329
|
+
env: Environment variables to merge with os.environ.
|
|
330
|
+
timeout: Timeout in seconds for the subprocess.
|
|
331
|
+
|
|
332
|
+
Returns:
|
|
333
|
+
The completed subprocess result.
|
|
334
|
+
|
|
335
|
+
Raises:
|
|
336
|
+
subprocess.TimeoutExpired: If the timeout is exceeded.
|
|
337
|
+
"""
|
|
338
|
+
cmd = [binary, "--path", str(project_path), *args]
|
|
339
|
+
merged_env = {**os.environ, **(env or {})}
|
|
340
|
+
return subprocess.run(
|
|
341
|
+
cmd,
|
|
342
|
+
capture_output=True,
|
|
343
|
+
text=True,
|
|
344
|
+
env=merged_env,
|
|
345
|
+
timeout=timeout,
|
|
346
|
+
)
|