avrae-ls 0.6.1__tar.gz → 0.6.3__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.
- {avrae_ls-0.6.1 → avrae_ls-0.6.3}/PKG-INFO +1 -1
- {avrae_ls-0.6.1 → avrae_ls-0.6.3}/pyproject.toml +1 -1
- {avrae_ls-0.6.1 → avrae_ls-0.6.3}/src/avrae_ls/__main__.py +71 -9
- {avrae_ls-0.6.1 → avrae_ls-0.6.3}/src/avrae_ls/alias_tests.py +40 -0
- {avrae_ls-0.6.1 → avrae_ls-0.6.3}/src/avrae_ls/config.py +10 -4
- {avrae_ls-0.6.1 → avrae_ls-0.6.3}/.gitignore +0 -0
- {avrae_ls-0.6.1 → avrae_ls-0.6.3}/LICENSE +0 -0
- {avrae_ls-0.6.1 → avrae_ls-0.6.3}/README.md +0 -0
- {avrae_ls-0.6.1 → avrae_ls-0.6.3}/src/avrae_ls/__init__.py +0 -0
- {avrae_ls-0.6.1 → avrae_ls-0.6.3}/src/avrae_ls/alias_preview.py +0 -0
- {avrae_ls-0.6.1 → avrae_ls-0.6.3}/src/avrae_ls/api.py +0 -0
- {avrae_ls-0.6.1 → avrae_ls-0.6.3}/src/avrae_ls/argparser.py +0 -0
- {avrae_ls-0.6.1 → avrae_ls-0.6.3}/src/avrae_ls/argument_parsing.py +0 -0
- {avrae_ls-0.6.1 → avrae_ls-0.6.3}/src/avrae_ls/code_actions.py +0 -0
- {avrae_ls-0.6.1 → avrae_ls-0.6.3}/src/avrae_ls/codes.py +0 -0
- {avrae_ls-0.6.1 → avrae_ls-0.6.3}/src/avrae_ls/completions.py +0 -0
- {avrae_ls-0.6.1 → avrae_ls-0.6.3}/src/avrae_ls/context.py +0 -0
- {avrae_ls-0.6.1 → avrae_ls-0.6.3}/src/avrae_ls/cvars.py +0 -0
- {avrae_ls-0.6.1 → avrae_ls-0.6.3}/src/avrae_ls/diagnostics.py +0 -0
- {avrae_ls-0.6.1 → avrae_ls-0.6.3}/src/avrae_ls/dice.py +0 -0
- {avrae_ls-0.6.1 → avrae_ls-0.6.3}/src/avrae_ls/parser.py +0 -0
- {avrae_ls-0.6.1 → avrae_ls-0.6.3}/src/avrae_ls/runtime.py +0 -0
- {avrae_ls-0.6.1 → avrae_ls-0.6.3}/src/avrae_ls/server.py +0 -0
- {avrae_ls-0.6.1 → avrae_ls-0.6.3}/src/avrae_ls/signature_help.py +0 -0
- {avrae_ls-0.6.1 → avrae_ls-0.6.3}/src/avrae_ls/symbols.py +0 -0
- {avrae_ls-0.6.1 → avrae_ls-0.6.3}/src/draconic/LICENSE +0 -0
- {avrae_ls-0.6.1 → avrae_ls-0.6.3}/src/draconic/__init__.py +0 -0
- {avrae_ls-0.6.1 → avrae_ls-0.6.3}/src/draconic/exceptions.py +0 -0
- {avrae_ls-0.6.1 → avrae_ls-0.6.3}/src/draconic/helpers.py +0 -0
- {avrae_ls-0.6.1 → avrae_ls-0.6.3}/src/draconic/interpreter.py +0 -0
- {avrae_ls-0.6.1 → avrae_ls-0.6.3}/src/draconic/string.py +0 -0
- {avrae_ls-0.6.1 → avrae_ls-0.6.3}/src/draconic/types.py +0 -0
- {avrae_ls-0.6.1 → avrae_ls-0.6.3}/src/draconic/utils.py +0 -0
- {avrae_ls-0.6.1 → avrae_ls-0.6.3}/src/draconic/versions.py +0 -0
|
@@ -2,16 +2,24 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import argparse
|
|
4
4
|
import asyncio
|
|
5
|
+
import difflib
|
|
5
6
|
import logging
|
|
6
7
|
import sys
|
|
7
8
|
from pathlib import Path
|
|
8
|
-
from typing import Iterable
|
|
9
|
+
from typing import Any, Iterable
|
|
9
10
|
|
|
10
11
|
import yaml
|
|
11
12
|
|
|
12
13
|
from lsprotocol import types
|
|
13
14
|
|
|
14
|
-
from .alias_tests import
|
|
15
|
+
from .alias_tests import (
|
|
16
|
+
AliasTestError,
|
|
17
|
+
AliasTestResult,
|
|
18
|
+
diff_mismatched_parts,
|
|
19
|
+
discover_test_files,
|
|
20
|
+
parse_alias_tests,
|
|
21
|
+
run_alias_tests,
|
|
22
|
+
)
|
|
15
23
|
from .config import CONFIG_FILENAME, load_config
|
|
16
24
|
from .context import ContextBuilder
|
|
17
25
|
from .diagnostics import DiagnosticProvider
|
|
@@ -81,7 +89,7 @@ def _run_analysis(path: Path) -> int:
|
|
|
81
89
|
log = logging.getLogger(__name__)
|
|
82
90
|
log.info("Analyzing %s (workspace root: %s)", path, workspace_root)
|
|
83
91
|
|
|
84
|
-
config, warnings = load_config(workspace_root)
|
|
92
|
+
config, warnings = load_config(workspace_root, default_enable_gvar_fetch=True)
|
|
85
93
|
for warning in warnings:
|
|
86
94
|
log.warning(warning)
|
|
87
95
|
|
|
@@ -105,7 +113,7 @@ def _run_alias_tests(target: Path) -> int:
|
|
|
105
113
|
log = logging.getLogger(__name__)
|
|
106
114
|
log.info("Running alias tests in %s (workspace root: %s)", target, workspace_root)
|
|
107
115
|
|
|
108
|
-
config, warnings = load_config(workspace_root)
|
|
116
|
+
config, warnings = load_config(workspace_root, default_enable_gvar_fetch=True)
|
|
109
117
|
for warning in warnings:
|
|
110
118
|
log.warning(warning)
|
|
111
119
|
|
|
@@ -152,15 +160,69 @@ def _print_test_results(results: Iterable[AliasTestResult], workspace_root: Path
|
|
|
152
160
|
print(f" Error: {res.error}")
|
|
153
161
|
if res.details:
|
|
154
162
|
print(f" {res.details}")
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
163
|
+
expected_val, actual_val = _summarize_mismatch(res.case.expected, res.actual)
|
|
164
|
+
expected = _format_value(expected_val)
|
|
165
|
+
actual = _format_value(actual_val)
|
|
166
|
+
_print_labeled_value("Expected", expected)
|
|
167
|
+
_print_labeled_value("Actual", actual)
|
|
168
|
+
diff = _render_diff(expected, actual)
|
|
169
|
+
if diff:
|
|
170
|
+
print(" Diff:")
|
|
171
|
+
for line in diff.splitlines():
|
|
172
|
+
print(f" {line}")
|
|
159
173
|
if res.stdout:
|
|
160
174
|
print(f" Stdout: {res.stdout.strip()}")
|
|
161
175
|
print(f"{passed}/{total} tests passed")
|
|
162
176
|
|
|
163
177
|
|
|
178
|
+
def _summarize_mismatch(expected: Any, actual: Any) -> tuple[Any, Any]:
|
|
179
|
+
diff = diff_mismatched_parts(expected, actual)
|
|
180
|
+
if diff is None:
|
|
181
|
+
return expected, actual
|
|
182
|
+
return diff
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def _render_diff(expected: str, actual: str) -> str:
|
|
186
|
+
expected_lines = expected.splitlines() or [""]
|
|
187
|
+
actual_lines = actual.splitlines() or [""]
|
|
188
|
+
if expected_lines == actual_lines:
|
|
189
|
+
return ""
|
|
190
|
+
diff_lines = list(
|
|
191
|
+
difflib.unified_diff(
|
|
192
|
+
expected_lines,
|
|
193
|
+
actual_lines,
|
|
194
|
+
fromfile="expected",
|
|
195
|
+
tofile="actual",
|
|
196
|
+
lineterm="",
|
|
197
|
+
)
|
|
198
|
+
)
|
|
199
|
+
if not diff_lines:
|
|
200
|
+
return ""
|
|
201
|
+
return "\n".join(_colorize_diff_line(line) for line in diff_lines)
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def _colorize_diff_line(line: str) -> str:
|
|
205
|
+
if not sys.stdout.isatty():
|
|
206
|
+
return line
|
|
207
|
+
if line.startswith("-"):
|
|
208
|
+
return f"\x1b[31m{line}\x1b[0m"
|
|
209
|
+
if line.startswith("+"):
|
|
210
|
+
return f"\x1b[32m{line}\x1b[0m"
|
|
211
|
+
if line.startswith("@@") or line.startswith("---") or line.startswith("+++"):
|
|
212
|
+
return f"\x1b[36m{line}\x1b[0m"
|
|
213
|
+
return line
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def _print_labeled_value(label: str, value: str) -> None:
|
|
217
|
+
lines = value.splitlines() or [""]
|
|
218
|
+
if len(lines) == 1:
|
|
219
|
+
print(f" {label}: {lines[0]}")
|
|
220
|
+
return
|
|
221
|
+
print(f" {label}:")
|
|
222
|
+
for line in lines:
|
|
223
|
+
print(f" {line}")
|
|
224
|
+
|
|
225
|
+
|
|
164
226
|
def _relative_to_workspace(path: Path, workspace_root: Path) -> str:
|
|
165
227
|
try:
|
|
166
228
|
return str(path.relative_to(workspace_root))
|
|
@@ -168,7 +230,7 @@ def _relative_to_workspace(path: Path, workspace_root: Path) -> str:
|
|
|
168
230
|
return str(path)
|
|
169
231
|
|
|
170
232
|
|
|
171
|
-
def _format_value(value) -> str:
|
|
233
|
+
def _format_value(value: Any) -> str:
|
|
172
234
|
if value is None:
|
|
173
235
|
return "None"
|
|
174
236
|
if isinstance(value, (dict, list)):
|
|
@@ -13,6 +13,8 @@ from .context import ContextBuilder
|
|
|
13
13
|
from .runtime import MockExecutor
|
|
14
14
|
from .config import VarSources
|
|
15
15
|
|
|
16
|
+
MISSING_VALUE = "<missing>"
|
|
17
|
+
|
|
16
18
|
|
|
17
19
|
class AliasTestError(Exception):
|
|
18
20
|
"""Raised when an alias test cannot be parsed or executed."""
|
|
@@ -224,6 +226,44 @@ def _value_matches(expected: Any, actual: Any) -> bool:
|
|
|
224
226
|
return _scalar_matches(expected, actual)
|
|
225
227
|
|
|
226
228
|
|
|
229
|
+
def diff_mismatched_parts(expected: Any, actual: Any) -> tuple[Any, Any] | None:
|
|
230
|
+
if _value_matches(expected, actual):
|
|
231
|
+
return None
|
|
232
|
+
|
|
233
|
+
if isinstance(expected, dict) and isinstance(actual, dict):
|
|
234
|
+
expected_diff: dict[str, Any] = {}
|
|
235
|
+
actual_diff: dict[str, Any] = {}
|
|
236
|
+
for key, expected_val in expected.items():
|
|
237
|
+
if key not in actual:
|
|
238
|
+
expected_diff[key] = expected_val
|
|
239
|
+
actual_diff[key] = MISSING_VALUE
|
|
240
|
+
continue
|
|
241
|
+
sub_diff = diff_mismatched_parts(expected_val, actual[key])
|
|
242
|
+
if sub_diff:
|
|
243
|
+
expected_diff[key], actual_diff[key] = sub_diff
|
|
244
|
+
if expected_diff:
|
|
245
|
+
return expected_diff, actual_diff
|
|
246
|
+
return expected, actual
|
|
247
|
+
|
|
248
|
+
if isinstance(expected, list) and isinstance(actual, list):
|
|
249
|
+
expected_diff: list[Any] = []
|
|
250
|
+
actual_diff: list[Any] = []
|
|
251
|
+
for idx, expected_val in enumerate(expected):
|
|
252
|
+
if idx >= len(actual):
|
|
253
|
+
expected_diff.append(expected_val)
|
|
254
|
+
actual_diff.append(MISSING_VALUE)
|
|
255
|
+
continue
|
|
256
|
+
sub_diff = diff_mismatched_parts(expected_val, actual[idx])
|
|
257
|
+
if sub_diff:
|
|
258
|
+
expected_diff.append(sub_diff[0])
|
|
259
|
+
actual_diff.append(sub_diff[1])
|
|
260
|
+
if expected_diff:
|
|
261
|
+
return expected_diff, actual_diff
|
|
262
|
+
return expected, actual
|
|
263
|
+
|
|
264
|
+
return expected, actual
|
|
265
|
+
|
|
266
|
+
|
|
227
267
|
def _split_command(command: str, path: Path) -> list[str]:
|
|
228
268
|
try:
|
|
229
269
|
tokens = shlex.split(command, posix=True)
|
|
@@ -387,11 +387,16 @@ def _coerce_optional_str(value: Any) -> str | None:
|
|
|
387
387
|
return value_str if value_str.strip() else None
|
|
388
388
|
|
|
389
389
|
|
|
390
|
-
def load_config(workspace_root: Path) -> Tuple[AvraeLSConfig, Iterable[str]]:
|
|
390
|
+
def load_config(workspace_root: Path, *, default_enable_gvar_fetch: bool = False) -> Tuple[AvraeLSConfig, Iterable[str]]:
|
|
391
391
|
"""Load `.avraels.json` from the workspace root, returning config and warnings."""
|
|
392
392
|
path = workspace_root / CONFIG_FILENAME
|
|
393
393
|
if not path.exists():
|
|
394
|
-
|
|
394
|
+
cfg = AvraeLSConfig.default(workspace_root)
|
|
395
|
+
cfg.enable_gvar_fetch = default_enable_gvar_fetch
|
|
396
|
+
env_token = _coerce_optional_str(os.environ.get("AVRAE_TOKEN"))
|
|
397
|
+
if env_token:
|
|
398
|
+
cfg.service.token = env_token
|
|
399
|
+
return cfg, []
|
|
395
400
|
|
|
396
401
|
try:
|
|
397
402
|
raw = json.loads(path.read_text())
|
|
@@ -411,12 +416,13 @@ def load_config(workspace_root: Path) -> Tuple[AvraeLSConfig, Iterable[str]]:
|
|
|
411
416
|
warnings.append(warning)
|
|
412
417
|
log.warning(warning)
|
|
413
418
|
|
|
414
|
-
enable_gvar_fetch = bool(raw.get("enableGvarFetch",
|
|
419
|
+
enable_gvar_fetch = bool(raw.get("enableGvarFetch", default_enable_gvar_fetch))
|
|
415
420
|
|
|
416
421
|
service_cfg = raw.get("avraeService") or {}
|
|
422
|
+
env_token = _coerce_optional_str(env.get("AVRAE_TOKEN"))
|
|
417
423
|
service = AvraeServiceConfig(
|
|
418
424
|
base_url=str(service_cfg.get("baseUrl") or AvraeServiceConfig.base_url),
|
|
419
|
-
token=_coerce_optional_str(service_cfg.get("token")),
|
|
425
|
+
token=_coerce_optional_str(service_cfg.get("token")) or env_token,
|
|
420
426
|
)
|
|
421
427
|
|
|
422
428
|
diag_cfg = raw.get("diagnostics") or {}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|