scratchattach 2.1.12__py3-none-any.whl → 2.1.14__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.
- scratchattach/cloud/_base.py +12 -8
- scratchattach/cloud/cloud.py +19 -7
- scratchattach/editor/asset.py +59 -5
- scratchattach/editor/base.py +82 -31
- scratchattach/editor/block.py +87 -15
- scratchattach/editor/blockshape.py +8 -4
- scratchattach/editor/build_defaulting.py +6 -2
- scratchattach/editor/code_translation/__init__.py +0 -0
- scratchattach/editor/code_translation/parse.py +177 -0
- scratchattach/editor/comment.py +6 -0
- scratchattach/editor/commons.py +82 -19
- scratchattach/editor/extension.py +10 -3
- scratchattach/editor/field.py +9 -0
- scratchattach/editor/inputs.py +4 -1
- scratchattach/editor/meta.py +11 -3
- scratchattach/editor/monitor.py +46 -38
- scratchattach/editor/mutation.py +11 -4
- scratchattach/editor/pallete.py +24 -25
- scratchattach/editor/prim.py +2 -2
- scratchattach/editor/project.py +9 -3
- scratchattach/editor/sprite.py +19 -6
- scratchattach/editor/twconfig.py +2 -1
- scratchattach/editor/vlb.py +1 -1
- scratchattach/eventhandlers/_base.py +3 -3
- scratchattach/eventhandlers/cloud_events.py +2 -2
- scratchattach/eventhandlers/cloud_requests.py +4 -7
- scratchattach/eventhandlers/cloud_server.py +3 -3
- scratchattach/eventhandlers/combine.py +2 -2
- scratchattach/eventhandlers/message_events.py +1 -1
- scratchattach/other/other_apis.py +4 -4
- scratchattach/other/project_json_capabilities.py +3 -3
- scratchattach/site/_base.py +13 -12
- scratchattach/site/activity.py +11 -43
- scratchattach/site/alert.py +227 -0
- scratchattach/site/backpack_asset.py +2 -2
- scratchattach/site/browser_cookie3_stub.py +17 -0
- scratchattach/site/browser_cookies.py +27 -21
- scratchattach/site/classroom.py +51 -34
- scratchattach/site/cloud_activity.py +4 -4
- scratchattach/site/comment.py +30 -8
- scratchattach/site/forum.py +101 -69
- scratchattach/site/project.py +37 -17
- scratchattach/site/session.py +177 -83
- scratchattach/site/studio.py +4 -4
- scratchattach/site/user.py +184 -62
- scratchattach/utils/commons.py +35 -23
- scratchattach/utils/enums.py +44 -5
- scratchattach/utils/exceptions.py +10 -0
- scratchattach/utils/requests.py +57 -31
- {scratchattach-2.1.12.dist-info → scratchattach-2.1.14.dist-info}/METADATA +9 -3
- scratchattach-2.1.14.dist-info/RECORD +66 -0
- {scratchattach-2.1.12.dist-info → scratchattach-2.1.14.dist-info}/WHEEL +1 -1
- scratchattach/editor/sbuild.py +0 -2837
- scratchattach-2.1.12.dist-info/RECORD +0 -63
- {scratchattach-2.1.12.dist-info → scratchattach-2.1.14.dist-info}/licenses/LICENSE +0 -0
- {scratchattach-2.1.12.dist-info → scratchattach-2.1.14.dist-info}/top_level.txt +0 -0
scratchattach/editor/block.py
CHANGED
|
@@ -1,19 +1,24 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import warnings
|
|
4
|
-
from typing import Optional, Iterable,
|
|
4
|
+
from typing import Optional, Iterable, Union
|
|
5
|
+
from typing_extensions import Self
|
|
5
6
|
|
|
6
7
|
from . import base, sprite, mutation, field, inputs, commons, vlb, blockshape, prim, comment, build_defaulting
|
|
7
|
-
from
|
|
8
|
+
from scratchattach.utils import exceptions
|
|
8
9
|
|
|
9
10
|
|
|
10
11
|
class Block(base.SpriteSubComponent):
|
|
12
|
+
"""
|
|
13
|
+
Represents a block in the scratch editor, as a subcomponent of a sprite.
|
|
14
|
+
"""
|
|
15
|
+
_id: Optional[str] = None
|
|
11
16
|
def __init__(self, _opcode: str, _shadow: bool = False, _top_level: Optional[bool] = None,
|
|
12
17
|
_mutation: Optional[mutation.Mutation] = None, _fields: Optional[dict[str, field.Field]] = None,
|
|
13
18
|
_inputs: Optional[dict[str, inputs.Input]] = None, x: int = 0, y: int = 0, pos: Optional[tuple[int, int]] = None,
|
|
14
19
|
|
|
15
20
|
_next: Optional[Block] = None, _parent: Optional[Block] = None,
|
|
16
|
-
*, _next_id: Optional[str] = None, _parent_id: Optional[str] = None, _sprite:
|
|
21
|
+
*, _next_id: Optional[str] = None, _parent_id: Optional[str] = None, _sprite: commons.SpriteInput = build_defaulting.SPRITE_DEFAULT):
|
|
17
22
|
# Defaulting for args
|
|
18
23
|
if _fields is None:
|
|
19
24
|
_fields = {}
|
|
@@ -33,14 +38,11 @@ class Block(base.SpriteSubComponent):
|
|
|
33
38
|
self.fields = _fields
|
|
34
39
|
self.inputs = _inputs
|
|
35
40
|
|
|
41
|
+
# Temporarily stores id of next block. Will be used later during project instantiation to find the next block object
|
|
36
42
|
self._next_id = _next_id
|
|
37
|
-
|
|
38
|
-
Temporarily stores id of
|
|
39
|
-
"""
|
|
43
|
+
|
|
44
|
+
# Temporarily stores id of parent block. Will be used later during project instantiation to find the parent block object
|
|
40
45
|
self._parent_id = _parent_id
|
|
41
|
-
"""
|
|
42
|
-
Temporarily stores id of parent block. Will be used later during project instantiation to find the parent block object
|
|
43
|
-
"""
|
|
44
46
|
|
|
45
47
|
self.next = _next
|
|
46
48
|
self.parent = _parent
|
|
@@ -54,6 +56,9 @@ class Block(base.SpriteSubComponent):
|
|
|
54
56
|
return f"Block<{self.opcode!r}>"
|
|
55
57
|
|
|
56
58
|
def link_subcomponents(self):
|
|
59
|
+
"""
|
|
60
|
+
Iterate through subcomponents and assign the 'block' attribute
|
|
61
|
+
"""
|
|
57
62
|
if self.mutation:
|
|
58
63
|
self.mutation.block = self
|
|
59
64
|
|
|
@@ -62,6 +67,9 @@ class Block(base.SpriteSubComponent):
|
|
|
62
67
|
subcomponent.block = self
|
|
63
68
|
|
|
64
69
|
def add_input(self, name: str, _input: inputs.Input) -> Self:
|
|
70
|
+
"""
|
|
71
|
+
Add an input to the block.
|
|
72
|
+
""" # not sure what else to say
|
|
65
73
|
self.inputs[name] = _input
|
|
66
74
|
for val in (_input.value, _input.obscurer):
|
|
67
75
|
if isinstance(val, Block):
|
|
@@ -69,22 +77,34 @@ class Block(base.SpriteSubComponent):
|
|
|
69
77
|
return self
|
|
70
78
|
|
|
71
79
|
def add_field(self, name: str, _field: field.Field) -> Self:
|
|
80
|
+
"""
|
|
81
|
+
Add a field to the block.
|
|
82
|
+
""" # not sure what else to sa
|
|
72
83
|
self.fields[name] = _field
|
|
73
84
|
return self
|
|
74
85
|
|
|
75
86
|
def set_mutation(self, _mutation: mutation.Mutation) -> Self:
|
|
87
|
+
"""
|
|
88
|
+
Attach a mutation object and call mutation.link_arguments()
|
|
89
|
+
""" # this comment explains *what* this does, not *why*
|
|
76
90
|
self.mutation = _mutation
|
|
77
91
|
_mutation.block = self
|
|
78
92
|
_mutation.link_arguments()
|
|
79
93
|
return self
|
|
80
94
|
|
|
81
95
|
def set_comment(self, _comment: comment.Comment) -> Self:
|
|
96
|
+
"""
|
|
97
|
+
Attach a comment and add it to the sprite.
|
|
98
|
+
"""
|
|
82
99
|
_comment.block = self
|
|
83
100
|
self.sprite.add_comment(_comment)
|
|
84
101
|
|
|
85
102
|
return self
|
|
86
103
|
|
|
87
104
|
def check_toplevel(self):
|
|
105
|
+
"""
|
|
106
|
+
Edit the toplevel, x, and y attributes based on whether the parent attribute is None
|
|
107
|
+
"""
|
|
88
108
|
self.is_top_level = self.parent is None
|
|
89
109
|
|
|
90
110
|
if not self.is_top_level:
|
|
@@ -94,7 +114,7 @@ class Block(base.SpriteSubComponent):
|
|
|
94
114
|
def target(self):
|
|
95
115
|
"""
|
|
96
116
|
Alias for sprite
|
|
97
|
-
"""
|
|
117
|
+
""" # remove this?
|
|
98
118
|
return self.sprite
|
|
99
119
|
|
|
100
120
|
@property
|
|
@@ -106,7 +126,8 @@ class Block(base.SpriteSubComponent):
|
|
|
106
126
|
_shape = blockshape.BlockShapes.find(self.opcode, "opcode")
|
|
107
127
|
if _shape is None:
|
|
108
128
|
warnings.warn(f"No blockshape {self.opcode!r} exists! Defaulting to {blockshape.BlockShapes.UNDEFINED}")
|
|
109
|
-
|
|
129
|
+
_shape = blockshape.BlockShapes.UNDEFINED
|
|
130
|
+
assert isinstance(_shape, blockshape.BlockShape)
|
|
110
131
|
return _shape
|
|
111
132
|
|
|
112
133
|
@property
|
|
@@ -126,21 +147,32 @@ class Block(base.SpriteSubComponent):
|
|
|
126
147
|
return self.mutation.has_next
|
|
127
148
|
|
|
128
149
|
@property
|
|
129
|
-
def id(self) -> str
|
|
150
|
+
def id(self) -> str:
|
|
130
151
|
"""
|
|
131
152
|
Work out the id of this block by searching through the sprite dictionary
|
|
132
153
|
"""
|
|
154
|
+
if self._id:
|
|
155
|
+
return self.id
|
|
133
156
|
# warnings.warn(f"Using block IDs can cause consistency issues and is not recommended")
|
|
134
157
|
# This property is used when converting comments to JSON (we don't want random warning when exporting a project)
|
|
135
158
|
for _block_id, _block in self.sprite.blocks.items():
|
|
136
159
|
if _block is self:
|
|
137
|
-
|
|
160
|
+
self._id = _block_id
|
|
161
|
+
return self.id
|
|
138
162
|
|
|
139
163
|
# Let's just automatically assign ourselves an id
|
|
140
164
|
self.sprite.add_block(self)
|
|
165
|
+
return self.id
|
|
166
|
+
|
|
167
|
+
@id.setter
|
|
168
|
+
def id(self, value: str) -> None:
|
|
169
|
+
self._id = value
|
|
141
170
|
|
|
142
171
|
@property
|
|
143
172
|
def parent_id(self):
|
|
173
|
+
"""
|
|
174
|
+
Get the id of the parent block, if applicable
|
|
175
|
+
"""
|
|
144
176
|
if self.parent is not None:
|
|
145
177
|
return self.parent.id
|
|
146
178
|
else:
|
|
@@ -148,6 +180,9 @@ class Block(base.SpriteSubComponent):
|
|
|
148
180
|
|
|
149
181
|
@property
|
|
150
182
|
def next_id(self):
|
|
183
|
+
"""
|
|
184
|
+
Get the id of the next block, if applicable
|
|
185
|
+
"""
|
|
151
186
|
if self.next is not None:
|
|
152
187
|
return self.next.id
|
|
153
188
|
else:
|
|
@@ -185,13 +220,19 @@ class Block(base.SpriteSubComponent):
|
|
|
185
220
|
|
|
186
221
|
@property
|
|
187
222
|
def previous_chain(self):
|
|
188
|
-
|
|
223
|
+
"""
|
|
224
|
+
Recursive getter method to get all previous blocks in the blockchain (until hitting a top-level block)
|
|
225
|
+
"""
|
|
226
|
+
if self.parent is None: # todo: use is_top_level?
|
|
189
227
|
return [self]
|
|
190
228
|
|
|
191
229
|
return [self] + self.parent.previous_chain
|
|
192
230
|
|
|
193
231
|
@property
|
|
194
232
|
def attached_chain(self):
|
|
233
|
+
"""
|
|
234
|
+
Recursive getter method to get all next blocks in the blockchain (until hitting a bottom-levell block)
|
|
235
|
+
"""
|
|
195
236
|
if self.next is None:
|
|
196
237
|
return [self]
|
|
197
238
|
|
|
@@ -199,23 +240,30 @@ class Block(base.SpriteSubComponent):
|
|
|
199
240
|
|
|
200
241
|
@property
|
|
201
242
|
def complete_chain(self):
|
|
202
|
-
|
|
243
|
+
"""
|
|
244
|
+
Attach previous and attached chains from this block
|
|
245
|
+
"""
|
|
203
246
|
return self.previous_chain[:1:-1] + self.attached_chain
|
|
204
247
|
|
|
205
248
|
@property
|
|
206
249
|
def top_level_block(self):
|
|
207
250
|
"""
|
|
251
|
+
Get the first block in the block stack that this block is part of
|
|
208
252
|
same as the old stack_parent property from sbedtior v1
|
|
209
253
|
"""
|
|
210
254
|
return self.previous_chain[-1]
|
|
211
255
|
|
|
212
256
|
@property
|
|
213
257
|
def bottom_level_block(self):
|
|
258
|
+
"""
|
|
259
|
+
Get the last block in the block stack that this block is part of
|
|
260
|
+
"""
|
|
214
261
|
return self.attached_chain[-1]
|
|
215
262
|
|
|
216
263
|
@property
|
|
217
264
|
def stack_tree(self):
|
|
218
265
|
"""
|
|
266
|
+
Useful for showing a block stack in the console, using pprint
|
|
219
267
|
:return: A tree-like nested list structure representing the stack of blocks, including inputs, starting at this block
|
|
220
268
|
"""
|
|
221
269
|
_tree = [self]
|
|
@@ -253,6 +301,9 @@ class Block(base.SpriteSubComponent):
|
|
|
253
301
|
|
|
254
302
|
@property
|
|
255
303
|
def parent_input(self):
|
|
304
|
+
"""
|
|
305
|
+
Fetch an input that this block is placed inside of (if applicable)
|
|
306
|
+
"""
|
|
256
307
|
if not self.parent:
|
|
257
308
|
return None
|
|
258
309
|
|
|
@@ -267,6 +318,9 @@ class Block(base.SpriteSubComponent):
|
|
|
267
318
|
|
|
268
319
|
@property
|
|
269
320
|
def comment(self) -> comment.Comment | None:
|
|
321
|
+
"""
|
|
322
|
+
Fetch an associated comment (if applicable) by searching the associated sprite
|
|
323
|
+
"""
|
|
270
324
|
for _comment in self.sprite.comments:
|
|
271
325
|
if _comment.block is self:
|
|
272
326
|
return _comment
|
|
@@ -319,6 +373,9 @@ class Block(base.SpriteSubComponent):
|
|
|
319
373
|
|
|
320
374
|
@property
|
|
321
375
|
def is_turbowarp_block(self):
|
|
376
|
+
"""
|
|
377
|
+
Return whether this block is actually a turbowarp debugger/boolean block, based on mutation
|
|
378
|
+
"""
|
|
322
379
|
return self.turbowarp_block_opcode is not None
|
|
323
380
|
|
|
324
381
|
@staticmethod
|
|
@@ -355,6 +412,9 @@ class Block(base.SpriteSubComponent):
|
|
|
355
412
|
_parent_id=_parent_id)
|
|
356
413
|
|
|
357
414
|
def to_json(self) -> dict:
|
|
415
|
+
"""
|
|
416
|
+
Convert a block to the project.json format
|
|
417
|
+
"""
|
|
358
418
|
self.check_toplevel()
|
|
359
419
|
|
|
360
420
|
_json = {
|
|
@@ -386,6 +446,9 @@ class Block(base.SpriteSubComponent):
|
|
|
386
446
|
return _json
|
|
387
447
|
|
|
388
448
|
def link_using_sprite(self, link_subs: bool = True):
|
|
449
|
+
"""
|
|
450
|
+
Link this block to various other blocks once the sprite has been assigned
|
|
451
|
+
"""
|
|
389
452
|
if link_subs:
|
|
390
453
|
self.link_subcomponents()
|
|
391
454
|
|
|
@@ -442,6 +505,9 @@ class Block(base.SpriteSubComponent):
|
|
|
442
505
|
|
|
443
506
|
# Adding/removing block
|
|
444
507
|
def attach_block(self, new: Block) -> Block:
|
|
508
|
+
"""
|
|
509
|
+
Connect another block onto the boottom of this block (not necessarily bottom of chain)
|
|
510
|
+
"""
|
|
445
511
|
if not self.can_next:
|
|
446
512
|
raise exceptions.BadBlockShape(f"{self.block_shape} cannot be stacked onto")
|
|
447
513
|
elif new.block_shape.is_hat or not new.block_shape.is_stack:
|
|
@@ -473,6 +539,9 @@ class Block(base.SpriteSubComponent):
|
|
|
473
539
|
)
|
|
474
540
|
|
|
475
541
|
def slot_above(self, new: Block) -> Block:
|
|
542
|
+
"""
|
|
543
|
+
Place a single block directly above this block
|
|
544
|
+
"""
|
|
476
545
|
if not new.can_next:
|
|
477
546
|
raise exceptions.BadBlockShape(f"{new.block_shape} cannot be stacked onto")
|
|
478
547
|
|
|
@@ -502,6 +571,9 @@ class Block(base.SpriteSubComponent):
|
|
|
502
571
|
self.sprite.remove_block(self)
|
|
503
572
|
|
|
504
573
|
def delete_chain(self):
|
|
574
|
+
"""
|
|
575
|
+
Delete all blocks in the attached blockchain (and self)
|
|
576
|
+
"""
|
|
505
577
|
for _block in self.attached_chain:
|
|
506
578
|
_block.delete_single_block()
|
|
507
579
|
|
|
@@ -3,15 +3,19 @@ Enums stating the shape of a block from its opcode (i.e: stack, c-mouth, cap, ha
|
|
|
3
3
|
"""
|
|
4
4
|
from __future__ import annotations
|
|
5
5
|
|
|
6
|
-
# Perhaps this should be merged with
|
|
6
|
+
# Perhaps this should be merged with pallete.py
|
|
7
7
|
from dataclasses import dataclass
|
|
8
8
|
from typing import Final
|
|
9
9
|
|
|
10
10
|
from . import commons
|
|
11
|
-
from
|
|
11
|
+
from scratchattach.utils.enums import _EnumWrapper
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
class _MutationDependent(commons.Singleton):
|
|
15
|
+
"""
|
|
16
|
+
Singleton value that represents the uncertainty of a vablue because it depends on block mutation data.
|
|
17
|
+
"""
|
|
18
|
+
INSTANCE = 0
|
|
15
19
|
def __bool__(self):
|
|
16
20
|
raise TypeError("Need mutation data to work out attribute value.")
|
|
17
21
|
|
|
@@ -23,7 +27,7 @@ MUTATION_DEPENDENT: Final[_MutationDependent] = _MutationDependent()
|
|
|
23
27
|
@dataclass(init=True, repr=True)
|
|
24
28
|
class BlockShape:
|
|
25
29
|
"""
|
|
26
|
-
|
|
30
|
+
The shape of a block; e.g. is it a stack, c-mouth, cap, hat reporter, boolean or menu block?
|
|
27
31
|
"""
|
|
28
32
|
is_stack: bool | _MutationDependent = False # Most blocks - e.g. move [10] steps
|
|
29
33
|
is_c_mouth: bool | _MutationDependent = False # Has substack - e.g. repeat
|
|
@@ -262,7 +266,7 @@ class BlockShapes(_EnumWrapper):
|
|
|
262
266
|
MAKEYMAKEY_MENU_KEY = BlockShape(is_reporter=True, is_menu=True, opcode="makeymakey_menu_KEY")
|
|
263
267
|
MAKEYMAKEY_MENU_SEQUENCE = BlockShape(is_reporter=True, is_menu=True, opcode="makeymakey_menu_SEQUENCE")
|
|
264
268
|
|
|
265
|
-
MICROBIT_WHENBUTTONPRESSED = BlockShape(opcode="microbit_whenButtonPressed")
|
|
269
|
+
MICROBIT_WHENBUTTONPRESSED = BlockShape(opcode="microbit_whenButtonPressed") # todo: finish this
|
|
266
270
|
MICROBIT_ISBUTTONPRESSED = BlockShape(opcode="microbit_isButtonPressed")
|
|
267
271
|
MICROBIT_WHENGESTURE = BlockShape(opcode="microbit_whenGesture")
|
|
268
272
|
MICROBIT_DISPLAYSYMBOL = BlockShape(opcode="microbit_displaySymbol")
|
|
@@ -3,7 +3,7 @@ Module which stores the 'default' or 'current' selected Sprite/project (stored a
|
|
|
3
3
|
"""
|
|
4
4
|
from __future__ import annotations
|
|
5
5
|
|
|
6
|
-
from typing import Iterable, TYPE_CHECKING, Final
|
|
6
|
+
from typing import Iterable, TYPE_CHECKING, Final, Literal
|
|
7
7
|
|
|
8
8
|
if TYPE_CHECKING:
|
|
9
9
|
from . import sprite, block, prim, comment
|
|
@@ -11,11 +11,12 @@ from . import commons
|
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
class _SetSprite(commons.Singleton):
|
|
14
|
+
INSTANCE = 0
|
|
14
15
|
def __repr__(self):
|
|
15
16
|
return f'<Reminder to default your sprite to {current_sprite()}>'
|
|
16
17
|
|
|
17
18
|
|
|
18
|
-
SPRITE_DEFAULT: Final[_SetSprite] = _SetSprite
|
|
19
|
+
SPRITE_DEFAULT: Final[Literal[_SetSprite.INSTANCE]] = _SetSprite.INSTANCE
|
|
19
20
|
|
|
20
21
|
_sprite_stack: list[sprite.Sprite] = []
|
|
21
22
|
|
|
@@ -25,6 +26,9 @@ def stack_add_sprite(_sprite: sprite.Sprite):
|
|
|
25
26
|
|
|
26
27
|
|
|
27
28
|
def current_sprite() -> sprite.Sprite | None:
|
|
29
|
+
"""
|
|
30
|
+
Retrieve the default sprite from the top of the sprite stack
|
|
31
|
+
"""
|
|
28
32
|
if len(_sprite_stack) == 0:
|
|
29
33
|
return None
|
|
30
34
|
return _sprite_stack[-1]
|
|
File without changes
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import Union, Generic, TypeVar
|
|
4
|
+
from abc import ABC, abstractmethod
|
|
5
|
+
from collections.abc import Sequence
|
|
6
|
+
|
|
7
|
+
from lark import Lark, Transformer, Tree, Token, v_args
|
|
8
|
+
from lark.reconstruct import Reconstructor
|
|
9
|
+
|
|
10
|
+
R = TypeVar("R")
|
|
11
|
+
class SupportsRead(ABC, Generic[R]):
|
|
12
|
+
@abstractmethod
|
|
13
|
+
def read(self, size: int | None = -1) -> R:
|
|
14
|
+
pass
|
|
15
|
+
|
|
16
|
+
LANG_PATH = Path(__file__).parent / "language.lark"
|
|
17
|
+
|
|
18
|
+
lang = Lark(LANG_PATH.read_text(), maybe_placeholders=False)
|
|
19
|
+
reconstructor = Reconstructor(lang)
|
|
20
|
+
|
|
21
|
+
def parse(script: Union[str, bytes, SupportsRead[str], Path]) -> Tree:
|
|
22
|
+
if isinstance(script, Path):
|
|
23
|
+
script = script.read_text()
|
|
24
|
+
if isinstance(script, SupportsRead):
|
|
25
|
+
read_data = script.read()
|
|
26
|
+
assert isinstance(read_data, str)
|
|
27
|
+
script = read_data
|
|
28
|
+
if isinstance(script, bytes):
|
|
29
|
+
script = script.decode("utf-8")
|
|
30
|
+
return lang.parse(script)
|
|
31
|
+
|
|
32
|
+
def unparse(tree: Tree) -> str:
|
|
33
|
+
return reconstructor.reconstruct(tree)
|
|
34
|
+
|
|
35
|
+
class PrettyUnparser(Transformer):
|
|
36
|
+
INDENT_STRING = " "
|
|
37
|
+
|
|
38
|
+
@classmethod
|
|
39
|
+
def _indent(cls, text):
|
|
40
|
+
if not text:
|
|
41
|
+
return ""
|
|
42
|
+
return "\n".join(cls.INDENT_STRING + line for line in text.splitlines())
|
|
43
|
+
|
|
44
|
+
def PARAM_NAME(self, token):
|
|
45
|
+
return token.value
|
|
46
|
+
|
|
47
|
+
def BLOCK_NAME(self, token):
|
|
48
|
+
return token.value
|
|
49
|
+
|
|
50
|
+
def EVENT(self, token):
|
|
51
|
+
return token.value
|
|
52
|
+
|
|
53
|
+
def CONTROL_BLOCK_NAME(self, token):
|
|
54
|
+
return token.value
|
|
55
|
+
|
|
56
|
+
def _PREPROC_INSTR_CONTENT(self, token):
|
|
57
|
+
return token.value
|
|
58
|
+
|
|
59
|
+
def _COMMMENT_CONTENT(self, token):
|
|
60
|
+
return token.value
|
|
61
|
+
|
|
62
|
+
@v_args(inline=True)
|
|
63
|
+
def hat(self, child):
|
|
64
|
+
return child
|
|
65
|
+
|
|
66
|
+
@v_args(inline=True)
|
|
67
|
+
def param(self, child):
|
|
68
|
+
return child
|
|
69
|
+
|
|
70
|
+
@v_args(inline=True)
|
|
71
|
+
def value_param(self, name):
|
|
72
|
+
return name
|
|
73
|
+
|
|
74
|
+
@v_args(inline=True)
|
|
75
|
+
def bool_param(self, name):
|
|
76
|
+
return f"<{name}>"
|
|
77
|
+
|
|
78
|
+
@v_args(inline=True)
|
|
79
|
+
def event_hat(self, event_name):
|
|
80
|
+
return f"when ({event_name})"
|
|
81
|
+
|
|
82
|
+
def block_hat(self, items):
|
|
83
|
+
name, *params = items
|
|
84
|
+
params_str = ", ".join(params)
|
|
85
|
+
return f"custom_block {name} ({params_str})"
|
|
86
|
+
|
|
87
|
+
@v_args(inline=True)
|
|
88
|
+
def PREPROC_INSTR(self, content):
|
|
89
|
+
return f"{content}"
|
|
90
|
+
|
|
91
|
+
@v_args(inline=True)
|
|
92
|
+
def COMMMENT(self, content):
|
|
93
|
+
return f"{content}"
|
|
94
|
+
|
|
95
|
+
def block(self, items):
|
|
96
|
+
params = []
|
|
97
|
+
inner_blocks = []
|
|
98
|
+
comments = []
|
|
99
|
+
for i in items[1:]:
|
|
100
|
+
if not isinstance(i, Tree):
|
|
101
|
+
continue
|
|
102
|
+
if str(i.data) == "block_content":
|
|
103
|
+
inner_blocks.extend(i.children)
|
|
104
|
+
if str(i.data) == "block_params":
|
|
105
|
+
params.extend(i.children)
|
|
106
|
+
if str(i.data) == "comments":
|
|
107
|
+
comments.extend(i.children)
|
|
108
|
+
block_name = items[0]
|
|
109
|
+
block_text = f"{block_name}({', '.join(params)})" if params or not inner_blocks else f"{block_name}"
|
|
110
|
+
if inner_blocks:
|
|
111
|
+
blocks_content = "\n".join(inner_blocks)
|
|
112
|
+
indented_content = self._indent(blocks_content)
|
|
113
|
+
block_text += f" {{\n{indented_content}\n}}"
|
|
114
|
+
if comments:
|
|
115
|
+
block_text += f" {' '.join(comments)}"
|
|
116
|
+
return block_text
|
|
117
|
+
|
|
118
|
+
def LITERAL_NUMBER(self, number: str):
|
|
119
|
+
return number
|
|
120
|
+
|
|
121
|
+
def expr(self, items):
|
|
122
|
+
text = items[0]
|
|
123
|
+
if len(items) > 1:
|
|
124
|
+
text += f" {' '.join(items[1].children)}"
|
|
125
|
+
return text
|
|
126
|
+
|
|
127
|
+
def low_expr1(self, items):
|
|
128
|
+
text = f"({items[0]})" if " " in items[0] else items[0]
|
|
129
|
+
if len(items) > 1:
|
|
130
|
+
text += f" {' '.join(items[1].children)}"
|
|
131
|
+
return text
|
|
132
|
+
|
|
133
|
+
@v_args(inline=True)
|
|
134
|
+
def low_expr2(self, item):
|
|
135
|
+
return item
|
|
136
|
+
|
|
137
|
+
def addition(self, items):
|
|
138
|
+
return items[0] + " + " + items[1]
|
|
139
|
+
|
|
140
|
+
def subtraction(self, items):
|
|
141
|
+
return items[0] + " - " + items[1]
|
|
142
|
+
|
|
143
|
+
def multiplication(self, items):
|
|
144
|
+
return items[0] + " * " + items[1]
|
|
145
|
+
|
|
146
|
+
def division(self, items):
|
|
147
|
+
return items[0] + " / " + items[1]
|
|
148
|
+
|
|
149
|
+
def top_level_block(self, items):
|
|
150
|
+
first_item = items[0]
|
|
151
|
+
if first_item.startswith("%%") or first_item.startswith("##"):
|
|
152
|
+
return first_item
|
|
153
|
+
|
|
154
|
+
hat, *blocks = items
|
|
155
|
+
blocks_content = "\n".join(blocks)
|
|
156
|
+
indented_content = self._indent(blocks_content)
|
|
157
|
+
return f"{hat} {{\n{indented_content}\n}}"
|
|
158
|
+
|
|
159
|
+
def start(self, items):
|
|
160
|
+
return "\n\n".join(items)
|
|
161
|
+
|
|
162
|
+
def pretty_unparse(tree: Tree):
|
|
163
|
+
return PrettyUnparser().transform(tree)
|
|
164
|
+
|
|
165
|
+
if __name__ == "__main__":
|
|
166
|
+
EXAMPLE_FILE = Path(__file__).parent / "example.txt"
|
|
167
|
+
tree = parse(EXAMPLE_FILE)
|
|
168
|
+
print(tree.pretty())
|
|
169
|
+
print()
|
|
170
|
+
print()
|
|
171
|
+
print(tree)
|
|
172
|
+
print()
|
|
173
|
+
print()
|
|
174
|
+
print(unparse(tree))
|
|
175
|
+
print()
|
|
176
|
+
print()
|
|
177
|
+
print(pretty_unparse(tree))
|
scratchattach/editor/comment.py
CHANGED
|
@@ -5,6 +5,9 @@ from typing import Optional
|
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
class Comment(base.IDComponent):
|
|
8
|
+
"""
|
|
9
|
+
Represents a comment in the scratch editor.
|
|
10
|
+
"""
|
|
8
11
|
def __init__(self, _id: Optional[str] = None, _block: Optional[block.Block] = None, x: int = 0, y: int = 0, width: int = 200,
|
|
9
12
|
height: int = 200, minimized: bool = False, text: str = '', *, _block_id: Optional[str] = None,
|
|
10
13
|
_sprite: sprite.Sprite = build_defaulting.SPRITE_DEFAULT, pos: Optional[tuple[int, int]] = None):
|
|
@@ -32,6 +35,9 @@ class Comment(base.IDComponent):
|
|
|
32
35
|
|
|
33
36
|
@property
|
|
34
37
|
def block_id(self):
|
|
38
|
+
"""
|
|
39
|
+
Retrieve the id of the associateed block (if applicable)
|
|
40
|
+
"""
|
|
35
41
|
if self.block is not None:
|
|
36
42
|
return self.block.id
|
|
37
43
|
elif self._block_id is not None:
|