gdharness 1.3.42 → 1.3.44

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 (106) hide show
  1. package/build/cli.js +383 -243
  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 +51 -17
  13. package/build/godot/addons/gdharness_editor/same_values.gd +4 -4
  14. package/build/godot/addons/gdharness_editor/scene_file.gd +127 -80
  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 +65 -42
  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 +51 -17
  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 +190 -109
  78. package/build/godot/operations/editor_tools.gd +10 -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 +61 -32
  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 +51 -17
  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/build/keeper.js +43 -43
  106. package/package.json +1 -1
@@ -1,6 +1,5 @@
1
1
  @tool
2
2
  extends Node
3
-
4
3
  ## Resource files, shaders and themes, written through the open editor.
5
4
 
6
5
  const GdhFileReads = preload("../file_reads.gd")
@@ -12,7 +11,6 @@ const GdhSame = preload("../same_values.gd")
12
11
  const GdhSceneFile = preload("../scene_file.gd")
13
12
  const GdhSceneUids = preload("../scene_uids.gd")
14
13
  const GdhSerialisation = preload("../serialisation.gd")
15
-
16
14
  # The minimal shader of a type: its declaration and the one function the engine calls for it.
17
15
  const SHADER_EMPTY: String = """shader_type %s;
18
16
 
@@ -20,7 +18,11 @@ void %s() {
20
18
  }
21
19
  """
22
20
  const SHADER_ENTRY: Dictionary[String, String] = {
23
- "canvas_item": "fragment", "spatial": "fragment", "particles": "process", "sky": "sky", "fog": "fog"
21
+ "canvas_item": "fragment",
22
+ "spatial": "fragment",
23
+ "particles": "process",
24
+ "sky": "sky",
25
+ "fog": "fog",
24
26
  }
25
27
 
26
28
  var _editor_plugin: EditorPlugin = null
@@ -59,7 +61,10 @@ func _taken(path: String, op: String, remedy: String = "Change it with op=modify
59
61
  if not FileAccess.file_exists(path):
60
62
  return ""
61
63
  return (
62
- "%s already exists, and %s would replace everything in it. %s, or create the new one under another path."
64
+ (
65
+ "%s already exists, and %s would replace everything in it. %s, or create the new one "
66
+ + "under another path."
67
+ )
63
68
  % [path, op, remedy]
64
69
  )
65
70
 
@@ -84,7 +89,9 @@ func _parse_properties_dict(raw: Variant) -> Dictionary:
84
89
  func _load_theme(theme_path: String) -> Theme:
85
90
  if not ResourceLoader.exists(theme_path, "Theme"):
86
91
  return null
87
- var loaded: Resource = ResourceLoader.load(theme_path, "Theme", ResourceLoader.CACHE_MODE_IGNORE)
92
+ var loaded: Resource = ResourceLoader.load(
93
+ theme_path, "Theme", ResourceLoader.CACHE_MODE_IGNORE
94
+ )
88
95
  if loaded is Theme:
89
96
  return loaded
90
97
  return null
@@ -97,7 +104,9 @@ func _load_theme(theme_path: String) -> Theme:
97
104
  ## Read back because a property can be set and still not be saved: one without storage, a
98
105
  ## script variable that is not exported. Both were answered as changed.
99
106
  func _save_checked(resource: Resource, path: String, names: Array, previous: Variant) -> String:
100
- var carried: Dictionary = GdhHeadlessCheck.embedded_in(GdhHeadlessCheck.stored(resource).values(), {})
107
+ var carried: Dictionary = GdhHeadlessCheck.embedded_in(
108
+ GdhHeadlessCheck.stored(resource).values(), {}
109
+ )
101
110
  var saving: Error = GdhSceneUids.save_keeping_uid(resource, path)
102
111
  if saving != OK:
103
112
  return "Failed to save %s: %s" % [path, error_string(saving)]
@@ -117,8 +126,8 @@ func _save_checked(resource: Resource, path: String, names: Array, previous: Var
117
126
  if not GdhSame.same(read, _properties.read_at(resource, property)):
118
127
  lost = (
119
128
  (
120
- "%s.%s would load from the file as %s: it is not a property the file keeps, so "
121
- + "nothing was changed."
129
+ "%s.%s would load from the file as %s: it is not a property the file "
130
+ + "keeps, so nothing was changed."
122
131
  )
123
132
  % [path, property, _properties.shown(read)]
124
133
  )
@@ -145,8 +154,9 @@ func _unkept_headless(
145
154
  return (
146
155
  (
147
156
  (
148
- "%s is a binary resource with a script that is not a tool, and what that script "
149
- + "changed when a headless engine loaded it cannot be told apart from the file here. "
157
+ "%s is a binary resource with a script that is not a tool, and what that "
158
+ + "script changed when a headless engine loaded it cannot be told apart "
159
+ + "from the file here. "
150
160
  )
151
161
  % path
152
162
  )
@@ -157,22 +167,23 @@ func _unkept_headless(
157
167
  if previous is PackedByteArray:
158
168
  var bytes: PackedByteArray = previous
159
169
  before = bytes.get_string_from_utf8()
160
- var asked: Dictionary = {"script": true}
170
+ var asked: Dictionary = { "script": true }
161
171
  var reached: Dictionary = {}
162
172
  for key: Variant in names:
163
173
  var property: String = str(key)
164
174
  asked[property] = true
165
175
  asked[property.get_slice(":", 0)] = true
166
176
  GdhHeadlessCheck.spare(reached, GdhHeadlessCheck.reached(resource, property))
177
+ var just_saved: GdhHeadlessCheck.GdhSaved = GdhHeadlessCheck.GdhSaved.new(
178
+ path, before, func() -> Error: return GdhSceneUids.save_keeping_uid(resource, path)
179
+ )
167
180
  return GdhHeadlessCheck.settle_saved(
168
- path,
169
- before,
181
+ just_saved,
170
182
  GdhHeadlessCheck.stored(resource).values(),
171
183
  resource if scripted else null,
172
184
  asked,
173
185
  reached,
174
- carried,
175
- func() -> Error: return GdhSceneUids.save_keeping_uid(resource, path)
186
+ carried
176
187
  )
177
188
 
178
189
 
@@ -180,17 +191,20 @@ func create_resource(args: Dictionary) -> Dictionary:
180
191
  var res_path: String = _ensure_res_path(str(args.get("resourcePath", "")))
181
192
  var resource_type: String = str(args.get("resourceType", "Resource"))
182
193
  if res_path == "res://":
183
- return {"ok": false, "error": "resourcePath is required"}
194
+ return { "ok": false, "error": "resourcePath is required" }
184
195
  var taken: String = _taken(res_path, "resource_edit op=create")
185
196
  if not taken.is_empty():
186
- return {"ok": false, "error": taken}
197
+ return { "ok": false, "error": taken }
187
198
 
188
199
  var script_path: String = str(args.get("script", ""))
189
200
  var made: Dictionary = GdhSceneFile.made_as(
190
- resource_type, "" if script_path == "" else _ensure_res_path(script_path), "Resource", "resourceType"
201
+ resource_type,
202
+ "" if script_path == "" else _ensure_res_path(script_path),
203
+ "Resource",
204
+ "resourceType"
191
205
  )
192
206
  if made.has("error"):
193
- return {"ok": false, "error": made["error"]}
207
+ return { "ok": false, "error": made["error"] }
194
208
  resource_type = str(made["type"])
195
209
  script_path = str(made["script"])
196
210
  if not ClassDB.class_exists(resource_type):
@@ -200,10 +214,15 @@ func create_resource(args: Dictionary) -> Dictionary:
200
214
  (
201
215
  "%s is neither an engine class nor a class_name a script in this project declares."
202
216
  % resource_type
203
- )
217
+ ),
218
+ }
219
+ if (
220
+ not ClassDB.is_parent_class(resource_type, "Resource")
221
+ or not ClassDB.can_instantiate(resource_type)
222
+ ):
223
+ return {
224
+ "ok": false, "error": "%s is not a resource class that can be created." % resource_type,
204
225
  }
205
- if not ClassDB.is_parent_class(resource_type, "Resource") or not ClassDB.can_instantiate(resource_type):
206
- return {"ok": false, "error": "%s is not a resource class that can be created." % resource_type}
207
226
  var resource: Resource = ClassDB.instantiate(resource_type)
208
227
 
209
228
  # A script that did not load was skipped, and a plain resource was saved in place of the custom
@@ -212,30 +231,37 @@ func create_resource(args: Dictionary) -> Dictionary:
212
231
  var script_file: String = _ensure_res_path(script_path)
213
232
  var script: Script = load(script_file) as Script
214
233
  if script == null:
215
- return {"ok": false, "error": "No script loads from " + script_file}
234
+ return { "ok": false, "error": "No script loads from " + script_file }
216
235
  var uncompiled: String = GdhSceneFile.uncompiled(script, script_file)
217
236
  if not uncompiled.is_empty():
218
- return {"ok": false, "error": uncompiled}
237
+ return { "ok": false, "error": uncompiled }
219
238
  var base: String = str(script.get_instance_base_type())
220
239
  if not ClassDB.is_parent_class(resource_type, base):
221
240
  return {
222
241
  "ok": false,
223
- "error": "%s extends %s, so it cannot be on a %s." % [script_file, base, resource_type]
242
+ "error":
243
+ "%s extends %s, so it cannot be on a %s." % [script_file, base, resource_type],
224
244
  }
225
245
  resource.set_script(script)
226
246
 
227
247
  var properties: Dictionary = _parse_properties_dict(args.get("properties", {}))
228
248
  var refused: String = _properties.write_all(resource, properties)
229
249
  if not refused.is_empty():
230
- return {"ok": false, "error": GdhHeadlessCheck.setter_ran(resource, _properties.unheld, refused)}
250
+ return {
251
+ "ok": false, "error": GdhHeadlessCheck.setter_ran(
252
+ resource,
253
+ _properties.unheld,
254
+ refused,
255
+ ),
256
+ }
231
257
 
232
258
  var folder: String = GdhSceneFile.make_folder_for(res_path)
233
259
  if not folder.is_empty():
234
- return {"ok": false, "error": folder}
235
- # As a read names it, the engine class under resourceType and the script's class beside it, since
236
- # a create given resourceType Resource and a script answered nothing of the class it had made, and
237
- # one given a class_name answered it where a read of the same file answers Resource.
238
- var answer: Dictionary = {"ok": true, "resourcePath": res_path, "resourceType": resource_type}
260
+ return { "ok": false, "error": folder }
261
+ # As a read names it, the engine class under resourceType and the script's class beside it,
262
+ # since a create given resourceType Resource and a script answered nothing of the class it had
263
+ # made, and one given a class_name answered it where a read of the same file answers Resource.
264
+ var answer: Dictionary = { "ok": true, "resourcePath": res_path, "resourceType": resource_type }
239
265
  var scripted: Variant = resource.get_script()
240
266
  if scripted is Script:
241
267
  var attached: Script = scripted
@@ -245,7 +271,7 @@ func create_resource(args: Dictionary) -> Dictionary:
245
271
  _properties.note_write(answer)
246
272
  var failed: String = _save_checked(resource, res_path, properties.keys(), null)
247
273
  if not failed.is_empty():
248
- return {"ok": false, "error": failed}
274
+ return { "ok": false, "error": failed }
249
275
  _note_put_back(answer, resource, res_path)
250
276
  if not properties.is_empty():
251
277
  answer["properties"] = _held(resource, properties.keys())
@@ -255,9 +281,9 @@ func create_resource(args: Dictionary) -> Dictionary:
255
281
  func modify_resource(args: Dictionary) -> Dictionary:
256
282
  var res_path: String = _ensure_res_path(str(args.get("resourcePath", "")))
257
283
  if res_path == "res://":
258
- return {"ok": false, "error": "resourcePath is required"}
284
+ return { "ok": false, "error": "resourcePath is required" }
259
285
  if not FileAccess.file_exists(res_path):
260
- return {"ok": false, "error": "No resource at %s; op=create makes one there." % res_path}
286
+ return { "ok": false, "error": "No resource at %s; op=create makes one there." % res_path }
261
287
  # The editor's copy of an imported file is rebuilt from the source on every import, and a save
262
288
  # to it fails; edited through the cache, the editor kept the changed copy after the refusal.
263
289
  if FileAccess.file_exists(res_path + ".import"):
@@ -266,17 +292,17 @@ func modify_resource(args: Dictionary) -> Dictionary:
266
292
  "error":
267
293
  (
268
294
  (
269
- "%s is imported, so what it holds comes from its source file and its import options. "
270
- + "Change those with project_import op=set_options."
295
+ "%s is imported, so what it holds comes from its source file and its import "
296
+ + "options. Change those with project_import op=set_options."
271
297
  )
272
298
  % res_path
273
- )
299
+ ),
274
300
  }
275
301
 
276
302
  # Past the cache, so a refusal leaves the copy open scenes hold as it was.
277
303
  var resource: Resource = ResourceLoader.load(res_path, "", ResourceLoader.CACHE_MODE_IGNORE)
278
304
  if resource == null:
279
- return {"ok": false, "error": "%s did not load as a resource." % res_path}
305
+ return { "ok": false, "error": "%s did not load as a resource." % res_path }
280
306
  # A scene's own properties are its packed bundle, so a property named for a scene is one of its
281
307
  # nodes', and "PackedScene has no property" left the caller looking for the tool that sets it.
282
308
  if resource is PackedScene:
@@ -284,9 +310,12 @@ func modify_resource(args: Dictionary) -> Dictionary:
284
310
  "ok": false,
285
311
  "error":
286
312
  (
287
- "%s is a scene: scene_node set changes one node's properties, the root's at nodePath \".\"."
313
+ (
314
+ "%s is a scene: scene_node set changes one node's properties, the root's at "
315
+ + "nodePath \".\"."
316
+ )
288
317
  % res_path
289
- )
318
+ ),
290
319
  }
291
320
 
292
321
  var properties: Dictionary = _parse_properties_dict(args.get("properties", ""))
@@ -296,13 +325,13 @@ func modify_resource(args: Dictionary) -> Dictionary:
296
325
  else:
297
326
  refused = GdhHeadlessCheck.setter_ran(resource, _properties.unheld, refused)
298
327
  if not refused.is_empty():
299
- return {"ok": false, "error": refused}
300
- var answer: Dictionary = {"ok": true, "resourcePath": res_path}
328
+ return { "ok": false, "error": refused }
329
+ var answer: Dictionary = { "ok": true, "resourcePath": res_path }
301
330
  _properties.note_write(answer)
302
331
  var previous: PackedByteArray = FileAccess.get_file_as_bytes(res_path)
303
332
  var failed: String = _save_checked(resource, res_path, properties.keys(), previous)
304
333
  if not failed.is_empty():
305
- return {"ok": false, "error": failed}
334
+ return { "ok": false, "error": failed }
306
335
  _note_put_back(answer, resource, res_path)
307
336
  answer["properties"] = _held(resource, properties.keys())
308
337
  return answer
@@ -328,12 +357,14 @@ func _note_put_back(answer: Dictionary, resource: Resource, path: String) -> voi
328
357
  func _holding_itself(resource: Resource, path: String, names: Array) -> String:
329
358
  for key: Variant in names:
330
359
  var property: String = str(key)
331
- for held: Resource in GdhHeadlessCheck.resources_in(_properties.read_at(resource, property)):
360
+ for held: Resource in GdhHeadlessCheck.resources_in(
361
+ _properties.read_at(resource, property)
362
+ ):
332
363
  if held == resource or held.resource_path == path:
333
364
  return (
334
365
  (
335
- "%s.%s would hold %s itself, and a resource cannot hold the file it is saved as: "
336
- + "Godot saves the reference as null. Nothing was written."
366
+ "%s.%s would hold %s itself, and a resource cannot hold the file it is "
367
+ + "saved as: Godot saves the reference as null. Nothing was written."
337
368
  )
338
369
  % [GdhPropertyPaths.class_called(resource), property, path]
339
370
  )
@@ -354,7 +385,7 @@ func _held(resource: Resource, asked: Array) -> Dictionary:
354
385
  func read_resource(args: Dictionary) -> Dictionary:
355
386
  var res_path: String = _ensure_res_path(str(args.get("resourcePath", "")))
356
387
  if res_path == "res://":
357
- return {"ok": false, "error": "resourcePath is required"}
388
+ return { "ok": false, "error": "resourcePath is required" }
358
389
  # The server takes a resource kept inside a file apart, so the boundary judges the file alone.
359
390
  var inner: String = str(args.get("subResource", ""))
360
391
  if not inner.is_empty():
@@ -366,10 +397,10 @@ func create_shader(args: Dictionary) -> Dictionary:
366
397
  var shader_path: String = _ensure_res_path(str(args.get("resourcePath", "")))
367
398
  var shader_type: String = str(args.get("shaderType", "canvas_item"))
368
399
  if shader_path == "res://":
369
- return {"ok": false, "error": "resourcePath is required"}
400
+ return { "ok": false, "error": "resourcePath is required" }
370
401
  var taken: String = _taken(shader_path, "resource_edit op=create_shader", "Edit its text")
371
402
  if not taken.is_empty():
372
- return {"ok": false, "error": taken}
403
+ return { "ok": false, "error": taken }
373
404
 
374
405
  var code: String = str(args.get("code", ""))
375
406
  if code == "":
@@ -377,21 +408,24 @@ func create_shader(args: Dictionary) -> Dictionary:
377
408
 
378
409
  var folder: String = GdhSceneFile.make_folder_for(shader_path)
379
410
  if not folder.is_empty():
380
- return {"ok": false, "error": folder}
411
+ return { "ok": false, "error": folder }
381
412
  var file: FileAccess = FileAccess.open(shader_path, FileAccess.WRITE)
382
413
  if file == null:
383
414
  return {
384
415
  "ok": false,
385
416
  "error":
386
- "Could not open %s for writing: %s" % [shader_path, error_string(FileAccess.get_open_error())]
417
+ (
418
+ "Could not open %s for writing: %s"
419
+ % [shader_path, error_string(FileAccess.get_open_error())]
420
+ ),
387
421
  }
388
422
  var written: bool = file.store_string(code)
389
423
  file.close()
390
424
  if not written:
391
- return {"ok": false, "error": "Failed to write the shader file " + shader_path}
425
+ return { "ok": false, "error": "Failed to write the shader file " + shader_path }
392
426
 
393
427
  _written(shader_path)
394
- var answer: Dictionary = {"ok": true, "shaderPath": shader_path, "shaderType": shader_type}
428
+ var answer: Dictionary = { "ok": true, "shaderPath": shader_path, "shaderType": shader_type }
395
429
  var errors: Array[Dictionary] = _shader_errors(shader_path)
396
430
  answer["compiles"] = errors.is_empty()
397
431
  if not errors.is_empty():
@@ -405,42 +439,21 @@ func create_shader(args: Dictionary) -> Dictionary:
405
439
  func _shader_errors(path: String) -> Array[Dictionary]:
406
440
  var caught: GdhShaderErrors = GdhShaderErrors.new()
407
441
  OS.add_logger(caught)
408
- var shader: Shader = ResourceLoader.load(path, "Shader", ResourceLoader.CACHE_MODE_IGNORE) as Shader
442
+ var shader: Shader = (
443
+ ResourceLoader.load(path, "Shader", ResourceLoader.CACHE_MODE_IGNORE) as Shader
444
+ )
409
445
  if shader != null:
410
446
  var _uniforms: Array = shader.get_shader_uniform_list()
411
447
  OS.remove_logger(caught)
412
448
  return caught.raised
413
449
 
414
450
 
415
- ## Every shader error the engine raises while it is attached, in order.
416
- class GdhShaderErrors:
417
- extends Logger
418
-
419
- var raised: Array[Dictionary] = []
420
-
421
- func _log_message(_message: String, _error: bool) -> void:
422
- pass
423
-
424
- # The engine's own signature for a Logger, eight parameters and not this project's to shape.
425
- # gdlint:ignore = function-arguments-number
426
- func _log_error(
427
- _function: String,
428
- _file: String,
429
- line: int,
430
- code: String,
431
- rationale: String,
432
- _editor_notify: bool,
433
- error_type: int,
434
- _script_backtraces: Array[ScriptBacktrace],
435
- ) -> void:
436
- if error_type == Logger.ERROR_TYPE_SHADER:
437
- raised.append({"line": line, "message": code if rationale.is_empty() else rationale})
438
-
439
-
440
451
  ## Saves [param theme] over [param theme_path] with [param items] changed, answering "" or why not.
441
452
  func _save_theme(theme: Theme, theme_path: String, items: Array) -> String:
442
453
  var previous: PackedByteArray = FileAccess.get_file_as_bytes(theme_path)
443
- var carried: Dictionary = GdhHeadlessCheck.embedded_in(GdhHeadlessCheck.stored(theme).values(), {})
454
+ var carried: Dictionary = GdhHeadlessCheck.embedded_in(
455
+ GdhHeadlessCheck.stored(theme).values(), {}
456
+ )
444
457
  var saving: Error = GdhSceneUids.save_keeping_uid(theme, theme_path)
445
458
  if saving != OK:
446
459
  return "Failed to save %s: %s" % [theme_path, error_string(saving)]
@@ -454,32 +467,37 @@ func _save_theme(theme: Theme, theme_path: String, items: Array) -> String:
454
467
  func set_theme_color(args: Dictionary) -> Dictionary:
455
468
  var theme_path: String = _ensure_res_path(str(args.get("resourcePath", "")))
456
469
  if theme_path == "res://":
457
- return {"ok": false, "error": "resourcePath is required"}
470
+ return { "ok": false, "error": "resourcePath is required" }
458
471
 
459
472
  var color_name: String = str(args.get("colorName", ""))
460
473
  var control_type: String = str(args.get("controlType", ""))
461
474
  if color_name.is_empty() or control_type.is_empty():
462
- return {"ok": false, "error": "colorName and controlType are required"}
475
+ return { "ok": false, "error": "colorName and controlType are required" }
463
476
 
464
477
  var theme: Theme = _load_theme(theme_path)
465
478
  if theme == null:
466
- return {"ok": false, "error": "No Theme at %s. Create one there first." % theme_path}
479
+ return { "ok": false, "error": "No Theme at %s. Create one there first." % theme_path }
467
480
 
468
481
  # Read as a property write reads a colour, html code included, so a value scene_node takes for a
469
482
  # Button's modulate is taken for its theme colour too; "#ff8000" was refused here alone.
470
483
  var parsed: Array = _properties.colour_from(args.get("color", {}), "color")
471
484
  if not parsed[0]:
472
- return {"ok": false, "error": parsed[1]}
485
+ return { "ok": false, "error": parsed[1] }
473
486
  var color: Color = parsed[1]
474
487
  theme.set_color(color_name, control_type, color)
475
488
 
476
- var unsaved: String = _save_theme(theme, theme_path, ["%s/colors/%s" % [control_type, color_name]])
489
+ var unsaved: String = _save_theme(
490
+ theme, theme_path, ["%s/colors/%s" % [control_type, color_name]]
491
+ )
477
492
  if not unsaved.is_empty():
478
- return {"ok": false, "error": unsaved}
493
+ return { "ok": false, "error": unsaved }
479
494
 
480
495
  var saved: Theme = _load_theme(theme_path)
481
496
  if saved == null or not saved.has_color(color_name, control_type):
482
- return {"ok": false, "error": "The theme saved without %s/%s" % [control_type, color_name]}
497
+ return {
498
+ "ok": false,
499
+ "error": "The theme saved without %s/%s" % [control_type, color_name],
500
+ }
483
501
 
484
502
  var stored: Color = saved.get_color(color_name, control_type)
485
503
  return _noting_variation(
@@ -488,7 +506,7 @@ func set_theme_color(args: Dictionary) -> Dictionary:
488
506
  "resourcePath": theme_path,
489
507
  "controlType": control_type,
490
508
  "colorName": color_name,
491
- "color": {"r": stored.r, "g": stored.g, "b": stored.b, "a": stored.a}
509
+ "color": { "r": stored.r, "g": stored.g, "b": stored.b, "a": stored.a },
492
510
  }
493
511
  )
494
512
 
@@ -496,33 +514,36 @@ func set_theme_color(args: Dictionary) -> Dictionary:
496
514
  func set_theme_font_size(args: Dictionary) -> Dictionary:
497
515
  var theme_path: String = _ensure_res_path(str(args.get("resourcePath", "")))
498
516
  if theme_path == "res://":
499
- return {"ok": false, "error": "resourcePath is required"}
517
+ return { "ok": false, "error": "resourcePath is required" }
500
518
 
501
519
  var font_size_name: String = str(args.get("fontSizeName", ""))
502
520
  var control_type: String = str(args.get("controlType", ""))
503
521
  if font_size_name.is_empty() or control_type.is_empty():
504
- return {"ok": false, "error": "fontSizeName and controlType are required"}
522
+ return { "ok": false, "error": "fontSizeName and controlType are required" }
505
523
 
506
524
  var theme: Theme = _load_theme(theme_path)
507
525
  if theme == null:
508
- return {"ok": false, "error": "No Theme at %s. Create one there first." % theme_path}
526
+ return { "ok": false, "error": "No Theme at %s. Create one there first." % theme_path }
509
527
 
510
528
  # Godot reads a size of 0 or less as unset and answers the theme's default, so -3 would be read
511
529
  # back as 16.
512
530
  var pixels: float = GdhRead.as_float(args.get("size"), 0.0)
513
531
  if not is_finite(pixels) or pixels != floorf(pixels):
514
- return {"ok": false, "error": "size is a whole number of pixels, and %s is not one." % str(pixels)}
532
+ return {
533
+ "ok": false,
534
+ "error": "size is a whole number of pixels, and %s is not one." % str(pixels),
535
+ }
515
536
  if pixels < 1:
516
537
  return {
517
538
  "ok": false,
518
539
  "error":
519
540
  (
520
541
  (
521
- "size must be 1 or more, not %d: the theme would hold it, and Godot reads a size of 0 or "
522
- + "less as unset and draws the default size instead."
542
+ "size must be 1 or more, not %d: the theme would hold it, and Godot reads a "
543
+ + "size of 0 or less as unset and draws the default size instead."
523
544
  )
524
545
  % int(pixels)
525
- )
546
+ ),
526
547
  }
527
548
  theme.set_font_size(font_size_name, control_type, int(pixels))
528
549
 
@@ -530,11 +551,13 @@ func set_theme_font_size(args: Dictionary) -> Dictionary:
530
551
  theme, theme_path, ["%s/font_sizes/%s" % [control_type, font_size_name]]
531
552
  )
532
553
  if not unsaved.is_empty():
533
- return {"ok": false, "error": unsaved}
554
+ return { "ok": false, "error": unsaved }
534
555
 
535
556
  var saved: Theme = _load_theme(theme_path)
536
557
  if saved == null or not saved.has_font_size(font_size_name, control_type):
537
- return {"ok": false, "error": "The theme saved without %s/%s" % [control_type, font_size_name]}
558
+ return {
559
+ "ok": false, "error": "The theme saved without %s/%s" % [control_type, font_size_name],
560
+ }
538
561
 
539
562
  return _noting_variation(
540
563
  {
@@ -542,7 +565,7 @@ func set_theme_font_size(args: Dictionary) -> Dictionary:
542
565
  "resourcePath": theme_path,
543
566
  "controlType": control_type,
544
567
  "fontSizeName": font_size_name,
545
- "size": saved.get_font_size(font_size_name, control_type)
568
+ "size": saved.get_font_size(font_size_name, control_type),
546
569
  }
547
570
  )
548
571
 
@@ -554,11 +577,11 @@ func remove_theme_item(args: Dictionary) -> Dictionary:
554
577
  var control_type: String = str(args.get("controlType", ""))
555
578
  var theme: Theme = _load_theme(theme_path)
556
579
  if theme == null:
557
- return {"ok": false, "error": "No Theme at %s." % theme_path}
580
+ return { "ok": false, "error": "No Theme at %s." % theme_path }
558
581
  if not theme.get_type_list().has(control_type):
559
582
  var types: PackedStringArray = theme.get_type_list()
560
583
  var said: String = "%s holds no type %s" % [theme_path, control_type]
561
- return {"ok": false, "error": GdhRead.offered(said, control_type, Array(types))}
584
+ return { "ok": false, "error": GdhRead.offered(said, control_type, Array(types)) }
562
585
  var kinds: Array[String] = ["colors", "constants", "fonts", "font_sizes", "icons", "styles"]
563
586
  var item: String = str(args.get("item", ""))
564
587
  var removed: Array[String] = []
@@ -581,13 +604,21 @@ func remove_theme_item(args: Dictionary) -> Dictionary:
581
604
  "%s holds no items, only the type it varies, %s; "
582
605
  + "without item, remove_theme_item takes it out."
583
606
  )
584
- return {"ok": false, "error": only % [control_type, base]}
607
+ return { "ok": false, "error": only % [control_type, base] }
585
608
  elif removed.is_empty():
586
- return {"ok": false, "error": GdhRead.offered("%s has no item %s" % [control_type, item], item, held)}
609
+ return {
610
+ "ok": false,
611
+ "error": GdhRead.offered("%s has no item %s" % [control_type, item], item, held),
612
+ }
587
613
  var unsaved: String = _save_theme(theme, theme_path, removed)
588
614
  if not unsaved.is_empty():
589
- return {"ok": false, "error": unsaved}
590
- return {"ok": true, "resourcePath": theme_path, "controlType": control_type, "removed": removed}
615
+ return { "ok": false, "error": unsaved }
616
+ return {
617
+ "ok": true,
618
+ "resourcePath": theme_path,
619
+ "controlType": control_type,
620
+ "removed": removed,
621
+ }
591
622
 
592
623
 
593
624
  ## [param answer] with a note when its controlType names no class a theme styles. Any name is a
@@ -602,8 +633,8 @@ func _noting_variation(answer: Dictionary) -> Dictionary:
602
633
  return answer
603
634
  answer["note"] = (
604
635
  (
605
- "%s is not a Control or Window class, so this is a type variation: it styles only a control "
606
- + "whose theme_type_variation is %s."
636
+ "%s is not a Control or Window class, so this is a type variation: it styles only a "
637
+ + "control whose theme_type_variation is %s."
607
638
  )
608
639
  % [type, type]
609
640
  )
@@ -616,7 +647,35 @@ func _noting_variation(answer: Dictionary) -> Dictionary:
616
647
  var close: PackedStringArray = GdhRead.nearest(type, themed)
617
648
  if not close.is_empty():
618
649
  answer["note"] += (
619
- (" If a class was meant, the nearest %s %s, and remove_theme_item with controlType %s takes this out.")
650
+ (
651
+ " If a class was meant, the nearest %s %s, and remove_theme_item with controlType "
652
+ + "%s takes this out."
653
+ )
620
654
  % ["is" if close.size() == 1 else "are", ", ".join(close), type]
621
655
  )
622
656
  return answer
657
+
658
+
659
+ ## Every shader error the engine raises while it is attached, in order.
660
+ class GdhShaderErrors:
661
+ extends Logger
662
+
663
+ var raised: Array[Dictionary] = []
664
+
665
+ func _log_message(_message: String, _error: bool) -> void:
666
+ pass
667
+
668
+ # The engine's own signature for a Logger, eight parameters and not this project's to shape.
669
+ # gdstyle:ignore=quality/max-parameters
670
+ func _log_error(
671
+ _function: String,
672
+ _file: String,
673
+ line: int,
674
+ code: String,
675
+ rationale: String,
676
+ _editor_notify: bool,
677
+ error_type: int,
678
+ _script_backtraces: Array[ScriptBacktrace],
679
+ ) -> void:
680
+ if error_type == Logger.ERROR_TYPE_SHADER:
681
+ raised.append({ "line": line, "message": code if rationale.is_empty() else rationale })
@@ -1,6 +1,5 @@
1
1
  @tool
2
2
  extends RefCounted
3
-
4
3
  ## The groups a node of a scene is in. A scene's own file puts a node in a group and can take it
5
4
  ## out of one it put it in; a group the scene that makes the node puts it in, the one it is
6
5
  ## instanced from or one this scene inherits, can be changed only there, as the editor's group dock
@@ -15,20 +14,20 @@ static func read(scene: GdhSceneFile, node: Node) -> Dictionary:
15
14
  var all: Array = _sorted(node.get_groups())
16
15
  if all.is_empty():
17
16
  return {}
18
- var answer: Dictionary = {"groups": all}
17
+ var answer: Dictionary = { "groups": all }
19
18
  var here: Array = _sorted(scene.groups_at(scene.path_of(node)))
20
19
  if here.size() != all.size():
21
20
  answer["groupsHere"] = here
22
21
  return answer
23
22
 
24
23
 
25
- ## The groups of [param node], just added to this scene and put in [param adding] there, in the shape
26
- ## [method read] answers: this scene's file does not hold the node until it is saved.
24
+ ## The groups of [param node], just added to this scene and put in [param adding] there, in the
25
+ ## shape [method read] answers: this scene's file does not hold the node until it is saved.
27
26
  static func read_added(node: Node, adding: Array) -> Dictionary:
28
27
  var all: Array = _sorted(node.get_groups())
29
28
  if all.is_empty():
30
29
  return {}
31
- var answer: Dictionary = {"groups": all}
30
+ var answer: Dictionary = { "groups": all }
32
31
  var here: Array = []
33
32
  for group: String in _sorted(adding):
34
33
  if not here.has(group):
@@ -65,19 +64,27 @@ static func change(scene: GdhSceneFile, node: Node, adding: Array, removing: Arr
65
64
  var held: Array = _sorted(node.get_groups())
66
65
  return (
67
66
  "%s is in no group %s; %s."
68
- % [node.name, name, "it is in " + ", ".join(held) if not held.is_empty() else "it is in none"]
67
+ % [
68
+ node.name,
69
+ name,
70
+ "it is in " + ", ".join(held) if not held.is_empty() else "it is in none",
71
+ ]
69
72
  )
70
73
  if not here.has(name):
71
74
  var from: String = scene.values_from(node, at)
72
75
  if from.is_empty():
73
76
  return (
74
- "%s is in %s, which no scene file puts it in: something puts it there as it is made, such as its script."
77
+ (
78
+ "%s is in %s, which no scene file puts it in: something puts it there as "
79
+ + "it is made, such as its script."
80
+ )
75
81
  % [node.name, name]
76
82
  )
77
83
  return (
78
84
  (
79
- "%s is in %s because %s puts it there, and no scene using that one can take it out: "
80
- + "remove the group in %s, which takes it out in every scene using that one."
85
+ "%s is in %s because %s puts it there, and no scene using that one can take it "
86
+ + "out: remove the group in %s, which takes it out in every scene using that "
87
+ + "one."
81
88
  )
82
89
  % [node.name, name, from, from]
83
90
  )