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