ZedScripts 0.0.2__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.
- ZedScripts/__about__.py +1 -0
- ZedScripts/__init__.py +38 -0
- ZedScripts/enums/Diagnostic.py +165 -0
- ZedScripts/enums/SyntaxErrorType.py +7 -0
- ZedScripts/environment/__init__.py +33 -0
- ZedScripts/environment/config.py +82 -0
- ZedScripts/environment/document.py +374 -0
- ZedScripts/environment/mod.py +62 -0
- ZedScripts/environment/version.py +202 -0
- ZedScripts/environment/workspace.py +320 -0
- ZedScripts/locale/en.json +38 -0
- ZedScripts/main.py +7 -0
- ZedScripts/providers/actions.py +13 -0
- ZedScripts/providers/capabilities.py +107 -0
- ZedScripts/providers/configuration.py +26 -0
- ZedScripts/providers/debug.py +59 -0
- ZedScripts/providers/diagnostics.py +101 -0
- ZedScripts/providers/hover.py +37 -0
- ZedScripts/providers/http.py +49 -0
- ZedScripts/providers/locale.py +80 -0
- ZedScripts/providers/notifications.py +45 -0
- ZedScripts/providers/semantic_tokens.py +189 -0
- ZedScripts/scripts/__init__.py +160 -0
- ZedScripts/scripts/block.py +547 -0
- ZedScripts/scripts/dataset.py +355 -0
- ZedScripts/scripts/value.py +706 -0
- ZedScripts/scripts/version.py +75 -0
- ZedScripts/server.py +288 -0
- ZedScripts/structure/ast.py +143 -0
- ZedScripts/structure/lexer.py +482 -0
- ZedScripts/structure/parser.py +243 -0
- ZedScripts/utils.py +85 -0
- zedscripts-0.0.2.dist-info/METADATA +105 -0
- zedscripts-0.0.2.dist-info/RECORD +37 -0
- zedscripts-0.0.2.dist-info/WHEEL +5 -0
- zedscripts-0.0.2.dist-info/licenses/LICENSE +21 -0
- zedscripts-0.0.2.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import re
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import TYPE_CHECKING, TypedDict
|
|
5
|
+
from pprint import pformat
|
|
6
|
+
|
|
7
|
+
from .. import SCRIPTS_BLOCKS_DATA_LINK, ROOTS_DATA_LINK
|
|
8
|
+
from . import ScriptBlockData, ScriptBlockParameter, ValueType, cant_self_validate
|
|
9
|
+
from .version import GameVersion, DataVersion
|
|
10
|
+
from .. import SCRIPTS_DATA_MANIFEST
|
|
11
|
+
from ..providers.http import load_json
|
|
12
|
+
from ..environment.config import DatasetModel, DatasetTag
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from ..environment.config import ReleaseModel
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class Release(TypedDict):
|
|
19
|
+
date: str
|
|
20
|
+
version: int
|
|
21
|
+
|
|
22
|
+
class Datasets(TypedDict):
|
|
23
|
+
blocks: str
|
|
24
|
+
roots: str
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class Manifest:
|
|
29
|
+
instance: 'Manifest | None' = None
|
|
30
|
+
|
|
31
|
+
def __init__(self, latest_build: GameVersion, releases: dict[GameVersion, Release], stable: DataVersion):
|
|
32
|
+
self.latest_build: GameVersion = latest_build
|
|
33
|
+
self.releases: dict[GameVersion, Release] = releases
|
|
34
|
+
self.stable: DataVersion = stable
|
|
35
|
+
|
|
36
|
+
self.releases_list: list[str] = self.make_releases_list()
|
|
37
|
+
|
|
38
|
+
# cache manifest
|
|
39
|
+
Manifest.instance = self
|
|
40
|
+
|
|
41
|
+
def __repr__(self) -> str:
|
|
42
|
+
return f"Manifest(latest_build={self.latest_build}, releases={len(self.releases)}, stable={self.stable})"
|
|
43
|
+
|
|
44
|
+
def make_releases_list(self) -> list[str]:
|
|
45
|
+
"""Transforms into a list of strings all available dataset versions."""
|
|
46
|
+
releases_list: list[str] = []
|
|
47
|
+
for version, release in self.releases.items():
|
|
48
|
+
for v in range(release['version']):
|
|
49
|
+
releases_list.append(str(version.to_data_version(v)))
|
|
50
|
+
return sorted(releases_list)
|
|
51
|
+
|
|
52
|
+
@staticmethod
|
|
53
|
+
def from_dict(data: dict) -> "Manifest":
|
|
54
|
+
return Manifest(
|
|
55
|
+
latest_build=GameVersion.find_or_make(data["latest_build"]),
|
|
56
|
+
releases={GameVersion.find_or_make(k): v for k, v in data["releases"].items()},
|
|
57
|
+
stable=DataVersion(data["stable"]),
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
@staticmethod
|
|
61
|
+
def find_release(game_version: GameVersion) -> Release | None:
|
|
62
|
+
assert Manifest.instance is not None
|
|
63
|
+
for version, release in Manifest.instance.releases.items():
|
|
64
|
+
if version == game_version:
|
|
65
|
+
return release
|
|
66
|
+
return None
|
|
67
|
+
|
|
68
|
+
@staticmethod
|
|
69
|
+
def get_latest_version(major: int, minor: int, patch: int) -> int:
|
|
70
|
+
assert Manifest.instance is not None
|
|
71
|
+
release = Manifest.find_release(GameVersion.from_values(major, minor, patch))
|
|
72
|
+
if release is None:
|
|
73
|
+
raise ValueError(f"No release found for version {major}.{minor}.{patch}")
|
|
74
|
+
return release['version']
|
|
75
|
+
|
|
76
|
+
@staticmethod
|
|
77
|
+
def from_release_model(dataset_config: 'ReleaseModel') -> 'DataVersion':
|
|
78
|
+
game_version = GameVersion.from_values(dataset_config.major, dataset_config.minor, dataset_config.patch)
|
|
79
|
+
version = dataset_config.version
|
|
80
|
+
if version is None:
|
|
81
|
+
version = Manifest.get_latest_version(dataset_config.major, dataset_config.minor, dataset_config.patch)
|
|
82
|
+
data_version = game_version.to_data_version(version)
|
|
83
|
+
return data_version
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@staticmethod
|
|
88
|
+
def load() -> "Manifest":
|
|
89
|
+
# if for some reason the LSP gets active a long time
|
|
90
|
+
# this will not get updated (but tbh this should absolutely not be a problem)
|
|
91
|
+
if Manifest.instance is not None:
|
|
92
|
+
return Manifest.instance
|
|
93
|
+
|
|
94
|
+
# load the manifest from the JSON file
|
|
95
|
+
logging.info("Loading manifest...")
|
|
96
|
+
manifest_data = load_json(SCRIPTS_DATA_MANIFEST)
|
|
97
|
+
if manifest_data is None:
|
|
98
|
+
raise RuntimeError("Failed to load manifest data")
|
|
99
|
+
|
|
100
|
+
logging.debug(pformat(manifest_data))
|
|
101
|
+
return Manifest.from_dict(manifest_data)
|
|
102
|
+
|
|
103
|
+
def get_latest(self) -> tuple[DataVersion,Release]:
|
|
104
|
+
versions = sorted(self.releases.keys())
|
|
105
|
+
latest_version = versions[-1]
|
|
106
|
+
release = self.releases[latest_version]
|
|
107
|
+
return latest_version.to_data_version(release['version']), release
|
|
108
|
+
|
|
109
|
+
def get_stable(self) -> DataVersion:
|
|
110
|
+
stable_version = self.stable
|
|
111
|
+
return stable_version
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def get_best_dataset_tag(self, key: DataVersion | DatasetTag) -> DataVersion:
|
|
115
|
+
# find classic keyword cases
|
|
116
|
+
match key:
|
|
117
|
+
case DatasetTag.LATEST:
|
|
118
|
+
return self.get_latest()[0]
|
|
119
|
+
case DatasetTag.STABLE:
|
|
120
|
+
return self.get_stable()
|
|
121
|
+
case _:
|
|
122
|
+
if not isinstance(key, DataVersion):
|
|
123
|
+
return NotImplemented
|
|
124
|
+
|
|
125
|
+
# try to find the key in available releases
|
|
126
|
+
to_find = str(key)
|
|
127
|
+
releases_list = self.releases_list
|
|
128
|
+
|
|
129
|
+
pos: int | None = releases_list.index(to_find) if to_find in releases_list else None
|
|
130
|
+
if pos is not None:
|
|
131
|
+
return DataVersion(releases_list[pos])
|
|
132
|
+
|
|
133
|
+
# if not found, return stable
|
|
134
|
+
logging.warning("Dataset version not found, falling back to stable.")
|
|
135
|
+
return self.get_stable()
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def get_dataset_links(self, dataset_config: DatasetModel) -> Datasets:
|
|
140
|
+
"""
|
|
141
|
+
Retrieve the dataset links for the specified tag_key.
|
|
142
|
+
"""
|
|
143
|
+
key = dataset_config.to_data_version()
|
|
144
|
+
best_tag = self.get_best_dataset_tag(key)
|
|
145
|
+
if best_tag is None:
|
|
146
|
+
raise ValueError(f"No dataset found for key: {key}")
|
|
147
|
+
|
|
148
|
+
return {
|
|
149
|
+
"blocks": SCRIPTS_BLOCKS_DATA_LINK.format(best_tag=best_tag),
|
|
150
|
+
"roots": ROOTS_DATA_LINK.format(best_tag=best_tag),
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
class Dataset:
|
|
159
|
+
def __init__(self):
|
|
160
|
+
self.manifest: Manifest = Manifest.load()
|
|
161
|
+
|
|
162
|
+
self.blocks: dict[str, ScriptBlockData]
|
|
163
|
+
self.roots: dict[str, ScriptBlockData]
|
|
164
|
+
|
|
165
|
+
def __repr__(self) -> str:
|
|
166
|
+
return f"Dataset(manifest={self.manifest}, blocks={len(self.blocks)}, roots={len(self.roots)})"
|
|
167
|
+
|
|
168
|
+
def load(self, dataset_config: DatasetModel = DatasetModel()) -> None:
|
|
169
|
+
logging.info(f"Loading dataset... ({dataset_config})")
|
|
170
|
+
links = self.manifest.get_dataset_links(dataset_config)
|
|
171
|
+
|
|
172
|
+
logging.info(f"Downloading from following dataset links:")
|
|
173
|
+
logging.info(f"Blocks: {links['blocks']}")
|
|
174
|
+
logging.info(f"Roots: {links['roots']}")
|
|
175
|
+
|
|
176
|
+
# load datasets (possibly from cache)
|
|
177
|
+
blocks = load_json(links["blocks"])
|
|
178
|
+
roots = load_json(links["roots"])
|
|
179
|
+
|
|
180
|
+
logging.debug("Blocks JSON:")
|
|
181
|
+
logging.debug(blocks)
|
|
182
|
+
logging.debug("Roots JSON:")
|
|
183
|
+
logging.debug(roots)
|
|
184
|
+
|
|
185
|
+
# convert to proper type and store it
|
|
186
|
+
self.blocks = {k.lower(): ScriptBlockData(v) for k, v in blocks.items()}
|
|
187
|
+
self.roots = {k.lower(): ScriptBlockData(v) for k, v in roots.items()}
|
|
188
|
+
|
|
189
|
+
def test_for_root(self, path: Path) -> str | None:
|
|
190
|
+
"""
|
|
191
|
+
Determine the root type for the given file path based on the dataset's root patterns.
|
|
192
|
+
|
|
193
|
+
Args:
|
|
194
|
+
path (Path): The file path to test against the dataset's root patterns.
|
|
195
|
+
|
|
196
|
+
Returns:
|
|
197
|
+
str | None: The name of the root type if a matching pattern is found, otherwise None.
|
|
198
|
+
"""
|
|
199
|
+
# the path is used to determine the root type
|
|
200
|
+
# for that we need to resolve, normalize and use posix paths
|
|
201
|
+
resolved_path = path.resolve().as_posix()
|
|
202
|
+
|
|
203
|
+
# for each rootFile type, we test their identification patterns
|
|
204
|
+
# to determine if the document is a zedscript doc
|
|
205
|
+
for rootFile in self.roots.values():
|
|
206
|
+
patterns = rootFile.get('pattern', [])
|
|
207
|
+
for pattern in patterns:
|
|
208
|
+
regex = re.compile(pattern)
|
|
209
|
+
if regex.search(resolved_path) is not None:
|
|
210
|
+
return rootFile['name']
|
|
211
|
+
return None
|
|
212
|
+
|
|
213
|
+
def is_script_block(self, type: str) -> bool:
|
|
214
|
+
"""
|
|
215
|
+
Determine if the given type corresponds to a script block in the dataset.
|
|
216
|
+
|
|
217
|
+
Args:
|
|
218
|
+
type (str): The type to check against the dataset's script blocks.
|
|
219
|
+
|
|
220
|
+
Returns:
|
|
221
|
+
bool: True if the type corresponds to a script block, False otherwise.
|
|
222
|
+
"""
|
|
223
|
+
return type.lower() in self.blocks.keys()
|
|
224
|
+
|
|
225
|
+
def is_root(self, type: str) -> bool:
|
|
226
|
+
"""
|
|
227
|
+
Determine if the given type corresponds to a root in the dataset.
|
|
228
|
+
|
|
229
|
+
Args:
|
|
230
|
+
type (str): The type to check against the dataset's roots.
|
|
231
|
+
|
|
232
|
+
Returns:
|
|
233
|
+
bool: True if the type corresponds to a root, False otherwise.
|
|
234
|
+
"""
|
|
235
|
+
return type.lower() in self.roots.keys()
|
|
236
|
+
|
|
237
|
+
def get_script_block_data(self, type: str) -> ScriptBlockData:
|
|
238
|
+
"""
|
|
239
|
+
Retrieve the script block data for the given type from the dataset.
|
|
240
|
+
You first need to verify that the type corresponds to a script block
|
|
241
|
+
or root in the dataset or the function will raise a ValueError.
|
|
242
|
+
|
|
243
|
+
Args:
|
|
244
|
+
type (str): The type of the script block or root to retrieve.
|
|
245
|
+
|
|
246
|
+
Raises:
|
|
247
|
+
ValueError: If the type does not correspond to a script block or root in the dataset.
|
|
248
|
+
|
|
249
|
+
Returns:
|
|
250
|
+
ScriptBlockData: The data associated with the specified script block or root type.
|
|
251
|
+
"""
|
|
252
|
+
if self.is_script_block(type):
|
|
253
|
+
return self.blocks[type.lower()]
|
|
254
|
+
if self.is_root(type):
|
|
255
|
+
return self.roots[type.lower()]
|
|
256
|
+
raise ValueError(f"Script block data for type '{type}' not found")
|
|
257
|
+
|
|
258
|
+
def get_block_tree(self, type: str) -> list[str]:
|
|
259
|
+
"""
|
|
260
|
+
Retrieve the variantOf tree of the specified script block type.
|
|
261
|
+
|
|
262
|
+
Args:
|
|
263
|
+
type (str): The type of the script block to retrieve the tree for.
|
|
264
|
+
|
|
265
|
+
Returns:
|
|
266
|
+
list[str]: A list representing the hierarchical tree of the script block type.
|
|
267
|
+
"""
|
|
268
|
+
block_data = self.get_script_block_data(type)
|
|
269
|
+
assert block_data is not None, f"{type} block should be validated before retrieving its tree"
|
|
270
|
+
|
|
271
|
+
# if it's a variant of another block then include that
|
|
272
|
+
# block's tree before the current block's type in the tree
|
|
273
|
+
variant_of = block_data.get('variantOf')
|
|
274
|
+
variant_tree = [type]
|
|
275
|
+
if variant_of is not None:
|
|
276
|
+
variant_tree = self.get_block_tree(variant_of) + variant_tree
|
|
277
|
+
|
|
278
|
+
return variant_tree
|
|
279
|
+
|
|
280
|
+
def can_block_have_parameter(self, type: str, parameter: str) -> bool:
|
|
281
|
+
block_data = self.get_script_block_data(type)
|
|
282
|
+
assert block_data is not None, f"{type} block should be validated before validating parameters"
|
|
283
|
+
parameters = block_data.get('parameters', [])
|
|
284
|
+
return parameter.lower() in parameters.keys()
|
|
285
|
+
|
|
286
|
+
def get_parameter_data(self, type: str, parameter: str) -> ScriptBlockParameter:
|
|
287
|
+
block_data = self.get_script_block_data(type)
|
|
288
|
+
assert block_data is not None, f"{type} block should be validated before retrieving parameter data"
|
|
289
|
+
parameters = block_data.get('parameters', {})
|
|
290
|
+
return parameters[parameter.lower()]
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
def get_parameter_type(self, value: str, parameter_data: ScriptBlockParameter) -> ValueType:
|
|
294
|
+
"""
|
|
295
|
+
Determine the type of a parameter value based on its content and
|
|
296
|
+
the expected type specified in the parameter data.
|
|
297
|
+
|
|
298
|
+
Args:
|
|
299
|
+
value (str): The value of the parameter to determine the type for.
|
|
300
|
+
parameter_data (ScriptBlockParameter): The metadata describing the expected type of the parameter.
|
|
301
|
+
|
|
302
|
+
Raises:
|
|
303
|
+
ValueError: If the expected type is an unsupported type from the dataset.
|
|
304
|
+
|
|
305
|
+
Returns:
|
|
306
|
+
ValueType: The determined type of the parameter value.
|
|
307
|
+
"""
|
|
308
|
+
type_data = parameter_data.get('type')
|
|
309
|
+
|
|
310
|
+
# default to string in any case
|
|
311
|
+
if type_data is None:
|
|
312
|
+
return ValueType.STRING
|
|
313
|
+
|
|
314
|
+
expected_type = type_data['main']
|
|
315
|
+
|
|
316
|
+
# assert type is something we know about
|
|
317
|
+
if expected_type not in ValueType:
|
|
318
|
+
raise ValueError(f"Unknown parameter type '{expected_type}'")
|
|
319
|
+
|
|
320
|
+
return self.test_for_type(expected_type, value)
|
|
321
|
+
|
|
322
|
+
def test_for_type(self, expected_type: ValueType, value: str) -> ValueType:
|
|
323
|
+
# return early types we can't really determine from the value itself
|
|
324
|
+
if cant_self_validate(expected_type):
|
|
325
|
+
return expected_type
|
|
326
|
+
|
|
327
|
+
# check if boolean
|
|
328
|
+
if value.lower() in ["true", "false"]:
|
|
329
|
+
return ValueType.BOOLEAN
|
|
330
|
+
|
|
331
|
+
# check if float or integer
|
|
332
|
+
try:
|
|
333
|
+
float(value) # try to convert it
|
|
334
|
+
|
|
335
|
+
# it means that our value is a float or an integer
|
|
336
|
+
# so we need to distinguish between float and integer
|
|
337
|
+
if "." in value:
|
|
338
|
+
return ValueType.FLOAT
|
|
339
|
+
|
|
340
|
+
# if the expected type is float, then it's a float
|
|
341
|
+
# because floats are allowed to not contain dots
|
|
342
|
+
# (simply representing whole numbers)
|
|
343
|
+
elif expected_type == ValueType.FLOAT:
|
|
344
|
+
return ValueType.FLOAT
|
|
345
|
+
|
|
346
|
+
# else then we are simply an integer number
|
|
347
|
+
return ValueType.INTEGER
|
|
348
|
+
except ValueError:
|
|
349
|
+
pass
|
|
350
|
+
|
|
351
|
+
# default to string if no other type matches
|
|
352
|
+
return ValueType.STRING
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
|