scratchattach 2.1.15b0__py3-none-any.whl → 3.0.0b1__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.
- cli/__about__.py +1 -0
- cli/__init__.py +26 -0
- cli/cmd/__init__.py +4 -0
- cli/cmd/group.py +127 -0
- cli/cmd/login.py +60 -0
- cli/cmd/profile.py +7 -0
- cli/cmd/sessions.py +5 -0
- cli/context.py +142 -0
- cli/db.py +66 -0
- cli/namespace.py +14 -0
- {scratchattach/cloud → cloud}/_base.py +112 -87
- {scratchattach/cloud → cloud}/cloud.py +16 -16
- {scratchattach/editor → editor}/__init__.py +2 -1
- {scratchattach/editor → editor}/asset.py +26 -14
- {scratchattach/editor → editor}/backpack_json.py +3 -5
- {scratchattach/editor → editor}/base.py +2 -4
- {scratchattach/editor → editor}/block.py +27 -22
- {scratchattach/editor → editor}/blockshape.py +1 -1
- {scratchattach/editor → editor}/build_defaulting.py +2 -2
- editor/commons.py +145 -0
- {scratchattach/editor → editor}/field.py +1 -1
- {scratchattach/editor → editor}/inputs.py +6 -3
- {scratchattach/editor → editor}/meta.py +10 -7
- {scratchattach/editor → editor}/monitor.py +10 -8
- {scratchattach/editor → editor}/mutation.py +68 -11
- {scratchattach/editor → editor}/pallete.py +1 -3
- {scratchattach/editor → editor}/prim.py +4 -0
- {scratchattach/editor → editor}/project.py +118 -16
- {scratchattach/editor → editor}/sprite.py +25 -15
- {scratchattach/editor → editor}/vlb.py +2 -2
- {scratchattach/eventhandlers → eventhandlers}/_base.py +1 -0
- {scratchattach/eventhandlers → eventhandlers}/cloud_events.py +26 -6
- {scratchattach/eventhandlers → eventhandlers}/cloud_recorder.py +4 -4
- {scratchattach/eventhandlers → eventhandlers}/cloud_requests.py +139 -54
- {scratchattach/eventhandlers → eventhandlers}/cloud_server.py +6 -3
- {scratchattach/eventhandlers → eventhandlers}/cloud_storage.py +1 -2
- eventhandlers/filterbot.py +163 -0
- other/other_apis.py +598 -0
- {scratchattach-2.1.15b0.dist-info → scratchattach-3.0.0b1.dist-info}/METADATA +7 -11
- scratchattach-3.0.0b1.dist-info/RECORD +79 -0
- {scratchattach-2.1.15b0.dist-info → scratchattach-3.0.0b1.dist-info}/WHEEL +1 -1
- scratchattach-3.0.0b1.dist-info/entry_points.txt +2 -0
- scratchattach-3.0.0b1.dist-info/top_level.txt +7 -0
- {scratchattach/site → site}/_base.py +32 -5
- site/activity.py +426 -0
- {scratchattach/site → site}/alert.py +4 -5
- {scratchattach/site → site}/backpack_asset.py +2 -1
- {scratchattach/site → site}/classroom.py +80 -73
- {scratchattach/site → site}/cloud_activity.py +43 -29
- {scratchattach/site → site}/comment.py +86 -100
- {scratchattach/site → site}/forum.py +8 -4
- site/placeholder.py +132 -0
- {scratchattach/site → site}/project.py +228 -122
- {scratchattach/site → site}/session.py +156 -71
- {scratchattach/site → site}/studio.py +139 -46
- site/typed_dicts.py +151 -0
- {scratchattach/site → site}/user.py +511 -215
- {scratchattach/utils → utils}/commons.py +12 -4
- {scratchattach/utils → utils}/encoder.py +7 -4
- {scratchattach/utils → utils}/enums.py +1 -0
- {scratchattach/utils → utils}/exceptions.py +36 -2
- utils/optional_async.py +154 -0
- utils/requests.py +306 -0
- scratchattach/__init__.py +0 -29
- scratchattach/editor/commons.py +0 -273
- scratchattach/eventhandlers/filterbot.py +0 -161
- scratchattach/other/other_apis.py +0 -284
- scratchattach/site/activity.py +0 -382
- scratchattach/utils/requests.py +0 -93
- scratchattach-2.1.15b0.dist-info/RECORD +0 -66
- scratchattach-2.1.15b0.dist-info/top_level.txt +0 -1
- {scratchattach/cloud → cloud}/__init__.py +0 -0
- {scratchattach/editor → editor}/code_translation/__init__.py +0 -0
- {scratchattach/editor → editor}/code_translation/parse.py +0 -0
- {scratchattach/editor → editor}/comment.py +0 -0
- {scratchattach/editor → editor}/extension.py +0 -0
- {scratchattach/editor → editor}/twconfig.py +0 -0
- {scratchattach/eventhandlers → eventhandlers}/__init__.py +0 -0
- {scratchattach/eventhandlers → eventhandlers}/combine.py +0 -0
- {scratchattach/eventhandlers → eventhandlers}/message_events.py +0 -0
- {scratchattach/other → other}/__init__.py +0 -0
- {scratchattach/other → other}/project_json_capabilities.py +0 -0
- {scratchattach-2.1.15b0.dist-info → scratchattach-3.0.0b1.dist-info}/licenses/LICENSE +0 -0
- {scratchattach/site → site}/__init__.py +0 -0
- {scratchattach/site → site}/browser_cookie3_stub.py +0 -0
- {scratchattach/site → site}/browser_cookies.py +0 -0
- {scratchattach/utils → utils}/__init__.py +0 -0
|
@@ -68,8 +68,8 @@ class Block(base.SpriteSubComponent):
|
|
|
68
68
|
|
|
69
69
|
def add_input(self, name: str, _input: inputs.Input) -> Self:
|
|
70
70
|
"""
|
|
71
|
-
|
|
72
|
-
"""
|
|
71
|
+
Attach an input object to the block.
|
|
72
|
+
"""
|
|
73
73
|
self.inputs[name] = _input
|
|
74
74
|
for val in (_input.value, _input.obscurer):
|
|
75
75
|
if isinstance(val, Block):
|
|
@@ -78,15 +78,15 @@ class Block(base.SpriteSubComponent):
|
|
|
78
78
|
|
|
79
79
|
def add_field(self, name: str, _field: field.Field) -> Self:
|
|
80
80
|
"""
|
|
81
|
-
|
|
82
|
-
"""
|
|
81
|
+
Attach a field object to the block.
|
|
82
|
+
"""
|
|
83
83
|
self.fields[name] = _field
|
|
84
84
|
return self
|
|
85
85
|
|
|
86
86
|
def set_mutation(self, _mutation: mutation.Mutation) -> Self:
|
|
87
87
|
"""
|
|
88
|
-
Attach a mutation object and
|
|
89
|
-
"""
|
|
88
|
+
Attach a mutation object and link it, also asking the mutation to link its own subcomponents.
|
|
89
|
+
"""
|
|
90
90
|
self.mutation = _mutation
|
|
91
91
|
_mutation.block = self
|
|
92
92
|
_mutation.link_arguments()
|
|
@@ -113,14 +113,15 @@ class Block(base.SpriteSubComponent):
|
|
|
113
113
|
@property
|
|
114
114
|
def target(self):
|
|
115
115
|
"""
|
|
116
|
-
Alias for sprite
|
|
117
|
-
"""
|
|
116
|
+
Alias for .sprite
|
|
117
|
+
"""
|
|
118
|
+
# should this be deprecated?
|
|
118
119
|
return self.sprite
|
|
119
120
|
|
|
120
121
|
@property
|
|
121
122
|
def block_shape(self) -> blockshape.BlockShape:
|
|
122
123
|
"""
|
|
123
|
-
Search for the blockshape stored in blockshape.py
|
|
124
|
+
Search for & return the associated blockshape stored in blockshape.py
|
|
124
125
|
:return: The block's block shape (by opcode)
|
|
125
126
|
"""
|
|
126
127
|
_shape = blockshape.BlockShapes.find(self.opcode, "opcode")
|
|
@@ -133,7 +134,7 @@ class Block(base.SpriteSubComponent):
|
|
|
133
134
|
@property
|
|
134
135
|
def can_next(self):
|
|
135
136
|
"""
|
|
136
|
-
:return: Whether the block *can* have a next block (
|
|
137
|
+
:return: Whether the block *can* have a next block (checks if it's not a cap block, but also considering the special behaviour of control_stop)
|
|
137
138
|
"""
|
|
138
139
|
_shape = self.block_shape
|
|
139
140
|
if _shape.is_cap is not blockshape.MUTATION_DEPENDENT:
|
|
@@ -141,7 +142,8 @@ class Block(base.SpriteSubComponent):
|
|
|
141
142
|
else:
|
|
142
143
|
if self.mutation is None:
|
|
143
144
|
# If there's no mutation, let's just assume yes
|
|
144
|
-
|
|
145
|
+
# add filterwarnings?
|
|
146
|
+
warnings.warn(f"{self} has no mutation! Assuming we can add blocks!")
|
|
145
147
|
return True
|
|
146
148
|
|
|
147
149
|
return self.mutation.has_next
|
|
@@ -150,19 +152,20 @@ class Block(base.SpriteSubComponent):
|
|
|
150
152
|
def id(self) -> str:
|
|
151
153
|
"""
|
|
152
154
|
Work out the id of this block by searching through the sprite dictionary
|
|
155
|
+
If one cannot be found, generate a new one and return that.
|
|
153
156
|
"""
|
|
154
157
|
if self._id:
|
|
155
|
-
return self.
|
|
158
|
+
return self._id
|
|
156
159
|
# warnings.warn(f"Using block IDs can cause consistency issues and is not recommended")
|
|
157
160
|
# This property is used when converting comments to JSON (we don't want random warning when exporting a project)
|
|
158
161
|
for _block_id, _block in self.sprite.blocks.items():
|
|
159
162
|
if _block is self:
|
|
160
163
|
self._id = _block_id
|
|
161
|
-
return self.
|
|
164
|
+
return self._id
|
|
162
165
|
|
|
163
166
|
# Let's just automatically assign ourselves an id
|
|
164
167
|
self.sprite.add_block(self)
|
|
165
|
-
return self.
|
|
168
|
+
return self._id
|
|
166
169
|
|
|
167
170
|
@id.setter
|
|
168
171
|
def id(self, value: str) -> None:
|
|
@@ -191,8 +194,9 @@ class Block(base.SpriteSubComponent):
|
|
|
191
194
|
@property
|
|
192
195
|
def relatives(self) -> list[Block]:
|
|
193
196
|
"""
|
|
194
|
-
:return: A list of blocks which are related to this block (e.
|
|
197
|
+
:return: A list of blocks which are related to this block (i.e. parent & next)
|
|
195
198
|
"""
|
|
199
|
+
# TODO: consider adding input blocks?
|
|
196
200
|
_ret = []
|
|
197
201
|
|
|
198
202
|
def yield_block(_block: Block | None):
|
|
@@ -209,6 +213,7 @@ class Block(base.SpriteSubComponent):
|
|
|
209
213
|
"""
|
|
210
214
|
:return: A list of blocks that are inside of this block, **NOT INCLUDING THE ATTACHED BLOCK**
|
|
211
215
|
"""
|
|
216
|
+
# does this include procedure definitions' inner block?
|
|
212
217
|
_children = []
|
|
213
218
|
for _input in self.inputs.values():
|
|
214
219
|
if isinstance(_input.value, Block) or isinstance(_input.value, prim.Prim):
|
|
@@ -223,7 +228,8 @@ class Block(base.SpriteSubComponent):
|
|
|
223
228
|
"""
|
|
224
229
|
Recursive getter method to get all previous blocks in the blockchain (until hitting a top-level block)
|
|
225
230
|
"""
|
|
226
|
-
|
|
231
|
+
# TODO: check if this hits the recursion limit
|
|
232
|
+
if self.parent is None: # TODO: use is_top_level?
|
|
227
233
|
return [self]
|
|
228
234
|
|
|
229
235
|
return [self] + self.parent.previous_chain
|
|
@@ -233,6 +239,7 @@ class Block(base.SpriteSubComponent):
|
|
|
233
239
|
"""
|
|
234
240
|
Recursive getter method to get all next blocks in the blockchain (until hitting a bottom-levell block)
|
|
235
241
|
"""
|
|
242
|
+
# TODO: check if this hits the recursion limit
|
|
236
243
|
if self.next is None:
|
|
237
244
|
return [self]
|
|
238
245
|
|
|
@@ -248,8 +255,7 @@ class Block(base.SpriteSubComponent):
|
|
|
248
255
|
@property
|
|
249
256
|
def top_level_block(self):
|
|
250
257
|
"""
|
|
251
|
-
Get the first block in the block stack that this block is part of
|
|
252
|
-
same as the old stack_parent property from sbedtior v1
|
|
258
|
+
Get the first (top level) block in the block stack that this block is part of
|
|
253
259
|
"""
|
|
254
260
|
return self.previous_chain[-1]
|
|
255
261
|
|
|
@@ -266,7 +272,7 @@ class Block(base.SpriteSubComponent):
|
|
|
266
272
|
Useful for showing a block stack in the console, using pprint
|
|
267
273
|
:return: A tree-like nested list structure representing the stack of blocks, including inputs, starting at this block
|
|
268
274
|
"""
|
|
269
|
-
_tree = [self]
|
|
275
|
+
_tree: list[Block | prim.Prim | list[Block]] = [self]
|
|
270
276
|
for child in self.children:
|
|
271
277
|
if isinstance(child, prim.Prim):
|
|
272
278
|
_tree.append(child)
|
|
@@ -281,7 +287,7 @@ class Block(base.SpriteSubComponent):
|
|
|
281
287
|
@property
|
|
282
288
|
def category(self):
|
|
283
289
|
"""
|
|
284
|
-
Works out what category of block this is using the opcode. Does not perform validation
|
|
290
|
+
Works out what category of block this is as a string, using the opcode. Does not perform validation
|
|
285
291
|
"""
|
|
286
292
|
return self.opcode.split('_')[0]
|
|
287
293
|
|
|
@@ -526,7 +532,7 @@ class Block(base.SpriteSubComponent):
|
|
|
526
532
|
def duplicate_single_block(self) -> Block:
|
|
527
533
|
return self.attach_block(self.dcopy())
|
|
528
534
|
|
|
529
|
-
def attach_chain(self, *chain:
|
|
535
|
+
def attach_chain(self, *chain: Block) -> Block:
|
|
530
536
|
attaching_block = self
|
|
531
537
|
for _block in chain:
|
|
532
538
|
attaching_block = attaching_block.attach_block(_block)
|
|
@@ -576,4 +582,3 @@ class Block(base.SpriteSubComponent):
|
|
|
576
582
|
"""
|
|
577
583
|
for _block in self.attached_chain:
|
|
578
584
|
_block.delete_single_block()
|
|
579
|
-
|
|
@@ -20,7 +20,7 @@ class _MutationDependent(commons.Singleton):
|
|
|
20
20
|
raise TypeError("Need mutation data to work out attribute value.")
|
|
21
21
|
|
|
22
22
|
|
|
23
|
-
MUTATION_DEPENDENT: Final[Literal[_MutationDependent]] = _MutationDependent.INSTANCE
|
|
23
|
+
MUTATION_DEPENDENT: Final[Literal[_MutationDependent.INSTANCE]] = _MutationDependent.INSTANCE
|
|
24
24
|
"""Value used when mutation data is required to work out the attribute value"""
|
|
25
25
|
|
|
26
26
|
|
|
@@ -9,7 +9,7 @@ if TYPE_CHECKING:
|
|
|
9
9
|
from . import sprite, block, prim, comment
|
|
10
10
|
from . import commons
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
# TODO: should deque be used here?
|
|
13
13
|
class _SetSprite(commons.Singleton):
|
|
14
14
|
INSTANCE = 0
|
|
15
15
|
def __repr__(self):
|
|
@@ -43,7 +43,7 @@ def add_block(_block: block.Block | prim.Prim) -> block.Block | prim.Prim:
|
|
|
43
43
|
return current_sprite().add_block(_block)
|
|
44
44
|
|
|
45
45
|
|
|
46
|
-
def add_chain(*chain: Iterable[block.Block
|
|
46
|
+
def add_chain(*chain: Iterable[block.Block | prim.Prim]) -> block.Block | prim.Prim:
|
|
47
47
|
return current_sprite().add_chain(*chain)
|
|
48
48
|
|
|
49
49
|
|
editor/commons.py
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Shared functions used by the editor module
|
|
3
|
+
"""
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import json
|
|
7
|
+
import random
|
|
8
|
+
import string
|
|
9
|
+
from typing_extensions import Optional, Final, Any, TYPE_CHECKING, Union, Self, TypeVar
|
|
10
|
+
from enum import EnumMeta, Enum
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from . import sprite, build_defaulting
|
|
14
|
+
|
|
15
|
+
SpriteInput = Union[sprite.Sprite, build_defaulting._SetSprite]
|
|
16
|
+
else:
|
|
17
|
+
SpriteInput = Any
|
|
18
|
+
|
|
19
|
+
T = TypeVar('T')
|
|
20
|
+
from scratchattach.utils import exceptions
|
|
21
|
+
|
|
22
|
+
DIGITS: Final[tuple[str, ...]] = tuple("0123456789")
|
|
23
|
+
|
|
24
|
+
# Strangely enough, it seems like something in string.punctuation causes issues. Not sure why
|
|
25
|
+
ID_CHARS: Final[str] = string.ascii_letters + string.digits # + string.punctuation
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def is_valid_json(_str: Any) -> bool:
|
|
29
|
+
"""
|
|
30
|
+
Try to load a json string, if it fails, return False, else return true.
|
|
31
|
+
"""
|
|
32
|
+
try:
|
|
33
|
+
json.loads(_str)
|
|
34
|
+
return True
|
|
35
|
+
except (ValueError, TypeError):
|
|
36
|
+
return False
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def noneless_update(obj: dict, update: dict) -> None:
|
|
40
|
+
"""
|
|
41
|
+
equivalent to dict.update, except and values of None are not assigned
|
|
42
|
+
"""
|
|
43
|
+
for key, value in update.items():
|
|
44
|
+
if value is not None:
|
|
45
|
+
obj[key] = value
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def remove_nones(obj: dict) -> None:
|
|
49
|
+
"""
|
|
50
|
+
Removes all None values from a dict.
|
|
51
|
+
:param obj: Dictionary to remove all None values.
|
|
52
|
+
"""
|
|
53
|
+
nones = []
|
|
54
|
+
for key, value in obj.items():
|
|
55
|
+
if value is None:
|
|
56
|
+
nones.append(key)
|
|
57
|
+
for key in nones:
|
|
58
|
+
del obj[key]
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def safe_get(lst: list | tuple, _i: int, default: Optional[Any] = None) -> Any:
|
|
62
|
+
"""
|
|
63
|
+
Like dict.get() but for lists
|
|
64
|
+
"""
|
|
65
|
+
if len(lst) <= _i:
|
|
66
|
+
return default
|
|
67
|
+
else:
|
|
68
|
+
return lst[_i]
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def trim_final_nones(lst: list[T]) -> list[T]:
|
|
72
|
+
"""
|
|
73
|
+
Removes the last None values from a list until a non-None value is hit.
|
|
74
|
+
:param lst: list which will **not** be modified.
|
|
75
|
+
"""
|
|
76
|
+
i = len(lst)
|
|
77
|
+
for item in lst[::-1]:
|
|
78
|
+
if item is not None:
|
|
79
|
+
break
|
|
80
|
+
i -= 1
|
|
81
|
+
return lst[:i]
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def dumps_ifnn(obj: Any) -> Optional[str]:
|
|
85
|
+
"""
|
|
86
|
+
Return json.dumps(obj) if the object is not None
|
|
87
|
+
"""
|
|
88
|
+
if obj is None:
|
|
89
|
+
return None
|
|
90
|
+
else:
|
|
91
|
+
return json.dumps(obj)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def gen_id() -> str:
|
|
95
|
+
"""
|
|
96
|
+
Generate an id for scratch blocks/variables/lists/broadcasts
|
|
97
|
+
|
|
98
|
+
The old 'naïve' method but that chances of a repeat are so miniscule
|
|
99
|
+
Have to check if whitespace chars break it
|
|
100
|
+
May later add checking within sprites so that we don't need such long ids (we can save space this way)
|
|
101
|
+
If this is implemented, we would need to be careful when merging sprites/adding blocks etc
|
|
102
|
+
"""
|
|
103
|
+
return ''.join(random.choices(ID_CHARS, k=20))
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def sanitize_fn(filename: str):
|
|
107
|
+
"""
|
|
108
|
+
Removes illegal chars from a filename
|
|
109
|
+
:return: Sanitized filename
|
|
110
|
+
"""
|
|
111
|
+
# Maybe could import a slugify module, but it's a bit overkill
|
|
112
|
+
ret = ''
|
|
113
|
+
for char in filename:
|
|
114
|
+
if char in string.ascii_letters + string.digits + "-_":
|
|
115
|
+
ret += char
|
|
116
|
+
else:
|
|
117
|
+
ret += '_'
|
|
118
|
+
return ret
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def get_folder_name(name: str) -> str | None:
|
|
122
|
+
"""
|
|
123
|
+
Get the name of the folder if this is a turbowarp-style costume name
|
|
124
|
+
"""
|
|
125
|
+
if name.startswith('//'):
|
|
126
|
+
return None
|
|
127
|
+
|
|
128
|
+
if '//' in name:
|
|
129
|
+
return name.split('//')[0]
|
|
130
|
+
else:
|
|
131
|
+
return None
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def get_name_nofldr(name: str) -> str:
|
|
135
|
+
"""
|
|
136
|
+
Get the sprite/asset name without the folder name
|
|
137
|
+
"""
|
|
138
|
+
fldr = get_folder_name(name)
|
|
139
|
+
if fldr is None:
|
|
140
|
+
return name
|
|
141
|
+
else:
|
|
142
|
+
return name[len(fldr) + 2:]
|
|
143
|
+
|
|
144
|
+
Singleton = Enum
|
|
145
|
+
|
|
@@ -11,7 +11,7 @@ from dataclasses import dataclass
|
|
|
11
11
|
@dataclass
|
|
12
12
|
class ShadowStatus:
|
|
13
13
|
"""
|
|
14
|
-
Dataclass representing a possible shadow value and giving it a name
|
|
14
|
+
Dataclass representing a possible shadow value for a block and giving it a name
|
|
15
15
|
"""
|
|
16
16
|
idx: int
|
|
17
17
|
name: str
|
|
@@ -33,8 +33,7 @@ class ShadowStatuses:
|
|
|
33
33
|
if status.idx == idx:
|
|
34
34
|
return status
|
|
35
35
|
|
|
36
|
-
|
|
37
|
-
raise ValueError(f"Invalid ShadowStatus idx={idx}")
|
|
36
|
+
raise ValueError(f"Invalid ShadowStatus idx={idx}")
|
|
38
37
|
|
|
39
38
|
|
|
40
39
|
class Input(base.BlockSubComponent):
|
|
@@ -111,6 +110,10 @@ class Input(base.BlockSubComponent):
|
|
|
111
110
|
return data
|
|
112
111
|
|
|
113
112
|
def link_using_block(self):
|
|
113
|
+
"""
|
|
114
|
+
Link the Input object to any menu blocks, obscurer blocks, sprites, and links any of its subcomponents
|
|
115
|
+
"""
|
|
116
|
+
|
|
114
117
|
# Link to value
|
|
115
118
|
if self._id is not None:
|
|
116
119
|
new_value = self.sprite.find_block(self._id, "id")
|
|
@@ -9,13 +9,16 @@ from typing import Optional
|
|
|
9
9
|
|
|
10
10
|
@dataclass
|
|
11
11
|
class PlatformMeta(base.JSONSerializable):
|
|
12
|
+
"""
|
|
13
|
+
Represents a TurboWarp platform meta object
|
|
14
|
+
"""
|
|
12
15
|
name: str = None
|
|
13
16
|
url: str = field(repr=True, default=None)
|
|
14
17
|
|
|
15
18
|
def __bool__(self):
|
|
16
19
|
return self.name is not None or self.url is not None
|
|
17
20
|
|
|
18
|
-
def to_json(self)
|
|
21
|
+
def to_json(self):
|
|
19
22
|
_json = {"name": self.name, "url": self.url}
|
|
20
23
|
commons.remove_nones(_json)
|
|
21
24
|
return _json
|
|
@@ -32,7 +35,7 @@ DEFAULT_VM = "0.1.0"
|
|
|
32
35
|
DEFAULT_AGENT = "scratchattach.editor by https://scratch.mit.edu/users/timmccool/"
|
|
33
36
|
DEFAULT_PLATFORM = PlatformMeta("scratchattach", "https://github.com/timMcCool/scratchattach/")
|
|
34
37
|
|
|
35
|
-
EDIT_META =
|
|
38
|
+
EDIT_META = False
|
|
36
39
|
META_SET_PLATFORM = False
|
|
37
40
|
|
|
38
41
|
|
|
@@ -42,7 +45,7 @@ def set_meta_platform(true_false: bool = None):
|
|
|
42
45
|
"""
|
|
43
46
|
global META_SET_PLATFORM
|
|
44
47
|
if true_false is None:
|
|
45
|
-
true_false = bool(1 -
|
|
48
|
+
true_false = bool(1 - META_SET_PLATFORM)
|
|
46
49
|
META_SET_PLATFORM = true_false
|
|
47
50
|
|
|
48
51
|
|
|
@@ -76,12 +79,12 @@ class Meta(base.JSONSerializable):
|
|
|
76
79
|
def vm_is_valid(self):
|
|
77
80
|
"""
|
|
78
81
|
Check whether the vm value is valid using a regex
|
|
79
|
-
|
|
82
|
+
regex pattern from TurboWarp ↓↓↓↓
|
|
80
83
|
"""
|
|
81
84
|
return re.match("^([0-9]+\\.[0-9]+\\.[0-9]+)($|-)", self.vm) is not None
|
|
82
85
|
|
|
83
86
|
def to_json(self):
|
|
84
|
-
_json = {
|
|
87
|
+
_json: dict[str, str | dict[str, str]] = {
|
|
85
88
|
"semver": self.semver,
|
|
86
89
|
"vm": self.vm,
|
|
87
90
|
"agent": self.agent
|
|
@@ -92,9 +95,9 @@ class Meta(base.JSONSerializable):
|
|
|
92
95
|
return _json
|
|
93
96
|
|
|
94
97
|
@staticmethod
|
|
95
|
-
def from_json(data):
|
|
98
|
+
def from_json(data: dict[str, str | dict[str, str]] | None):
|
|
96
99
|
if data is None:
|
|
97
|
-
data = ""
|
|
100
|
+
data = {"semver": "3.0.0"}
|
|
98
101
|
|
|
99
102
|
semver = data["semver"]
|
|
100
103
|
vm = data.get("vm")
|
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
import warnings
|
|
4
3
|
from typing import Optional, TYPE_CHECKING
|
|
5
4
|
|
|
6
5
|
from typing_extensions import deprecated
|
|
6
|
+
import warnings
|
|
7
7
|
|
|
8
8
|
if TYPE_CHECKING:
|
|
9
|
-
from .block import Block
|
|
10
9
|
from . import project
|
|
11
10
|
|
|
12
|
-
from . import base
|
|
11
|
+
from . import base, block
|
|
13
12
|
|
|
14
13
|
|
|
15
14
|
class Monitor(base.ProjectSubcomponent):
|
|
@@ -35,6 +34,9 @@ class Monitor(base.ProjectSubcomponent):
|
|
|
35
34
|
"""
|
|
36
35
|
assert isinstance(reporter, base.SpriteSubComponent) or reporter is None
|
|
37
36
|
|
|
37
|
+
if sprite_name is None and hasattr(reporter, 'sprite'):
|
|
38
|
+
sprite_name = None if reporter.sprite.is_stage else reporter.sprite.name
|
|
39
|
+
|
|
38
40
|
self.reporter_id = reporter_id
|
|
39
41
|
"""
|
|
40
42
|
ID referencing the VLB being referenced. Replaced with None during project instantiation, where the reporter attribute is updated
|
|
@@ -142,16 +144,16 @@ class Monitor(base.ProjectSubcomponent):
|
|
|
142
144
|
self.reporter_id = None
|
|
143
145
|
|
|
144
146
|
# todo: consider reimplementing this
|
|
145
|
-
@deprecated("This method does not work correctly (This may be fixed in the future)")
|
|
146
147
|
@staticmethod
|
|
147
|
-
|
|
148
|
+
@deprecated("This method does not work correctly (This may be fixed in the future)")
|
|
149
|
+
def from_reporter(reporter: block.Block, _id: str = None, mode: str = "default",
|
|
148
150
|
opcode: str = None, sprite_name: str = None, value=0, width: int | float = 0,
|
|
149
151
|
height: int | float = 0,
|
|
150
152
|
x: int | float = 5, y: int | float = 5, visible: bool = False, slider_min: int | float = 0,
|
|
151
153
|
slider_max: int | float = 100, is_discrete: bool = True, params: dict = None):
|
|
152
|
-
if
|
|
154
|
+
if not reporter.block_shape.is_reporter:
|
|
153
155
|
warnings.warn(f"{reporter} is not a reporter block; the monitor will return '0'")
|
|
154
|
-
elif
|
|
156
|
+
elif reporter.block_shape.is_menu:
|
|
155
157
|
warnings.warn(f"{reporter} is a menu block; the monitor will return '0'")
|
|
156
158
|
# Maybe add note that length of list doesn't work fsr?? idk
|
|
157
159
|
if _id is None:
|
|
@@ -168,7 +170,7 @@ class Monitor(base.ProjectSubcomponent):
|
|
|
168
170
|
params[field.id] = field.value, field.value_id
|
|
169
171
|
|
|
170
172
|
return Monitor(
|
|
171
|
-
|
|
173
|
+
reporter,
|
|
172
174
|
mode,
|
|
173
175
|
opcode,
|
|
174
176
|
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Contains the mutation class and handlers for Arguments/ArgumentTypes/ArgSettings, and utilities for handling proc codes
|
|
3
|
+
"""
|
|
4
|
+
|
|
1
5
|
from __future__ import annotations
|
|
2
6
|
|
|
3
7
|
import json
|
|
@@ -18,6 +22,7 @@ class ArgumentType(base.Base):
|
|
|
18
22
|
proc_str: str
|
|
19
23
|
|
|
20
24
|
def __eq__(self, other):
|
|
25
|
+
# noinspection PyProtectedMember
|
|
21
26
|
if isinstance(other, enums._EnumWrapper):
|
|
22
27
|
other = other.value
|
|
23
28
|
|
|
@@ -40,6 +45,9 @@ class ArgumentType(base.Base):
|
|
|
40
45
|
|
|
41
46
|
@dataclass
|
|
42
47
|
class ArgSettings(base.Base):
|
|
48
|
+
"""
|
|
49
|
+
Contains whether the ids, names, and defaults of arguments in a mutation are None or not - i.e. the configuration of the arguments
|
|
50
|
+
"""
|
|
43
51
|
ids: bool
|
|
44
52
|
names: bool
|
|
45
53
|
defaults: bool
|
|
@@ -65,25 +73,37 @@ class ArgSettings(base.Base):
|
|
|
65
73
|
class Argument(base.MutationSubComponent):
|
|
66
74
|
name: str
|
|
67
75
|
default: str = ''
|
|
76
|
+
_type: Optional[ArgumentType] = None
|
|
68
77
|
|
|
69
78
|
_id: str = None
|
|
70
79
|
"""
|
|
71
80
|
Argument ID: Will be used to replace other parameters during block instantiation.
|
|
72
81
|
"""
|
|
73
82
|
|
|
83
|
+
def __post_init__(self):
|
|
84
|
+
super().__init__()
|
|
85
|
+
|
|
74
86
|
@property
|
|
75
87
|
def index(self):
|
|
76
88
|
return self.mutation.arguments.index(self)
|
|
77
89
|
|
|
78
90
|
@property
|
|
79
|
-
def type(self) ->
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
91
|
+
def type(self) -> Optional[ArgumentType]:
|
|
92
|
+
if not self._type:
|
|
93
|
+
if not self.mutation:
|
|
94
|
+
raise ValueError(f"Cannot infer 'type' of {self} when there is no mutation attached. "
|
|
95
|
+
f"Consider providing a type manually.")
|
|
96
|
+
|
|
97
|
+
i = 0
|
|
98
|
+
goal = self.index
|
|
99
|
+
for token in parse_proc_code(self.mutation.proc_code):
|
|
100
|
+
if isinstance(token, ArgumentType):
|
|
101
|
+
if i == goal:
|
|
102
|
+
self._type = token
|
|
103
|
+
break
|
|
104
|
+
i += 1
|
|
105
|
+
|
|
106
|
+
return self._type
|
|
87
107
|
|
|
88
108
|
@staticmethod
|
|
89
109
|
def from_json(data: dict | list | Any):
|
|
@@ -97,12 +117,13 @@ class Argument(base.MutationSubComponent):
|
|
|
97
117
|
self._id = self.block.new_id
|
|
98
118
|
|
|
99
119
|
|
|
120
|
+
# noinspection PyProtectedMember
|
|
100
121
|
class ArgTypes(enums._EnumWrapper):
|
|
101
122
|
BOOLEAN = ArgumentType("boolean", "%b")
|
|
102
123
|
NUMBER_OR_TEXT = ArgumentType("number or text", "%s")
|
|
103
124
|
|
|
104
125
|
|
|
105
|
-
def parse_proc_code(_proc_code: str) -> list[str
|
|
126
|
+
def parse_proc_code(_proc_code: str) -> Optional[list[str | ArgumentType]]:
|
|
106
127
|
"""
|
|
107
128
|
Parse a proccode (part of a mutation) into argument types and strings
|
|
108
129
|
"""
|
|
@@ -120,6 +141,10 @@ def parse_proc_code(_proc_code: str) -> list[str, ArgumentType] | None:
|
|
|
120
141
|
token = token[:-1]
|
|
121
142
|
# Clip the % sign off the token
|
|
122
143
|
|
|
144
|
+
if token.endswith(' '):
|
|
145
|
+
# A space is required before params, but this should not be part of the parsed output
|
|
146
|
+
token = token[:-1]
|
|
147
|
+
|
|
123
148
|
if token != '':
|
|
124
149
|
# Make sure not to append an empty token
|
|
125
150
|
tokens.append(token)
|
|
@@ -142,6 +167,37 @@ def parse_proc_code(_proc_code: str) -> list[str, ArgumentType] | None:
|
|
|
142
167
|
|
|
143
168
|
return tokens
|
|
144
169
|
|
|
170
|
+
def construct_proccode(*components: ArgumentType | ArgTypes | Argument | str) -> str:
|
|
171
|
+
"""
|
|
172
|
+
Create a proccode from strings/ArgumentType enum members/Argument instances
|
|
173
|
+
|
|
174
|
+
:param components: list of strings/Arguments/ArgumentType instances
|
|
175
|
+
:return: A proccode, e.g. 'move %s steps' or 'say %s for %n seconds'
|
|
176
|
+
"""
|
|
177
|
+
|
|
178
|
+
result = ""
|
|
179
|
+
|
|
180
|
+
for comp in components:
|
|
181
|
+
if isinstance(comp, ArgumentType):
|
|
182
|
+
result += comp.proc_str
|
|
183
|
+
|
|
184
|
+
elif isinstance(comp, ArgTypes):
|
|
185
|
+
new: ArgumentType = comp.value
|
|
186
|
+
result += new.proc_str
|
|
187
|
+
|
|
188
|
+
elif isinstance(comp, Argument):
|
|
189
|
+
result += comp.type.proc_str
|
|
190
|
+
|
|
191
|
+
elif isinstance(comp, str):
|
|
192
|
+
result += comp
|
|
193
|
+
|
|
194
|
+
else:
|
|
195
|
+
raise TypeError(f"Unsupported component type: {type(comp)}")
|
|
196
|
+
|
|
197
|
+
result += ' '
|
|
198
|
+
|
|
199
|
+
return result
|
|
200
|
+
|
|
145
201
|
|
|
146
202
|
class Mutation(base.BlockSubComponent):
|
|
147
203
|
def __init__(self, _tag_name: str = "mutation", _children: Optional[list] = None, _proc_code: Optional[str] = None,
|
|
@@ -212,7 +268,7 @@ class Mutation(base.BlockSubComponent):
|
|
|
212
268
|
bool(commons.safe_get(self.argument_defaults, 0)))
|
|
213
269
|
|
|
214
270
|
@property
|
|
215
|
-
def parsed_proc_code(self) -> list[str
|
|
271
|
+
def parsed_proc_code(self) -> list[str | ArgumentType] | None:
|
|
216
272
|
"""
|
|
217
273
|
Parse the proc code into arguments & strings
|
|
218
274
|
"""
|
|
@@ -272,7 +328,7 @@ class Mutation(base.BlockSubComponent):
|
|
|
272
328
|
_arg_name = get(_argument_names, i)
|
|
273
329
|
_arg_default = get(_argument_defaults, i)
|
|
274
330
|
|
|
275
|
-
_arguments.append(Argument(_arg_name, _arg_default, _arg_id))
|
|
331
|
+
_arguments.append(Argument(_arg_name, _arg_default, _id=_arg_id))
|
|
276
332
|
|
|
277
333
|
return Mutation(_tag_name, _children, _proc_code, _is_warp, _arguments, _has_next, _argument_settings)
|
|
278
334
|
|
|
@@ -322,3 +378,4 @@ class Mutation(base.BlockSubComponent):
|
|
|
322
378
|
for _argument in self.arguments:
|
|
323
379
|
_argument.mutation = self
|
|
324
380
|
_argument.link_using_mutation()
|
|
381
|
+
|