scratchattach 3.0.0b0__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
- cloud/__init__.py +2 -0
- cloud/_base.py +483 -0
- cloud/cloud.py +183 -0
- editor/__init__.py +22 -0
- editor/asset.py +265 -0
- editor/backpack_json.py +115 -0
- editor/base.py +191 -0
- editor/block.py +584 -0
- editor/blockshape.py +357 -0
- editor/build_defaulting.py +51 -0
- editor/code_translation/__init__.py +0 -0
- editor/code_translation/parse.py +177 -0
- editor/comment.py +80 -0
- editor/commons.py +145 -0
- editor/extension.py +50 -0
- editor/field.py +99 -0
- editor/inputs.py +138 -0
- editor/meta.py +117 -0
- editor/monitor.py +185 -0
- editor/mutation.py +381 -0
- editor/pallete.py +88 -0
- editor/prim.py +174 -0
- editor/project.py +381 -0
- editor/sprite.py +609 -0
- editor/twconfig.py +114 -0
- editor/vlb.py +134 -0
- eventhandlers/__init__.py +0 -0
- eventhandlers/_base.py +101 -0
- eventhandlers/cloud_events.py +130 -0
- eventhandlers/cloud_recorder.py +26 -0
- eventhandlers/cloud_requests.py +544 -0
- eventhandlers/cloud_server.py +249 -0
- eventhandlers/cloud_storage.py +135 -0
- eventhandlers/combine.py +30 -0
- eventhandlers/filterbot.py +163 -0
- eventhandlers/message_events.py +42 -0
- other/__init__.py +0 -0
- other/other_apis.py +598 -0
- other/project_json_capabilities.py +475 -0
- {scratchattach-3.0.0b0.dist-info → scratchattach-3.0.0b1.dist-info}/METADATA +1 -1
- scratchattach-3.0.0b1.dist-info/RECORD +79 -0
- scratchattach-3.0.0b1.dist-info/top_level.txt +7 -0
- site/__init__.py +0 -0
- site/_base.py +93 -0
- site/activity.py +426 -0
- site/alert.py +226 -0
- site/backpack_asset.py +119 -0
- site/browser_cookie3_stub.py +17 -0
- site/browser_cookies.py +61 -0
- site/classroom.py +454 -0
- site/cloud_activity.py +121 -0
- site/comment.py +228 -0
- site/forum.py +436 -0
- site/placeholder.py +132 -0
- site/project.py +932 -0
- site/session.py +1323 -0
- site/studio.py +704 -0
- site/typed_dicts.py +151 -0
- site/user.py +1252 -0
- utils/__init__.py +0 -0
- utils/commons.py +263 -0
- utils/encoder.py +161 -0
- utils/enums.py +237 -0
- utils/exceptions.py +277 -0
- utils/optional_async.py +154 -0
- utils/requests.py +306 -0
- scratchattach/__init__.py +0 -37
- scratchattach/__main__.py +0 -93
- scratchattach-3.0.0b0.dist-info/RECORD +0 -8
- scratchattach-3.0.0b0.dist-info/top_level.txt +0 -1
- {scratchattach-3.0.0b0.dist-info → scratchattach-3.0.0b1.dist-info}/WHEEL +0 -0
- {scratchattach-3.0.0b0.dist-info → scratchattach-3.0.0b1.dist-info}/entry_points.txt +0 -0
- {scratchattach-3.0.0b0.dist-info → scratchattach-3.0.0b1.dist-info}/licenses/LICENSE +0 -0
editor/monitor.py
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Optional, TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
from typing_extensions import deprecated
|
|
6
|
+
import warnings
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from . import project
|
|
10
|
+
|
|
11
|
+
from . import base, block
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Monitor(base.ProjectSubcomponent):
|
|
15
|
+
def __init__(self, reporter: Optional[base.NamedIDComponent] = None,
|
|
16
|
+
mode: str = "default",
|
|
17
|
+
opcode: str = "data_variable",
|
|
18
|
+
params: Optional[dict] = None,
|
|
19
|
+
sprite_name: Optional[str] = None,
|
|
20
|
+
value=0,
|
|
21
|
+
width: int | float = 0,
|
|
22
|
+
height: int | float = 0,
|
|
23
|
+
x: int | float = 5,
|
|
24
|
+
y: int | float = 5,
|
|
25
|
+
visible: bool = False,
|
|
26
|
+
slider_min: int | float = 0,
|
|
27
|
+
slider_max: int | float = 100,
|
|
28
|
+
is_discrete: bool = True, *, reporter_id: Optional[str] = None, _project: Optional[project.Project] = None):
|
|
29
|
+
"""
|
|
30
|
+
Represents a variable/list monitor
|
|
31
|
+
https://en.scratch-wiki.info/wiki/Scratch_File_Format#Monitors
|
|
32
|
+
|
|
33
|
+
Instantiating these yourself and attaching these to projects can lead to interesting results!
|
|
34
|
+
"""
|
|
35
|
+
assert isinstance(reporter, base.SpriteSubComponent) or reporter is None
|
|
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
|
+
|
|
40
|
+
self.reporter_id = reporter_id
|
|
41
|
+
"""
|
|
42
|
+
ID referencing the VLB being referenced. Replaced with None during project instantiation, where the reporter attribute is updated
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
self.reporter = reporter
|
|
46
|
+
if params is None:
|
|
47
|
+
params = {}
|
|
48
|
+
|
|
49
|
+
self.mode = mode
|
|
50
|
+
|
|
51
|
+
self.opcode = opcode
|
|
52
|
+
self.params = params
|
|
53
|
+
|
|
54
|
+
self.sprite_name = sprite_name
|
|
55
|
+
|
|
56
|
+
self.value = value
|
|
57
|
+
|
|
58
|
+
self.width, self.height = width, height
|
|
59
|
+
self.x, self.y = x, y
|
|
60
|
+
|
|
61
|
+
self.visible = visible
|
|
62
|
+
|
|
63
|
+
self.slider_min, self.slider_max = slider_min, slider_max
|
|
64
|
+
self.is_discrete = is_discrete
|
|
65
|
+
|
|
66
|
+
super().__init__(_project)
|
|
67
|
+
|
|
68
|
+
def __repr__(self):
|
|
69
|
+
return f"Monitor<{self.opcode}>"
|
|
70
|
+
|
|
71
|
+
@property
|
|
72
|
+
def id(self):
|
|
73
|
+
if self.reporter is not None:
|
|
74
|
+
return self.reporter.id
|
|
75
|
+
# if isinstance(self.reporter, str):
|
|
76
|
+
# return self.reporter
|
|
77
|
+
# else:
|
|
78
|
+
# return self.reporter.id
|
|
79
|
+
else:
|
|
80
|
+
return self.reporter_id
|
|
81
|
+
|
|
82
|
+
@staticmethod
|
|
83
|
+
def from_json(data: dict):
|
|
84
|
+
_id = data["id"]
|
|
85
|
+
# ^^ NEED TO FIND REPORTER OBJECT
|
|
86
|
+
|
|
87
|
+
mode = data["mode"]
|
|
88
|
+
|
|
89
|
+
opcode = data["opcode"]
|
|
90
|
+
params: dict = data["params"]
|
|
91
|
+
|
|
92
|
+
sprite_name = data["spriteName"]
|
|
93
|
+
|
|
94
|
+
value = data["value"]
|
|
95
|
+
|
|
96
|
+
width, height = data["width"], data["height"]
|
|
97
|
+
x, y = data["x"], data["y"]
|
|
98
|
+
|
|
99
|
+
visible = data["visible"]
|
|
100
|
+
|
|
101
|
+
if "isDiscrete" in data.keys():
|
|
102
|
+
slider_min, slider_max = data["sliderMin"], data["sliderMax"]
|
|
103
|
+
is_discrete = data["isDiscrete"]
|
|
104
|
+
else:
|
|
105
|
+
slider_min, slider_max, is_discrete = None, None, None
|
|
106
|
+
|
|
107
|
+
return Monitor(None, mode, opcode, params, sprite_name, value, width, height, x, y, visible, slider_min,
|
|
108
|
+
slider_max, is_discrete, reporter_id=_id)
|
|
109
|
+
|
|
110
|
+
def to_json(self):
|
|
111
|
+
_json = {
|
|
112
|
+
"id": self.id,
|
|
113
|
+
"mode": self.mode,
|
|
114
|
+
|
|
115
|
+
"opcode": self.opcode,
|
|
116
|
+
"params": self.params,
|
|
117
|
+
|
|
118
|
+
"spriteName": self.sprite_name,
|
|
119
|
+
|
|
120
|
+
"value": self.value,
|
|
121
|
+
|
|
122
|
+
"width": self.width,
|
|
123
|
+
"height": self.height,
|
|
124
|
+
|
|
125
|
+
"x": self.x,
|
|
126
|
+
"y": self.y,
|
|
127
|
+
|
|
128
|
+
"visible": self.visible
|
|
129
|
+
}
|
|
130
|
+
if self.is_discrete is not None:
|
|
131
|
+
_json["sliderMin"] = self.slider_min
|
|
132
|
+
_json["sliderMax"] = self.slider_max
|
|
133
|
+
_json["isDiscrete"] = self.is_discrete
|
|
134
|
+
|
|
135
|
+
return _json
|
|
136
|
+
|
|
137
|
+
def link_using_project(self):
|
|
138
|
+
assert self.project is not None
|
|
139
|
+
|
|
140
|
+
if self.opcode in ("data_variable", "data_listcontents", "event_broadcast_menu"):
|
|
141
|
+
new_vlb = self.project.find_vlb(self.reporter_id, "id")
|
|
142
|
+
if new_vlb is not None:
|
|
143
|
+
self.reporter = new_vlb
|
|
144
|
+
self.reporter_id = None
|
|
145
|
+
|
|
146
|
+
# todo: consider reimplementing this
|
|
147
|
+
@staticmethod
|
|
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",
|
|
150
|
+
opcode: str = None, sprite_name: str = None, value=0, width: int | float = 0,
|
|
151
|
+
height: int | float = 0,
|
|
152
|
+
x: int | float = 5, y: int | float = 5, visible: bool = False, slider_min: int | float = 0,
|
|
153
|
+
slider_max: int | float = 100, is_discrete: bool = True, params: dict = None):
|
|
154
|
+
if not reporter.block_shape.is_reporter:
|
|
155
|
+
warnings.warn(f"{reporter} is not a reporter block; the monitor will return '0'")
|
|
156
|
+
elif reporter.block_shape.is_menu:
|
|
157
|
+
warnings.warn(f"{reporter} is a menu block; the monitor will return '0'")
|
|
158
|
+
# Maybe add note that length of list doesn't work fsr?? idk
|
|
159
|
+
if _id is None:
|
|
160
|
+
_id = reporter.opcode
|
|
161
|
+
if opcode is None:
|
|
162
|
+
opcode = reporter.opcode # .replace('_', ' ')
|
|
163
|
+
|
|
164
|
+
if params is None:
|
|
165
|
+
params = {}
|
|
166
|
+
for field in reporter.fields:
|
|
167
|
+
if field.value_id is None:
|
|
168
|
+
params[field.id] = field.value
|
|
169
|
+
else:
|
|
170
|
+
params[field.id] = field.value, field.value_id
|
|
171
|
+
|
|
172
|
+
return Monitor(
|
|
173
|
+
reporter,
|
|
174
|
+
mode,
|
|
175
|
+
opcode,
|
|
176
|
+
|
|
177
|
+
params,
|
|
178
|
+
sprite_name,
|
|
179
|
+
value,
|
|
180
|
+
|
|
181
|
+
width, height,
|
|
182
|
+
x, y,
|
|
183
|
+
visible,
|
|
184
|
+
slider_min, slider_max, is_discrete
|
|
185
|
+
)
|
editor/mutation.py
ADDED
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Contains the mutation class and handlers for Arguments/ArgumentTypes/ArgSettings, and utilities for handling proc codes
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
import warnings
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
from typing import Optional, TYPE_CHECKING, Iterable, Any
|
|
11
|
+
|
|
12
|
+
from . import base, commons
|
|
13
|
+
from scratchattach.utils import enums
|
|
14
|
+
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
from . import block
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass
|
|
20
|
+
class ArgumentType(base.Base):
|
|
21
|
+
type: str
|
|
22
|
+
proc_str: str
|
|
23
|
+
|
|
24
|
+
def __eq__(self, other):
|
|
25
|
+
# noinspection PyProtectedMember
|
|
26
|
+
if isinstance(other, enums._EnumWrapper):
|
|
27
|
+
other = other.value
|
|
28
|
+
|
|
29
|
+
assert isinstance(other, ArgumentType)
|
|
30
|
+
|
|
31
|
+
return self.type == other.type
|
|
32
|
+
|
|
33
|
+
def __repr__(self):
|
|
34
|
+
return f"<ArgType {self.type!r}>"
|
|
35
|
+
|
|
36
|
+
@property
|
|
37
|
+
def default(self) -> str | None:
|
|
38
|
+
if self.proc_str == "%b":
|
|
39
|
+
return "false"
|
|
40
|
+
elif self.proc_str == "%s":
|
|
41
|
+
return ''
|
|
42
|
+
else:
|
|
43
|
+
return None
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@dataclass
|
|
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
|
+
"""
|
|
51
|
+
ids: bool
|
|
52
|
+
names: bool
|
|
53
|
+
defaults: bool
|
|
54
|
+
|
|
55
|
+
def __int__(self):
|
|
56
|
+
return (int(self.ids) +
|
|
57
|
+
int(self.names) +
|
|
58
|
+
int(self.defaults))
|
|
59
|
+
|
|
60
|
+
def __eq__(self, other):
|
|
61
|
+
return (self.ids == other.ids and
|
|
62
|
+
self.names == other.names and
|
|
63
|
+
self.defaults == other.defaults)
|
|
64
|
+
|
|
65
|
+
def __gt__(self, other):
|
|
66
|
+
return int(self) > int(other)
|
|
67
|
+
|
|
68
|
+
def __lt__(self, other):
|
|
69
|
+
return int(self) > int(other)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@dataclass
|
|
73
|
+
class Argument(base.MutationSubComponent):
|
|
74
|
+
name: str
|
|
75
|
+
default: str = ''
|
|
76
|
+
_type: Optional[ArgumentType] = None
|
|
77
|
+
|
|
78
|
+
_id: str = None
|
|
79
|
+
"""
|
|
80
|
+
Argument ID: Will be used to replace other parameters during block instantiation.
|
|
81
|
+
"""
|
|
82
|
+
|
|
83
|
+
def __post_init__(self):
|
|
84
|
+
super().__init__()
|
|
85
|
+
|
|
86
|
+
@property
|
|
87
|
+
def index(self):
|
|
88
|
+
return self.mutation.arguments.index(self)
|
|
89
|
+
|
|
90
|
+
@property
|
|
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
|
|
107
|
+
|
|
108
|
+
@staticmethod
|
|
109
|
+
def from_json(data: dict | list | Any):
|
|
110
|
+
warnings.warn("No from_json method defined for Arguments (yet?)")
|
|
111
|
+
|
|
112
|
+
def to_json(self) -> dict | list | Any:
|
|
113
|
+
warnings.warn("No to_json method defined for Arguments (yet?)")
|
|
114
|
+
|
|
115
|
+
def link_using_mutation(self):
|
|
116
|
+
if self._id is None:
|
|
117
|
+
self._id = self.block.new_id
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
# noinspection PyProtectedMember
|
|
121
|
+
class ArgTypes(enums._EnumWrapper):
|
|
122
|
+
BOOLEAN = ArgumentType("boolean", "%b")
|
|
123
|
+
NUMBER_OR_TEXT = ArgumentType("number or text", "%s")
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def parse_proc_code(_proc_code: str) -> Optional[list[str | ArgumentType]]:
|
|
127
|
+
"""
|
|
128
|
+
Parse a proccode (part of a mutation) into argument types and strings
|
|
129
|
+
"""
|
|
130
|
+
|
|
131
|
+
if _proc_code is None:
|
|
132
|
+
return None
|
|
133
|
+
token = ''
|
|
134
|
+
tokens = []
|
|
135
|
+
|
|
136
|
+
last_char = ''
|
|
137
|
+
for char in _proc_code:
|
|
138
|
+
if last_char == '%':
|
|
139
|
+
if char in "sb":
|
|
140
|
+
# If we've hit an %s or %b
|
|
141
|
+
token = token[:-1]
|
|
142
|
+
# Clip the % sign off the token
|
|
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
|
+
|
|
148
|
+
if token != '':
|
|
149
|
+
# Make sure not to append an empty token
|
|
150
|
+
tokens.append(token)
|
|
151
|
+
|
|
152
|
+
# Add the parameter token
|
|
153
|
+
token = f"%{char}"
|
|
154
|
+
if token == "%b":
|
|
155
|
+
tokens.append(ArgTypes.BOOLEAN.value.dcopy())
|
|
156
|
+
elif token == "%s":
|
|
157
|
+
tokens.append(ArgTypes.NUMBER_OR_TEXT.value.dcopy())
|
|
158
|
+
|
|
159
|
+
token = ''
|
|
160
|
+
continue
|
|
161
|
+
|
|
162
|
+
token += char
|
|
163
|
+
last_char = char
|
|
164
|
+
|
|
165
|
+
if token != '':
|
|
166
|
+
tokens.append(token)
|
|
167
|
+
|
|
168
|
+
return tokens
|
|
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
|
+
|
|
201
|
+
|
|
202
|
+
class Mutation(base.BlockSubComponent):
|
|
203
|
+
def __init__(self, _tag_name: str = "mutation", _children: Optional[list] = None, _proc_code: Optional[str] = None,
|
|
204
|
+
_is_warp: Optional[bool] = None, _arguments: Optional[list[Argument]] = None, _has_next: Optional[bool] = None,
|
|
205
|
+
_argument_settings: Optional[ArgSettings] = None, *,
|
|
206
|
+
_block: Optional[block.Block] = None):
|
|
207
|
+
"""
|
|
208
|
+
Mutation for Control:stop block and procedures
|
|
209
|
+
https://en.scratch-wiki.info/wiki/Scratch_File_Format#Mutations
|
|
210
|
+
"""
|
|
211
|
+
# Defaulting for args
|
|
212
|
+
if _children is None:
|
|
213
|
+
_children = []
|
|
214
|
+
|
|
215
|
+
if _argument_settings is None:
|
|
216
|
+
if _arguments:
|
|
217
|
+
_argument_settings = ArgSettings(
|
|
218
|
+
_arguments[0]._id is None,
|
|
219
|
+
_arguments[0].name is None,
|
|
220
|
+
_arguments[0].default is None
|
|
221
|
+
)
|
|
222
|
+
else:
|
|
223
|
+
_argument_settings = ArgSettings(False, False, False)
|
|
224
|
+
|
|
225
|
+
self.tag_name = _tag_name
|
|
226
|
+
self.children = _children
|
|
227
|
+
|
|
228
|
+
self.proc_code = _proc_code
|
|
229
|
+
self.is_warp = _is_warp
|
|
230
|
+
self.arguments = _arguments
|
|
231
|
+
self.og_argument_settings = _argument_settings
|
|
232
|
+
|
|
233
|
+
self.has_next = _has_next
|
|
234
|
+
|
|
235
|
+
super().__init__(_block)
|
|
236
|
+
|
|
237
|
+
def __repr__(self):
|
|
238
|
+
if self.arguments is not None:
|
|
239
|
+
return f"Mutation<args={self.arguments}>"
|
|
240
|
+
else:
|
|
241
|
+
return f"Mutation<hasnext={self.has_next}>"
|
|
242
|
+
|
|
243
|
+
@property
|
|
244
|
+
def argument_ids(self):
|
|
245
|
+
if self.arguments is not None:
|
|
246
|
+
return [_arg._id for _arg in self.arguments]
|
|
247
|
+
else:
|
|
248
|
+
return None
|
|
249
|
+
|
|
250
|
+
@property
|
|
251
|
+
def argument_names(self):
|
|
252
|
+
if self.arguments is not None:
|
|
253
|
+
return [_arg.name for _arg in self.arguments]
|
|
254
|
+
else:
|
|
255
|
+
return None
|
|
256
|
+
|
|
257
|
+
@property
|
|
258
|
+
def argument_defaults(self):
|
|
259
|
+
if self.arguments is not None:
|
|
260
|
+
return [_arg.default for _arg in self.arguments]
|
|
261
|
+
else:
|
|
262
|
+
return None
|
|
263
|
+
|
|
264
|
+
@property
|
|
265
|
+
def argument_settings(self) -> ArgSettings:
|
|
266
|
+
return ArgSettings(bool(commons.safe_get(self.argument_ids, 0)),
|
|
267
|
+
bool(commons.safe_get(self.argument_names, 0)),
|
|
268
|
+
bool(commons.safe_get(self.argument_defaults, 0)))
|
|
269
|
+
|
|
270
|
+
@property
|
|
271
|
+
def parsed_proc_code(self) -> list[str | ArgumentType] | None:
|
|
272
|
+
"""
|
|
273
|
+
Parse the proc code into arguments & strings
|
|
274
|
+
"""
|
|
275
|
+
return parse_proc_code(self.proc_code)
|
|
276
|
+
|
|
277
|
+
@staticmethod
|
|
278
|
+
def from_json(data: dict) -> Mutation:
|
|
279
|
+
assert isinstance(data, dict)
|
|
280
|
+
|
|
281
|
+
_tag_name = data.get("tagName", "mutation")
|
|
282
|
+
_children = data.get("children", [])
|
|
283
|
+
|
|
284
|
+
# procedures_prototype & procedures_call attrs
|
|
285
|
+
_proc_code = data.get("proccode")
|
|
286
|
+
_is_warp = data.get("warp")
|
|
287
|
+
if isinstance(_is_warp, str):
|
|
288
|
+
_is_warp = json.loads(_is_warp)
|
|
289
|
+
|
|
290
|
+
_argument_ids = data.get("argumentids")
|
|
291
|
+
# For some reason these are stored as JSON strings
|
|
292
|
+
if _argument_ids is not None:
|
|
293
|
+
_argument_ids = json.loads(_argument_ids)
|
|
294
|
+
|
|
295
|
+
# procedures_prototype attrs
|
|
296
|
+
_argument_names = data.get("argumentnames")
|
|
297
|
+
_argument_defaults = data.get("argumentdefaults")
|
|
298
|
+
# For some reason these are stored as JSON strings
|
|
299
|
+
if _argument_names is not None:
|
|
300
|
+
assert isinstance(_argument_names, str)
|
|
301
|
+
_argument_names = json.loads(_argument_names)
|
|
302
|
+
if _argument_defaults is not None:
|
|
303
|
+
assert isinstance(_argument_defaults, str)
|
|
304
|
+
_argument_defaults = json.loads(_argument_defaults)
|
|
305
|
+
_argument_settings = ArgSettings(_argument_ids is not None,
|
|
306
|
+
_argument_names is not None,
|
|
307
|
+
_argument_defaults is not None)
|
|
308
|
+
|
|
309
|
+
# control_stop attrs
|
|
310
|
+
_has_next = data.get("hasnext")
|
|
311
|
+
if isinstance(_has_next, str):
|
|
312
|
+
_has_next = json.loads(_has_next)
|
|
313
|
+
|
|
314
|
+
def get(_lst: list | tuple | None, _idx: int):
|
|
315
|
+
if _lst is None:
|
|
316
|
+
return None
|
|
317
|
+
|
|
318
|
+
if len(_lst) <= _idx:
|
|
319
|
+
return None
|
|
320
|
+
else:
|
|
321
|
+
return _lst[_idx]
|
|
322
|
+
|
|
323
|
+
if _argument_ids is None:
|
|
324
|
+
_arguments = None
|
|
325
|
+
else:
|
|
326
|
+
_arguments = []
|
|
327
|
+
for i, _arg_id in enumerate(_argument_ids):
|
|
328
|
+
_arg_name = get(_argument_names, i)
|
|
329
|
+
_arg_default = get(_argument_defaults, i)
|
|
330
|
+
|
|
331
|
+
_arguments.append(Argument(_arg_name, _arg_default, _id=_arg_id))
|
|
332
|
+
|
|
333
|
+
return Mutation(_tag_name, _children, _proc_code, _is_warp, _arguments, _has_next, _argument_settings)
|
|
334
|
+
|
|
335
|
+
def to_json(self) -> dict | None:
|
|
336
|
+
_json = {
|
|
337
|
+
"tagName": self.tag_name,
|
|
338
|
+
"children": self.children,
|
|
339
|
+
}
|
|
340
|
+
commons.noneless_update(_json, {
|
|
341
|
+
"proccode": self.proc_code,
|
|
342
|
+
"warp": commons.dumps_ifnn(self.is_warp),
|
|
343
|
+
"argumentids": commons.dumps_ifnn(self.argument_ids),
|
|
344
|
+
"argumentnames": commons.dumps_ifnn(self.argument_names),
|
|
345
|
+
"argumentdefaults": commons.dumps_ifnn(self.argument_defaults),
|
|
346
|
+
|
|
347
|
+
"hasNext": commons.dumps_ifnn(self.has_next)
|
|
348
|
+
})
|
|
349
|
+
|
|
350
|
+
return _json
|
|
351
|
+
|
|
352
|
+
def link_arguments(self):
|
|
353
|
+
if self.arguments is None:
|
|
354
|
+
return
|
|
355
|
+
|
|
356
|
+
# You only need to fetch argument data if you actually have arguments
|
|
357
|
+
if len(self.arguments) > 0:
|
|
358
|
+
if self.arguments[0].name is None:
|
|
359
|
+
# This requires linking
|
|
360
|
+
_proc_uses = self.sprite.find_block(self.argument_ids, "argument ids", True)
|
|
361
|
+
# Note: Sometimes there may not be any argument ids provided. There will be no way to find out the names
|
|
362
|
+
# Technically, defaults can be found by using the proc code
|
|
363
|
+
for _use in _proc_uses:
|
|
364
|
+
if _use.mutation.argument_settings > self.argument_settings:
|
|
365
|
+
self.arguments = _use.mutation.arguments
|
|
366
|
+
if int(self.argument_settings) == 3:
|
|
367
|
+
# If all of our argument data is filled, we can stop early
|
|
368
|
+
return
|
|
369
|
+
|
|
370
|
+
# We can still work out argument defaults from parsing the proc code
|
|
371
|
+
if self.arguments[0].default is None:
|
|
372
|
+
_parsed = self.parsed_proc_code
|
|
373
|
+
_arg_phs: Iterable[ArgumentType] = filter(lambda tkn: isinstance(tkn, ArgumentType),
|
|
374
|
+
_parsed)
|
|
375
|
+
for i, _arg_ph in enumerate(_arg_phs):
|
|
376
|
+
self.arguments[i].default = _arg_ph.default
|
|
377
|
+
|
|
378
|
+
for _argument in self.arguments:
|
|
379
|
+
_argument.mutation = self
|
|
380
|
+
_argument.link_using_mutation()
|
|
381
|
+
|
editor/pallete.py
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Collection of block information, stating input/field names and opcodes
|
|
3
|
+
Not ready for use
|
|
4
|
+
"""
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from dataclasses import dataclass, field
|
|
8
|
+
from typing import Optional
|
|
9
|
+
|
|
10
|
+
from . import prim
|
|
11
|
+
from scratchattach.utils.enums import _EnumWrapper
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass
|
|
15
|
+
class FieldUsage:
|
|
16
|
+
name: str
|
|
17
|
+
value_type: Optional[prim.PrimTypes] = None
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass
|
|
21
|
+
class SpecialFieldUsage(FieldUsage):
|
|
22
|
+
name: str
|
|
23
|
+
value_type: None = None # Order cannot be changed
|
|
24
|
+
attrs: list[str] = field(default_factory=list)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass
|
|
29
|
+
class InputUsage:
|
|
30
|
+
name: str
|
|
31
|
+
value_type: Optional[prim.PrimTypes] = None
|
|
32
|
+
default_obscurer: Optional[BlockUsage] = None
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass
|
|
36
|
+
class BlockUsage:
|
|
37
|
+
opcode: str
|
|
38
|
+
fields: Optional[list[FieldUsage]] = None
|
|
39
|
+
if fields is None:
|
|
40
|
+
fields = []
|
|
41
|
+
|
|
42
|
+
inputs: Optional[list[InputUsage]] = None
|
|
43
|
+
if inputs is None:
|
|
44
|
+
inputs = []
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class BlockUsages(_EnumWrapper):
|
|
48
|
+
# Special Enum blocks
|
|
49
|
+
MATH_NUMBER = BlockUsage(
|
|
50
|
+
"math_number",
|
|
51
|
+
[SpecialFieldUsage("NUM", attrs=["name", "value"])]
|
|
52
|
+
)
|
|
53
|
+
MATH_POSITIVE_NUMBER = BlockUsage(
|
|
54
|
+
"math_positive_number",
|
|
55
|
+
[SpecialFieldUsage("NUM", attrs=["name", "value"])]
|
|
56
|
+
)
|
|
57
|
+
MATH_WHOLE_NUMBER = BlockUsage(
|
|
58
|
+
"math_whole_number",
|
|
59
|
+
[SpecialFieldUsage("NUM", attrs=["name", "value"])]
|
|
60
|
+
)
|
|
61
|
+
MATH_INTEGER = BlockUsage(
|
|
62
|
+
"math_integer",
|
|
63
|
+
[SpecialFieldUsage("NUM", attrs=["name", "value"])]
|
|
64
|
+
)
|
|
65
|
+
MATH_ANGLE = BlockUsage(
|
|
66
|
+
"math_angle",
|
|
67
|
+
[SpecialFieldUsage("NUM", attrs=["name", "value"])]
|
|
68
|
+
)
|
|
69
|
+
COLOUR_PICKER = BlockUsage(
|
|
70
|
+
"colour_picker",
|
|
71
|
+
[SpecialFieldUsage("COLOUR", attrs=["name", "value"])]
|
|
72
|
+
)
|
|
73
|
+
TEXT = BlockUsage(
|
|
74
|
+
"text",
|
|
75
|
+
[SpecialFieldUsage("TEXT", attrs=["name", "value"])]
|
|
76
|
+
)
|
|
77
|
+
EVENT_BROADCAST_MENU = BlockUsage(
|
|
78
|
+
"event_broadcast_menu",
|
|
79
|
+
[SpecialFieldUsage("BROADCAST_OPTION", attrs=["name", "id", "value", "variableType"])]
|
|
80
|
+
)
|
|
81
|
+
DATA_VARIABLE = BlockUsage(
|
|
82
|
+
"data_variable",
|
|
83
|
+
[SpecialFieldUsage("VARIABLE", attrs=["name", "id", "value", "variableType"])]
|
|
84
|
+
)
|
|
85
|
+
DATA_LISTCONTENTS = BlockUsage(
|
|
86
|
+
"data_listcontents",
|
|
87
|
+
[SpecialFieldUsage("LIST", attrs=["name", "id", "value", "variableType"])]
|
|
88
|
+
)
|