avrae-ls 0.2.1__py3-none-any.whl → 0.3.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 +84 -0
- avrae_ls/api.py +113 -10
- avrae_ls/completions.py +461 -53
- avrae_ls/context.py +25 -7
- avrae_ls/diagnostics.py +32 -8
- avrae_ls/runtime.py +161 -39
- avrae_ls/server.py +1 -1
- avrae_ls/signature_help.py +73 -19
- {avrae_ls-0.2.1.dist-info → avrae_ls-0.3.1.dist-info}/METADATA +4 -3
- {avrae_ls-0.2.1.dist-info → avrae_ls-0.3.1.dist-info}/RECORD +14 -14
- {avrae_ls-0.2.1.dist-info → avrae_ls-0.3.1.dist-info}/WHEEL +0 -0
- {avrae_ls-0.2.1.dist-info → avrae_ls-0.3.1.dist-info}/entry_points.txt +0 -0
- {avrae_ls-0.2.1.dist-info → avrae_ls-0.3.1.dist-info}/licenses/LICENSE +0 -0
- {avrae_ls-0.2.1.dist-info → avrae_ls-0.3.1.dist-info}/top_level.txt +0 -0
avrae_ls/context.py
CHANGED
|
@@ -93,31 +93,49 @@ class GVarResolver:
|
|
|
93
93
|
async def ensure(self, key: str) -> bool:
|
|
94
94
|
key = str(key)
|
|
95
95
|
if key in self._cache:
|
|
96
|
+
log.debug("GVAR ensure cache hit for %s", key)
|
|
96
97
|
return True
|
|
97
98
|
if not self._config.enable_gvar_fetch:
|
|
99
|
+
log.warning("GVAR fetch disabled; skipping %s", key)
|
|
98
100
|
return False
|
|
99
101
|
if not self._config.service.token:
|
|
100
102
|
log.debug("GVAR fetch skipped for %s: no token configured", key)
|
|
101
103
|
return False
|
|
102
104
|
|
|
103
105
|
base_url = self._config.service.base_url.rstrip("/")
|
|
104
|
-
url = f"{base_url}/gvars/{key}"
|
|
105
|
-
|
|
106
|
+
url = f"{base_url}/customizations/gvars/{key}"
|
|
107
|
+
# Avrae service expects the JWT directly in Authorization (no Bearer prefix).
|
|
108
|
+
headers = {"Authorization": str(self._config.service.token)}
|
|
106
109
|
try:
|
|
110
|
+
log.debug("GVAR fetching %s from %s", key, url)
|
|
107
111
|
async with httpx.AsyncClient(timeout=5) as client:
|
|
108
112
|
resp = await client.get(url, headers=headers)
|
|
109
113
|
except Exception as exc:
|
|
110
|
-
log.
|
|
114
|
+
log.error("GVAR fetch failed for %s: %s", key, exc)
|
|
111
115
|
return False
|
|
112
116
|
|
|
113
117
|
if resp.status_code != 200:
|
|
114
|
-
log.
|
|
118
|
+
log.warning(
|
|
119
|
+
"GVAR fetch returned %s for %s (body: %s)",
|
|
120
|
+
resp.status_code,
|
|
121
|
+
key,
|
|
122
|
+
(resp.text or "").strip(),
|
|
123
|
+
)
|
|
115
124
|
return False
|
|
116
125
|
|
|
117
|
-
|
|
118
|
-
|
|
126
|
+
value: Any = None
|
|
127
|
+
try:
|
|
128
|
+
payload = resp.json()
|
|
129
|
+
except Exception:
|
|
130
|
+
payload = None
|
|
131
|
+
|
|
132
|
+
if isinstance(payload, dict) and "value" in payload:
|
|
133
|
+
value = payload["value"]
|
|
134
|
+
|
|
135
|
+
log.debug("GVAR fetch parsed value for %s (type=%s)", key, type(value).__name__)
|
|
136
|
+
|
|
119
137
|
if value is None:
|
|
120
|
-
log.
|
|
138
|
+
log.error("GVAR %s payload missing value", key)
|
|
121
139
|
return False
|
|
122
140
|
self._cache[key] = value
|
|
123
141
|
return True
|
avrae_ls/diagnostics.py
CHANGED
|
@@ -149,6 +149,13 @@ class DiagnosticProvider:
|
|
|
149
149
|
)
|
|
150
150
|
)
|
|
151
151
|
|
|
152
|
+
def visit_Call(self, node: ast.Call):
|
|
153
|
+
if isinstance(node.func, ast.Name) and node.func.id == "using":
|
|
154
|
+
for kw in node.keywords:
|
|
155
|
+
if kw.arg:
|
|
156
|
+
self.tracker.add(str(kw.arg))
|
|
157
|
+
self.generic_visit(node)
|
|
158
|
+
|
|
152
159
|
walker = Walker(known)
|
|
153
160
|
for stmt in body:
|
|
154
161
|
walker.visit(stmt)
|
|
@@ -200,24 +207,41 @@ async def _check_gvars(
|
|
|
200
207
|
settings: DiagnosticSettings,
|
|
201
208
|
) -> List[types.Diagnostic]:
|
|
202
209
|
diagnostics: list[types.Diagnostic] = []
|
|
210
|
+
seen: set[str] = set()
|
|
211
|
+
|
|
212
|
+
def _literal_value(node: ast.AST) -> str | None:
|
|
213
|
+
if isinstance(node, ast.Constant) and isinstance(node.value, str):
|
|
214
|
+
return node.value
|
|
215
|
+
if isinstance(node, ast.Str):
|
|
216
|
+
return node.s
|
|
217
|
+
return None
|
|
218
|
+
|
|
203
219
|
for node in _iter_calls(body):
|
|
204
|
-
if not isinstance(node.func, ast.Name)
|
|
220
|
+
if not isinstance(node.func, ast.Name):
|
|
205
221
|
continue
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
222
|
+
|
|
223
|
+
async def _validate_gvar(arg_node: ast.AST):
|
|
224
|
+
gvar_id = _literal_value(arg_node)
|
|
225
|
+
if gvar_id is None or gvar_id in seen:
|
|
226
|
+
return
|
|
227
|
+
seen.add(gvar_id)
|
|
211
228
|
found_local = resolver.get_local(gvar_id)
|
|
212
229
|
ensured = found_local is not None or await resolver.ensure(gvar_id)
|
|
213
230
|
if not ensured:
|
|
214
231
|
diagnostics.append(
|
|
215
232
|
_make_diagnostic(
|
|
216
|
-
|
|
233
|
+
arg_node,
|
|
217
234
|
f"Unknown gvar '{gvar_id}'",
|
|
218
235
|
settings.semantic_level,
|
|
219
236
|
)
|
|
220
237
|
)
|
|
238
|
+
|
|
239
|
+
if node.func.id == "get_gvar":
|
|
240
|
+
if node.args:
|
|
241
|
+
await _validate_gvar(node.args[0])
|
|
242
|
+
elif node.func.id == "using":
|
|
243
|
+
for kw in node.keywords:
|
|
244
|
+
await _validate_gvar(kw.value)
|
|
221
245
|
return diagnostics
|
|
222
246
|
|
|
223
247
|
|
|
@@ -342,7 +366,7 @@ def _build_builtin_signatures() -> dict[str, inspect.Signature]:
|
|
|
342
366
|
def get(name, default=None): ...
|
|
343
367
|
def using(**imports): ...
|
|
344
368
|
def signature(data=0): ...
|
|
345
|
-
def verify_signature(
|
|
369
|
+
def verify_signature(data): ...
|
|
346
370
|
def print_fn(*args, sep=" ", end="\n"): ...
|
|
347
371
|
|
|
348
372
|
helpers = {
|
avrae_ls/runtime.py
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
import io
|
|
4
|
-
import logging
|
|
5
|
-
import time
|
|
6
3
|
import ast
|
|
4
|
+
import io
|
|
7
5
|
import json
|
|
8
|
-
import
|
|
6
|
+
import logging
|
|
9
7
|
import math
|
|
8
|
+
import random
|
|
9
|
+
import time
|
|
10
|
+
from types import SimpleNamespace
|
|
10
11
|
try: # optional dependency
|
|
11
12
|
import yaml
|
|
12
13
|
except ImportError: # pragma: no cover - fallback when PyYAML is absent
|
|
@@ -16,10 +17,11 @@ from typing import Any, Dict, Set, Callable
|
|
|
16
17
|
|
|
17
18
|
import d20
|
|
18
19
|
import draconic
|
|
20
|
+
import httpx
|
|
19
21
|
from draconic.interpreter import _Break, _Continue, _Return
|
|
20
22
|
|
|
21
23
|
from .context import ContextData, GVarResolver
|
|
22
|
-
from .config import VarSources
|
|
24
|
+
from .config import AvraeServiceConfig, VarSources
|
|
23
25
|
from .api import AliasContextAPI, CharacterAPI, SimpleCombat, SimpleRollResult
|
|
24
26
|
from . import argparser as avrae_argparser
|
|
25
27
|
# Minimal stand-in for Avrae's AliasException
|
|
@@ -127,12 +129,9 @@ def _default_builtins() -> Dict[str, Any]:
|
|
|
127
129
|
"abs": abs,
|
|
128
130
|
"range": range,
|
|
129
131
|
"enumerate": enumerate,
|
|
130
|
-
"sorted": sorted,
|
|
131
|
-
"reversed": reversed,
|
|
132
132
|
"int": int,
|
|
133
133
|
"float": float,
|
|
134
134
|
"str": str,
|
|
135
|
-
"bool": bool,
|
|
136
135
|
"round": round,
|
|
137
136
|
"ceil": math.ceil,
|
|
138
137
|
"floor": math.floor,
|
|
@@ -156,8 +155,9 @@ def _default_builtins() -> Dict[str, Any]:
|
|
|
156
155
|
|
|
157
156
|
|
|
158
157
|
class MockExecutor:
|
|
159
|
-
def __init__(self):
|
|
158
|
+
def __init__(self, service_config: AvraeServiceConfig | None = None):
|
|
160
159
|
self._base_builtins = _default_builtins()
|
|
160
|
+
self._service_config = service_config or AvraeServiceConfig()
|
|
161
161
|
|
|
162
162
|
def available_names(self, ctx_data: ContextData) -> Set[str]:
|
|
163
163
|
builtin_names = set(self._base_builtins.keys())
|
|
@@ -220,12 +220,16 @@ class MockExecutor:
|
|
|
220
220
|
runtime_character = CharacterAPI(ctx_data.character)
|
|
221
221
|
return runtime_character # type: ignore[return-value]
|
|
222
222
|
|
|
223
|
+
import_cache: dict[str, SimpleNamespace] = {}
|
|
224
|
+
import_stack: list[str] = []
|
|
223
225
|
builtins = self._build_builtins(
|
|
224
226
|
ctx_data,
|
|
225
227
|
resolver,
|
|
226
228
|
buffer,
|
|
227
229
|
character_provider=_character_provider,
|
|
228
230
|
interpreter_ref=interpreter_ref,
|
|
231
|
+
import_cache=import_cache,
|
|
232
|
+
import_stack=import_stack,
|
|
229
233
|
)
|
|
230
234
|
interpreter = draconic.DraconicInterpreter(
|
|
231
235
|
builtins=builtins,
|
|
@@ -266,10 +270,18 @@ class MockExecutor:
|
|
|
266
270
|
buffer: io.StringIO,
|
|
267
271
|
character_provider: Callable[[], CharacterAPI] | None = None,
|
|
268
272
|
interpreter_ref: Dict[str, draconic.DraconicInterpreter | None] | None = None,
|
|
273
|
+
import_cache: Dict[str, SimpleNamespace] | None = None,
|
|
274
|
+
import_stack: list[str] | None = None,
|
|
269
275
|
) -> Dict[str, Any]:
|
|
270
276
|
builtins = dict(self._base_builtins)
|
|
271
277
|
var_store = ctx_data.vars
|
|
272
278
|
interpreter_ref = interpreter_ref or {"interpreter": None}
|
|
279
|
+
import_cache = import_cache or {}
|
|
280
|
+
import_stack = import_stack or []
|
|
281
|
+
service_cfg = self._service_config
|
|
282
|
+
verify_cache_sig: str | None = None
|
|
283
|
+
verify_cache_result: Dict[str, Any] | None = None
|
|
284
|
+
verify_cache_error: ValueError | None = None
|
|
273
285
|
|
|
274
286
|
def _print(*args, sep=" ", end="\n"):
|
|
275
287
|
buffer.write(sep.join(map(str, args)) + end)
|
|
@@ -283,22 +295,25 @@ class MockExecutor:
|
|
|
283
295
|
return var_store.svars.get(str(name), default)
|
|
284
296
|
|
|
285
297
|
def _get_cvar(name: str, default=None):
|
|
286
|
-
|
|
298
|
+
val = var_store.cvars.get(str(name), default)
|
|
299
|
+
return str(val) if val is not None else default
|
|
287
300
|
|
|
288
301
|
def _get_uvar(name: str, default=None):
|
|
289
|
-
|
|
302
|
+
val = var_store.uvars.get(str(name), default)
|
|
303
|
+
return str(val) if val is not None else default
|
|
290
304
|
|
|
291
305
|
def _get_uvars():
|
|
292
|
-
return
|
|
306
|
+
return {k: (str(v) if v is not None else v) for k, v in var_store.uvars.items()}
|
|
293
307
|
|
|
294
308
|
def _set_uvar(name: str, value: Any):
|
|
295
|
-
|
|
296
|
-
|
|
309
|
+
str_val = str(value) if value is not None else None
|
|
310
|
+
var_store.uvars[str(name)] = str_val
|
|
311
|
+
return str_val
|
|
297
312
|
|
|
298
313
|
def _set_uvar_nx(name: str, value: Any):
|
|
299
314
|
key = str(name)
|
|
300
315
|
if key not in var_store.uvars:
|
|
301
|
-
var_store.uvars[key] = value
|
|
316
|
+
var_store.uvars[key] = str(value) if value is not None else None
|
|
302
317
|
return var_store.uvars[key]
|
|
303
318
|
|
|
304
319
|
def _delete_uvar(name: str):
|
|
@@ -307,37 +322,136 @@ class MockExecutor:
|
|
|
307
322
|
def _uvar_exists(name: str) -> bool:
|
|
308
323
|
return str(name) in var_store.uvars
|
|
309
324
|
|
|
310
|
-
def
|
|
325
|
+
def _resolve_name(key: str) -> tuple[bool, Any]:
|
|
326
|
+
key = str(key)
|
|
311
327
|
interp = interpreter_ref.get("interpreter")
|
|
312
|
-
if interp is None:
|
|
313
|
-
|
|
314
|
-
|
|
328
|
+
if interp is not None:
|
|
329
|
+
names = getattr(interp, "_names", {})
|
|
330
|
+
if key in names:
|
|
331
|
+
return True, names[key]
|
|
332
|
+
|
|
333
|
+
if key in var_store.cvars:
|
|
334
|
+
return True, var_store.cvars[key]
|
|
335
|
+
|
|
336
|
+
if key in var_store.uvars:
|
|
337
|
+
return True, var_store.uvars[key]
|
|
338
|
+
|
|
339
|
+
return False, None
|
|
340
|
+
|
|
341
|
+
def _exists(name: str) -> bool:
|
|
342
|
+
found, _ = _resolve_name(name)
|
|
343
|
+
return found
|
|
315
344
|
|
|
316
345
|
def _get(name: str, default=None):
|
|
317
|
-
|
|
318
|
-
if
|
|
319
|
-
return default
|
|
320
|
-
return getattr(interp, "_names", {}).get(str(name), default)
|
|
346
|
+
found, value = _resolve_name(name)
|
|
347
|
+
return value if found else default
|
|
321
348
|
|
|
322
349
|
def _using(**imports):
|
|
323
350
|
interp = interpreter_ref.get("interpreter")
|
|
324
351
|
if interp is None:
|
|
325
352
|
return None
|
|
353
|
+
user_ns = getattr(interp, "_names", {})
|
|
354
|
+
|
|
355
|
+
def _load_module(addr: str) -> SimpleNamespace:
|
|
356
|
+
if addr in import_cache:
|
|
357
|
+
return import_cache[addr]
|
|
358
|
+
if resolver is None:
|
|
359
|
+
raise ModuleNotFoundError(f"No gvar named {addr!r}")
|
|
360
|
+
mod_contents = resolver.get_local(addr)
|
|
361
|
+
if mod_contents is None:
|
|
362
|
+
raise ModuleNotFoundError(f"No gvar named {addr!r}")
|
|
363
|
+
|
|
364
|
+
old_names = getattr(interp, "_names", {})
|
|
365
|
+
depth_increased = False
|
|
366
|
+
try:
|
|
367
|
+
interp._names = {}
|
|
368
|
+
interp._depth += 1
|
|
369
|
+
depth_increased = True
|
|
370
|
+
if interp._depth > interp._config.max_recursion_depth:
|
|
371
|
+
raise RecursionError("Maximum recursion depth exceeded")
|
|
372
|
+
interp.execute_module(str(mod_contents), module_name=addr)
|
|
373
|
+
mod_ns = SimpleNamespace(**getattr(interp, "_names", {}))
|
|
374
|
+
import_cache[addr] = mod_ns
|
|
375
|
+
return mod_ns
|
|
376
|
+
finally:
|
|
377
|
+
if depth_increased:
|
|
378
|
+
interp._depth -= 1
|
|
379
|
+
interp._names = old_names
|
|
380
|
+
|
|
326
381
|
for ns, addr in imports.items():
|
|
327
|
-
|
|
328
|
-
if
|
|
329
|
-
|
|
330
|
-
|
|
382
|
+
addr_str = str(addr)
|
|
383
|
+
if addr_str in import_stack:
|
|
384
|
+
circle = " imports\n".join(import_stack)
|
|
385
|
+
raise ImportError(f"Circular import detected!\n{circle} imports\n{addr_str}")
|
|
386
|
+
import_stack.append(addr_str)
|
|
387
|
+
try:
|
|
388
|
+
mod_ns = _load_module(addr_str)
|
|
389
|
+
finally:
|
|
390
|
+
import_stack.pop()
|
|
391
|
+
name = str(ns)
|
|
392
|
+
if name in interp.builtins:
|
|
393
|
+
raise ValueError(f"{name} is already builtin (no shadow assignments).")
|
|
394
|
+
user_ns[name] = mod_ns
|
|
395
|
+
|
|
396
|
+
interp._names = user_ns
|
|
331
397
|
return None
|
|
332
398
|
|
|
333
399
|
def _signature(data=0):
|
|
334
|
-
|
|
400
|
+
try:
|
|
401
|
+
data = int(data)
|
|
402
|
+
except ValueError:
|
|
403
|
+
raise TypeError(f"Data {data} could not be converted to integer.")
|
|
404
|
+
return f"mock-signature:{int(data)}"
|
|
335
405
|
|
|
336
406
|
def _verify_signature(sig):
|
|
407
|
+
nonlocal verify_cache_sig, verify_cache_result, verify_cache_error
|
|
408
|
+
sig_str = str(sig)
|
|
409
|
+
if sig_str == verify_cache_sig:
|
|
410
|
+
if verify_cache_error:
|
|
411
|
+
raise verify_cache_error
|
|
412
|
+
return verify_cache_result
|
|
413
|
+
|
|
414
|
+
verify_cache_sig = sig_str
|
|
415
|
+
verify_cache_error = None
|
|
416
|
+
verify_cache_result = None
|
|
417
|
+
|
|
418
|
+
def _call_verify_api(signature: str) -> Dict[str, Any]:
|
|
419
|
+
base_url = (service_cfg.base_url if service_cfg else AvraeServiceConfig.base_url).rstrip("/")
|
|
420
|
+
url = f"{base_url}/bot/signature/verify"
|
|
421
|
+
headers = {"Content-Type": "application/json"}
|
|
422
|
+
if service_cfg and service_cfg.token:
|
|
423
|
+
headers["Authorization"] = str(service_cfg.token)
|
|
424
|
+
try:
|
|
425
|
+
resp = httpx.post(url, json={"signature": signature}, headers=headers, timeout=5)
|
|
426
|
+
except Exception as exc:
|
|
427
|
+
raise ValueError(f"Failed to verify signature: {exc}") from exc
|
|
428
|
+
|
|
429
|
+
try:
|
|
430
|
+
payload = resp.json()
|
|
431
|
+
except Exception as exc:
|
|
432
|
+
raise ValueError("Failed to verify signature: invalid response body") from exc
|
|
433
|
+
|
|
434
|
+
if resp.status_code != 200:
|
|
435
|
+
message = payload.get("error") if isinstance(payload, dict) else None
|
|
436
|
+
raise ValueError(message or f"Failed to verify signature: HTTP {resp.status_code}")
|
|
437
|
+
|
|
438
|
+
if not isinstance(payload, dict):
|
|
439
|
+
raise ValueError("Failed to verify signature: invalid response")
|
|
440
|
+
if payload.get("success") is not True:
|
|
441
|
+
message = payload.get("error")
|
|
442
|
+
raise ValueError(message or "Failed to verify signature: unsuccessful response")
|
|
443
|
+
|
|
444
|
+
data = payload.get("data")
|
|
445
|
+
if not isinstance(data, dict):
|
|
446
|
+
raise ValueError("Failed to verify signature: malformed response")
|
|
447
|
+
return data
|
|
448
|
+
|
|
337
449
|
try:
|
|
338
|
-
|
|
339
|
-
except
|
|
340
|
-
|
|
450
|
+
verify_cache_result = _call_verify_api(sig_str)
|
|
451
|
+
except ValueError as exc:
|
|
452
|
+
verify_cache_error = exc
|
|
453
|
+
raise
|
|
454
|
+
return verify_cache_result
|
|
341
455
|
|
|
342
456
|
def _argparse(args, character=None, splitter=avrae_argparser.argsplit, parse_ephem=True):
|
|
343
457
|
return avrae_argparser.argparse(args, character=character, splitter=splitter, parse_ephem=parse_ephem)
|
|
@@ -449,12 +563,20 @@ def _literal_gvars(code: str) -> Set[str]:
|
|
|
449
563
|
|
|
450
564
|
gvars: set[str] = set()
|
|
451
565
|
for node in ast.walk(tree):
|
|
452
|
-
if isinstance(node, ast.Call) and isinstance(node.func, ast.Name)
|
|
453
|
-
if
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
566
|
+
if isinstance(node, ast.Call) and isinstance(node.func, ast.Name):
|
|
567
|
+
if node.func.id == "get_gvar":
|
|
568
|
+
if not node.args:
|
|
569
|
+
continue
|
|
570
|
+
arg = node.args[0]
|
|
571
|
+
if isinstance(arg, ast.Constant) and isinstance(arg.value, str):
|
|
572
|
+
gvars.add(arg.value)
|
|
573
|
+
elif isinstance(arg, ast.Str):
|
|
574
|
+
gvars.add(arg.s)
|
|
575
|
+
elif node.func.id == "using":
|
|
576
|
+
for kw in node.keywords:
|
|
577
|
+
val = kw.value
|
|
578
|
+
if isinstance(val, ast.Constant) and isinstance(val.value, str):
|
|
579
|
+
gvars.add(val.value)
|
|
580
|
+
elif isinstance(val, ast.Str):
|
|
581
|
+
gvars.add(val.s)
|
|
460
582
|
return gvars
|
avrae_ls/server.py
CHANGED
|
@@ -68,7 +68,7 @@ class AvraeLanguageServer(LanguageServer):
|
|
|
68
68
|
|
|
69
69
|
def load_workspace(self, root: Path) -> None:
|
|
70
70
|
config, warnings = load_config(root)
|
|
71
|
-
executor = MockExecutor()
|
|
71
|
+
executor = MockExecutor(config.service)
|
|
72
72
|
context_builder = ContextBuilder(config)
|
|
73
73
|
diagnostics = DiagnosticProvider(executor, config.diagnostics)
|
|
74
74
|
self._state = ServerState(
|
avrae_ls/signature_help.py
CHANGED
|
@@ -45,26 +45,80 @@ def _builtin_sigs() -> Dict[str, FunctionSig]:
|
|
|
45
45
|
|
|
46
46
|
def _runtime_helper_sigs() -> Dict[str, FunctionSig]:
|
|
47
47
|
helpers = {
|
|
48
|
-
"get_gvar":
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
"
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
"
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
"
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
"
|
|
65
|
-
|
|
48
|
+
"get_gvar": (
|
|
49
|
+
["address"],
|
|
50
|
+
"Retrieves and returns the value of a global variable (gvar) by address.",
|
|
51
|
+
),
|
|
52
|
+
"get_svar": (
|
|
53
|
+
["name", "default=None"],
|
|
54
|
+
"Gets a server variable by name, returning default if it is not present.",
|
|
55
|
+
),
|
|
56
|
+
"get_cvar": (
|
|
57
|
+
["name", "default=None"],
|
|
58
|
+
"Gets a character variable by name as a string, returning default if it is not present.",
|
|
59
|
+
),
|
|
60
|
+
"get_uvar": (
|
|
61
|
+
["name", "default=None"],
|
|
62
|
+
"Gets a user variable by name as a string, returning default if it is not present.",
|
|
63
|
+
),
|
|
64
|
+
"get_uvars": (
|
|
65
|
+
[],
|
|
66
|
+
"Returns the mapping of user variables available to the caller (values are strings).",
|
|
67
|
+
),
|
|
68
|
+
"set_uvar": (
|
|
69
|
+
["name", "value"],
|
|
70
|
+
"Sets a user variable (stored as a string) and returns the stored value.",
|
|
71
|
+
),
|
|
72
|
+
"set_uvar_nx": (
|
|
73
|
+
["name", "value"],
|
|
74
|
+
"Sets a user variable only if it does not already exist; returns the stored string value.",
|
|
75
|
+
),
|
|
76
|
+
"delete_uvar": (
|
|
77
|
+
["name"],
|
|
78
|
+
"Deletes a user variable and returns its previous value or None if missing.",
|
|
79
|
+
),
|
|
80
|
+
"uvar_exists": (
|
|
81
|
+
["name"],
|
|
82
|
+
"Returns whether a user variable is set.",
|
|
83
|
+
),
|
|
84
|
+
"exists": (
|
|
85
|
+
["name"],
|
|
86
|
+
"Returns whether a name is set in the current evaluation context.",
|
|
87
|
+
),
|
|
88
|
+
"get": (
|
|
89
|
+
["name", "default=None"],
|
|
90
|
+
"Gets the value of a name using local > cvar > uvar resolution order; returns default if not set.",
|
|
91
|
+
),
|
|
92
|
+
"using": (
|
|
93
|
+
["**imports"],
|
|
94
|
+
"Imports one or more gvars as modules into the current namespace with the provided aliases.",
|
|
95
|
+
),
|
|
96
|
+
"signature": (
|
|
97
|
+
["data=0"],
|
|
98
|
+
"Generates a signed invocation signature encoding invocation context and optional 5-bit user data.",
|
|
99
|
+
),
|
|
100
|
+
"verify_signature": (
|
|
101
|
+
["data"],
|
|
102
|
+
"Verifies a signature generated by signature(); returns context data or raises ValueError when invalid.",
|
|
103
|
+
),
|
|
104
|
+
"print": (
|
|
105
|
+
["*values"],
|
|
106
|
+
"Writes values to alias output using the configured separator/end (mirrors Python print).",
|
|
107
|
+
),
|
|
108
|
+
"character": (
|
|
109
|
+
[],
|
|
110
|
+
"Returns the active character object for this alias, or raises if none is available.",
|
|
111
|
+
),
|
|
112
|
+
"combat": (
|
|
113
|
+
[],
|
|
114
|
+
"Returns the current combat context if one exists, otherwise None.",
|
|
115
|
+
),
|
|
116
|
+
"argparse": (
|
|
117
|
+
["args", "character=None", "splitter=argsplit", "parse_ephem=True"],
|
|
118
|
+
"Parses alias arguments using Avrae's argparse helper.",
|
|
119
|
+
),
|
|
66
120
|
}
|
|
67
|
-
return {name: FunctionSig(name=name, params=params) for name, params in helpers.items()}
|
|
121
|
+
return {name: FunctionSig(name=name, params=params, doc=doc) for name, (params, doc) in helpers.items()}
|
|
68
122
|
|
|
69
123
|
|
|
70
124
|
def _avrae_function_sigs() -> Dict[str, FunctionSig]:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: avrae-ls
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.1
|
|
4
4
|
Summary: Language server for Avrae draconic aliases
|
|
5
5
|
Author: 1drturtle
|
|
6
6
|
Requires-Python: >=3.11
|
|
@@ -23,7 +23,7 @@ Language Server Protocol (LSP) implementation targeting Avrae-style draconic ali
|
|
|
23
23
|
|
|
24
24
|
## Install (released package)
|
|
25
25
|
|
|
26
|
-
- CLI/server via `uv tool` (preferred): `uv tool install avrae-ls` then `
|
|
26
|
+
- CLI/server via `uv tool` (preferred): `uv tool install avrae-ls` then `avrae-ls --help` to see stdio/TCP options (same as `python -m avrae_ls`). The VS Code extension uses this invocation by default. The draconic interpreter is vendored, so no Git deps are needed.
|
|
27
27
|
|
|
28
28
|
## VS Code extension (released)
|
|
29
29
|
|
|
@@ -37,10 +37,11 @@ Language Server Protocol (LSP) implementation targeting Avrae-style draconic ali
|
|
|
37
37
|
- Build everything locally: `make package` (wheel + VSIX in `dist/`).
|
|
38
38
|
- Run tests/lint: `make check`.
|
|
39
39
|
- Run via uv tool from source: `uv tool install --from . avrae-ls`.
|
|
40
|
+
- Run diagnostics for a single file (stdout + stderr logs): `avrae-ls --analyze path/to/alias.txt --log-level DEBUG`.
|
|
40
41
|
|
|
41
42
|
## Releasing (maintainers)
|
|
42
43
|
|
|
43
44
|
1. Bump `pyproject.toml` version.
|
|
44
|
-
2. `make release` (clean, build,
|
|
45
|
+
2. `make release` (clean, build, upload to PyPI).
|
|
45
46
|
3. Build and attach the VSIX to the GitHub release (`make vsix`).
|
|
46
47
|
4. Tag and push.
|
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
avrae_ls/__init__.py,sha256=BmjrnksGkbG7TPqwbyQvgYj9uei8pFSFpfkRpaGVdJU,63
|
|
2
|
-
avrae_ls/__main__.py,sha256=
|
|
2
|
+
avrae_ls/__main__.py,sha256=ch287lWe11go5xHAE9OkVppixt0vRF401E4zTs2tqQ0,3557
|
|
3
3
|
avrae_ls/alias_preview.py,sha256=Wy_ZNRq73ojPiLFf6P6fJ7rG7DeFFsFLkLlhFrAjkKI,6117
|
|
4
|
-
avrae_ls/api.py,sha256=
|
|
4
|
+
avrae_ls/api.py,sha256=sGbwWs5o6M49lyk3qZi6gNdSH_u_BMdghsTlAjpviqQ,64852
|
|
5
5
|
avrae_ls/argparser.py,sha256=-6RKrXavbSjEyyEeaoz8hRamnB-MEmJt3Cw2smRbmdI,13483
|
|
6
6
|
avrae_ls/argument_parsing.py,sha256=ezKl65VwuNEDxt6KlYwVQcpy1110UDvf4BqZqgZTcqk,2122
|
|
7
|
-
avrae_ls/completions.py,sha256=
|
|
7
|
+
avrae_ls/completions.py,sha256=WSyP967_t_oBhRMjLk-QQKyXWiNh4sJTH560VdMBQeg,34336
|
|
8
8
|
avrae_ls/config.py,sha256=C51wfG6-82uX4dsffqcDLMiXbZZL9JQgfpulAr8eiqs,15267
|
|
9
|
-
avrae_ls/context.py,sha256=
|
|
9
|
+
avrae_ls/context.py,sha256=w0uVSR6Pis3q1fF3kSPsqbTirKOE6n3k2XMO8UZI7sk,5719
|
|
10
10
|
avrae_ls/cvars.py,sha256=0tcVbUHx_CKJ6aou3kEsKX37LRWAjkUWlqqIuSRFlXk,3197
|
|
11
|
-
avrae_ls/diagnostics.py,sha256=
|
|
11
|
+
avrae_ls/diagnostics.py,sha256=EAdbckvvWu0pswopaZ6JyVGCHXUl5ZvGBhP2PDVCrVw,16180
|
|
12
12
|
avrae_ls/dice.py,sha256=DY7V7L-EwAXaCgddgVe9xU1s9lVtiw5Zc2reipNgdTk,874
|
|
13
13
|
avrae_ls/parser.py,sha256=AuNxNkkfquN9dcyTpmzAZxWcAQ7CV3PQLHUDYLMz_7U,1148
|
|
14
|
-
avrae_ls/runtime.py,sha256=
|
|
15
|
-
avrae_ls/server.py,sha256=
|
|
16
|
-
avrae_ls/signature_help.py,sha256=
|
|
14
|
+
avrae_ls/runtime.py,sha256=sSN_C48TrF7F5NtsUpgHr_cZyYX6HCQHBfCpAYxsDJo,20792
|
|
15
|
+
avrae_ls/server.py,sha256=L3qGTpCDOa64hWFmfWfoKbrDZ3262gmu5wYhHkdLBPY,13070
|
|
16
|
+
avrae_ls/signature_help.py,sha256=JheaEzINV4FO72t5U0AJfL2ZX15y3-gcA6xk3M1jHcY,6980
|
|
17
17
|
avrae_ls/symbols.py,sha256=8aMalHBDnRsRvhwdbmf0nOazio7G185qw9le45Xb5Mk,4449
|
|
18
|
-
avrae_ls-0.
|
|
18
|
+
avrae_ls-0.3.1.dist-info/licenses/LICENSE,sha256=O-0zMbcEi6wXz1DiSdVgzMlQjJcNqNe5KDv08uYzqR0,1055
|
|
19
19
|
draconic/LICENSE,sha256=Fzvu32_DafLKKn2mzxhEdlmrKZzAsigDZ87O7uoVqZI,1067
|
|
20
20
|
draconic/__init__.py,sha256=YPH420Pcn_nTkfB62hJy_YqC5kpJdzSa78jP8n4z_xY,109
|
|
21
21
|
draconic/exceptions.py,sha256=siahnHIsumbaUhKBDSrw_DmLZ-0oZks8L5oytPH8hD4,3753
|
|
@@ -25,8 +25,8 @@ draconic/string.py,sha256=kGrRc6wNHRq1y5xw8Os-fBhfINDtIY2nBWQWkyLSfQI,2858
|
|
|
25
25
|
draconic/types.py,sha256=1Lsr6z8bW5agglGI4hLt_nPtYuZOIf_ueSpPDB4WDrs,13686
|
|
26
26
|
draconic/utils.py,sha256=D4vJ-txqS2-rlqsEpXAC46_j1sZX4UjY-9zIgElo96k,3122
|
|
27
27
|
draconic/versions.py,sha256=CUEsgUWjAmjez0432WwiBwZlIzWPIObwZUf8Yld18EE,84
|
|
28
|
-
avrae_ls-0.
|
|
29
|
-
avrae_ls-0.
|
|
30
|
-
avrae_ls-0.
|
|
31
|
-
avrae_ls-0.
|
|
32
|
-
avrae_ls-0.
|
|
28
|
+
avrae_ls-0.3.1.dist-info/METADATA,sha256=TRJQs5mkNC4TPeYBkO2rXDnvkJhjf2jws1ginLXaXq4,2107
|
|
29
|
+
avrae_ls-0.3.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
30
|
+
avrae_ls-0.3.1.dist-info/entry_points.txt,sha256=OtYXipMQzqmxpMoApgo0MeJYFmMbkbFN51Ibhpb8hF4,52
|
|
31
|
+
avrae_ls-0.3.1.dist-info/top_level.txt,sha256=TL68uzGHmB2R2ID32_s2zocmcNnpMJVQ6_4NBvyo8a4,18
|
|
32
|
+
avrae_ls-0.3.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|