gdharness 1.3.42 → 1.3.43

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 (105) hide show
  1. package/build/cli.js +62 -9
  2. package/build/godot/addons/auto_reload/auto_reload.gd +18 -19
  3. package/build/godot/addons/gdharness_editor/animation_reads.gd +55 -33
  4. package/build/godot/addons/gdharness_editor/bridge_client.gd +53 -58
  5. package/build/godot/addons/gdharness_editor/declared_values.gd +68 -32
  6. package/build/godot/addons/gdharness_editor/file_reads.gd +83 -42
  7. package/build/godot/addons/gdharness_editor/file_walk.gd +12 -5
  8. package/build/godot/addons/gdharness_editor/headless_check.gd +68 -44
  9. package/build/godot/addons/gdharness_editor/plugin.gd +2 -3
  10. package/build/godot/addons/gdharness_editor/property_paths.gd +22 -12
  11. package/build/godot/addons/gdharness_editor/property_values.gd +143 -78
  12. package/build/godot/addons/gdharness_editor/reading.gd +37 -12
  13. package/build/godot/addons/gdharness_editor/same_values.gd +4 -4
  14. package/build/godot/addons/gdharness_editor/scene_file.gd +109 -79
  15. package/build/godot/addons/gdharness_editor/scene_uids.gd +12 -6
  16. package/build/godot/addons/gdharness_editor/serialisation.gd +75 -51
  17. package/build/godot/addons/gdharness_editor/setting_reads.gd +7 -6
  18. package/build/godot/addons/gdharness_editor/tool_executor.gd +3 -6
  19. package/build/godot/addons/gdharness_editor/tools/animation_states.gd +37 -27
  20. package/build/godot/addons/gdharness_editor/tools/animation_tools.gd +79 -49
  21. package/build/godot/addons/gdharness_editor/tools/animation_tracks.gd +70 -40
  22. package/build/godot/addons/gdharness_editor/tools/class_tools.gd +34 -30
  23. package/build/godot/addons/gdharness_editor/tools/filesystem_tools.gd +24 -22
  24. package/build/godot/addons/gdharness_editor/tools/navigation_bake.gd +49 -29
  25. package/build/godot/addons/gdharness_editor/tools/play_tools.gd +26 -21
  26. package/build/godot/addons/gdharness_editor/tools/profile_tools.gd +40 -40
  27. package/build/godot/addons/gdharness_editor/tools/resource_tools.gd +166 -107
  28. package/build/godot/addons/gdharness_editor/tools/scene_groups.gd +16 -9
  29. package/build/godot/addons/gdharness_editor/tools/scene_kin.gd +68 -41
  30. package/build/godot/addons/gdharness_editor/tools/scene_paths.gd +35 -20
  31. package/build/godot/addons/gdharness_editor/tools/scene_placing.gd +28 -13
  32. package/build/godot/addons/gdharness_editor/tools/scene_sharing.gd +12 -5
  33. package/build/godot/addons/gdharness_editor/tools/scene_tools.gd +113 -67
  34. package/build/godot/addons/gdharness_editor/tools/script_paths.gd +20 -8
  35. package/build/godot/addons/gdharness_editor/tools/settings_tools.gd +11 -8
  36. package/build/godot/addons/gdharness_editor/tools/signal_tools.gd +47 -34
  37. package/build/godot/addons/gdharness_editor/tools/tileset_tools.gd +77 -50
  38. package/build/godot/addons/gdharness_editor/value_shapes.gd +5 -6
  39. package/build/godot/addons/gdharness_runtime/call_errors.gd +1 -2
  40. package/build/godot/addons/gdharness_runtime/declared_values.gd +68 -32
  41. package/build/godot/addons/gdharness_runtime/error_report.gd +1 -2
  42. package/build/godot/addons/gdharness_runtime/reading.gd +37 -12
  43. package/build/godot/addons/gdharness_runtime/runtime_accessibility.gd +84 -63
  44. package/build/godot/addons/gdharness_runtime/runtime_autoload.gd +149 -147
  45. package/build/godot/addons/gdharness_runtime/runtime_capture.gd +64 -42
  46. package/build/godot/addons/gdharness_runtime/runtime_changes.gd +184 -122
  47. package/build/godot/addons/gdharness_runtime/runtime_frames.gd +2 -2
  48. package/build/godot/addons/gdharness_runtime/runtime_hover.gd +24 -20
  49. package/build/godot/addons/gdharness_runtime/runtime_input.gd +134 -92
  50. package/build/godot/addons/gdharness_runtime/runtime_items.gd +29 -13
  51. package/build/godot/addons/gdharness_runtime/runtime_menus.gd +0 -1
  52. package/build/godot/addons/gdharness_runtime/runtime_mouse.gd +34 -17
  53. package/build/godot/addons/gdharness_runtime/runtime_pad.gd +17 -13
  54. package/build/godot/addons/gdharness_runtime/runtime_paths.gd +84 -46
  55. package/build/godot/addons/gdharness_runtime/runtime_queries.gd +117 -79
  56. package/build/godot/addons/gdharness_runtime/runtime_says.gd +14 -17
  57. package/build/godot/addons/gdharness_runtime/runtime_screen.gd +23 -16
  58. package/build/godot/addons/gdharness_runtime/runtime_targets.gd +117 -66
  59. package/build/godot/addons/gdharness_runtime/runtime_touch.gd +15 -15
  60. package/build/godot/addons/gdharness_runtime/runtime_typing.gd +44 -27
  61. package/build/godot/addons/gdharness_runtime/runtime_values.gd +108 -73
  62. package/build/godot/addons/gdharness_runtime/runtime_waits.gd +111 -84
  63. package/build/godot/addons/gdharness_runtime/runtime_words.gd +10 -7
  64. package/build/godot/addons/gdharness_runtime/serialisation.gd +75 -51
  65. package/build/godot/addons/gdharness_runtime/value_shapes.gd +5 -6
  66. package/build/godot/addons/gdharness_tests/bench.gd +4 -5
  67. package/build/godot/addons/gdharness_tests/event.gd +10 -5
  68. package/build/godot/addons/gdharness_tests/share.gd +38 -18
  69. package/build/godot/addons/gdharness_tests/suites.gd +3 -3
  70. package/build/godot/addons/gdharness_tests/wait.gd +3 -2
  71. package/build/godot/loader/loader.gd +9 -9
  72. package/build/godot/operations/animation_reads.gd +55 -33
  73. package/build/godot/operations/audio_buses.gd +48 -19
  74. package/build/godot/operations/class_cache.gd +16 -11
  75. package/build/godot/operations/classdb_queries.gd +89 -44
  76. package/build/godot/operations/declared_values.gd +68 -32
  77. package/build/godot/operations/dependencies.gd +139 -93
  78. package/build/godot/operations/editor_tools.gd +9 -10
  79. package/build/godot/operations/export_presets.gd +100 -54
  80. package/build/godot/operations/file_reads.gd +83 -42
  81. package/build/godot/operations/file_walk.gd +12 -5
  82. package/build/godot/operations/gdscript_analysis.gd +86 -65
  83. package/build/godot/operations/gdscript_authoring.gd +83 -36
  84. package/build/godot/operations/godot_operations.gd +17 -11
  85. package/build/godot/operations/import_pipeline.gd +110 -59
  86. package/build/godot/operations/input_actions.gd +62 -39
  87. package/build/godot/operations/patterns.gd +3 -1
  88. package/build/godot/operations/plugins.gd +36 -16
  89. package/build/godot/operations/project_config.gd +53 -29
  90. package/build/godot/operations/project_diagnostics.gd +24 -11
  91. package/build/godot/operations/project_file.gd +16 -16
  92. package/build/godot/operations/project_validation.gd +71 -44
  93. package/build/godot/operations/property_paths.gd +22 -12
  94. package/build/godot/operations/property_values.gd +143 -78
  95. package/build/godot/operations/reading.gd +37 -12
  96. package/build/godot/operations/rename_checks.gd +2 -2
  97. package/build/godot/operations/resource_files.gd +11 -9
  98. package/build/godot/operations/same_values.gd +4 -4
  99. package/build/godot/operations/scene_connections.gd +27 -13
  100. package/build/godot/operations/script_check.gd +52 -49
  101. package/build/godot/operations/serialisation.gd +75 -51
  102. package/build/godot/operations/setting_reads.gd +7 -6
  103. package/build/godot/operations/value_shapes.gd +5 -6
  104. package/build/godot/tests/listed.gd +321 -268
  105. package/package.json +1 -1
@@ -1,12 +1,10 @@
1
1
  @tool
2
2
  extends RefCounted
3
-
4
3
  ## Whether a value read back is the value that was given, as the tools that write properties judge
5
4
  ## what a write kept and what it changed besides.
6
5
 
7
6
  const GdhRead = preload("reading.gd")
8
7
  const GdhSerialisation = preload("serialisation.gd")
9
-
10
8
  # The types made of floats, which a read back compares within float precision: a Vector2 property
11
9
  # stores 32-bit components, so 0.1 comes back as 0.100000001.
12
10
  const FLOAT_SHAPES: Array[int] = [
@@ -23,7 +21,6 @@ const FLOAT_SHAPES: Array[int] = [
23
21
  TYPE_AABB,
24
22
  TYPE_PROJECTION,
25
23
  ]
26
-
27
24
  const PACKED: Array[int] = [
28
25
  TYPE_PACKED_BYTE_ARRAY,
29
26
  TYPE_PACKED_INT32_ARRAY,
@@ -63,7 +60,10 @@ static func same(held: Variant, given: Variant) -> bool:
63
60
  return held_type == given_type and held == given
64
61
  var held_number: float = GdhRead.as_float(held)
65
62
  var given_number: float = GdhRead.as_float(given)
66
- return (is_nan(held_number) and is_nan(given_number)) or is_equal_approx(held_number, given_number)
63
+ return (
64
+ (is_nan(held_number) and is_nan(given_number))
65
+ or is_equal_approx(held_number, given_number)
66
+ )
67
67
  if GdhSerialisation.TEXTS.has(held_type) and GdhSerialisation.TEXTS.has(given_type):
68
68
  return str(held) == str(given)
69
69
  if _listed(held_type) and _listed(given_type):
@@ -1,6 +1,5 @@
1
1
  @tool
2
2
  extends RefCounted
3
-
4
3
  ## One scene file opened for a tool, read past the resource cache and written back only once what
5
4
  ## would be saved has been checked.
6
5
  ##
@@ -26,7 +25,6 @@ extends RefCounted
26
25
  ## anything is saved, and a write is refused when that does not hold. See [GdhHeadlessCheck].
27
26
 
28
27
  const GdhHeadlessCheck = preload("headless_check.gd")
29
-
30
28
  ## How many times what scripts changed is put back before a write is refused for it.
31
29
  const PUTTING_BACK_PASSES: int = 4
32
30
  const GdhRead = preload("reading.gd")
@@ -87,19 +85,19 @@ var _engine_kept: Dictionary = {}
87
85
  func open(scene_path: String, for_writing: bool) -> Dictionary:
88
86
  path = scene_path
89
87
  if not FileAccess.file_exists(path):
90
- return {"ok": false, "error": "Scene not found: " + path}
88
+ return { "ok": false, "error": "Scene not found: " + path }
91
89
  if for_writing:
92
90
  var held_back: String = unsaved_in_editor(path)
93
91
  if not held_back.is_empty():
94
- return {"ok": false, "error": held_back}
92
+ return { "ok": false, "error": held_back }
95
93
  var packed: PackedScene = (
96
94
  ResourceLoader.load(path, "PackedScene", ResourceLoader.CACHE_MODE_IGNORE) as PackedScene
97
95
  )
98
96
  if packed == null:
99
- return {"ok": false, "error": "Failed to load %s as a scene" % path}
97
+ return { "ok": false, "error": "Failed to load %s as a scene" % path }
100
98
  root = packed.instantiate(PackedScene.GEN_EDIT_STATE_MAIN)
101
99
  if root == null:
102
- return {"ok": false, "error": "Failed to instantiate " + path}
100
+ return { "ok": false, "error": "Failed to instantiate " + path }
103
101
  _state = packed.get_state()
104
102
  _base_paths = makers(_state)
105
103
  # On a read as well, which would otherwise answer what the scripts set as what the file holds.
@@ -144,15 +142,16 @@ func asked(node: Node, names: Array) -> void:
144
142
  GdhHeadlessCheck.spare(_reached, GdhHeadlessCheck.reached(node, property))
145
143
 
146
144
 
147
- ## Takes [param resource], built into the scene, as one the tool changed, which a write saves as it is.
145
+ ## Takes [param resource], built into the scene, as one the tool changed, which a write saves as it
146
+ ## is.
148
147
  func reached_into(resource: Resource) -> void:
149
148
  var id: String = resource.get_scene_unique_id()
150
149
  if not id.is_empty():
151
150
  _reached[id] = true
152
151
 
153
152
 
154
- ## Takes each copy in [param copies], keyed by the node it was made from, as holding what that node's
155
- ## file entry holds.
153
+ ## Takes each copy in [param copies], keyed by the node it was made from, as holding what that
154
+ ## node's file entry holds.
156
155
  func copied(copies: Dictionary) -> void:
157
156
  for source: Variant in copies:
158
157
  var original: Node = source
@@ -166,8 +165,9 @@ static func unsaved_in_editor(scene_path: String) -> String:
166
165
  return ""
167
166
  return (
168
167
  (
169
- "%s has unsaved changes in the editor, and writing the file would discard them when the "
170
- + "editor reloads it. Save it first (scene_create op=save) or close it without saving."
168
+ "%s has unsaved changes in the editor, and writing the file would discard them when "
169
+ + "the editor reloads it. Save it first (scene_create op=save) or close it without "
170
+ + "saving."
171
171
  )
172
172
  % scene_path
173
173
  )
@@ -176,7 +176,7 @@ static func unsaved_in_editor(scene_path: String) -> String:
176
176
  ## Frees [member root] and answers [param error] as a refusal.
177
177
  func refuse(error: String) -> Dictionary:
178
178
  close()
179
- return {"ok": false, "error": error}
179
+ return { "ok": false, "error": error }
180
180
 
181
181
 
182
182
  ## [param said], the refusal for [param node_path] found nowhere in this scene, with how a path is
@@ -213,10 +213,10 @@ func close() -> void:
213
213
 
214
214
 
215
215
  ## What the file holds under the instance at [param at] that goes once its children are not
216
- ## editable, as {values, nodes}: the paths of the instance's nodes it sets values on, which stay with
217
- ## the instance's own values, and of the nodes the scene added below one of them, which are gone,
218
- ## since the packer skips everything below a node of an instance that is not editable. A node added
219
- ## straight under [param at] is the scene's own and stays.
216
+ ## editable, as {values, nodes}: the paths of the instance's nodes it sets values on, which stay
217
+ ## with the instance's own values, and of the nodes the scene added below one of them, which are
218
+ ## gone, since the packer skips everything below a node of an instance that is not editable. A node
219
+ ## added straight under [param at] is the scene's own and stays.
220
220
  func held_under(at: String) -> Dictionary:
221
221
  var values: Array[String] = []
222
222
  var nodes: Array[String] = []
@@ -230,7 +230,7 @@ func held_under(at: String) -> Dictionary:
230
230
  values.append(entry)
231
231
  elif not values_only and parent.begins_with(at + "/"):
232
232
  nodes.append(entry)
233
- return {"values": values, "nodes": nodes}
233
+ return { "values": values, "nodes": nodes }
234
234
 
235
235
 
236
236
  ## The path of [param node] as a tool names it: "." for the root.
@@ -241,7 +241,7 @@ func path_of(node: Node) -> String:
241
241
  ## The class of [param node] as a read answers it: nodeType the engine class, and scriptClass the
242
242
  ## class its script declares, when it declares one.
243
243
  static func class_of(node: Node) -> Dictionary:
244
- var named: Dictionary = {"nodeType": node.get_class()}
244
+ var named: Dictionary = { "nodeType": node.get_class() }
245
245
  var script: Script = node.get_script()
246
246
  if script != null and not str(script.get_global_name()).is_empty():
247
247
  named["scriptClass"] = str(script.get_global_name())
@@ -271,8 +271,8 @@ static func node_at(top: Node, at: String) -> Node:
271
271
  ## Why the property or signal [param missing] could not be found on [param node], adding the one
272
272
  ## cause the name alone does not show: a script that does not compile leaves the editor holding a
273
273
  ## placeholder with no properties or signals at all, and the refusal named the property instead.
274
- ## Such a script has no engine class beneath it. Compiling a copy of the source to find out failed on
275
- ## every script with a class_name, since the copy hides the global class the original declares.
274
+ ## Such a script has no engine class beneath it. Compiling a copy of the source to find out failed
275
+ ## on every script with a class_name, since the copy hides the global class the original declares.
276
276
  static func missing_on(node: Node, missing: String) -> String:
277
277
  var script: Script = node.get_script()
278
278
  if script == null or not str(script.get_instance_base_type()).is_empty():
@@ -290,15 +290,18 @@ func foreign(node: Node) -> String:
290
290
  return ""
291
291
  if node.owner == null:
292
292
  return (
293
- "%s is made by a script as the scene is instanced, and no scene file saves anything about it."
293
+ (
294
+ "%s is made by a script as the scene is instanced, and no scene file saves "
295
+ + "anything about it."
296
+ )
294
297
  % path_of(node)
295
298
  )
296
299
  if root.is_editable_instance(node.owner):
297
300
  return ""
298
301
  var said: String = (
299
302
  (
300
- "%s belongs to the scene instanced at %s (%s), and this scene saves nothing about it. Edit "
301
- + "that scene instead, which changes every instance of it"
303
+ "%s belongs to the scene instanced at %s (%s), and this scene saves nothing about it. "
304
+ + "Edit that scene instead, which changes every instance of it"
302
305
  )
303
306
  % [path_of(node), path_of(node.owner), node.owner.scene_file_path]
304
307
  )
@@ -349,19 +352,22 @@ func fixed(node: Node, deleting: bool = false) -> String:
349
352
  "%s is inside the scene instanced at %s (%s), which creates it again on load."
350
353
  % [path_of(node), path_of(node.owner), instanced]
351
354
  )
352
- return inside + (_deleted_elsewhere(node, maker) if deleting else " Edit %s instead." % maker)
355
+ return (
356
+ inside + (_deleted_elsewhere(node, maker) if deleting else " Edit %s instead." % maker)
357
+ )
353
358
  var at: String = path_of(node)
354
359
  if _base_paths.has(at):
355
360
  var maker: String = str(_base_paths[at])
356
361
  var made: String = (
357
- "%s is made by %s, a scene %s inherits, which creates it again on load." % [at, maker, path]
362
+ "%s is made by %s, a scene %s inherits, which creates it again on load."
363
+ % [at, maker, path]
358
364
  )
359
365
  return made + (_deleted_elsewhere(node, maker) if deleting else " Edit that scene instead.")
360
366
  return ""
361
367
 
362
368
 
363
- ## Each node the scenes [param state] inherits give it, by path, with the deepest file making it more
364
- ## than values; a node [param state] makes itself is not in it.
369
+ ## Each node the scenes [param state] inherits give it, by path, with the deepest file making it
370
+ ## more than values; a node [param state] makes itself is not in it.
365
371
  static func makers(state: SceneState) -> Dictionary:
366
372
  var made_by: Dictionary = {}
367
373
  var at: SceneState = state.get_base_scene_state()
@@ -380,20 +386,22 @@ static func makers(state: SceneState) -> Dictionary:
380
386
  ## What a caller can do with [param node], made by [param maker], instead of deleting it here.
381
387
  static func _deleted_elsewhere(node: Node, maker: String) -> String:
382
388
  var hidden: String = (
383
- "set its visible to false here to hide it in this scene alone, or " if "visible" in node else ""
389
+ "set its visible to false here to hide it in this scene alone, or "
390
+ if "visible" in node
391
+ else ""
384
392
  )
385
393
  return (
386
394
  (
387
- " No scene can delete a node another scene makes: %sdelete it in %s, which removes it from "
388
- + "every scene using that one."
395
+ " No scene can delete a node another scene makes: %sdelete it in %s, which removes it "
396
+ + "from every scene using that one."
389
397
  )
390
398
  % [hidden, maker]
391
399
  )
392
400
 
393
401
 
394
- ## Why the folder a new file at [param file_path] goes in cannot be made, or "" once it is there. The
395
- ## engine makes no folder when it saves, so a create into one not yet made failed with no more than
396
- ## the engine's error, while script_edit create made its folder and went ahead.
402
+ ## Why the folder a new file at [param file_path] goes in cannot be made, or "" once it is there.
403
+ ## The engine makes no folder when it saves, so a create into one not yet made failed with no more
404
+ ## than the engine's error, while script_edit create made its folder and went ahead.
397
405
  static func make_folder_for(file_path: String) -> String:
398
406
  var folder: String = file_path.get_base_dir()
399
407
  if DirAccess.dir_exists_absolute(folder):
@@ -402,10 +410,10 @@ static func make_folder_for(file_path: String) -> String:
402
410
  return "" if made == OK else "Could not make the folder %s: %s" % [folder, error_string(made)]
403
411
 
404
412
 
405
- ## Why [param script], loaded from [param script_path], cannot be attached because the editor could not
406
- ## compile it, or "". A script that did not compile has no base type, and the refusal built from that
407
- ## said "start.gd extends , so it cannot be on a Control root" about a script that was fine, whose
408
- ## failure was a class it called being behind in the editor. A headless engine compiles every
413
+ ## Why [param script], loaded from [param script_path], cannot be attached because the editor could
414
+ ## not compile it, or "". A script that did not compile has no base type, and the refusal built from
415
+ ## that said "start.gd extends , so it cannot be on a Control root" about a script that was fine,
416
+ ## whose failure was a class it called being behind in the editor. A headless engine compiles every
409
417
  ## script from its file, so only an editor can hold a copy that is behind.
410
418
  static func uncompiled(script: Script, script_path: String) -> String:
411
419
  if not str(script.get_instance_base_type()).is_empty():
@@ -421,9 +429,9 @@ static func uncompiled(script: Script, script_path: String) -> String:
421
429
  return (
422
430
  (
423
431
  "%s does not compile in the editor, so it cannot be attached. script_diagnostics on it "
424
- + "gives the parser's errors. If they deny a member that another class's file declares, "
425
- + "the editor's copy of that class is behind: editor_rescan with reloadScript on that "
426
- + "class, then ask again."
432
+ + "gives the parser's errors. If they deny a member that another class's file "
433
+ + "declares, the editor's copy of that class is behind: editor_rescan with "
434
+ + "reloadScript on that class, then ask again."
427
435
  )
428
436
  % script_path
429
437
  )
@@ -441,21 +449,29 @@ static func script_class(type_name: String, kind: String) -> Dictionary:
441
449
  if entry.get("is_abstract", false) == true:
442
450
  return {
443
451
  "error":
444
- "%s, declared in %s, is abstract, so nothing can be made of it." % [type_name, declaring]
452
+ (
453
+ "%s, declared in %s, is abstract, so nothing can be made of it."
454
+ % [type_name, declaring]
455
+ ),
445
456
  }
446
457
  var script: Script = load(declaring) as Script
447
458
  if script == null:
448
- return {"error": "%s is declared in %s, which does not load." % [type_name, declaring]}
459
+ return {
460
+ "error": "%s is declared in %s, which does not load." % [type_name, declaring],
461
+ }
449
462
  var failed: String = uncompiled(script, declaring)
450
463
  if not failed.is_empty():
451
- return {"error": failed}
464
+ return { "error": failed }
452
465
  var base: String = str(script.get_instance_base_type())
453
466
  if not ClassDB.is_parent_class(base, kind):
454
467
  return {
455
468
  "error":
456
- "%s, declared in %s, extends %s, which is not a %s." % [type_name, declaring, base, kind]
469
+ (
470
+ "%s, declared in %s, extends %s, which is not a %s."
471
+ % [type_name, declaring, base, kind]
472
+ ),
457
473
  }
458
- return {"script": script, "path": declaring, "base": base}
474
+ return { "script": script, "path": declaring, "base": base }
459
475
  return {}
460
476
 
461
477
 
@@ -465,12 +481,12 @@ static func script_class(type_name: String, kind: String) -> Dictionary:
465
481
  ## argument the type came in, for the refusal when the call also named another script.
466
482
  static func made_as(type_name: String, given: String, kind: String, argument: String) -> Dictionary:
467
483
  if ClassDB.class_exists(type_name):
468
- return {"type": type_name, "script": given}
484
+ return { "type": type_name, "script": given }
469
485
  var declared: Dictionary = script_class(type_name, kind)
470
486
  if declared.has("error"):
471
487
  return declared
472
488
  if declared.is_empty():
473
- return {"type": type_name, "script": given}
489
+ return { "type": type_name, "script": given }
474
490
  var declaring: String = str(declared["path"])
475
491
  if not given.is_empty() and given != declaring:
476
492
  return {
@@ -478,9 +494,9 @@ static func made_as(type_name: String, given: String, kind: String, argument: St
478
494
  (
479
495
  "%s %s is the class %s declares, and script names %s: pass one of them."
480
496
  % [argument, type_name, declaring, given]
481
- )
497
+ ),
482
498
  }
483
- return {"type": str(declared["base"]), "script": declaring}
499
+ return { "type": str(declared["base"]), "script": declaring }
484
500
 
485
501
 
486
502
  ## Why no node can be called [param node_name], wherever it sits, or "".
@@ -550,7 +566,9 @@ func set_at(at: String) -> Dictionary:
550
566
  if _relative(str(_state.get_node_path(index))) != at:
551
567
  continue
552
568
  for p: int in _state.get_node_property_count(index):
553
- held[str(_state.get_node_property_name(index, p))] = _state.get_node_property_value(index, p)
569
+ held[str(_state.get_node_property_name(index, p))] = _state.get_node_property_value(
570
+ index, p
571
+ )
554
572
  return held
555
573
 
556
574
 
@@ -607,15 +625,17 @@ func write(check: Callable = Callable()) -> Dictionary:
607
625
  packed = settled[0]
608
626
  if not path.ends_with(".tscn") and _holds_scripted_embedded():
609
627
  var binary: String = (
610
- "Nothing was written: %s is a binary scene holding an embedded resource with a script "
611
- + "that is not a tool, and what that script changed when a headless engine loaded it "
612
- + "cannot be told apart from the file here. "
628
+ "Nothing was written: %s is a binary scene holding an embedded resource with a "
629
+ + "script that is not a tool, and what that script changed when a headless engine "
630
+ + "loaded it cannot be told apart from the file here. "
613
631
  )
614
632
  return refuse(binary % path + GdhHeadlessCheck.REMEDY)
615
633
  if check.is_valid():
616
634
  var saved: Node = packed.instantiate(PackedScene.GEN_EDIT_STATE_MAIN)
617
635
  if saved == null:
618
- return refuse("The packed copy of %s did not instantiate, so nothing was written." % path)
636
+ return refuse(
637
+ "The packed copy of %s did not instantiate, so nothing was written." % path
638
+ )
619
639
  _checked_pack = packed
620
640
  var missing: String = str(check.call(saved))
621
641
  saved.free()
@@ -637,30 +657,33 @@ func write(check: Callable = Callable()) -> Dictionary:
637
657
  if written.is_empty():
638
658
  also_saved.append_array(also)
639
659
  return written
660
+ var just_saved: GdhHeadlessCheck.GdhSaved = GdhHeadlessCheck.GdhSaved.new(
661
+ path, _text, func() -> Error: return GdhSceneUids.save_keeping_uid(packed, path)
662
+ )
640
663
  var unkept: String = GdhHeadlessCheck.settle_saved(
641
- path,
642
- _text,
664
+ just_saved,
643
665
  values,
644
666
  null,
645
667
  {},
646
668
  _reached,
647
669
  carried,
648
- func() -> Error: return GdhSceneUids.save_keeping_uid(packed, path)
649
670
  )
650
671
  if unkept.is_empty():
651
672
  also_saved.append_array(also)
652
673
  return {}
653
674
  return {
654
- "ok": false, "error": "Nothing was written: " + unkept + GdhHeadlessCheck.put_back(path, previous)
675
+ "ok": false,
676
+ "error": "Nothing was written: " + unkept + GdhHeadlessCheck.put_back(path, previous),
655
677
  }
656
678
 
657
679
 
658
680
  ## Each property [param state] stores differently from the file, on a node the file held, that the
659
- ## call neither asked for nor changed, as {node, property, was, saved}: was left out where the file did
660
- ## not set it, saved where the write does not. The engine writes a node as it holds it once the file
661
- ## has loaded, and one property can set others as it loads, as anchors_preset gives a centred Control
662
- ## both grow directions, so a file not written by the editor gains lines on any node, the one a set
663
- ## names among them. What the call's own change moved on that node is its answer's alsoChanged.
681
+ ## call neither asked for nor changed, as {node, property, was, saved}: was left out where the file
682
+ ## did not set it, saved where the write does not. The engine writes a node as it holds it once the
683
+ ## file has loaded, and one property can set others as it loads, as anchors_preset gives a centred
684
+ ## Control both grow directions, so a file not written by the editor gains lines on any node, the
685
+ ## one a set names among them. What the call's own change moved on that node is its answer's
686
+ ## alsoChanged.
664
687
  func _also_saved(state: SceneState) -> Array[Dictionary]:
665
688
  var noted: Array[Dictionary] = []
666
689
  var values: GdhSerialisation = GdhSerialisation.new()
@@ -685,7 +708,7 @@ func _also_saved(state: SceneState) -> Array[Dictionary]:
685
708
  var held: bool = filed.has(name)
686
709
  if held == stored.has(name) and (not held or GdhSame.same(stored[name], filed[name])):
687
710
  continue
688
- var entry: Dictionary = {"node": path_of(node), "property": name}
711
+ var entry: Dictionary = { "node": path_of(node), "property": name }
689
712
  if held:
690
713
  entry["was"] = values.serialize_value(filed[name])
691
714
  if stored.has(name):
@@ -712,10 +735,15 @@ static func save_packed(packed: PackedScene, scene_path: String) -> Dictionary:
712
735
  if not DirAccess.dir_exists_absolute(directory):
713
736
  var made: Error = DirAccess.make_dir_recursive_absolute(directory)
714
737
  if made != OK:
715
- return {"ok": false, "error": "Could not create %s: %s" % [directory, error_string(made)]}
738
+ return {
739
+ "ok": false, "error": "Could not create %s: %s" % [directory, error_string(made)],
740
+ }
716
741
  var saving: Error = GdhSceneUids.save_keeping_uid(packed, scene_path)
717
742
  if saving != OK:
718
- return {"ok": false, "error": "Failed to save %s: %s" % [scene_path, error_string(saving)]}
743
+ return {
744
+ "ok": false,
745
+ "error": "Failed to save %s: %s" % [scene_path, error_string(saving)],
746
+ }
719
747
  refresh(scene_path)
720
748
  return {}
721
749
 
@@ -724,7 +752,9 @@ static func save_packed(packed: PackedScene, scene_path: String) -> Dictionary:
724
752
  ## the editor's file list, and the tab showing it, whether current or not.
725
753
  static func refresh(scene_path: String) -> void:
726
754
  if ResourceLoader.has_cached(scene_path):
727
- var _replaced: Resource = ResourceLoader.load(scene_path, "", ResourceLoader.CACHE_MODE_REPLACE)
755
+ var _replaced: Resource = ResourceLoader.load(
756
+ scene_path, "", ResourceLoader.CACHE_MODE_REPLACE
757
+ )
728
758
  if not Engine.is_editor_hint():
729
759
  return
730
760
  scan_later()
@@ -773,11 +803,11 @@ static func unsaved(scene_path: String) -> bool:
773
803
 
774
804
  ## [param packed] with what the scene's scripts changed put back, as [pack, ""], or [null, why not].
775
805
  ##
776
- ## On a node the tool did not ask to change, a difference is put back to what the file holds, or, for
777
- ## a property the file does not set, to what the node would load with without it. On a node it did
778
- ## ask to change, through a script that is not a tool, every property it did not name is put back to
779
- ## what it held before. Packed again afterwards and put back again until nothing differs, since
780
- ## putting one property back can run a setter that changes another already put back (a script
806
+ ## On a node the tool did not ask to change, a difference is put back to what the file holds, or,
807
+ ## for a property the file does not set, to what the node would load with without it. On a node it
808
+ ## did ask to change, through a script that is not a tool, every property it did not name is put
809
+ ## back to what it held before. Packed again afterwards and put back again until nothing differs,
810
+ ## since putting one property back can run a setter that changes another already put back (a script
781
811
  ## setting speed in _init, whose setter tints modulate), and refused if something still does.
782
812
  func _settled(packed: PackedScene) -> Array:
783
813
  var current: PackedScene = packed
@@ -819,8 +849,8 @@ func _settled(packed: PackedScene) -> Array:
819
849
  return [null, _stray_text(stray_node, named)]
820
850
 
821
851
 
822
- ## Each node the tool asked to change through a script that is not a tool, with the properties it did
823
- ## not name that no longer hold what they held before, as [node, names].
852
+ ## Each node the tool asked to change through a script that is not a tool, with the properties it
853
+ ## did not name that no longer hold what they held before, as [node, names].
824
854
  func _moved_by_scripts() -> Array[Array]:
825
855
  var moved: Array[Array] = []
826
856
  for key: Variant in _asked:
@@ -882,8 +912,8 @@ func _stray_text(node: Node, name: String) -> String:
882
912
  return (
883
913
  (
884
914
  (
885
- "the script of %s did not start when a headless engine instanced %s, so the node would be "
886
- + "saved without it; the engine's messages give its errors. "
915
+ "the script of %s did not start when a headless engine instanced %s, so the "
916
+ + "node would be saved without it; the engine's messages give its errors. "
887
917
  )
888
918
  % [at, path]
889
919
  )
@@ -892,9 +922,9 @@ func _stray_text(node: Node, name: String) -> String:
892
922
  return (
893
923
  (
894
924
  (
895
- "%s.%s would be saved differently from what %s holds, and the call did not ask for it: the "
896
- + "scene's scripts ran when a headless engine instanced it, as they do in the game and not in "
897
- + "an editor. "
925
+ "%s.%s would be saved differently from what %s holds, and the call did not ask for "
926
+ + "it: the scene's scripts ran when a headless engine instanced it, as they do in "
927
+ + "the game and not in an editor. "
898
928
  )
899
929
  % [at, name, path]
900
930
  )
@@ -1,6 +1,5 @@
1
1
  @tool
2
2
  extends RefCounted
3
-
4
3
  ## How a scene or resource a tool saves keeps naming files by uid, as the editor's saves do.
5
4
 
6
5
  ## The files a headless save named by path alone because the uid cache holds no uid for them,
@@ -84,12 +83,17 @@ static func note_by_path(named: String) -> void:
84
83
 
85
84
  ## The uid attribute a reference to [param named] is written with, as ' uid="uid://..."', or "".
86
85
  ##
87
- ## Only a uid the uid cache gives that path, which is what the loader resolves it by. A uid the cache
88
- ## does not know (a file made with no editor since the last scan) is answered with a warning on every
89
- ## load, and one it gives another file (a scene copied whole keeps its original's) loads that file.
86
+ ## Only a uid the uid cache gives that path, which is what the loader resolves it by. A uid the
87
+ ## cache does not know (a file made with no editor since the last scan) is answered with a warning
88
+ ## on every load, and one it gives another file (a scene copied whole keeps its original's) loads
89
+ ## that file.
90
90
  static func uid_attribute(named: String) -> String:
91
91
  var id: int = ResourceLoader.get_resource_uid(named)
92
- if id == ResourceUID.INVALID_ID or not ResourceUID.has_id(id) or ResourceUID.get_id_path(id) != named:
92
+ if (
93
+ id == ResourceUID.INVALID_ID
94
+ or not ResourceUID.has_id(id)
95
+ or ResourceUID.get_id_path(id) != named
96
+ ):
93
97
  return ""
94
98
  return ' uid="%s"' % ResourceUID.id_to_text(id)
95
99
 
@@ -100,7 +104,9 @@ static func uid_in(file_path: String) -> int:
100
104
  return ResourceUID.INVALID_ID
101
105
  var file: FileAccess = FileAccess.open(file_path, FileAccess.READ)
102
106
  if file != null:
103
- var header: RegExMatch = RegEx.create_from_string('uid="(uid://[a-z0-9]+)"').search(file.get_line())
107
+ var header: RegExMatch = RegEx.create_from_string('uid="(uid://[a-z0-9]+)"').search(
108
+ file.get_line()
109
+ )
104
110
  file.close()
105
111
  if header != null:
106
112
  return ResourceUID.text_to_id(header.get_string(1))