avrae-ls 0.7.0__py3-none-any.whl → 0.8.1__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.
- avrae_ls/__main__.py +16 -4
- avrae_ls/alias_preview.py +35 -7
- avrae_ls/alias_tests.py +11 -1
- avrae_ls/context.py +48 -1
- {avrae_ls-0.7.0.dist-info → avrae_ls-0.8.1.dist-info}/METADATA +5 -3
- {avrae_ls-0.7.0.dist-info → avrae_ls-0.8.1.dist-info}/RECORD +9 -9
- {avrae_ls-0.7.0.dist-info → avrae_ls-0.8.1.dist-info}/WHEEL +0 -0
- {avrae_ls-0.7.0.dist-info → avrae_ls-0.8.1.dist-info}/entry_points.txt +0 -0
- {avrae_ls-0.7.0.dist-info → avrae_ls-0.8.1.dist-info}/licenses/LICENSE +0 -0
avrae_ls/__main__.py
CHANGED
|
@@ -202,11 +202,17 @@ def _print_test_results(results: Iterable[AliasTestResult], workspace_root: Path
|
|
|
202
202
|
passed += 1
|
|
203
203
|
continue
|
|
204
204
|
if res.error:
|
|
205
|
-
if res.error_line is not None:
|
|
206
|
-
|
|
207
|
-
|
|
205
|
+
if res.error_line is not None and res.error_col is not None:
|
|
206
|
+
label = f" Execution Error (line {res.error_line} col {res.error_col})"
|
|
207
|
+
elif res.error_line is not None:
|
|
208
|
+
label = f" Execution Error (line {res.error_line})"
|
|
208
209
|
else:
|
|
209
|
-
|
|
210
|
+
label = " Execution Error"
|
|
211
|
+
print(_colorize_error_line(label))
|
|
212
|
+
print(f" {res.error}")
|
|
213
|
+
if res.stdout:
|
|
214
|
+
print(f" Stdout: {res.stdout.strip()}")
|
|
215
|
+
continue
|
|
210
216
|
if res.details:
|
|
211
217
|
print(f" {res.details}")
|
|
212
218
|
expected_val, actual_val = _summarize_mismatch(res.case.expected, res.actual)
|
|
@@ -262,6 +268,12 @@ def _colorize_diff_line(line: str) -> str:
|
|
|
262
268
|
return line
|
|
263
269
|
|
|
264
270
|
|
|
271
|
+
def _colorize_error_line(line: str) -> str:
|
|
272
|
+
if not sys.stdout.isatty():
|
|
273
|
+
return line
|
|
274
|
+
return f"\x1b[31m{line}\x1b[0m"
|
|
275
|
+
|
|
276
|
+
|
|
265
277
|
def _print_labeled_value(label: str, value: str) -> None:
|
|
266
278
|
lines = value.splitlines() or [""]
|
|
267
279
|
if len(lines) == 1:
|
avrae_ls/alias_preview.py
CHANGED
|
@@ -18,6 +18,7 @@ class RenderedAlias:
|
|
|
18
18
|
error: Optional[BaseException]
|
|
19
19
|
last_value: Any | None = None
|
|
20
20
|
error_line: int | None = None
|
|
21
|
+
error_col: int | None = None
|
|
21
22
|
|
|
22
23
|
|
|
23
24
|
@dataclass
|
|
@@ -81,18 +82,43 @@ def _line_index_for_offset(text: str, offset: int) -> int:
|
|
|
81
82
|
return text.count("\n", 0, offset)
|
|
82
83
|
|
|
83
84
|
|
|
84
|
-
def
|
|
85
|
+
def _error_position_for_match(
|
|
85
86
|
body: str, match: re.Match[str], error: BaseException, line_offset: int
|
|
86
|
-
) -> int | None:
|
|
87
|
+
) -> tuple[int | None, int | None]:
|
|
87
88
|
base_line = _line_index_for_offset(body, match.start(1))
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
89
|
+
base_line_start = body.rfind("\n", 0, match.start(1))
|
|
90
|
+
base_col = match.start(1) - (base_line_start + 1 if base_line_start != -1 else 0)
|
|
91
|
+
line_in_code: int | None = None
|
|
92
|
+
col_in_code: int | None = None
|
|
93
|
+
node = getattr(error, "node", None)
|
|
94
|
+
if node is not None:
|
|
95
|
+
node_line = getattr(node, "lineno", None)
|
|
96
|
+
if isinstance(node_line, int) and node_line > 0:
|
|
97
|
+
line_in_code = node_line
|
|
98
|
+
node_col = getattr(node, "col_offset", None)
|
|
99
|
+
if isinstance(node_col, int) and node_col >= 0:
|
|
100
|
+
col_in_code = node_col
|
|
101
|
+
if line_in_code is None:
|
|
102
|
+
lineno = getattr(error, "lineno", None)
|
|
103
|
+
if isinstance(lineno, int) and lineno > 0:
|
|
104
|
+
line_in_code = lineno
|
|
105
|
+
offset = getattr(error, "offset", None)
|
|
106
|
+
if isinstance(offset, int) and offset > 0:
|
|
107
|
+
col_in_code = offset - 1
|
|
108
|
+
if line_in_code is not None:
|
|
109
|
+
line_index = base_line + (line_in_code - 1)
|
|
91
110
|
else:
|
|
92
111
|
code = match.group(1)
|
|
93
112
|
leading_newlines = len(code) - len(code.lstrip("\n"))
|
|
94
113
|
line_index = base_line + leading_newlines
|
|
95
|
-
|
|
114
|
+
col_in_code = 0 if col_in_code is None else col_in_code
|
|
115
|
+
if col_in_code is None:
|
|
116
|
+
col_in_code = 0
|
|
117
|
+
if line_in_code is None or line_in_code == 1:
|
|
118
|
+
col_index = base_col + col_in_code
|
|
119
|
+
else:
|
|
120
|
+
col_index = col_in_code
|
|
121
|
+
return line_index + line_offset + 1, col_index + 1
|
|
96
122
|
|
|
97
123
|
|
|
98
124
|
async def render_alias_command(
|
|
@@ -110,6 +136,7 @@ async def render_alias_command(
|
|
|
110
136
|
last_value = None
|
|
111
137
|
error: BaseException | None = None
|
|
112
138
|
error_line: int | None = None
|
|
139
|
+
error_col: int | None = None
|
|
113
140
|
|
|
114
141
|
pos = 0
|
|
115
142
|
matches: list[tuple[str, re.Match[str]]] = []
|
|
@@ -134,7 +161,7 @@ async def render_alias_command(
|
|
|
134
161
|
stdout_parts.append(result.stdout)
|
|
135
162
|
if result.error:
|
|
136
163
|
error = result.error
|
|
137
|
-
error_line =
|
|
164
|
+
error_line, error_col = _error_position_for_match(body, match, result.error, line_offset)
|
|
138
165
|
break
|
|
139
166
|
last_value = result.value
|
|
140
167
|
if result.value is not None:
|
|
@@ -156,6 +183,7 @@ async def render_alias_command(
|
|
|
156
183
|
error=error,
|
|
157
184
|
last_value=last_value,
|
|
158
185
|
error_line=error_line,
|
|
186
|
+
error_col=error_col,
|
|
159
187
|
)
|
|
160
188
|
|
|
161
189
|
|
avrae_ls/alias_tests.py
CHANGED
|
@@ -43,6 +43,7 @@ class AliasTestResult:
|
|
|
43
43
|
error: str | None = None
|
|
44
44
|
details: str | None = None
|
|
45
45
|
error_line: int | None = None
|
|
46
|
+
error_col: int | None = None
|
|
46
47
|
|
|
47
48
|
|
|
48
49
|
def discover_test_files(
|
|
@@ -161,6 +162,7 @@ async def run_alias_test(case: AliasTestCase, builder: ContextBuilder, executor:
|
|
|
161
162
|
stdout=rendered.stdout,
|
|
162
163
|
error=str(rendered.error),
|
|
163
164
|
error_line=rendered.error_line,
|
|
165
|
+
error_col=rendered.error_col,
|
|
164
166
|
)
|
|
165
167
|
|
|
166
168
|
preview = simulate_command(rendered.command)
|
|
@@ -173,7 +175,15 @@ async def run_alias_test(case: AliasTestCase, builder: ContextBuilder, executor:
|
|
|
173
175
|
error=preview.validation_error,
|
|
174
176
|
)
|
|
175
177
|
|
|
176
|
-
|
|
178
|
+
if preview.preview is not None:
|
|
179
|
+
actual = preview.preview
|
|
180
|
+
else:
|
|
181
|
+
if rendered.command.strip() == "" and rendered.last_value is None:
|
|
182
|
+
actual = None
|
|
183
|
+
elif rendered.last_value is not None and rendered.command.strip() == str(rendered.last_value):
|
|
184
|
+
actual = rendered.last_value
|
|
185
|
+
else:
|
|
186
|
+
actual = rendered.command
|
|
177
187
|
embed_dict = preview.embed.to_dict() if preview.embed else None
|
|
178
188
|
|
|
179
189
|
if embed_dict is not None and isinstance(case.expected, dict):
|
avrae_ls/context.py
CHANGED
|
@@ -14,6 +14,7 @@ from .config import AvraeLSConfig, ContextProfile, VarSources
|
|
|
14
14
|
from .cvars import derive_character_cvars
|
|
15
15
|
|
|
16
16
|
log = logging.getLogger(__name__)
|
|
17
|
+
_SKIP_GVAR = object()
|
|
17
18
|
|
|
18
19
|
|
|
19
20
|
@dataclass
|
|
@@ -63,7 +64,7 @@ class ContextBuilder:
|
|
|
63
64
|
data = _read_json_file(path)
|
|
64
65
|
if data is None:
|
|
65
66
|
continue
|
|
66
|
-
merged = merged.merge(
|
|
67
|
+
merged = merged.merge(_var_sources_from_file(path, data))
|
|
67
68
|
return merged
|
|
68
69
|
|
|
69
70
|
def _merge_character_cvars(self, character: Dict[str, Any], vars: VarSources) -> VarSources:
|
|
@@ -366,3 +367,49 @@ def _read_json_file(path: Path) -> Dict[str, Any] | None:
|
|
|
366
367
|
except json.JSONDecodeError as exc:
|
|
367
368
|
log.warning("Failed to parse var file %s: %s", path, exc)
|
|
368
369
|
return None
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
def _var_sources_from_file(path: Path, data: Dict[str, Any]) -> VarSources:
|
|
373
|
+
parsed = VarSources.from_data(data)
|
|
374
|
+
return VarSources(
|
|
375
|
+
cvars=parsed.cvars,
|
|
376
|
+
uvars=parsed.uvars,
|
|
377
|
+
svars=parsed.svars,
|
|
378
|
+
gvars=_resolve_gvar_file_refs(path, parsed.gvars),
|
|
379
|
+
)
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
def _resolve_gvar_file_refs(var_file: Path, gvars: Dict[str, Any]) -> Dict[str, Any]:
|
|
383
|
+
resolved: dict[str, Any] = {}
|
|
384
|
+
for key, value in gvars.items():
|
|
385
|
+
parsed = _parse_gvar_value(var_file, key, value)
|
|
386
|
+
if parsed is _SKIP_GVAR:
|
|
387
|
+
continue
|
|
388
|
+
resolved[str(key)] = parsed
|
|
389
|
+
return resolved
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
def _parse_gvar_value(var_file: Path, key: Any, value: Any) -> Any:
|
|
393
|
+
if not isinstance(value, dict):
|
|
394
|
+
return value
|
|
395
|
+
|
|
396
|
+
file_path = value.get("filePath")
|
|
397
|
+
if file_path is None:
|
|
398
|
+
file_path = value.get("path")
|
|
399
|
+
if file_path is None:
|
|
400
|
+
return value
|
|
401
|
+
if not isinstance(file_path, str) or not file_path.strip():
|
|
402
|
+
log.warning("Invalid gvar file path for '%s' in %s; expected a non-empty string.", key, var_file)
|
|
403
|
+
return _SKIP_GVAR
|
|
404
|
+
|
|
405
|
+
gvar_path = Path(file_path)
|
|
406
|
+
if not gvar_path.is_absolute():
|
|
407
|
+
gvar_path = var_file.parent / gvar_path
|
|
408
|
+
try:
|
|
409
|
+
return gvar_path.read_text()
|
|
410
|
+
except FileNotFoundError:
|
|
411
|
+
log.warning("Gvar content file not found for '%s': %s", key, gvar_path)
|
|
412
|
+
return _SKIP_GVAR
|
|
413
|
+
except OSError as exc:
|
|
414
|
+
log.warning("Failed to read gvar content file for '%s' (%s): %s", key, gvar_path, exc)
|
|
415
|
+
return _SKIP_GVAR
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: avrae-ls
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.8.1
|
|
4
4
|
Summary: Language server for Avrae draconic aliases
|
|
5
5
|
Author: 1drturtle
|
|
6
6
|
License: MIT License
|
|
@@ -40,6 +40,8 @@ Description-Content-Type: text/markdown
|
|
|
40
40
|
|
|
41
41
|
# Avrae Draconic Alias Language Server
|
|
42
42
|
|
|
43
|
+
[](https://github.com/1drturtle/avrae-ls/actions/workflows/ci.yml)
|
|
44
|
+
|
|
43
45
|
Language Server Protocol (LSP) implementation targeting Avrae-style draconic aliases. It provides syntax/semantic diagnostics, a mocked execution command, and a thin configuration layer driven by a workspace `.avraels.json` file. Credit to Avrae team for all code yoinked!
|
|
44
46
|
|
|
45
47
|
## Install (released package)
|
|
@@ -105,12 +107,12 @@ Language Server Protocol (LSP) implementation targeting Avrae-style draconic ali
|
|
|
105
107
|
|
|
106
108
|
- Mock execution never writes back to Avrae: cvar/uvar/gvar mutations only live for the current run and reset before the next.
|
|
107
109
|
- Network is limited to gvar fetches (when `enableGvarFetch` is true) and `verify_signature`; other Avrae/Discord calls are replaced with mocked context data from `.avraels.json`.
|
|
108
|
-
- `get_gvar`/`using` values are pulled from local var files first; remote fetches go to `https://api.avrae.io/customizations/gvars/<id>` (or your `avraeService.baseUrl`) using `avraeService.token` and are cached for the session.
|
|
110
|
+
- `get_gvar`/`using` values are pulled from local var files first; remote fetches go to `https://api.avrae.io/customizations/gvars/<id>` (or your `avraeService.baseUrl`) using `avraeService.token` and are cached for the session. In var files, a gvar can be a direct value or a `{ "filePath": "relative/or/absolute/path" }` object (also supports `"path"`) that loads file contents as the gvar value.
|
|
109
111
|
- `signature()` returns a mock string (`mock-signature:<int>`). `verify_signature()` POSTs to `/bot/signature/verify`, reuses the last successful response per signature, and includes `avraeService.token` if present.
|
|
110
112
|
|
|
111
113
|
## Troubleshooting gvar fetch / verify_signature
|
|
112
114
|
|
|
113
|
-
- `get_gvar` returns `None` or `using(...)` raises `ModuleNotFoundError`: ensure the workspace `.avraels.json` sets `enableGvarFetch: true`, includes a valid `avraeService.token`, or seed the gvar in a var file referenced by `varFiles
|
|
115
|
+
- `get_gvar` returns `None` or `using(...)` raises `ModuleNotFoundError`: ensure the workspace `.avraels.json` sets `enableGvarFetch: true`, includes a valid `avraeService.token`, or seed the gvar in a var file referenced by `varFiles` (including `filePath` gvar entries).
|
|
114
116
|
- HTTP 401/403/404 from fetch/verify calls: check the token (401/403) and the gvar/signature id (404). Override `avraeService.baseUrl` if you mirror the API.
|
|
115
117
|
- Slow or flaky calls: disable remote fetches by flipping `enableGvarFetch` off to rely purely on local vars.
|
|
116
118
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
avrae_ls/__init__.py,sha256=BmjrnksGkbG7TPqwbyQvgYj9uei8pFSFpfkRpaGVdJU,63
|
|
2
|
-
avrae_ls/__main__.py,sha256=
|
|
3
|
-
avrae_ls/alias_preview.py,sha256=
|
|
4
|
-
avrae_ls/alias_tests.py,sha256=
|
|
2
|
+
avrae_ls/__main__.py,sha256=lx_Sncc4uiAPeoLtdspnZIdexgNEZNkD2hh-vJgWt30,10743
|
|
3
|
+
avrae_ls/alias_preview.py,sha256=GAYc-lURawY3cO_ykfGAk_tdnjuy1CtIXnX6gYACftM,14601
|
|
4
|
+
avrae_ls/alias_tests.py,sha256=lhb7pZFd_XYmu2BggVXn7sLNAoxLtT_V4mzy871xV4I,12361
|
|
5
5
|
avrae_ls/api.py,sha256=7QVJAmqgKkiS22qj39_aSVsDuO_kUkyl2Ho1IsD58yE,65110
|
|
6
6
|
avrae_ls/argparser.py,sha256=DRptXGyK4f0r7NsuV1Sg4apG-qihIClOX408jgk4HH4,13762
|
|
7
7
|
avrae_ls/argument_parsing.py,sha256=ezKl65VwuNEDxt6KlYwVQcpy1110UDvf4BqZqgZTcqk,2122
|
|
@@ -10,7 +10,7 @@ avrae_ls/code_actions.py,sha256=MLZ5euETh2G4lRO33QkHnIwWPt-XlXMCqy-BlRXz0pk,9562
|
|
|
10
10
|
avrae_ls/codes.py,sha256=iPRPQ6i9DZheae4_ra1y29vCw3Y4SEu6Udf5WiZj_RY,136
|
|
11
11
|
avrae_ls/completions.py,sha256=YJNK85MkFRJAf7IzltdcWnoKv3jMtFEoIsJwt-hnXJM,25062
|
|
12
12
|
avrae_ls/config.py,sha256=PJakf5JF7dbq1NbSFkoobgjKLzazyyMCyfveoqKzoLU,16810
|
|
13
|
-
avrae_ls/context.py,sha256=
|
|
13
|
+
avrae_ls/context.py,sha256=gsSi2ExDVRItzuWoMgRhUf2FqqlkxATFDXgPOF-VcoE,15298
|
|
14
14
|
avrae_ls/cvars.py,sha256=0tcVbUHx_CKJ6aou3kEsKX37LRWAjkUWlqqIuSRFlXk,3197
|
|
15
15
|
avrae_ls/diagnostics.py,sha256=7B5rL_zu3UByPD1GrGFK5cr6dOQYXz2b362x9MZgUEk,26990
|
|
16
16
|
avrae_ls/dice.py,sha256=DY7V7L-EwAXaCgddgVe9xU1s9lVtiw5Zc2reipNgdTk,874
|
|
@@ -32,8 +32,8 @@ draconic/string.py,sha256=kGrRc6wNHRq1y5xw8Os-fBhfINDtIY2nBWQWkyLSfQI,2858
|
|
|
32
32
|
draconic/types.py,sha256=1Lsr6z8bW5agglGI4hLt_nPtYuZOIf_ueSpPDB4WDrs,13686
|
|
33
33
|
draconic/utils.py,sha256=D4vJ-txqS2-rlqsEpXAC46_j1sZX4UjY-9zIgElo96k,3122
|
|
34
34
|
draconic/versions.py,sha256=CUEsgUWjAmjez0432WwiBwZlIzWPIObwZUf8Yld18EE,84
|
|
35
|
-
avrae_ls-0.
|
|
36
|
-
avrae_ls-0.
|
|
37
|
-
avrae_ls-0.
|
|
38
|
-
avrae_ls-0.
|
|
39
|
-
avrae_ls-0.
|
|
35
|
+
avrae_ls-0.8.1.dist-info/METADATA,sha256=HmuearSQW6CJEYhxN-qz-MSGvDhh_4Q_KhAVwtd7hiw,7980
|
|
36
|
+
avrae_ls-0.8.1.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
37
|
+
avrae_ls-0.8.1.dist-info/entry_points.txt,sha256=OtYXipMQzqmxpMoApgo0MeJYFmMbkbFN51Ibhpb8hF4,52
|
|
38
|
+
avrae_ls-0.8.1.dist-info/licenses/LICENSE,sha256=O-0zMbcEi6wXz1DiSdVgzMlQjJcNqNe5KDv08uYzqR0,1055
|
|
39
|
+
avrae_ls-0.8.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|