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.
@@ -0,0 +1,243 @@
1
+ import re
2
+ from typing import TYPE_CHECKING
3
+
4
+ from ..enums.SyntaxErrorType import SyntaxErrorType
5
+ from .lexer import (
6
+ Lexer,
7
+ TokenType, Token, TokenCollection,
8
+ TextRange, TextPosition,
9
+ ELEMENTS_DELIMITERS
10
+ )
11
+ from .ast import Node, Chunk, BlockNode, ValueNode
12
+ from ..scripts.block import Block, ScriptBlock, Root
13
+ from ..scripts.value import Value
14
+
15
+ if TYPE_CHECKING:
16
+ from ..environment.document import Document
17
+
18
+ class SyntaxError:
19
+ def __init__(self, type: SyntaxErrorType, location: TextRange) -> None:
20
+ self.type: SyntaxErrorType = type
21
+ self.location: TextRange = location
22
+
23
+
24
+ class ParseResult:
25
+ def __init__(self, chunk: Chunk) -> None:
26
+ self.chunk: Chunk = chunk
27
+ self.errors: list[SyntaxError] = []
28
+
29
+
30
+ class Parser:
31
+ def __init__(self) -> None:
32
+ self.chunk: Chunk = Chunk()
33
+ """Root of the chunk being parsed."""
34
+ self.block_stack: list[BlockNode] = []
35
+ """Stack of the currently parsing blocks."""
36
+
37
+ def add_block(self, block: BlockNode) -> None:
38
+ """
39
+ Adds a block to the top of the stack.
40
+ :return: The block that was added.
41
+ """
42
+ if len(self.block_stack) > 0:
43
+ self.block_stack[-1].children.append(block)
44
+ else:
45
+ self.chunk.children.append(block)
46
+ self.block_stack.append(block)
47
+
48
+ def add_value(self, value: ValueNode) -> None:
49
+ """
50
+ Adds a value to the block at the top of the stack, or the root if the stack is empty.
51
+ :param value: The value to push.
52
+ :return:
53
+ """
54
+ if len(self.block_stack) > 0:
55
+ self.block_stack[-1].children.append(value)
56
+ else:
57
+ self.chunk.children.append(value)
58
+
59
+ def pop_block(self) -> BlockNode:
60
+ """
61
+ Removes the block at the top of the stack.
62
+ :return: The block that was at the top of the stack.
63
+ """
64
+ assert len(self.block_stack) > 0, "Attempted to pop from empty stack"
65
+ return self.block_stack.pop()
66
+
67
+
68
+ def parse_type_id(tokens: list[Token]) -> tuple[TokenCollection | None, TokenCollection | None]:
69
+ type_token = TokenCollection()
70
+ id_token = TokenCollection()
71
+ hit_whitespace = False
72
+ for unparsed_token in tokens:
73
+ if unparsed_token.type is TokenType.TEXT:
74
+ # all tokens before the first whitespace are the type
75
+ if not hit_whitespace:
76
+ type_token.append(unparsed_token)
77
+ # all tokens between the first and second are the id
78
+ else:
79
+ id_token.append(unparsed_token)
80
+ elif len(type_token) > 0 and unparsed_token.type is TokenType.WHITESPACE:
81
+ if not hit_whitespace:
82
+ hit_whitespace = True
83
+ else:
84
+ # after we hit whitespace for a second time, break
85
+ # FIXME: whitespace -> comment -> whitespace should not break
86
+ break
87
+
88
+ if len(type_token) < 1:
89
+ type_token = None
90
+ if len(id_token) < 1:
91
+ id_token = None
92
+
93
+ return type_token, id_token
94
+
95
+
96
+ def parse(text: str) -> ParseResult:
97
+ return parse_tokens(Lexer.tokenize(text))
98
+
99
+
100
+ def parse_tokens(tokens: TokenCollection) -> ParseResult:
101
+ parser = Parser()
102
+ unparsed_tokens: list[Token] = []
103
+ result: ParseResult = ParseResult(parser.chunk)
104
+ unassigned_comments: list[Token] = []
105
+
106
+ for token in tokens:
107
+ match token.type:
108
+ case TokenType.ELEMENT_DELIMITER:
109
+ match token.text:
110
+ case "}":
111
+ if len(parser.block_stack) == 0:
112
+ result.errors.append(
113
+ SyntaxError(SyntaxErrorType.TOO_MANY_CLOSING_BRACKETS,
114
+ TextRange(token.pos, token.end))
115
+ )
116
+ break
117
+ if len(unparsed_tokens) > 1:
118
+ # necessary when when writing between two block end }
119
+ # should be -1 when after a key-value def
120
+ start = 1
121
+ end = -2 if len(unparsed_tokens) > 2 else -1
122
+ result.errors.append(
123
+ SyntaxError(SyntaxErrorType.UNPARSED_TOKENS,
124
+ TextRange(unparsed_tokens[start].pos, unparsed_tokens[end].end))
125
+ )
126
+ top_block = parser.pop_block()
127
+ top_block.close_bracket = token
128
+ case "{":
129
+ type_token, id_token = parse_type_id(unparsed_tokens)
130
+
131
+ if type_token is None:
132
+ result.errors.append(
133
+ SyntaxError(SyntaxErrorType.BLOCK_MISSING_TYPE,
134
+ TextRange(token.pos, token.end))
135
+ )
136
+
137
+ parser.add_block(
138
+ BlockNode(
139
+ type_token, id_token, token, None,
140
+ comments=unassigned_comments
141
+ )
142
+ )
143
+ case ",":
144
+ value = ValueNode(
145
+ TokenCollection(unparsed_tokens),
146
+ token, # comma token
147
+ comments=unassigned_comments
148
+ )
149
+ parser.add_value(value)
150
+ case _:
151
+ raise RuntimeError("Unknown punctuator " + token.text)
152
+ unparsed_tokens = []
153
+ unassigned_comments = []
154
+ case TokenType.COMMENT:
155
+ unassigned_comments.append(token)
156
+ case _:
157
+ unparsed_tokens.append(token)
158
+
159
+ if len(parser.block_stack) > 0:
160
+ result.errors.append(
161
+ SyntaxError(SyntaxErrorType.BLOCK_NOT_CLOSED,
162
+ TextRange(parser.block_stack[0].open_bracket.pos, tokens[-1].end))
163
+ )
164
+
165
+ return result
166
+
167
+
168
+ def ast_to_comment(node: Node, start: TextPosition) -> str:
169
+ """
170
+ Parses comment tokens from a node into a single string.
171
+ :param node: The node to parse comments from.
172
+ :param start: The start position of the actual object represented by the node.
173
+ :return: The comments merged into a string.
174
+ """
175
+ comment: str = ""
176
+
177
+ # we loop backwards to easily ignore comment tokens separated by a non-comment line
178
+ for token in reversed(node.comments):
179
+ if token.end.line < start.line - 1:
180
+ continue
181
+
182
+ comment = str(token)[2:-2] + comment
183
+
184
+ if token.pos < start:
185
+ start = token.pos
186
+
187
+ # remove leading whitespace from each line
188
+ comment = re.sub(r'\n\s+', '\n', comment)
189
+ # remove leading/trailing newlines
190
+ comment = re.sub(r'^\n+', '', comment)
191
+ comment = re.sub(r'\n+$', '', comment)
192
+
193
+ return comment
194
+
195
+
196
+ def ast_to_value(node: ValueNode, parent: Block) -> Value:
197
+ tokens = node.tokens.strip()
198
+ if len(tokens) > 0:
199
+ comment = ast_to_comment(node, tokens[0].pos)
200
+ else:
201
+ comment = ""
202
+
203
+ value = Value(
204
+ str(node.tokens).strip(),
205
+ node,
206
+ parent,
207
+ comment
208
+ )
209
+
210
+ return value
211
+
212
+ def ast_to_block(document: 'Document', node: BlockNode, parent: Block) -> ScriptBlock:
213
+ if node.type is None:
214
+ type = ""
215
+ else:
216
+ type = str(node.type)
217
+
218
+ if node.id is None:
219
+ id = None
220
+ else:
221
+ id = str(node.id)
222
+
223
+ comment = ast_to_comment(node, node.open_bracket.pos)
224
+ block = ScriptBlock(document, type, id, node, parent, comment)
225
+ ast_to_any(document, block)
226
+
227
+ return block
228
+
229
+
230
+ def ast_to_any(document, parent: ScriptBlock | Root):
231
+ if parent.node is None:
232
+ raise RuntimeError("Parent block has no associated AST node.")
233
+ for child in parent.node.children:
234
+ if isinstance(child, ValueNode):
235
+ parent.values.append(ast_to_value(child, parent))
236
+ elif isinstance(child, BlockNode):
237
+ parent.children.append(ast_to_block(document, child, parent))
238
+
239
+
240
+ def chunk_to_root(document: 'Document', chunk: Chunk, rootType: str) -> Root:
241
+ root = Root(document, rootType, chunk, "")
242
+ ast_to_any(document, root)
243
+ return root
ZedScripts/utils.py ADDED
@@ -0,0 +1,85 @@
1
+ from pathlib import Path
2
+ from typing import Iterable, TypeVar
3
+ from pydantic import BaseModel
4
+ from deepmerge import always_merger
5
+
6
+ from lsprotocol import types
7
+ from pygls.uris import from_fs_path, to_fs_path
8
+
9
+ from .structure.lexer import TextRange, TextPosition
10
+
11
+
12
+ def textrange_to_lsp(range: TextRange) -> types.Range:
13
+ return types.Range(
14
+ types.Position(range.start.line, range.start.offset),
15
+ types.Position(range.end.line, range.end.offset)
16
+ )
17
+ def position_to_texposition(position: types.Position) -> TextPosition:
18
+ return TextPosition(position.line, position.character)
19
+
20
+ def uri_to_path(uri: str) -> Path:
21
+ fs_path = to_fs_path(uri)
22
+ if fs_path is None:
23
+ raise ValueError(f"Cannot convert URI to file path: {uri}")
24
+ return Path(fs_path)
25
+
26
+ def path_to_uri(path: Path | str) -> str:
27
+ uri = from_fs_path(str(path))
28
+ if uri is None:
29
+ raise ValueError(f"Cannot convert file path to URI: {path}")
30
+ return uri
31
+
32
+
33
+
34
+ def sort_by_load_order(files: Iterable[Path]) -> Iterable[Path]:
35
+ """
36
+ Sorts an iterable of script paths into the order that the game would load them.
37
+ :param files: List of script filepaths.
38
+ :return: Sorted list of filepaths.
39
+ """
40
+ templates: list[Path] = []
41
+ other: list[Path] = []
42
+
43
+ for path in files:
44
+ if path.name.startswith("template_"):
45
+ templates.append(path)
46
+ else:
47
+ other.append(path)
48
+
49
+ templates.sort()
50
+ other.sort()
51
+
52
+ return templates + other
53
+
54
+
55
+
56
+ def glob_files_by_extensions(folder: Path, extensions: set[str]) -> Iterable[Path]:
57
+ """
58
+ Glob all files in the given folder and its subfolders
59
+ that have one of the specified extensions.
60
+ https://stackoverflow.com/a/57054058/33113221
61
+ """
62
+ files = (p.resolve() for p in Path(folder).rglob("*") if p.suffix in extensions)
63
+ return files
64
+
65
+
66
+ T = TypeVar("T", bound=BaseModel)
67
+
68
+ def merge_pydantic_models(base: T, *models: T) -> T:
69
+ """Merge multiple Pydantic model instances.
70
+
71
+ The attributes of 'base' and all subsequent models that weren't explicitly set are dumped into dicts
72
+ using '.model_dump(exclude_unset=True)', which are then merged using 'deepmerge',
73
+ and the merged result is turned into a model instance using '.model_validate'.
74
+
75
+ For attributes set on both 'base' and the subsequent models, the value from the last model in the arguments will be used in
76
+ the output result.
77
+
78
+ Modified from:
79
+ @source: https://github.com/pydantic/pydantic/discussions/3416#discussioncomment-12267413
80
+ """
81
+ base_dict = base.model_dump(exclude_unset=True)
82
+ for nxt in models:
83
+ nxt_dict = nxt.model_dump(exclude_unset=True)
84
+ base_dict = always_merger.merge(base_dict, nxt_dict)
85
+ return base.model_validate(base_dict)
@@ -0,0 +1,105 @@
1
+ Metadata-Version: 2.4
2
+ Name: ZedScripts
3
+ Version: 0.0.2
4
+ Author: SimKDT
5
+ License-Expression: MIT
6
+ Requires-Python: >=3.12.3
7
+ Description-Content-Type: text/markdown
8
+ License-File: LICENSE
9
+ Requires-Dist: lsprotocol
10
+ Requires-Dist: pygls
11
+ Requires-Dist: requests-cache
12
+ Requires-Dist: pydantic
13
+ Requires-Dist: deepmerge
14
+ Provides-Extra: build
15
+ Requires-Dist: pyinstaller; extra == "build"
16
+ Provides-Extra: debug
17
+ Requires-Dist: debugpy; extra == "debug"
18
+ Dynamic: license-file
19
+
20
+ # ZedScripts LSP
21
+
22
+ ![ZedScripts-LSP](https://img.shields.io/badge/Latest_version-0.0.2-blue)
23
+ [![License](https://img.shields.io/github/license/PZ-Wiki-Modding/ZedScripts-LSP?label=License)](LICENSE)
24
+ ![Code Size](https://img.shields.io/github/languages/code-size/PZ-Wiki-Modding/ZedScripts-LSP?label=Code%20Size)
25
+ [![PyPi Version](https://img.shields.io/pypi/v/ZedScripts-LSP)](https://pypi.org/project/ZedScripts-LSP/)
26
+ [![PyPi Downloads](https://img.shields.io/pypi/dm/ZedScripts-LSP)](https://pypi.org/project/ZedScripts-LSP/)
27
+
28
+ > [!IMPORTANT]
29
+ > Work In Progress, this is a BETA release
30
+
31
+ ZedScripts LSP is a language server protocol implementation in Python for the [scripting format]() of Project Zomboid. Previously a VSCode extension, it is now reimplemented as a standalone LSP.
32
+
33
+ ## Installation
34
+ ### Releases
35
+
36
+ ### Python package
37
+
38
+ <!-- <details open>
39
+ <summary>PyPi</summary>
40
+
41
+ ```bash
42
+ pip install ZedScripts-LSP
43
+ ```
44
+ </details> -->
45
+
46
+ <details>
47
+ <summary>Git</summary>
48
+
49
+ ```bash
50
+ pip install git+https://github.com/PZ-Wiki-Modding/ZedScripts-LSP.git
51
+ ```
52
+ </details>
53
+
54
+ <details>
55
+ <summary>Local</summary>
56
+
57
+ ```bash
58
+ pip install .
59
+ ```
60
+ </details>
61
+
62
+ ## Usage
63
+
64
+ You can either launch the LSP from Python or you can launch the software as is.
65
+
66
+ ### LSP cache directory
67
+
68
+ The LSP uses a cache directory on your system to store temporary files and logs as well as configuration files. You can find it here:
69
+
70
+ <details>
71
+ <summary>Linux</summary>
72
+
73
+ ```bash
74
+ ~/.zedscripts
75
+ ```
76
+ </details>
77
+
78
+ <details>
79
+ <summary>Windows</summary>
80
+
81
+ ```bash
82
+ %USERPROFILE%\.zedscripts
83
+ ```
84
+ </details>
85
+
86
+ ### Configuration
87
+
88
+ The LSP uses configurations files which provide information to the LSP about various settings. You have access to a global configuration file
89
+
90
+ Example configuration:
91
+ ```json
92
+ {
93
+ "dataset": {
94
+ "latest": true
95
+ },
96
+ "libraries": [
97
+ "/home/simon/.steam/debian-installation/steamapps/common/ProjectZomboid/projectzomboid/media",
98
+ ]
99
+ }
100
+ ```
101
+
102
+ WIP: proper documentation will come soon
103
+
104
+ ## License
105
+ See [LICENSE](LICENSE) for details.
@@ -0,0 +1,37 @@
1
+ ZedScripts/__about__.py,sha256=ry5O5SW74ugwOUMKSV2_LbWjYa8B0IEvUm7S-lHPA2c,23
2
+ ZedScripts/__init__.py,sha256=FhE0ANZpYH8YyjZXQGzplZ9VrqEXFsD8S0wx9Q-E-As,1282
3
+ ZedScripts/main.py,sha256=hoa-oRPmUy0mmIkUyoAWLh8wCwo1jCgvtEQOKw-HSf8,124
4
+ ZedScripts/server.py,sha256=4b4C5hLG5Dw2tdcptd2uo655dI0QFw3H5wvWdGPM0xk,11047
5
+ ZedScripts/utils.py,sha256=eQFqgWKMO0riKeb_IzHTY6tj2lPUT3NATqW5Pk6_EVA,2852
6
+ ZedScripts/enums/Diagnostic.py,sha256=a2PANTKDy1PZd1flAVQXJy1L5QpTTbwAHwbK_PgR7Qo,8075
7
+ ZedScripts/enums/SyntaxErrorType.py,sha256=MC9VWpoxfwPaPGRGwi-jpKISiHJaO63DFvUg_nBsPQ4,196
8
+ ZedScripts/environment/__init__.py,sha256=uv-ez4lW8KkpvZMNZlq5npKJVzAO72YF6Vot4Dz6MZU,1107
9
+ ZedScripts/environment/config.py,sha256=7YsQHkUvPfbySShRIHoZJtnUdstHk1gC5693KqQwtco,2896
10
+ ZedScripts/environment/document.py,sha256=2Mx1ZSin6fiBoKaww15YSDbRrcfAu80GzJ5UZHyTObU,14552
11
+ ZedScripts/environment/mod.py,sha256=wlfnIU5LmTesrFsD5Ynh64qRRms6ua6y5Si0cxMp1Yg,2434
12
+ ZedScripts/environment/version.py,sha256=0gmsqIyWIO1Ctk0FTddAR2SrMacUdlFjzUn2VyqAANw,7441
13
+ ZedScripts/environment/workspace.py,sha256=ghrNQ0Um93-vvpsZAOIVDOhD1YX8ss9057BuvwP1L3I,12877
14
+ ZedScripts/locale/en.json,sha256=82CWRO9_G2y5-fr-3aM-6laEI67FI7YNQUW0EXSukqk,3580
15
+ ZedScripts/providers/actions.py,sha256=WyWOtlw_YOO4J-Hn0OkTA9NZFFeaB4wyXpKPPBFi02o,411
16
+ ZedScripts/providers/capabilities.py,sha256=_cMMuFTucqa-UDobOcTQ-6bkiGE23K8cQ-fFAetBq7s,3485
17
+ ZedScripts/providers/configuration.py,sha256=dCr1EwuHjvqOKZVMpl7P-JVyMkSMHYyG3Blkzn3Z0SQ,564
18
+ ZedScripts/providers/debug.py,sha256=_uAA-78y4k4Qe1yvsIHSPVhZNOVHnP6vZ_iEepwfCBU,2204
19
+ ZedScripts/providers/diagnostics.py,sha256=DHkG4WOjaFQlK5wa4DAHSxqCkgdGK4_EFKS5VtQ7hno,3739
20
+ ZedScripts/providers/hover.py,sha256=Mju1hfdKODThE_DH7BXc6Bfkiel5HM5czX5LGCjW-Us,1051
21
+ ZedScripts/providers/http.py,sha256=DwQ1qMPJSOB2tUAOcQXy5WU3Yd2uvoejG2g7pWeS-Wo,1648
22
+ ZedScripts/providers/locale.py,sha256=eEOdPZ8FKYPdmshB7kvpCFLD5AjNrZIO3DeT45Jukyo,3042
23
+ ZedScripts/providers/notifications.py,sha256=xfUb4HDs5LoR0by_l4PQs9xIQmr7h0CsD77LugKtkPk,1090
24
+ ZedScripts/providers/semantic_tokens.py,sha256=6HI3ixWz8uahjSBehD00c3K9RmEXR5FZoSKXET3lERw,6431
25
+ ZedScripts/scripts/__init__.py,sha256=3En0FJDKn1gR49T0NzV6rhie-ZvU3s59v47t8paH0Xc,4638
26
+ ZedScripts/scripts/block.py,sha256=TEtSiGjXASIRKT6g0S_Dk5lvCRWtpM-ObsqmQd31KYI,18947
27
+ ZedScripts/scripts/dataset.py,sha256=YnsMHfxE75cQC4dBgFAPN9eqcaOfpWqX0KTjhMXs0Xs,13062
28
+ ZedScripts/scripts/value.py,sha256=HTgoae5dCUCCwPFO6oOEyOH1B3JXW_IqesMVPfzOlKs,28001
29
+ ZedScripts/scripts/version.py,sha256=-R7VofQyJrOqqq86JnmukWTv2P6RmqDX6O4vDIXmJbw,2897
30
+ ZedScripts/structure/ast.py,sha256=ukRATCVenrKjwsUHKUYR4wP9LBOwFhV3enjwBNNmtB4,4624
31
+ ZedScripts/structure/lexer.py,sha256=2cp5eYIyGPond1W-41ALlut8wDitxHDJcpjdSSPEv7c,16339
32
+ ZedScripts/structure/parser.py,sha256=bK4taAONQOpoEW6fZ222GSd-EDOpsBFtHIRmmZXOJW0,8577
33
+ zedscripts-0.0.2.dist-info/licenses/LICENSE,sha256=U4VZ5OLZttGqVxQfLdbpLIvMEDpj5KRXLqltyHwWxsA,1072
34
+ zedscripts-0.0.2.dist-info/METADATA,sha256=dPXVBMQS35QFRzhuerYjQlOfIpNAX32ID1TECHmT4PI,2474
35
+ zedscripts-0.0.2.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
36
+ zedscripts-0.0.2.dist-info/top_level.txt,sha256=FkHjeVoFW1B97gPjIPEMXwVDyWr_9RoNYnOlWfDnYTg,11
37
+ zedscripts-0.0.2.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 PZ Wiki Modding
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ ZedScripts