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.
Files changed (34) hide show
  1. {avrae_ls-0.6.1 → avrae_ls-0.6.3}/PKG-INFO +1 -1
  2. {avrae_ls-0.6.1 → avrae_ls-0.6.3}/pyproject.toml +1 -1
  3. {avrae_ls-0.6.1 → avrae_ls-0.6.3}/src/avrae_ls/__main__.py +71 -9
  4. {avrae_ls-0.6.1 → avrae_ls-0.6.3}/src/avrae_ls/alias_tests.py +40 -0
  5. {avrae_ls-0.6.1 → avrae_ls-0.6.3}/src/avrae_ls/config.py +10 -4
  6. {avrae_ls-0.6.1 → avrae_ls-0.6.3}/.gitignore +0 -0
  7. {avrae_ls-0.6.1 → avrae_ls-0.6.3}/LICENSE +0 -0
  8. {avrae_ls-0.6.1 → avrae_ls-0.6.3}/README.md +0 -0
  9. {avrae_ls-0.6.1 → avrae_ls-0.6.3}/src/avrae_ls/__init__.py +0 -0
  10. {avrae_ls-0.6.1 → avrae_ls-0.6.3}/src/avrae_ls/alias_preview.py +0 -0
  11. {avrae_ls-0.6.1 → avrae_ls-0.6.3}/src/avrae_ls/api.py +0 -0
  12. {avrae_ls-0.6.1 → avrae_ls-0.6.3}/src/avrae_ls/argparser.py +0 -0
  13. {avrae_ls-0.6.1 → avrae_ls-0.6.3}/src/avrae_ls/argument_parsing.py +0 -0
  14. {avrae_ls-0.6.1 → avrae_ls-0.6.3}/src/avrae_ls/code_actions.py +0 -0
  15. {avrae_ls-0.6.1 → avrae_ls-0.6.3}/src/avrae_ls/codes.py +0 -0
  16. {avrae_ls-0.6.1 → avrae_ls-0.6.3}/src/avrae_ls/completions.py +0 -0
  17. {avrae_ls-0.6.1 → avrae_ls-0.6.3}/src/avrae_ls/context.py +0 -0
  18. {avrae_ls-0.6.1 → avrae_ls-0.6.3}/src/avrae_ls/cvars.py +0 -0
  19. {avrae_ls-0.6.1 → avrae_ls-0.6.3}/src/avrae_ls/diagnostics.py +0 -0
  20. {avrae_ls-0.6.1 → avrae_ls-0.6.3}/src/avrae_ls/dice.py +0 -0
  21. {avrae_ls-0.6.1 → avrae_ls-0.6.3}/src/avrae_ls/parser.py +0 -0
  22. {avrae_ls-0.6.1 → avrae_ls-0.6.3}/src/avrae_ls/runtime.py +0 -0
  23. {avrae_ls-0.6.1 → avrae_ls-0.6.3}/src/avrae_ls/server.py +0 -0
  24. {avrae_ls-0.6.1 → avrae_ls-0.6.3}/src/avrae_ls/signature_help.py +0 -0
  25. {avrae_ls-0.6.1 → avrae_ls-0.6.3}/src/avrae_ls/symbols.py +0 -0
  26. {avrae_ls-0.6.1 → avrae_ls-0.6.3}/src/draconic/LICENSE +0 -0
  27. {avrae_ls-0.6.1 → avrae_ls-0.6.3}/src/draconic/__init__.py +0 -0
  28. {avrae_ls-0.6.1 → avrae_ls-0.6.3}/src/draconic/exceptions.py +0 -0
  29. {avrae_ls-0.6.1 → avrae_ls-0.6.3}/src/draconic/helpers.py +0 -0
  30. {avrae_ls-0.6.1 → avrae_ls-0.6.3}/src/draconic/interpreter.py +0 -0
  31. {avrae_ls-0.6.1 → avrae_ls-0.6.3}/src/draconic/string.py +0 -0
  32. {avrae_ls-0.6.1 → avrae_ls-0.6.3}/src/draconic/types.py +0 -0
  33. {avrae_ls-0.6.1 → avrae_ls-0.6.3}/src/draconic/utils.py +0 -0
  34. {avrae_ls-0.6.1 → avrae_ls-0.6.3}/src/draconic/versions.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: avrae-ls
3
- Version: 0.6.1
3
+ Version: 0.6.3
4
4
  Summary: Language server for Avrae draconic aliases
5
5
  Author: 1drturtle
6
6
  License: MIT License
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "avrae-ls"
7
- version = "0.6.1"
7
+ version = "0.6.3"
8
8
  description = "Language server for Avrae draconic aliases"
9
9
  authors = [
10
10
  { name = "1drturtle" }
@@ -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 AliasTestError, AliasTestResult, discover_test_files, parse_alias_tests, run_alias_tests
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
- expected = _format_value(res.case.expected)
156
- actual = _format_value(res.actual)
157
- print(f" Expected: {expected}")
158
- print(f" Actual: {actual}")
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
- return AvraeLSConfig.default(workspace_root), []
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", False))
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