scratchattach 2.1.14__py3-none-any.whl → 3.0.0b0__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.
Files changed (69) hide show
  1. scratchattach/__init__.py +14 -6
  2. scratchattach/__main__.py +93 -0
  3. {scratchattach-2.1.14.dist-info → scratchattach-3.0.0b0.dist-info}/METADATA +7 -11
  4. scratchattach-3.0.0b0.dist-info/RECORD +8 -0
  5. {scratchattach-2.1.14.dist-info → scratchattach-3.0.0b0.dist-info}/WHEEL +1 -1
  6. scratchattach-3.0.0b0.dist-info/entry_points.txt +2 -0
  7. scratchattach/cloud/__init__.py +0 -2
  8. scratchattach/cloud/_base.py +0 -458
  9. scratchattach/cloud/cloud.py +0 -183
  10. scratchattach/editor/__init__.py +0 -21
  11. scratchattach/editor/asset.py +0 -253
  12. scratchattach/editor/backpack_json.py +0 -117
  13. scratchattach/editor/base.py +0 -193
  14. scratchattach/editor/block.py +0 -579
  15. scratchattach/editor/blockshape.py +0 -357
  16. scratchattach/editor/build_defaulting.py +0 -51
  17. scratchattach/editor/code_translation/__init__.py +0 -0
  18. scratchattach/editor/code_translation/parse.py +0 -177
  19. scratchattach/editor/comment.py +0 -80
  20. scratchattach/editor/commons.py +0 -306
  21. scratchattach/editor/extension.py +0 -50
  22. scratchattach/editor/field.py +0 -99
  23. scratchattach/editor/inputs.py +0 -135
  24. scratchattach/editor/meta.py +0 -114
  25. scratchattach/editor/monitor.py +0 -183
  26. scratchattach/editor/mutation.py +0 -324
  27. scratchattach/editor/pallete.py +0 -90
  28. scratchattach/editor/prim.py +0 -170
  29. scratchattach/editor/project.py +0 -279
  30. scratchattach/editor/sprite.py +0 -599
  31. scratchattach/editor/twconfig.py +0 -114
  32. scratchattach/editor/vlb.py +0 -134
  33. scratchattach/eventhandlers/__init__.py +0 -0
  34. scratchattach/eventhandlers/_base.py +0 -100
  35. scratchattach/eventhandlers/cloud_events.py +0 -110
  36. scratchattach/eventhandlers/cloud_recorder.py +0 -26
  37. scratchattach/eventhandlers/cloud_requests.py +0 -459
  38. scratchattach/eventhandlers/cloud_server.py +0 -246
  39. scratchattach/eventhandlers/cloud_storage.py +0 -136
  40. scratchattach/eventhandlers/combine.py +0 -30
  41. scratchattach/eventhandlers/filterbot.py +0 -161
  42. scratchattach/eventhandlers/message_events.py +0 -42
  43. scratchattach/other/__init__.py +0 -0
  44. scratchattach/other/other_apis.py +0 -284
  45. scratchattach/other/project_json_capabilities.py +0 -475
  46. scratchattach/site/__init__.py +0 -0
  47. scratchattach/site/_base.py +0 -66
  48. scratchattach/site/activity.py +0 -382
  49. scratchattach/site/alert.py +0 -227
  50. scratchattach/site/backpack_asset.py +0 -118
  51. scratchattach/site/browser_cookie3_stub.py +0 -17
  52. scratchattach/site/browser_cookies.py +0 -61
  53. scratchattach/site/classroom.py +0 -447
  54. scratchattach/site/cloud_activity.py +0 -107
  55. scratchattach/site/comment.py +0 -242
  56. scratchattach/site/forum.py +0 -432
  57. scratchattach/site/project.py +0 -825
  58. scratchattach/site/session.py +0 -1238
  59. scratchattach/site/studio.py +0 -611
  60. scratchattach/site/user.py +0 -956
  61. scratchattach/utils/__init__.py +0 -0
  62. scratchattach/utils/commons.py +0 -255
  63. scratchattach/utils/encoder.py +0 -158
  64. scratchattach/utils/enums.py +0 -236
  65. scratchattach/utils/exceptions.py +0 -243
  66. scratchattach/utils/requests.py +0 -93
  67. scratchattach-2.1.14.dist-info/RECORD +0 -66
  68. {scratchattach-2.1.14.dist-info → scratchattach-3.0.0b0.dist-info}/licenses/LICENSE +0 -0
  69. {scratchattach-2.1.14.dist-info → scratchattach-3.0.0b0.dist-info}/top_level.txt +0 -0
@@ -1,306 +0,0 @@
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 import Optional, Final, Any, TYPE_CHECKING, Union
10
- from enum import Enum, EnumMeta
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
- from scratchattach.utils import exceptions
20
-
21
- DIGITS: Final[tuple[str, ...]] = tuple("0123456789")
22
-
23
- ID_CHARS: Final[str] = string.ascii_letters + string.digits # + string.punctuation
24
-
25
-
26
- # Strangely enough, it seems like something in string.punctuation causes issues. Not sure why
27
-
28
-
29
- def _read_json_number(_str: str) -> float | int:
30
- ret = ''
31
-
32
- minus = _str[0] == '-'
33
- if minus:
34
- ret += '-'
35
- _str = _str[1:]
36
-
37
- def read_fraction(sub: str):
38
- sub_ret = ''
39
- if sub[0] == '.':
40
- sub_ret += '.'
41
- sub = sub[1:]
42
- while sub[0] in DIGITS:
43
- sub_ret += sub[0]
44
- sub = sub[1:]
45
-
46
- return sub_ret, sub
47
-
48
- def read_exponent(sub: str):
49
- sub_ret = ''
50
- if sub[0].lower() == 'e':
51
- sub_ret += sub[0]
52
- sub = sub[1:]
53
-
54
- if sub[0] in "-+":
55
- sub_ret += sub[0]
56
- sub = sub[1:]
57
-
58
- if sub[0] not in DIGITS:
59
- raise exceptions.UnclosedJSONError(f"Invalid exponent {sub}")
60
-
61
- while sub[0] in DIGITS:
62
- sub_ret += sub[0]
63
- sub = sub[1:]
64
-
65
- return sub_ret
66
-
67
- if _str[0] == '0':
68
- ret += '0'
69
- _str = _str[1:]
70
-
71
- elif _str[0] in DIGITS[1:9]:
72
- while _str[0] in DIGITS:
73
- ret += _str[0]
74
- _str = _str[1:]
75
-
76
- frac, _str = read_fraction(_str)
77
- ret += frac
78
-
79
- ret += read_exponent(_str)
80
-
81
- return json.loads(ret)
82
-
83
- # todo: consider if this should be moved to util.commons instead of editor.commons
84
- # note: this is currently unused code
85
- def consume_json(_str: str, i: int = 0) -> str | float | int | dict | list | bool | None:
86
- """
87
- *'gobble up some JSON until we hit something not quite so tasty'*
88
-
89
- Reads a JSON string and stops at the natural end (i.e. when brackets close, or when quotes end, etc.)
90
- """
91
- # Named by ChatGPT
92
- section = ''.join(_str[i:])
93
- if section.startswith("true"):
94
- return True
95
- elif section.startswith("false"):
96
- return False
97
- elif section.startswith("null"):
98
- return None
99
- elif section[0] in "0123456789.-":
100
- return _read_json_number(section)
101
-
102
- depth = 0
103
- json_text = ''
104
- out_string = True
105
-
106
- for char in section:
107
- json_text += char
108
-
109
- if char == '"':
110
- if len(json_text) > 1:
111
- unescaped = json_text[-2] != '\\'
112
- else:
113
- unescaped = True
114
- if unescaped:
115
- out_string ^= True
116
- if out_string:
117
- depth -= 1
118
- else:
119
- depth += 1
120
-
121
- if out_string:
122
- if char in "[{":
123
- depth += 1
124
- elif char in "}]":
125
- depth -= 1
126
-
127
- if depth == 0 and json_text.strip():
128
- return json.loads(json_text.strip())
129
-
130
- raise exceptions.UnclosedJSONError(f"Unclosed JSON string, read {json_text}")
131
-
132
-
133
- def is_partial_json(_str: str, i: int = 0) -> bool:
134
- try:
135
- consume_json(_str, i)
136
- return True
137
-
138
- except exceptions.UnclosedJSONError:
139
- return False
140
-
141
- except ValueError:
142
- return False
143
-
144
-
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
- """
149
- try:
150
- json.loads(_str)
151
- return True
152
- except (ValueError, TypeError):
153
- return False
154
-
155
-
156
- def noneless_update(obj: dict, update: dict) -> None:
157
- """
158
- equivalent to dict.update, except and values of None are not assigned
159
- """
160
- for key, value in update.items():
161
- if value is not None:
162
- obj[key] = value
163
-
164
-
165
- def remove_nones(obj: dict) -> None:
166
- """
167
- Removes all None values from a dict.
168
- :param obj: Dictionary to remove all None values.
169
- """
170
- nones = []
171
- for key, value in obj.items():
172
- if value is None:
173
- nones.append(key)
174
- for key in nones:
175
- del obj[key]
176
-
177
-
178
- def safe_get(lst: list | tuple, _i: int, default: Optional[Any] = None) -> Any:
179
- """
180
- Like dict.get() but for lists
181
- """
182
- if len(lst) <= _i:
183
- return default
184
- else:
185
- return lst[_i]
186
-
187
-
188
- def trim_final_nones(lst: list) -> list:
189
- """
190
- Removes the last None values from a list until a non-None value is hit.
191
- :param lst: list which will **not** be modified.
192
- """
193
- i = len(lst)
194
- for item in lst[::-1]:
195
- if item is not None:
196
- break
197
- i -= 1
198
- return lst[:i]
199
-
200
-
201
- def dumps_ifnn(obj: Any) -> Optional[str]:
202
- """
203
- Return json.dumps(obj) if the object is not None
204
- """
205
- if obj is None:
206
- return None
207
- else:
208
- return json.dumps(obj)
209
-
210
-
211
- def gen_id() -> str:
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
- """
219
- return ''.join(random.choices(ID_CHARS, k=20))
220
-
221
-
222
- def sanitize_fn(filename: str):
223
- """
224
- Removes illegal chars from a filename
225
- :return: Sanitized filename
226
- """
227
- # Maybe could import a slugify module, but it's a bit overkill
228
- ret = ''
229
- for char in filename:
230
- if char in string.ascii_letters + string.digits + "-_":
231
- ret += char
232
- else:
233
- ret += '_'
234
- return ret
235
-
236
-
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
- """
241
- if name.startswith('//'):
242
- return None
243
-
244
- if '//' in name:
245
- return name.split('//')[0]
246
- else:
247
- return None
248
-
249
-
250
- def get_name_nofldr(name: str) -> str:
251
- """
252
- Get the sprite/asset name without the folder name
253
- """
254
- fldr = get_folder_name(name)
255
- if fldr is None:
256
- return name
257
- else:
258
- return name[len(fldr) + 2:]
259
-
260
- # Parent enum class
261
- class SingletonMeta(EnumMeta):
262
-
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
271
-
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,50 +0,0 @@
1
- """
2
- Enum & dataclass representing extension categories
3
- """
4
-
5
- from __future__ import annotations
6
-
7
-
8
- from dataclasses import dataclass
9
-
10
- from . import base
11
- from scratchattach.utils import enums
12
-
13
-
14
- @dataclass
15
- class Extension(base.JSONSerializable):
16
- """
17
- Represents an extension in the Scratch block pallete - e.g. video sensing
18
- """
19
- code: str
20
- name: str = None
21
-
22
- def __eq__(self, other):
23
- return self.code == other.code
24
-
25
- @staticmethod
26
- def from_json(data: str):
27
- assert isinstance(data, str)
28
- _extension = Extensions.find(data, "code")
29
- if _extension is None:
30
- _extension = Extension(data)
31
-
32
- return _extension
33
-
34
- def to_json(self) -> str:
35
- return self.code
36
-
37
-
38
- class Extensions(enums._EnumWrapper):
39
- BOOST = Extension("boost", "LEGO BOOST Extension")
40
- EV3 = Extension("ev3", "LEGO MINDSTORMS EV3 Extension")
41
- GDXFOR = Extension("gdxfor", "Go Direct Force & Acceleration Extension")
42
- MAKEYMAKEY = Extension("makeymakey", "Makey Makey Extension")
43
- MICROBIT = Extension("microbit", "micro:bit Extension")
44
- MUSIC = Extension("music", "Music Extension")
45
- PEN = Extension("pen", "Pen Extension")
46
- TEXT2SPEECH = Extension("text2speech", "Text to Speech Extension")
47
- TRANSLATE = Extension("translate", "Translate Extension")
48
- VIDEOSENSING = Extension("videoSensing", "Video Sensing Extension")
49
- WEDO2 = Extension("wedo2", "LEGO Education WeDo 2.0 Extension")
50
- COREEXAMPLE = Extension("coreExample", "CoreEx Extension") # hidden extension!
@@ -1,99 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from typing import Optional, TYPE_CHECKING, Final
4
-
5
-
6
- if TYPE_CHECKING:
7
- from . import block, vlb
8
-
9
- from . import base, commons
10
-
11
-
12
- class Types:
13
- VARIABLE: Final[str] = "variable"
14
- LIST: Final[str] = "list"
15
- BROADCAST: Final[str] = "broadcast"
16
- DEFAULT: Final[str] = "default"
17
-
18
-
19
- class Field(base.BlockSubComponent):
20
- def __init__(self, _value: str | vlb.Variable | vlb.List | vlb.Broadcast, _id: Optional[str] = None, *, _block: Optional[block.Block] = None):
21
- """
22
- A field for a scratch block
23
- https://en.scratch-wiki.info/wiki/Scratch_File_Format#Blocks:~:text=it.%5B9%5D-,fields,element%2C%20which%20is%20the%20ID%20of%20the%20field%27s%20value.%5B10%5D,-shadow
24
- """
25
- self.value = _value
26
- self.id = _id
27
- """
28
- ID of associated VLB. Will be used to get VLB object during sprite initialisation, where it will be replaced with 'None'
29
- """
30
- super().__init__(_block)
31
-
32
- def __repr__(self):
33
- if self.id is not None:
34
- # This shouldn't occur after sprite initialisation
35
- return f"<Field {self.value!r} : {self.id!r}>"
36
- else:
37
- return f"<Field {self.value!r}>"
38
-
39
- @property
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
- """
44
- if self.id is not None:
45
- return self.id
46
- else:
47
- if hasattr(self.value, "id"):
48
- return self.value.id
49
- else:
50
- return None
51
-
52
- @property
53
- def value_str(self):
54
- """
55
- Convert the associated value to a string - if this is a VLB, return the VLB name
56
- """
57
- if not isinstance(self.value, base.NamedIDComponent):
58
- return self.value
59
- else:
60
- return self.value.name
61
-
62
- @property
63
- def name(self) -> str:
64
- """
65
- Fetch the name of this field using the associated block
66
- """
67
- for _name, _field in self.block.fields.items():
68
- if _field is self:
69
- return _name
70
-
71
- @property
72
- def type(self):
73
- """
74
- Infer the type of value that this field holds
75
- :return: A string (from field.Types) as a name of the type
76
- """
77
- if "variable" in self.name.lower():
78
- return Types.VARIABLE
79
- elif "list" in self.name.lower():
80
- return Types.LIST
81
- elif "broadcast" in self.name.lower():
82
- return Types.BROADCAST
83
- else:
84
- return Types.DEFAULT
85
-
86
- @staticmethod
87
- def from_json(data: list[str, str | None]):
88
- # Sometimes you may have a stray field with no id. Not sure why
89
- while len(data) < 2:
90
- data.append(None)
91
- data = data[:2]
92
-
93
- _value, _id = data
94
- return Field(_value, _id)
95
-
96
- def to_json(self) -> dict:
97
- return commons.trim_final_nones([
98
- self.value_str, self.value_id
99
- ])
@@ -1,135 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import warnings
4
- from typing import Optional, Final
5
-
6
- from . import block
7
- from . import base, commons, prim
8
- from dataclasses import dataclass
9
-
10
-
11
- @dataclass
12
- class ShadowStatus:
13
- """
14
- Dataclass representing a possible shadow value and giving it a name
15
- """
16
- idx: int
17
- name: str
18
-
19
- def __repr__(self):
20
- return f"<ShadowStatus {self.name!r} ({self.idx})>"
21
-
22
-
23
- class ShadowStatuses:
24
- # Not an enum so you don't need to do .value
25
- # Uh why?
26
- HAS_SHADOW: Final[ShadowStatus] = ShadowStatus(1, "has shadow")
27
- NO_SHADOW: Final[ShadowStatus] = ShadowStatus(2, "no shadow")
28
- OBSCURED: Final[ShadowStatus] = ShadowStatus(3, "obscured")
29
-
30
- @classmethod
31
- def find(cls, idx: int) -> ShadowStatus:
32
- for status in (cls.HAS_SHADOW, cls.NO_SHADOW, cls.OBSCURED):
33
- if status.idx == idx:
34
- return status
35
-
36
- if not 1 <= idx <= 3:
37
- raise ValueError(f"Invalid ShadowStatus idx={idx}")
38
-
39
-
40
- class Input(base.BlockSubComponent):
41
- def __init__(self, _shadow: ShadowStatus | None = ShadowStatuses.HAS_SHADOW, _value: Optional[prim.Prim | block.Block | str] = None, _id: Optional[str] = None,
42
- _obscurer: Optional[prim.Prim | block.Block | str] = None, *, _obscurer_id: Optional[str] = None, _block: Optional[block.Block] = None):
43
- """
44
- An input for a scratch block
45
- https://en.scratch-wiki.info/wiki/Scratch_File_Format#Blocks:~:text=inputs,it.%5B9%5D
46
- """
47
- super().__init__(_block)
48
-
49
- # If the shadow is None, we'll have to work it out later
50
- self.shadow = _shadow
51
-
52
- self.value: prim.Prim | block.Block = _value
53
- self.obscurer: prim.Prim | block.Block = _obscurer
54
-
55
- self._id = _id
56
- """
57
- ID referring to the input value. Upon project initialisation, this will be set to None and the value attribute will be set to the relevant object
58
- """
59
- self._obscurer_id = _obscurer_id
60
- """
61
- ID referring to the obscurer. Upon project initialisation, this will be set to None and the obscurer attribute will be set to the relevant block
62
- """
63
-
64
- def __repr__(self):
65
- if self._id is not None:
66
- return f"<Input<id={self._id!r}>"
67
- else:
68
- return f"<Input {self.value!r}>"
69
-
70
- @staticmethod
71
- def from_json(data: list):
72
- _shadow = ShadowStatuses.find(data[0])
73
-
74
- _value, _id = None, None
75
- if isinstance(data[1], list):
76
- _value = prim.Prim.from_json(data[1])
77
- else:
78
- _id = data[1]
79
-
80
- _obscurer_data = commons.safe_get(data, 2)
81
-
82
- _obscurer, _obscurer_id = None, None
83
- if isinstance(_obscurer_data, list):
84
- _obscurer = prim.Prim.from_json(_obscurer_data)
85
- else:
86
- _obscurer_id = _obscurer_data
87
- return Input(_shadow, _value, _id, _obscurer, _obscurer_id=_obscurer_id)
88
-
89
- def to_json(self) -> list:
90
- data = [self.shadow.idx]
91
-
92
- def add_pblock(pblock: prim.Prim | block.Block | None):
93
- """
94
- Adds a primitive or a block to the data in the right format
95
- """
96
- if pblock is None:
97
- return
98
-
99
- if isinstance(pblock, prim.Prim):
100
- data.append(pblock.to_json())
101
-
102
- elif isinstance(pblock, block.Block):
103
- data.append(pblock.id)
104
-
105
- else:
106
- warnings.warn(f"Bad prim/block {pblock!r} of type {type(pblock)}")
107
-
108
- add_pblock(self.value)
109
- add_pblock(self.obscurer)
110
-
111
- return data
112
-
113
- def link_using_block(self):
114
- # Link to value
115
- if self._id is not None:
116
- new_value = self.sprite.find_block(self._id, "id")
117
- if new_value is not None:
118
- self.value = new_value
119
- self._id = None
120
-
121
- # Link to obscurer
122
- if self._obscurer_id is not None:
123
- new_block = self.sprite.find_block(self._obscurer_id, "id")
124
- if new_block is not None:
125
- self.obscurer = new_block
126
- self._obscurer_id = None
127
-
128
- # Link value to sprite
129
- if isinstance(self.value, prim.Prim):
130
- self.value.sprite = self.sprite
131
- self.value.link_using_sprite()
132
-
133
- # Link obscurer to sprite
134
- if self.obscurer is not None:
135
- self.obscurer.sprite = self.sprite
@@ -1,114 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import re
4
- from dataclasses import dataclass, field
5
-
6
- from . import base, commons
7
- from typing import Optional
8
-
9
-
10
- @dataclass
11
- class PlatformMeta(base.JSONSerializable):
12
- name: str = None
13
- url: str = field(repr=True, default=None)
14
-
15
- def __bool__(self):
16
- return self.name is not None or self.url is not None
17
-
18
- def to_json(self) -> dict:
19
- _json = {"name": self.name, "url": self.url}
20
- commons.remove_nones(_json)
21
- return _json
22
-
23
- @staticmethod
24
- def from_json(data: dict | None):
25
- if data is None:
26
- return PlatformMeta()
27
- else:
28
- return PlatformMeta(data.get("name"), data.get("url"))
29
-
30
-
31
- DEFAULT_VM = "0.1.0"
32
- DEFAULT_AGENT = "scratchattach.editor by https://scratch.mit.edu/users/timmccool/"
33
- DEFAULT_PLATFORM = PlatformMeta("scratchattach", "https://github.com/timMcCool/scratchattach/")
34
-
35
- EDIT_META = True
36
- META_SET_PLATFORM = False
37
-
38
-
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
- """
43
- global META_SET_PLATFORM
44
- if true_false is None:
45
- true_false = bool(1 - true_false)
46
- META_SET_PLATFORM = true_false
47
-
48
-
49
- class Meta(base.JSONSerializable):
50
- def __init__(self, semver: str = "3.0.0", vm: str = DEFAULT_VM, agent: str = DEFAULT_AGENT,
51
- platform: Optional[PlatformMeta] = None):
52
- """
53
- Represents metadata of the project
54
- https://en.scratch-wiki.info/wiki/Scratch_File_Format#Metadata
55
- """
56
- if platform is None and META_SET_PLATFORM:
57
- platform = DEFAULT_PLATFORM.dcopy()
58
-
59
- self.semver = semver
60
- self.vm = vm
61
- self.agent = agent
62
- self.platform = platform
63
-
64
- if not self.vm_is_valid:
65
- raise ValueError(
66
- f"{vm!r} does not match pattern '^([0-9]+\\.[0-9]+\\.[0-9]+)($|-)' - maybe try '0.0.0'?")
67
-
68
- def __repr__(self):
69
- data = f"{self.semver} : {self.vm} : {self.agent}"
70
- if self.platform:
71
- data += f": {self.platform}"
72
-
73
- return f"Meta<{data}>"
74
-
75
- @property
76
- def vm_is_valid(self):
77
- """
78
- Check whether the vm value is valid using a regex
79
- Thanks to TurboWarp for this pattern ↓↓↓↓, I just copied it
80
- """
81
- return re.match("^([0-9]+\\.[0-9]+\\.[0-9]+)($|-)", self.vm) is not None
82
-
83
- def to_json(self):
84
- _json = {
85
- "semver": self.semver,
86
- "vm": self.vm,
87
- "agent": self.agent
88
- }
89
-
90
- if self.platform:
91
- _json["platform"] = self.platform.to_json()
92
- return _json
93
-
94
- @staticmethod
95
- def from_json(data):
96
- if data is None:
97
- data = ""
98
-
99
- semver = data["semver"]
100
- vm = data.get("vm")
101
- agent = data.get("agent")
102
- platform = PlatformMeta.from_json(data.get("platform"))
103
-
104
- if EDIT_META or vm is None:
105
- vm = DEFAULT_VM
106
-
107
- if EDIT_META or agent is None:
108
- agent = DEFAULT_AGENT
109
-
110
- if EDIT_META:
111
- if META_SET_PLATFORM and not platform:
112
- platform = DEFAULT_PLATFORM.dcopy()
113
-
114
- return Meta(semver, vm, agent, platform)