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/commons.py
CHANGED
|
@@ -6,11 +6,19 @@ from __future__ import annotations
|
|
|
6
6
|
import json
|
|
7
7
|
import random
|
|
8
8
|
import string
|
|
9
|
-
from typing import Optional, Final, Any
|
|
9
|
+
from typing import Optional, Final, Any, TYPE_CHECKING, Union
|
|
10
|
+
from enum import Enum, EnumMeta
|
|
10
11
|
|
|
11
|
-
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from . import sprite, build_defaulting
|
|
12
14
|
|
|
13
|
-
|
|
15
|
+
SpriteInput = Union[sprite.Sprite, build_defaulting._SetSprite]
|
|
16
|
+
else:
|
|
17
|
+
SpriteInput = Any
|
|
18
|
+
|
|
19
|
+
from scratchattach.utils import exceptions
|
|
20
|
+
|
|
21
|
+
DIGITS: Final[tuple[str, ...]] = tuple("0123456789")
|
|
14
22
|
|
|
15
23
|
ID_CHARS: Final[str] = string.ascii_letters + string.digits # + string.punctuation
|
|
16
24
|
|
|
@@ -72,7 +80,8 @@ def _read_json_number(_str: str) -> float | int:
|
|
|
72
80
|
|
|
73
81
|
return json.loads(ret)
|
|
74
82
|
|
|
75
|
-
|
|
83
|
+
# todo: consider if this should be moved to util.commons instead of editor.commons
|
|
84
|
+
# note: this is currently unused code
|
|
76
85
|
def consume_json(_str: str, i: int = 0) -> str | float | int | dict | list | bool | None:
|
|
77
86
|
"""
|
|
78
87
|
*'gobble up some JSON until we hit something not quite so tasty'*
|
|
@@ -134,16 +143,20 @@ def is_partial_json(_str: str, i: int = 0) -> bool:
|
|
|
134
143
|
|
|
135
144
|
|
|
136
145
|
def is_valid_json(_str: Any) -> bool:
|
|
146
|
+
"""
|
|
147
|
+
Try to load a json string, if it fails, return False, else return true.
|
|
148
|
+
"""
|
|
137
149
|
try:
|
|
138
150
|
json.loads(_str)
|
|
139
151
|
return True
|
|
140
|
-
except ValueError:
|
|
141
|
-
return False
|
|
142
|
-
except TypeError:
|
|
152
|
+
except (ValueError, TypeError):
|
|
143
153
|
return False
|
|
144
154
|
|
|
145
155
|
|
|
146
156
|
def noneless_update(obj: dict, update: dict) -> None:
|
|
157
|
+
"""
|
|
158
|
+
equivalent to dict.update, except and values of None are not assigned
|
|
159
|
+
"""
|
|
147
160
|
for key, value in update.items():
|
|
148
161
|
if value is not None:
|
|
149
162
|
obj[key] = value
|
|
@@ -163,6 +176,9 @@ def remove_nones(obj: dict) -> None:
|
|
|
163
176
|
|
|
164
177
|
|
|
165
178
|
def safe_get(lst: list | tuple, _i: int, default: Optional[Any] = None) -> Any:
|
|
179
|
+
"""
|
|
180
|
+
Like dict.get() but for lists
|
|
181
|
+
"""
|
|
166
182
|
if len(lst) <= _i:
|
|
167
183
|
return default
|
|
168
184
|
else:
|
|
@@ -182,7 +198,10 @@ def trim_final_nones(lst: list) -> list:
|
|
|
182
198
|
return lst[:i]
|
|
183
199
|
|
|
184
200
|
|
|
185
|
-
def dumps_ifnn(obj: Any) -> str:
|
|
201
|
+
def dumps_ifnn(obj: Any) -> Optional[str]:
|
|
202
|
+
"""
|
|
203
|
+
Return json.dumps(obj) if the object is not None
|
|
204
|
+
"""
|
|
186
205
|
if obj is None:
|
|
187
206
|
return None
|
|
188
207
|
else:
|
|
@@ -190,9 +209,13 @@ def dumps_ifnn(obj: Any) -> str:
|
|
|
190
209
|
|
|
191
210
|
|
|
192
211
|
def gen_id() -> str:
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
212
|
+
"""
|
|
213
|
+
Generate an id for scratch blocks/variables/lists/broadcasts
|
|
214
|
+
|
|
215
|
+
The old 'naïve' method but that chances of a repeat are so miniscule
|
|
216
|
+
Have to check if whitespace chars break it
|
|
217
|
+
May later add checking within sprites so that we don't need such long ids (we can save space this way)
|
|
218
|
+
"""
|
|
196
219
|
return ''.join(random.choices(ID_CHARS, k=20))
|
|
197
220
|
|
|
198
221
|
|
|
@@ -212,6 +235,9 @@ def sanitize_fn(filename: str):
|
|
|
212
235
|
|
|
213
236
|
|
|
214
237
|
def get_folder_name(name: str) -> str | None:
|
|
238
|
+
"""
|
|
239
|
+
Get the name of the folder if this is a turbowarp-style costume name
|
|
240
|
+
"""
|
|
215
241
|
if name.startswith('//'):
|
|
216
242
|
return None
|
|
217
243
|
|
|
@@ -231,13 +257,50 @@ def get_name_nofldr(name: str) -> str:
|
|
|
231
257
|
else:
|
|
232
258
|
return name[len(fldr) + 2:]
|
|
233
259
|
|
|
260
|
+
# Parent enum class
|
|
261
|
+
class SingletonMeta(EnumMeta):
|
|
234
262
|
|
|
235
|
-
|
|
236
|
-
|
|
263
|
+
def __call__(self, value=0, *args, **kwds):
|
|
264
|
+
if value != 0:
|
|
265
|
+
raise ValueError("Value must be 0.")
|
|
266
|
+
old_bases = self.__bases__
|
|
267
|
+
self.__bases__ = old_bases + (Enum,)
|
|
268
|
+
result = super().__call__(value, *args, **kwds)
|
|
269
|
+
self.__bases__ = old_bases
|
|
270
|
+
return result
|
|
237
271
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
272
|
+
|
|
273
|
+
if TYPE_CHECKING:
|
|
274
|
+
Singleton = Enum
|
|
275
|
+
else:
|
|
276
|
+
class Singleton(metaclass=SingletonMeta):
|
|
277
|
+
|
|
278
|
+
def __new__(cls, val=None):
|
|
279
|
+
if cls is Singleton:
|
|
280
|
+
raise TypeError("Singleton cannot be created directly.")
|
|
281
|
+
if hasattr(cls, "INSTANCE"):
|
|
282
|
+
return getattr(cls, "INSTANCE")
|
|
283
|
+
if val == 0:
|
|
284
|
+
return super().__new__(cls)
|
|
285
|
+
raise TypeError("Has no instance.")
|
|
286
|
+
|
|
287
|
+
def __init__(self, *args, **kwds):
|
|
288
|
+
pass
|
|
289
|
+
|
|
290
|
+
def __repr__(self):
|
|
291
|
+
return self.__class__.__name__
|
|
292
|
+
|
|
293
|
+
def __str__(self):
|
|
294
|
+
return self.__class__.__name__
|
|
295
|
+
|
|
296
|
+
def __format__(self, format_spec):
|
|
297
|
+
return str.__format__(str(self), format_spec)
|
|
298
|
+
|
|
299
|
+
def __hash__(self):
|
|
300
|
+
return hash(self.__class__)
|
|
301
|
+
|
|
302
|
+
def __reduce_ex__(self, proto):
|
|
303
|
+
return self.__class__, ()
|
|
304
|
+
|
|
305
|
+
def __deepcopy__(self, memo):
|
|
306
|
+
return self
|
|
@@ -1,14 +1,21 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Enum & dataclass representing extension categories
|
|
3
|
+
"""
|
|
4
|
+
|
|
1
5
|
from __future__ import annotations
|
|
2
6
|
|
|
3
7
|
|
|
4
8
|
from dataclasses import dataclass
|
|
5
9
|
|
|
6
10
|
from . import base
|
|
7
|
-
from
|
|
11
|
+
from scratchattach.utils import enums
|
|
8
12
|
|
|
9
13
|
|
|
10
|
-
@dataclass
|
|
14
|
+
@dataclass
|
|
11
15
|
class Extension(base.JSONSerializable):
|
|
16
|
+
"""
|
|
17
|
+
Represents an extension in the Scratch block pallete - e.g. video sensing
|
|
18
|
+
"""
|
|
12
19
|
code: str
|
|
13
20
|
name: str = None
|
|
14
21
|
|
|
@@ -40,4 +47,4 @@ class Extensions(enums._EnumWrapper):
|
|
|
40
47
|
TRANSLATE = Extension("translate", "Translate Extension")
|
|
41
48
|
VIDEOSENSING = Extension("videoSensing", "Video Sensing Extension")
|
|
42
49
|
WEDO2 = Extension("wedo2", "LEGO Education WeDo 2.0 Extension")
|
|
43
|
-
COREEXAMPLE = Extension("coreExample", "CoreEx Extension")
|
|
50
|
+
COREEXAMPLE = Extension("coreExample", "CoreEx Extension") # hidden extension!
|
scratchattach/editor/field.py
CHANGED
|
@@ -38,6 +38,9 @@ class Field(base.BlockSubComponent):
|
|
|
38
38
|
|
|
39
39
|
@property
|
|
40
40
|
def value_id(self):
|
|
41
|
+
"""
|
|
42
|
+
Get the id of the value associated with this field (if applicable) - when value is var/list/broadcast
|
|
43
|
+
"""
|
|
41
44
|
if self.id is not None:
|
|
42
45
|
return self.id
|
|
43
46
|
else:
|
|
@@ -48,6 +51,9 @@ class Field(base.BlockSubComponent):
|
|
|
48
51
|
|
|
49
52
|
@property
|
|
50
53
|
def value_str(self):
|
|
54
|
+
"""
|
|
55
|
+
Convert the associated value to a string - if this is a VLB, return the VLB name
|
|
56
|
+
"""
|
|
51
57
|
if not isinstance(self.value, base.NamedIDComponent):
|
|
52
58
|
return self.value
|
|
53
59
|
else:
|
|
@@ -55,6 +61,9 @@ class Field(base.BlockSubComponent):
|
|
|
55
61
|
|
|
56
62
|
@property
|
|
57
63
|
def name(self) -> str:
|
|
64
|
+
"""
|
|
65
|
+
Fetch the name of this field using the associated block
|
|
66
|
+
"""
|
|
58
67
|
for _name, _field in self.block.fields.items():
|
|
59
68
|
if _field is self:
|
|
60
69
|
return _name
|
scratchattach/editor/inputs.py
CHANGED
scratchattach/editor/meta.py
CHANGED
|
@@ -7,7 +7,7 @@ from . import base, commons
|
|
|
7
7
|
from typing import Optional
|
|
8
8
|
|
|
9
9
|
|
|
10
|
-
@dataclass
|
|
10
|
+
@dataclass
|
|
11
11
|
class PlatformMeta(base.JSONSerializable):
|
|
12
12
|
name: str = None
|
|
13
13
|
url: str = field(repr=True, default=None)
|
|
@@ -36,8 +36,13 @@ EDIT_META = True
|
|
|
36
36
|
META_SET_PLATFORM = False
|
|
37
37
|
|
|
38
38
|
|
|
39
|
-
def set_meta_platform(true_false: bool =
|
|
39
|
+
def set_meta_platform(true_false: bool = None):
|
|
40
|
+
"""
|
|
41
|
+
toggle whether to set the meta platform by default (or specify a value)
|
|
42
|
+
"""
|
|
40
43
|
global META_SET_PLATFORM
|
|
44
|
+
if true_false is None:
|
|
45
|
+
true_false = bool(1 - true_false)
|
|
41
46
|
META_SET_PLATFORM = true_false
|
|
42
47
|
|
|
43
48
|
|
|
@@ -69,7 +74,10 @@ class Meta(base.JSONSerializable):
|
|
|
69
74
|
|
|
70
75
|
@property
|
|
71
76
|
def vm_is_valid(self):
|
|
72
|
-
|
|
77
|
+
"""
|
|
78
|
+
Check whether the vm value is valid using a regex
|
|
79
|
+
Thanks to TurboWarp for this pattern ↓↓↓↓, I just copied it
|
|
80
|
+
"""
|
|
73
81
|
return re.match("^([0-9]+\\.[0-9]+\\.[0-9]+)($|-)", self.vm) is not None
|
|
74
82
|
|
|
75
83
|
def to_json(self):
|
scratchattach/editor/monitor.py
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import warnings
|
|
3
4
|
from typing import Optional, TYPE_CHECKING
|
|
4
5
|
|
|
6
|
+
from typing_extensions import deprecated
|
|
7
|
+
|
|
5
8
|
if TYPE_CHECKING:
|
|
9
|
+
from .block import Block
|
|
6
10
|
from . import project
|
|
7
11
|
|
|
8
12
|
from . import base
|
|
@@ -26,6 +30,8 @@ class Monitor(base.ProjectSubcomponent):
|
|
|
26
30
|
"""
|
|
27
31
|
Represents a variable/list monitor
|
|
28
32
|
https://en.scratch-wiki.info/wiki/Scratch_File_Format#Monitors
|
|
33
|
+
|
|
34
|
+
Instantiating these yourself and attaching these to projects can lead to interesting results!
|
|
29
35
|
"""
|
|
30
36
|
assert isinstance(reporter, base.SpriteSubComponent) or reporter is None
|
|
31
37
|
|
|
@@ -135,41 +141,43 @@ class Monitor(base.ProjectSubcomponent):
|
|
|
135
141
|
self.reporter = new_vlb
|
|
136
142
|
self.reporter_id = None
|
|
137
143
|
|
|
138
|
-
#
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
144
|
+
# todo: consider reimplementing this
|
|
145
|
+
@deprecated("This method does not work correctly (This may be fixed in the future)")
|
|
146
|
+
@staticmethod
|
|
147
|
+
def from_reporter(reporter: "Block", _id: str = None, mode: str = "default",
|
|
148
|
+
opcode: str = None, sprite_name: str = None, value=0, width: int | float = 0,
|
|
149
|
+
height: int | float = 0,
|
|
150
|
+
x: int | float = 5, y: int | float = 5, visible: bool = False, slider_min: int | float = 0,
|
|
151
|
+
slider_max: int | float = 100, is_discrete: bool = True, params: dict = None):
|
|
152
|
+
if "reporter" not in reporter.stack_type:
|
|
153
|
+
warnings.warn(f"{reporter} is not a reporter block; the monitor will return '0'")
|
|
154
|
+
elif "(menu)" in reporter.stack_type:
|
|
155
|
+
warnings.warn(f"{reporter} is a menu block; the monitor will return '0'")
|
|
156
|
+
# Maybe add note that length of list doesn't work fsr?? idk
|
|
157
|
+
if _id is None:
|
|
158
|
+
_id = reporter.opcode
|
|
159
|
+
if opcode is None:
|
|
160
|
+
opcode = reporter.opcode # .replace('_', ' ')
|
|
161
|
+
|
|
162
|
+
if params is None:
|
|
163
|
+
params = {}
|
|
164
|
+
for field in reporter.fields:
|
|
165
|
+
if field.value_id is None:
|
|
166
|
+
params[field.id] = field.value
|
|
167
|
+
else:
|
|
168
|
+
params[field.id] = field.value, field.value_id
|
|
169
|
+
|
|
170
|
+
return Monitor(
|
|
171
|
+
_id,
|
|
172
|
+
mode,
|
|
173
|
+
opcode,
|
|
174
|
+
|
|
175
|
+
params,
|
|
176
|
+
sprite_name,
|
|
177
|
+
value,
|
|
178
|
+
|
|
179
|
+
width, height,
|
|
180
|
+
x, y,
|
|
181
|
+
visible,
|
|
182
|
+
slider_min, slider_max, is_discrete
|
|
183
|
+
)
|
scratchattach/editor/mutation.py
CHANGED
|
@@ -6,13 +6,13 @@ from dataclasses import dataclass
|
|
|
6
6
|
from typing import Optional, TYPE_CHECKING, Iterable, Any
|
|
7
7
|
|
|
8
8
|
from . import base, commons
|
|
9
|
-
from
|
|
9
|
+
from scratchattach.utils import enums
|
|
10
10
|
|
|
11
11
|
if TYPE_CHECKING:
|
|
12
12
|
from . import block
|
|
13
13
|
|
|
14
14
|
|
|
15
|
-
@dataclass
|
|
15
|
+
@dataclass
|
|
16
16
|
class ArgumentType(base.Base):
|
|
17
17
|
type: str
|
|
18
18
|
proc_str: str
|
|
@@ -38,7 +38,7 @@ class ArgumentType(base.Base):
|
|
|
38
38
|
return None
|
|
39
39
|
|
|
40
40
|
|
|
41
|
-
@dataclass
|
|
41
|
+
@dataclass
|
|
42
42
|
class ArgSettings(base.Base):
|
|
43
43
|
ids: bool
|
|
44
44
|
names: bool
|
|
@@ -61,7 +61,7 @@ class ArgSettings(base.Base):
|
|
|
61
61
|
return int(self) > int(other)
|
|
62
62
|
|
|
63
63
|
|
|
64
|
-
@dataclass
|
|
64
|
+
@dataclass
|
|
65
65
|
class Argument(base.MutationSubComponent):
|
|
66
66
|
name: str
|
|
67
67
|
default: str = ''
|
|
@@ -103,6 +103,10 @@ class ArgTypes(enums._EnumWrapper):
|
|
|
103
103
|
|
|
104
104
|
|
|
105
105
|
def parse_proc_code(_proc_code: str) -> list[str, ArgumentType] | None:
|
|
106
|
+
"""
|
|
107
|
+
Parse a proccode (part of a mutation) into argument types and strings
|
|
108
|
+
"""
|
|
109
|
+
|
|
106
110
|
if _proc_code is None:
|
|
107
111
|
return None
|
|
108
112
|
token = ''
|
|
@@ -209,6 +213,9 @@ class Mutation(base.BlockSubComponent):
|
|
|
209
213
|
|
|
210
214
|
@property
|
|
211
215
|
def parsed_proc_code(self) -> list[str, ArgumentType] | None:
|
|
216
|
+
"""
|
|
217
|
+
Parse the proc code into arguments & strings
|
|
218
|
+
"""
|
|
212
219
|
return parse_proc_code(self.proc_code)
|
|
213
220
|
|
|
214
221
|
@staticmethod
|
scratchattach/editor/pallete.py
CHANGED
|
@@ -6,43 +6,42 @@ May want to completely change this later
|
|
|
6
6
|
"""
|
|
7
7
|
from __future__ import annotations
|
|
8
8
|
|
|
9
|
-
from dataclasses import dataclass
|
|
9
|
+
from dataclasses import dataclass, field
|
|
10
|
+
from typing import Optional
|
|
10
11
|
|
|
11
12
|
from . import prim
|
|
12
|
-
from
|
|
13
|
+
from scratchattach.utils.enums import _EnumWrapper
|
|
13
14
|
|
|
14
15
|
|
|
15
|
-
@dataclass
|
|
16
|
+
@dataclass
|
|
16
17
|
class FieldUsage:
|
|
17
18
|
name: str
|
|
18
|
-
value_type: prim.PrimTypes = None
|
|
19
|
+
value_type: Optional[prim.PrimTypes] = None
|
|
19
20
|
|
|
20
21
|
|
|
21
|
-
@dataclass
|
|
22
|
+
@dataclass
|
|
22
23
|
class SpecialFieldUsage(FieldUsage):
|
|
23
24
|
name: str
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
attrs = []
|
|
25
|
+
value_type: None = None # Order cannot be changed
|
|
26
|
+
attrs: list[str] = field(default_factory=list)
|
|
27
27
|
|
|
28
|
-
value_type: None = None
|
|
29
28
|
|
|
30
29
|
|
|
31
|
-
@dataclass
|
|
30
|
+
@dataclass
|
|
32
31
|
class InputUsage:
|
|
33
32
|
name: str
|
|
34
|
-
value_type: prim.PrimTypes = None
|
|
35
|
-
default_obscurer: BlockUsage = None
|
|
33
|
+
value_type: Optional[prim.PrimTypes] = None
|
|
34
|
+
default_obscurer: Optional[BlockUsage] = None
|
|
36
35
|
|
|
37
36
|
|
|
38
|
-
@dataclass
|
|
37
|
+
@dataclass
|
|
39
38
|
class BlockUsage:
|
|
40
39
|
opcode: str
|
|
41
|
-
fields: list[FieldUsage] = None
|
|
40
|
+
fields: Optional[list[FieldUsage]] = None
|
|
42
41
|
if fields is None:
|
|
43
42
|
fields = []
|
|
44
43
|
|
|
45
|
-
inputs: list[InputUsage] = None
|
|
44
|
+
inputs: Optional[list[InputUsage]] = None
|
|
46
45
|
if inputs is None:
|
|
47
46
|
inputs = []
|
|
48
47
|
|
|
@@ -51,41 +50,41 @@ class BlockUsages(_EnumWrapper):
|
|
|
51
50
|
# Special Enum blocks
|
|
52
51
|
MATH_NUMBER = BlockUsage(
|
|
53
52
|
"math_number",
|
|
54
|
-
[SpecialFieldUsage("NUM", ["name", "value"])]
|
|
53
|
+
[SpecialFieldUsage("NUM", attrs=["name", "value"])]
|
|
55
54
|
)
|
|
56
55
|
MATH_POSITIVE_NUMBER = BlockUsage(
|
|
57
56
|
"math_positive_number",
|
|
58
|
-
[SpecialFieldUsage("NUM", ["name", "value"])]
|
|
57
|
+
[SpecialFieldUsage("NUM", attrs=["name", "value"])]
|
|
59
58
|
)
|
|
60
59
|
MATH_WHOLE_NUMBER = BlockUsage(
|
|
61
60
|
"math_whole_number",
|
|
62
|
-
[SpecialFieldUsage("NUM", ["name", "value"])]
|
|
61
|
+
[SpecialFieldUsage("NUM", attrs=["name", "value"])]
|
|
63
62
|
)
|
|
64
63
|
MATH_INTEGER = BlockUsage(
|
|
65
64
|
"math_integer",
|
|
66
|
-
[SpecialFieldUsage("NUM", ["name", "value"])]
|
|
65
|
+
[SpecialFieldUsage("NUM", attrs=["name", "value"])]
|
|
67
66
|
)
|
|
68
67
|
MATH_ANGLE = BlockUsage(
|
|
69
68
|
"math_angle",
|
|
70
|
-
[SpecialFieldUsage("NUM", ["name", "value"])]
|
|
69
|
+
[SpecialFieldUsage("NUM", attrs=["name", "value"])]
|
|
71
70
|
)
|
|
72
71
|
COLOUR_PICKER = BlockUsage(
|
|
73
72
|
"colour_picker",
|
|
74
|
-
[SpecialFieldUsage("COLOUR", ["name", "value"])]
|
|
73
|
+
[SpecialFieldUsage("COLOUR", attrs=["name", "value"])]
|
|
75
74
|
)
|
|
76
75
|
TEXT = BlockUsage(
|
|
77
76
|
"text",
|
|
78
|
-
[SpecialFieldUsage("TEXT", ["name", "value"])]
|
|
77
|
+
[SpecialFieldUsage("TEXT", attrs=["name", "value"])]
|
|
79
78
|
)
|
|
80
79
|
EVENT_BROADCAST_MENU = BlockUsage(
|
|
81
80
|
"event_broadcast_menu",
|
|
82
|
-
[SpecialFieldUsage("BROADCAST_OPTION", ["name", "id", "value", "variableType"])]
|
|
81
|
+
[SpecialFieldUsage("BROADCAST_OPTION", attrs=["name", "id", "value", "variableType"])]
|
|
83
82
|
)
|
|
84
83
|
DATA_VARIABLE = BlockUsage(
|
|
85
84
|
"data_variable",
|
|
86
|
-
[SpecialFieldUsage("VARIABLE", ["name", "id", "value", "variableType"])]
|
|
85
|
+
[SpecialFieldUsage("VARIABLE", attrs=["name", "id", "value", "variableType"])]
|
|
87
86
|
)
|
|
88
87
|
DATA_LISTCONTENTS = BlockUsage(
|
|
89
88
|
"data_listcontents",
|
|
90
|
-
[SpecialFieldUsage("LIST", ["name", "id", "value", "variableType"])]
|
|
89
|
+
[SpecialFieldUsage("LIST", attrs=["name", "id", "value", "variableType"])]
|
|
91
90
|
)
|
scratchattach/editor/prim.py
CHANGED
|
@@ -5,10 +5,10 @@ from dataclasses import dataclass
|
|
|
5
5
|
from typing import Optional, Callable, Final
|
|
6
6
|
|
|
7
7
|
from . import base, sprite, vlb, commons, build_defaulting
|
|
8
|
-
from
|
|
8
|
+
from scratchattach.utils import enums, exceptions
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
@dataclass
|
|
11
|
+
@dataclass
|
|
12
12
|
class PrimType(base.JSONSerializable):
|
|
13
13
|
code: int
|
|
14
14
|
name: str
|
scratchattach/editor/project.py
CHANGED
|
@@ -8,12 +8,15 @@ from typing import Optional, Iterable, Generator, BinaryIO
|
|
|
8
8
|
from zipfile import ZipFile
|
|
9
9
|
|
|
10
10
|
from . import base, meta, extension, monitor, sprite, asset, vlb, twconfig, comment, commons
|
|
11
|
-
from
|
|
12
|
-
from
|
|
13
|
-
from
|
|
11
|
+
from scratchattach.site import session
|
|
12
|
+
from scratchattach.site.project import get_project
|
|
13
|
+
from scratchattach.utils import exceptions
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
class Project(base.JSONExtractable):
|
|
17
|
+
"""
|
|
18
|
+
sa.editor's equivalent of the ProjectBody. Represents the editor contents of a scratch project
|
|
19
|
+
"""
|
|
17
20
|
def __init__(self, _name: Optional[str] = None, _meta: Optional[meta.Meta] = None, _extensions: Iterable[extension.Extension] = (),
|
|
18
21
|
_monitors: Iterable[monitor.Monitor] = (), _sprites: Iterable[sprite.Sprite] = (), *,
|
|
19
22
|
_asset_data: Optional[list[asset.AssetFile]] = None, _session: Optional[session.Session] = None):
|
|
@@ -268,6 +271,9 @@ class Project(base.JSONExtractable):
|
|
|
268
271
|
os.system(f"explorer.exe \"{fp}\"")
|
|
269
272
|
|
|
270
273
|
def add_monitor(self, _monitor: monitor.Monitor) -> monitor.Monitor:
|
|
274
|
+
"""
|
|
275
|
+
Bind a monitor to this project. Doing these manually can lead to interesting results.
|
|
276
|
+
"""
|
|
271
277
|
_monitor.project = self
|
|
272
278
|
_monitor.reporter_id = self.new_id
|
|
273
279
|
self.monitors.append(_monitor)
|
scratchattach/editor/sprite.py
CHANGED
|
@@ -11,6 +11,8 @@ if TYPE_CHECKING:
|
|
|
11
11
|
from . import asset
|
|
12
12
|
|
|
13
13
|
class Sprite(base.ProjectSubcomponent, base.JSONExtractable):
|
|
14
|
+
_local_globals: list[base.NamedIDComponent]
|
|
15
|
+
asset_data: list[asset.AssetFile]
|
|
14
16
|
def __init__(self, is_stage: bool = False, name: str = '', _current_costume: int = 1, _layer_order: Optional[int] = None,
|
|
15
17
|
_volume: int = 100,
|
|
16
18
|
_broadcasts: Optional[list[vlb.Broadcast]] = None,
|
|
@@ -120,6 +122,9 @@ class Sprite(base.ProjectSubcomponent, base.JSONExtractable):
|
|
|
120
122
|
_comment.link_using_sprite()
|
|
121
123
|
|
|
122
124
|
def add_local_global(self, _vlb: base.NamedIDComponent):
|
|
125
|
+
"""
|
|
126
|
+
Add a global variable/list to this sprite (for when an overarching project/stage is not available)
|
|
127
|
+
"""
|
|
123
128
|
self._local_globals.append(_vlb)
|
|
124
129
|
_vlb.sprite = self
|
|
125
130
|
|
|
@@ -128,11 +133,11 @@ class Sprite(base.ProjectSubcomponent, base.JSONExtractable):
|
|
|
128
133
|
_variable.sprite = self
|
|
129
134
|
|
|
130
135
|
def add_list(self, _list: vlb.List):
|
|
131
|
-
self.
|
|
136
|
+
self.lists.append(_list)
|
|
132
137
|
_list.sprite = self
|
|
133
138
|
|
|
134
139
|
def add_broadcast(self, _broadcast: vlb.Broadcast):
|
|
135
|
-
self.
|
|
140
|
+
self.broadcasts.append(_broadcast)
|
|
136
141
|
_broadcast.sprite = self
|
|
137
142
|
|
|
138
143
|
def add_vlb(self, _vlb: base.NamedIDComponent):
|
|
@@ -151,18 +156,20 @@ class Sprite(base.ProjectSubcomponent, base.JSONExtractable):
|
|
|
151
156
|
return _block
|
|
152
157
|
|
|
153
158
|
_block.sprite = self
|
|
159
|
+
new_id = self.new_id
|
|
154
160
|
|
|
155
161
|
if isinstance(_block, block.Block):
|
|
156
|
-
self.blocks[
|
|
162
|
+
self.blocks[new_id] = _block
|
|
163
|
+
_block.id = new_id
|
|
157
164
|
_block.link_using_sprite()
|
|
158
165
|
|
|
159
166
|
elif isinstance(_block, prim.Prim):
|
|
160
|
-
self.prims[
|
|
167
|
+
self.prims[new_id] = _block
|
|
161
168
|
_block.link_using_sprite()
|
|
162
169
|
|
|
163
170
|
return _block
|
|
164
171
|
|
|
165
|
-
def add_chain(self, *chain:
|
|
172
|
+
def add_chain(self, *chain: block.Block | prim.Prim) -> block.Block | prim.Prim:
|
|
166
173
|
"""
|
|
167
174
|
Adds a list of blocks to the sprite **AND RETURNS THE FIRST BLOCK**
|
|
168
175
|
:param chain:
|
|
@@ -173,6 +180,8 @@ class Sprite(base.ProjectSubcomponent, base.JSONExtractable):
|
|
|
173
180
|
_prev = self.add_block(chain[0])
|
|
174
181
|
|
|
175
182
|
for _block in chain[1:]:
|
|
183
|
+
if not isinstance(_prev, block.Block) or not isinstance(_block, block.Block):
|
|
184
|
+
continue
|
|
176
185
|
_prev = _prev.attach_block(_block)
|
|
177
186
|
|
|
178
187
|
return chain[0]
|
|
@@ -206,7 +215,11 @@ class Sprite(base.ProjectSubcomponent, base.JSONExtractable):
|
|
|
206
215
|
"""
|
|
207
216
|
:return: All vlbs associated with the sprite. No local globals are added
|
|
208
217
|
"""
|
|
209
|
-
|
|
218
|
+
vlbs: list[base.NamedIDComponent] = []
|
|
219
|
+
vlbs.extend(self.variables)
|
|
220
|
+
vlbs.extend(self.lists)
|
|
221
|
+
vlbs.extend(self.broadcasts)
|
|
222
|
+
return vlbs
|
|
210
223
|
|
|
211
224
|
@property
|
|
212
225
|
def assets(self) -> list[asset.Costume | asset.Sound]:
|