gdharness 1.3.0 → 1.3.1

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 (68) hide show
  1. package/build/cli.js +14 -13
  2. package/build/godot/addons/gdharness_editor/animation_reads.gd +7 -19
  3. package/build/godot/addons/gdharness_editor/bridge_client.gd +8 -8
  4. package/build/godot/addons/gdharness_editor/file_reads.gd +60 -25
  5. package/build/godot/addons/gdharness_editor/headless_check.gd +7 -7
  6. package/build/godot/addons/gdharness_editor/plugin.gd +6 -6
  7. package/build/godot/addons/gdharness_editor/property_values.gd +120 -53
  8. package/build/godot/addons/gdharness_editor/reading.gd +28 -0
  9. package/build/godot/addons/gdharness_editor/scene_file.gd +28 -23
  10. package/build/godot/addons/gdharness_editor/serialisation.gd +45 -94
  11. package/build/godot/addons/gdharness_editor/tool_executor.gd +24 -24
  12. package/build/godot/addons/gdharness_editor/tools/animation_tools.gd +52 -41
  13. package/build/godot/addons/gdharness_editor/tools/filesystem_tools.gd +4 -4
  14. package/build/godot/addons/gdharness_editor/tools/play_tools.gd +2 -2
  15. package/build/godot/addons/gdharness_editor/tools/profile_tools.gd +14 -14
  16. package/build/godot/addons/gdharness_editor/tools/resource_tools.gd +48 -41
  17. package/build/godot/addons/gdharness_editor/tools/scene_kin.gd +17 -17
  18. package/build/godot/addons/gdharness_editor/tools/scene_paths.gd +14 -12
  19. package/build/godot/addons/gdharness_editor/tools/scene_placing.gd +7 -7
  20. package/build/godot/addons/gdharness_editor/tools/scene_tools.gd +81 -77
  21. package/build/godot/addons/gdharness_editor/tools/settings_tools.gd +4 -4
  22. package/build/godot/addons/gdharness_editor/value_shapes.gd +111 -0
  23. package/build/godot/addons/gdharness_editor/value_shapes.gd.uid +1 -0
  24. package/build/godot/addons/gdharness_runtime/reading.gd +28 -0
  25. package/build/godot/addons/gdharness_runtime/runtime_accessibility.gd +18 -18
  26. package/build/godot/addons/gdharness_runtime/runtime_autoload.gd +29 -29
  27. package/build/godot/addons/gdharness_runtime/runtime_capture.gd +21 -18
  28. package/build/godot/addons/gdharness_runtime/runtime_changes.gd +86 -80
  29. package/build/godot/addons/gdharness_runtime/runtime_input.gd +65 -64
  30. package/build/godot/addons/gdharness_runtime/runtime_items.gd +6 -6
  31. package/build/godot/addons/gdharness_runtime/runtime_menus.gd +8 -8
  32. package/build/godot/addons/gdharness_runtime/runtime_mouse.gd +6 -6
  33. package/build/godot/addons/gdharness_runtime/runtime_pad.gd +10 -10
  34. package/build/godot/addons/gdharness_runtime/runtime_paths.gd +7 -7
  35. package/build/godot/addons/gdharness_runtime/runtime_queries.gd +41 -41
  36. package/build/godot/addons/gdharness_runtime/runtime_says.gd +3 -3
  37. package/build/godot/addons/gdharness_runtime/runtime_targets.gd +33 -33
  38. package/build/godot/addons/gdharness_runtime/runtime_typing.gd +20 -20
  39. package/build/godot/addons/gdharness_runtime/runtime_values.gd +25 -24
  40. package/build/godot/addons/gdharness_runtime/runtime_waits.gd +56 -50
  41. package/build/godot/addons/gdharness_runtime/serialisation.gd +45 -94
  42. package/build/godot/addons/gdharness_runtime/value_shapes.gd +111 -0
  43. package/build/godot/addons/gdharness_runtime/value_shapes.gd.uid +1 -0
  44. package/build/godot/operations/animation_reads.gd +7 -19
  45. package/build/godot/operations/audio_buses.gd +10 -10
  46. package/build/godot/operations/class_cache.gd +16 -16
  47. package/build/godot/operations/classdb_queries.gd +12 -12
  48. package/build/godot/operations/dependencies.gd +33 -29
  49. package/build/godot/operations/editor_tools.gd +12 -12
  50. package/build/godot/operations/export_presets.gd +10 -8
  51. package/build/godot/operations/file_reads.gd +60 -25
  52. package/build/godot/operations/gdscript_analysis.gd +20 -20
  53. package/build/godot/operations/gdscript_authoring.gd +17 -15
  54. package/build/godot/operations/godot_operations.gd +64 -63
  55. package/build/godot/operations/import_pipeline.gd +20 -20
  56. package/build/godot/operations/input_actions.gd +17 -17
  57. package/build/godot/operations/logger.gd +1 -1
  58. package/build/godot/operations/plugins.gd +8 -8
  59. package/build/godot/operations/project_config.gd +24 -17
  60. package/build/godot/operations/project_diagnostics.gd +9 -9
  61. package/build/godot/operations/property_values.gd +120 -53
  62. package/build/godot/operations/reading.gd +28 -0
  63. package/build/godot/operations/rename_checks.gd +3 -3
  64. package/build/godot/operations/resource_files.gd +5 -5
  65. package/build/godot/operations/script_check.gd +7 -7
  66. package/build/godot/operations/serialisation.gd +45 -94
  67. package/build/godot/operations/value_shapes.gd +111 -0
  68. package/package.json +1 -1
@@ -1,10 +1,10 @@
1
1
  extends RefCounted
2
2
 
3
- const Patterns = preload("patterns.gd")
4
- const Read = preload("reading.gd")
5
- const FileWalk = preload("file_walk.gd")
6
- const Log = preload("logger.gd")
7
- const ResourceFiles = preload("resource_files.gd")
3
+ const GdhPatterns = preload("patterns.gd")
4
+ const GdhRead = preload("reading.gd")
5
+ const GdhFileWalk = preload("file_walk.gd")
6
+ const GdhLog = preload("logger.gd")
7
+ const GdhResourceFiles = preload("resource_files.gd")
8
8
 
9
9
  ## What a reverse search reads unless told otherwise: the resource and code files, and the settings
10
10
  ## files that name resources, "godot" for project.godot, "cfg" for plugin.cfg and export_presets.cfg,
@@ -21,8 +21,8 @@ const REFERENCE_PATTERNS: Array[String] = [
21
21
  'ext_resource.*path="([^"]+)"',
22
22
  ]
23
23
 
24
- var _log: Log
25
- var _files: FileWalk = FileWalk.new()
24
+ var _log: GdhLog
25
+ var _files: GdhFileWalk = GdhFileWalk.new()
26
26
  ## The project's class names and the scripts declaring them, read the first time a script is walked
27
27
  ## and kept for the call: walking every resource starts a walk per root, and each reading every
28
28
  ## script again made the whole-project answer quadratic in the number of scripts.
@@ -33,7 +33,7 @@ var _classes_read: bool = false
33
33
  # Everything one dependency walk carries: the settings it was started with, and the state it
34
34
  # accumulates as it recurses. Kept together so the recursion passes one object rather than
35
35
  # six positional arguments that have to stay in the same order at every call site.
36
- class DependencyWalk:
36
+ class GdhDependencyWalk:
37
37
  var max_depth: int
38
38
  var include_built_in: bool
39
39
  var visited: Dictionary = {}
@@ -46,7 +46,7 @@ class DependencyWalk:
46
46
  circular_references = p_circular_references
47
47
 
48
48
 
49
- func _init(p_log: Log) -> void:
49
+ func _init(p_log: GdhLog) -> void:
50
50
  _log = p_log
51
51
 
52
52
 
@@ -54,9 +54,9 @@ func _init(p_log: Log) -> void:
54
54
  func get_dependencies(params: Dictionary) -> Dictionary:
55
55
  var resource_path: String = str(params.get("resource_path", ""))
56
56
  # No depth, or a depth of zero or less, means the whole chain; the walk stops at cycles.
57
- var depth: int = Read.as_int(params.get("depth", 0))
57
+ var depth: int = GdhRead.as_int(params.get("depth", 0))
58
58
  var max_depth: int = depth if depth > 0 else 1000
59
- var include_built_in: bool = Read.as_bool(params.get("include_builtin", false))
59
+ var include_built_in: bool = GdhRead.as_bool(params.get("include_builtin", false))
60
60
 
61
61
  _log.info(
62
62
  (
@@ -77,7 +77,7 @@ func get_dependencies(params: Dictionary) -> Dictionary:
77
77
  if not FileAccess.file_exists(full_path):
78
78
  return _log.failure("Resource file does not exist: " + full_path)
79
79
 
80
- var walk: DependencyWalk = DependencyWalk.new(max_depth, include_built_in, circular_references)
80
+ var walk: GdhDependencyWalk = GdhDependencyWalk.new(max_depth, include_built_in, circular_references)
81
81
  dependencies[full_path] = _analyze_resource(full_path, 0, walk)
82
82
  total_resources = 1
83
83
  else:
@@ -89,7 +89,9 @@ func get_dependencies(params: Dictionary) -> Dictionary:
89
89
  # Each root gets a fresh walk so a cycle is reported from every resource it passes
90
90
  # through, rather than only from whichever one happened to be walked first.
91
91
  for res_path: String in all_resources:
92
- var walk: DependencyWalk = DependencyWalk.new(max_depth, include_built_in, circular_references)
92
+ var walk: GdhDependencyWalk = GdhDependencyWalk.new(
93
+ max_depth, include_built_in, circular_references
94
+ )
93
95
  var deps: Array[Dictionary] = _analyze_resource(res_path, 0, walk)
94
96
  if deps.size() > 0:
95
97
  dependencies[res_path] = deps
@@ -136,9 +138,9 @@ func find_resource_usages(params: Dictionary) -> Dictionary:
136
138
  var class_name_declared: String = _declared_class_name(resource_path)
137
139
  var by_class: RegEx = null
138
140
  if not class_name_declared.is_empty():
139
- by_class = Patterns.compiled("\\b" + _regex_escaped(class_name_declared) + "\\b")
141
+ by_class = GdhPatterns.compiled("\\b" + _regex_escaped(class_name_declared) + "\\b")
140
142
  var target: Dictionary = {
141
- "path": resource_path, "uid": str(ResourceFiles.uid_of(resource_path).get("uid", ""))
143
+ "path": resource_path, "uid": str(GdhResourceFiles.uid_of(resource_path).get("uid", ""))
142
144
  }
143
145
 
144
146
  var all_files: Array[String] = _usage_files(file_types)
@@ -189,10 +191,12 @@ func find_resource_usages(params: Dictionary) -> Dictionary:
189
191
  kind = _path_reference_kind(line, file_path)
190
192
  else:
191
193
  kind = (
192
- "extends" if Patterns.without_annotations(line).begins_with("extends ") else "class_name"
194
+ "extends"
195
+ if GdhPatterns.without_annotations(line).begins_with("extends ")
196
+ else "class_name"
193
197
  )
194
198
  references.append({"line": i + 1, "kind": kind, "text": trimmed})
195
- by_kind[kind] = Read.as_int(by_kind.get(kind, 0)) + 1
199
+ by_kind[kind] = GdhRead.as_int(by_kind.get(kind, 0)) + 1
196
200
  if kind != "doc" and kind != "comment":
197
201
  in_code += 1
198
202
 
@@ -229,7 +233,7 @@ func _declared_class_name(path: String) -> String:
229
233
  return ""
230
234
  while not file.eof_reached():
231
235
  # Annotations first, since `@abstract class_name X` is one line and this read it as none.
232
- var found: RegExMatch = Patterns.declared_class(Patterns.without_annotations(file.get_line()))
236
+ var found: RegExMatch = GdhPatterns.declared_class(GdhPatterns.without_annotations(file.get_line()))
233
237
  if found != null:
234
238
  file.close()
235
239
  return found.get_string(1)
@@ -282,14 +286,14 @@ func _names_target(line: String, file_path: String, target: Dictionary) -> bool:
282
286
  # The contents of each string literal on [param line], of either quote.
283
287
  static func _string_literals(line: String) -> Array[String]:
284
288
  var literals: Array[String] = []
285
- for m: RegExMatch in Patterns.compiled(STRING_LITERAL).search_all(line):
289
+ for m: RegExMatch in GdhPatterns.compiled(STRING_LITERAL).search_all(line):
286
290
  literals.append(m.get_string(1) if not m.get_string(1).is_empty() else m.get_string(2))
287
291
  return literals
288
292
 
289
293
 
290
294
  # [param line] with its strings emptied and its comment taken off: the part a class can be used in.
291
295
  static func _code_of(line: String) -> String:
292
- var bare: String = Patterns.compiled(STRING_LITERAL).sub(line, '""', true)
296
+ var bare: String = GdhPatterns.compiled(STRING_LITERAL).sub(line, '""', true)
293
297
  var mark: int = bare.find("#")
294
298
  return bare if mark == -1 else bare.substr(0, mark)
295
299
 
@@ -299,13 +303,13 @@ static func _code_of(line: String) -> String:
299
303
  static func _code_lines(content: String) -> String:
300
304
  var kept: Array[String] = []
301
305
  for line: String in content.split("\n"):
302
- kept.append(Patterns.without_comment(line))
306
+ kept.append(GdhPatterns.without_comment(line))
303
307
  return "\n".join(kept)
304
308
 
305
309
 
306
310
  # The comment on [param line], strings emptied first so a # inside one does not start it.
307
311
  static func _comment_of(line: String) -> String:
308
- var bare: String = Patterns.compiled(STRING_LITERAL).sub(line, '""', true)
312
+ var bare: String = GdhPatterns.compiled(STRING_LITERAL).sub(line, '""', true)
309
313
  var mark: int = bare.find("#")
310
314
  return "" if mark == -1 else bare.substr(mark)
311
315
 
@@ -321,7 +325,7 @@ func _path_reference_kind(line: String, file_path: String) -> String:
321
325
  return "export_preset"
322
326
  if file_path.ends_with(".import"):
323
327
  return "import"
324
- var trimmed: String = Patterns.without_annotations(line)
328
+ var trimmed: String = GdhPatterns.without_annotations(line)
325
329
  if trimmed.begins_with("extends "):
326
330
  return "extends"
327
331
  if trimmed.begins_with("[ext_resource"):
@@ -366,7 +370,7 @@ func _count_recursive(deps: Array[Dictionary]) -> int:
366
370
  return count
367
371
 
368
372
 
369
- func _analyze_resource(path: String, current_depth: int, walk: DependencyWalk) -> Array[Dictionary]:
373
+ func _analyze_resource(path: String, current_depth: int, walk: GdhDependencyWalk) -> Array[Dictionary]:
370
374
  var deps: Array[Dictionary] = []
371
375
 
372
376
  if current_depth >= walk.max_depth:
@@ -385,7 +389,7 @@ func _analyze_resource(path: String, current_depth: int, walk: DependencyWalk) -
385
389
  var remaining: int = walk.max_depth - current_depth
386
390
  if walk.visited.has(path):
387
391
  var cached: Dictionary = walk.visited[path]
388
- if Read.as_int(cached["remaining"]) == remaining:
392
+ if GdhRead.as_int(cached["remaining"]) == remaining:
389
393
  var cached_deps: Array[Dictionary] = cached["deps"]
390
394
  return cached_deps
391
395
 
@@ -402,7 +406,7 @@ func _analyze_resource(path: String, current_depth: int, walk: DependencyWalk) -
402
406
  content = _code_lines(content)
403
407
 
404
408
  for pattern: String in REFERENCE_PATTERNS:
405
- var regex: RegEx = Patterns.compiled(pattern)
409
+ var regex: RegEx = GdhPatterns.compiled(pattern)
406
410
  for m: RegExMatch in regex.search_all(content):
407
411
  var dep_path: String = _resolved(_referenced_path(m.get_string()), path)
408
412
 
@@ -439,7 +443,7 @@ func _depend(
439
443
  dep_path: String,
440
444
  by_class: String,
441
445
  current_depth: int,
442
- walk: DependencyWalk
446
+ walk: GdhDependencyWalk
443
447
  ) -> void:
444
448
  if dep_path == path:
445
449
  return
@@ -474,7 +478,7 @@ static func classes_used_in(content: String, classes: Dictionary) -> Array[Strin
474
478
  var used: Array[String] = []
475
479
  if classes.is_empty():
476
480
  return used
477
- var word: RegEx = Patterns.compiled("[A-Za-z_][A-Za-z0-9_]*")
481
+ var word: RegEx = GdhPatterns.compiled("[A-Za-z_][A-Za-z0-9_]*")
478
482
  for line: String in content.split("\n"):
479
483
  for m: RegExMatch in word.search_all(_code_of(line)):
480
484
  var name: String = m.get_string()
@@ -507,6 +511,6 @@ func _referenced_path(matched: String) -> String:
507
511
  expression = 'path="([^"]+)"'
508
512
  else:
509
513
  return matched
510
- var inner: RegEx = Patterns.compiled(expression)
514
+ var inner: RegEx = GdhPatterns.compiled(expression)
511
515
  var inner_match: RegExMatch = inner.search(matched)
512
516
  return inner_match.get_string(1) if inner_match else matched
@@ -7,10 +7,10 @@ extends RefCounted
7
7
  ## made here passes the same checks and answers in the same shape. What the engine does that an
8
8
  ## editor does not, running every script, is put back in scene_file.gd and headless_check.gd.
9
9
 
10
- const AnimationTools = preload("../addons/gdharness_editor/tools/animation_tools.gd")
11
- const ResourceTools = preload("../addons/gdharness_editor/tools/resource_tools.gd")
12
- const SceneTools = preload("../addons/gdharness_editor/tools/scene_tools.gd")
13
- const SceneFile = preload("../addons/gdharness_editor/scene_file.gd")
10
+ const GdhAnimationTools = preload("../addons/gdharness_editor/tools/animation_tools.gd")
11
+ const GdhResourceTools = preload("../addons/gdharness_editor/tools/resource_tools.gd")
12
+ const GdhSceneTools = preload("../addons/gdharness_editor/tools/scene_tools.gd")
13
+ const GdhSceneFile = preload("../addons/gdharness_editor/scene_file.gd")
14
14
 
15
15
  const SCENE_COMMANDS: Array[String] = [
16
16
  "create_scene",
@@ -51,18 +51,18 @@ static func run(params: Dictionary) -> Dictionary:
51
51
  var arguments: Dictionary = given
52
52
  var tools: Node = null
53
53
  if command in SCENE_COMMANDS:
54
- tools = SceneTools.new()
54
+ tools = GdhSceneTools.new()
55
55
  elif command in RESOURCE_COMMANDS:
56
- tools = ResourceTools.new()
56
+ tools = GdhResourceTools.new()
57
57
  elif command in ANIMATION_COMMANDS:
58
- tools = AnimationTools.new()
58
+ tools = GdhAnimationTools.new()
59
59
  else:
60
60
  return {"ok": false, "error": "No editor tool runs headless as " + command}
61
61
  var answer: Variant = tools.call(command, arguments)
62
62
  tools.free()
63
63
  if answer is Dictionary:
64
64
  var answered: Dictionary = answer
65
- if not SceneFile.by_path.is_empty():
65
+ if not GdhSceneFile.by_path.is_empty():
66
66
  answered["namedByPath"] = (
67
67
  (
68
68
  "%s %s named by path rather than by uid. A uid in a file's own header is resolved "
@@ -72,10 +72,10 @@ static func run(params: Dictionary) -> Dictionary:
72
72
  + "it name %s by uid."
73
73
  )
74
74
  % [
75
- ", ".join(SceneFile.by_path),
76
- "is" if SceneFile.by_path.size() == 1 else "are",
77
- "it" if SceneFile.by_path.size() == 1 else "them",
78
- "it" if SceneFile.by_path.size() == 1 else "them",
75
+ ", ".join(GdhSceneFile.by_path),
76
+ "is" if GdhSceneFile.by_path.size() == 1 else "are",
77
+ "it" if GdhSceneFile.by_path.size() == 1 else "them",
78
+ "it" if GdhSceneFile.by_path.size() == 1 else "them",
79
79
  ]
80
80
  )
81
81
  return answered
@@ -2,8 +2,8 @@ extends RefCounted
2
2
 
3
3
  ## export_presets.cfg, read and written in the format the editor's Project > Export saves it in.
4
4
 
5
- const Read = preload("reading.gd")
6
- const Log = preload("logger.gd")
5
+ const GdhRead = preload("reading.gd")
6
+ const GdhLog = preload("logger.gd")
7
7
 
8
8
  const PRESETS_FILE: String = "res://export_presets.cfg"
9
9
  ## Where the editor names the one preset of each platform it runs on a device.
@@ -19,10 +19,10 @@ const PLATFORMS: Dictionary[String, String] = {
19
19
  "Web": "web_",
20
20
  }
21
21
 
22
- var _log: Log
22
+ var _log: GdhLog
23
23
 
24
24
 
25
- func _init(p_log: Log) -> void:
25
+ func _init(p_log: GdhLog) -> void:
26
26
  _log = p_log
27
27
 
28
28
 
@@ -88,7 +88,7 @@ static func _runnable(config: ConfigFile, section: String) -> bool:
88
88
  var platform: String = str(config.get_value(section, "platform", ""))
89
89
  if config.has_section_key(RUNNABLE_SECTION, platform):
90
90
  return str(config.get_value(RUNNABLE_SECTION, platform)) == str(config.get_value(section, "name", ""))
91
- return Read.as_bool(config.get_value(section, "runnable", false))
91
+ return GdhRead.as_bool(config.get_value(section, "runnable", false))
92
92
 
93
93
 
94
94
  ## Adds an export preset to export_presets.cfg, written with the keys the editor's Project > Export
@@ -188,9 +188,11 @@ func add_export_preset(params: Dictionary) -> Dictionary:
188
188
  ## self-contained one, otherwise in the user's data directory, as the editor's own paths have it.
189
189
  static func _templates_dir() -> String:
190
190
  var version: Dictionary = Engine.get_version_info()
191
- var named: String = "%d.%d" % [Read.as_int(version.get("major", 0)), Read.as_int(version.get("minor", 0))]
192
- if Read.as_int(version.get("patch", 0)) != 0:
193
- named += "." + str(Read.as_int(version.get("patch", 0)))
191
+ var named: String = (
192
+ "%d.%d" % [GdhRead.as_int(version.get("major", 0)), GdhRead.as_int(version.get("minor", 0))]
193
+ )
194
+ if GdhRead.as_int(version.get("patch", 0)) != 0:
195
+ named += "." + str(GdhRead.as_int(version.get("patch", 0)))
194
196
  named += "." + str(version.get("status", ""))
195
197
  if ClassDB.class_exists("CSharpScript"):
196
198
  named += ".mono"
@@ -10,9 +10,9 @@ extends RefCounted
10
10
  ## without making a node. Loading the file still does what loading does in the game: static
11
11
  ## initialisers run, and a resource with a script is made with it.
12
12
 
13
- const PropertyValues = preload("property_values.gd")
14
- const Read = preload("reading.gd")
15
- const Serialisation = preload("serialisation.gd")
13
+ const GdhPropertyValues = preload("property_values.gd")
14
+ const GdhRead = preload("reading.gd")
15
+ const GdhSerialisation = preload("serialisation.gd")
16
16
 
17
17
 
18
18
  ## The nodes of [param scene_path] as {name, type, path, children}, with instanceOf and
@@ -42,13 +42,43 @@ static func scene_node(scene_path: String, node_path: String) -> Dictionary:
42
42
  return {"ok": true, "name": found["name"], "type": found["type"], "properties": found["properties"]}
43
43
 
44
44
 
45
- ## [param said], the refusal for [param node_path], which [param scene_path] does not have, with
46
- ## where the path leaves the scene as [method Read.missing_below] says it.
47
- static func missing_from(said: String, scene_path: String, node_path: String) -> String:
45
+ ## The node at [param node_path] in [param scene_path] as [method scene_node] answers it, when it is
46
+ ## a [param kind]; otherwise a refusal saying what the scene has there and where its nodes of that
47
+ ## kind are, so the caller has the path to pass. The file is read once, because every load prints
48
+ ## the engine's warnings about it again.
49
+ static func scene_node_of_kind(scene_path: String, node_path: String, kind: String) -> Dictionary:
48
50
  var laid: Dictionary = _laid(scene_path)
49
51
  if not laid.get("ok", false):
50
- return said
52
+ return laid
51
53
  var nodes: Dictionary = laid["nodes"]
54
+ var path: String = _joined(".", node_path)
55
+ var said: String = "%s not found at: %s" % [kind, node_path]
56
+ var found: String = ""
57
+ if nodes.has(path):
58
+ found = str(nodes[path]["type"])
59
+ if ClassDB.class_exists(found) and ClassDB.is_parent_class(found, kind):
60
+ return {
61
+ "ok": true,
62
+ "name": nodes[path]["name"],
63
+ "type": found,
64
+ "properties": nodes[path]["properties"]
65
+ }
66
+ else:
67
+ var hinted: String = said
68
+ if not nodes.has(_joined(".", node_path.get_slice("/", 0))):
69
+ hinted = GdhRead.from_the_root(said, node_path, str(nodes["."]["name"]))
70
+ said = hinted if hinted != said else _missing_in(nodes, said, node_path)
71
+ var held: PackedStringArray = []
72
+ for at: Variant in nodes:
73
+ var type: String = str(nodes[at]["type"])
74
+ if ClassDB.class_exists(type) and ClassDB.is_parent_class(type, kind):
75
+ var _held: bool = held.append(str(at))
76
+ return {"ok": false, "error": GdhRead.not_of_kind(said, node_path, found, kind, held)}
77
+
78
+
79
+ ## [param said], the refusal for [param node_path], which the scene laid as [param nodes] does not
80
+ ## have, with where the path leaves the scene as [method GdhRead.missing_below] says it.
81
+ static func _missing_in(nodes: Dictionary, said: String, node_path: String) -> String:
52
82
  var parts: PackedStringArray = node_path.trim_prefix("./").split("/", false)
53
83
  var at: String = "."
54
84
  var reached: PackedStringArray = []
@@ -64,7 +94,7 @@ static func missing_from(said: String, scene_path: String, node_path: String) ->
64
94
  var children: Array[String] = nodes[at]["children"]
65
95
  for child: String in children:
66
96
  var _named: bool = names.append(str(nodes[child]["name"]))
67
- return Read.missing_below(said, str(nodes["."]["name"]), reached, parts[reached.size()], names)
97
+ return GdhRead.missing_below(said, str(nodes["."]["name"]), reached, parts[reached.size()], names)
68
98
 
69
99
 
70
100
  ## Every node of [param scene_path] as {ok, nodes}, keyed by path, or a refusal.
@@ -77,15 +107,14 @@ static func _laid(scene_path: String) -> Dictionary:
77
107
  if loaded == null:
78
108
  return {"ok": false, "error": "Failed to load %s as a scene" % scene_path}
79
109
  if not loaded is PackedScene:
80
- var kind: String = loaded.get_class()
81
- var sort: String = ("an " if kind.left(1).to_upper() in ["A", "E", "I", "O", "U"] else "a ") + kind
110
+ var sort: String = GdhRead.with_article(loaded.get_class())
82
111
  return {
83
112
  "ok": false,
84
113
  "error": "%s is %s, not a scene: resource_edit read reads its properties." % [scene_path, sort]
85
114
  }
86
115
  var packed: PackedScene = loaded
87
116
  var nodes: Dictionary = {}
88
- var refused: String = _lay(packed.get_state(), ".", nodes, [scene_path])
117
+ var refused: String = _lay(packed.get_state(), ".", nodes, [scene_path], packed)
89
118
  if not refused.is_empty():
90
119
  return {"ok": false, "error": refused}
91
120
  if not nodes.has("."):
@@ -99,8 +128,11 @@ static func _laid(scene_path: String) -> Dictionary:
99
128
  ##
100
129
  ## [param within] is the files being laid, outermost first, so a scene that instances one of them
101
130
  ## is refused rather than laid forever; the engine refuses to load such a scene, so this is only
102
- ## reached through a state built some other way.
103
- static func _lay(state: SceneState, prefix: String, nodes: Dictionary, within: Array) -> String:
131
+ ## reached through a state built some other way. [param loaded] is the scene [param state] is read
132
+ ## from, when the caller has it loaded already.
133
+ static func _lay(
134
+ state: SceneState, prefix: String, nodes: Dictionary, within: Array, loaded: PackedScene = null
135
+ ) -> String:
104
136
  var base: SceneState = state.get_base_scene_state()
105
137
  if base != null and not nodes.has(prefix):
106
138
  var refused: String = _lay(base, prefix, nodes, within)
@@ -164,7 +196,7 @@ static func _lay(state: SceneState, prefix: String, nodes: Dictionary, within: A
164
196
  # Only the scene being read and the scenes it inherits share its root, the owner whose editable
165
197
  # instances a write here sets; a file instanced below makes its own owner's editable.
166
198
  if prefix == ".":
167
- for editable: String in _editable_in(state.get_path()):
199
+ for editable: String in _editable_in(state.get_path(), loaded):
168
200
  var at: String = _joined(prefix, editable)
169
201
  var marked: Dictionary = nodes.get(at, {})
170
202
  if marked.has("editableChildren"):
@@ -173,10 +205,13 @@ static func _lay(state: SceneState, prefix: String, nodes: Dictionary, within: A
173
205
 
174
206
 
175
207
  ## The paths of the instances [param scene_path] gives editable children, from its root. SceneState
176
- ## does not give them to a script, so they are read off the scene's bundle.
177
- static func _editable_in(scene_path: String) -> PackedStringArray:
208
+ ## does not give them to a script, so they are read off the scene's bundle: [param given_scene]'s,
209
+ ## when the caller has the file loaded, since loading it again prints its warnings again.
210
+ static func _editable_in(scene_path: String, given_scene: PackedScene = null) -> PackedStringArray:
178
211
  var found: PackedStringArray = []
179
- var loaded: Resource = ResourceLoader.load(scene_path, "", ResourceLoader.CACHE_MODE_IGNORE)
212
+ var loaded: Resource = given_scene
213
+ if loaded == null:
214
+ loaded = ResourceLoader.load(scene_path, "", ResourceLoader.CACHE_MODE_IGNORE)
180
215
  if not loaded is PackedScene:
181
216
  return found
182
217
  var given: Variant = loaded.get("_bundled")
@@ -236,7 +271,7 @@ static func _by_id(nodes: Dictionary, owner: String, id: int) -> String:
236
271
  if path == owner or not (owner == "." or path.begins_with(owner + "/")):
237
272
  continue
238
273
  var entry: Dictionary = nodes[path]
239
- if Read.as_int(entry.get("uniqueId", -1), -1) != id:
274
+ if GdhRead.as_int(entry.get("uniqueId", -1), -1) != id:
240
275
  continue
241
276
  var above: String = "." if not path.contains("/") else path.get_base_dir()
242
277
  if _owner_of(nodes, above, owner) == owner:
@@ -311,7 +346,7 @@ static func _stored(type: String, given: Dictionary) -> Dictionary:
311
346
  if script != null:
312
347
  for declared: Dictionary in script.get_script_property_list():
313
348
  var property: String = str(declared.get("name", ""))
314
- if not property.is_empty() and Read.as_int(declared.get("usage", 0)) & PROPERTY_USAGE_STORAGE:
349
+ if not property.is_empty() and GdhRead.as_int(declared.get("usage", 0)) & PROPERTY_USAGE_STORAGE:
315
350
  scripted[property] = script.get_property_default_value(property)
316
351
  var values: Dictionary = {}
317
352
  if ClassDB.class_exists(type) and ClassDB.can_instantiate(type):
@@ -321,7 +356,7 @@ static func _stored(type: String, given: Dictionary) -> Dictionary:
321
356
  bare.set(str(property), given[property])
322
357
  for declared: Dictionary in bare.get_property_list():
323
358
  var property: String = str(declared.get("name", ""))
324
- if not property.is_empty() and Read.as_int(declared.get("usage", 0)) & PROPERTY_USAGE_STORAGE:
359
+ if not property.is_empty() and GdhRead.as_int(declared.get("usage", 0)) & PROPERTY_USAGE_STORAGE:
325
360
  values[property] = bare.get(property)
326
361
  if bare is Node:
327
362
  bare.free()
@@ -330,7 +365,7 @@ static func _stored(type: String, given: Dictionary) -> Dictionary:
330
365
  # Given last, which keeps what a bare node cannot hold: the script itself, and a path to a node
331
366
  # for a property that holds the node once it is in a tree.
332
367
  values.merge(given, true)
333
- var serialiser: Serialisation = Serialisation.new()
368
+ var serialiser: GdhSerialisation = GdhSerialisation.new()
334
369
  var answered: Dictionary = {}
335
370
  for property: Variant in values:
336
371
  answered[property] = serialiser.serialize_value(values[property])
@@ -369,13 +404,13 @@ static func resource(res_path: String, include_defaults: bool) -> Dictionary:
369
404
  if script != null:
370
405
  for declared: Dictionary in script.get_script_property_list():
371
406
  scripted[str(declared.get("name", ""))] = true
372
- var serialiser: Serialisation = Serialisation.new()
407
+ var serialiser: GdhSerialisation = GdhSerialisation.new()
373
408
  var properties: Dictionary = {}
374
409
  for p: Dictionary in loaded.get_property_list():
375
410
  var property: String = str(p.get("name", ""))
376
411
  if property.is_empty() or property == "script":
377
412
  continue
378
- if not (Read.as_int(p.get("usage", 0)) & PROPERTY_USAGE_STORAGE):
413
+ if not (GdhRead.as_int(p.get("usage", 0)) & PROPERTY_USAGE_STORAGE):
379
414
  continue
380
415
  var value: Variant = loaded.get(property)
381
416
  if not include_defaults:
@@ -384,7 +419,7 @@ static func resource(res_path: String, include_defaults: bool) -> Dictionary:
384
419
  if scripted.has(property)
385
420
  else ClassDB.class_get_property_default_value(loaded.get_class(), property)
386
421
  )
387
- if PropertyValues.same(value, default):
422
+ if GdhPropertyValues.same(value, default):
388
423
  continue
389
424
  properties[property] = serialiser.serialize_value(value)
390
425
  var answer: Dictionary = {
@@ -412,7 +447,7 @@ static func _kept_in(value: Variant, file: String, id: String, seen: Dictionary)
412
447
  if held.resource_scene_unique_id == id:
413
448
  return held
414
449
  for p: Dictionary in held.get_property_list():
415
- if Read.as_int(p.get("usage", 0)) & PROPERTY_USAGE_STORAGE:
450
+ if GdhRead.as_int(p.get("usage", 0)) & PROPERTY_USAGE_STORAGE:
416
451
  var found: Resource = _kept_in(held.get(str(p.get("name", ""))), file, id, seen)
417
452
  if found != null:
418
453
  return found
@@ -1,16 +1,16 @@
1
1
  extends RefCounted
2
2
 
3
- const Dependencies = preload("dependencies.gd")
4
- const Patterns = preload("patterns.gd")
5
- const Read = preload("reading.gd")
6
- const Log = preload("logger.gd")
3
+ const GdhDependencies = preload("dependencies.gd")
4
+ const GdhPatterns = preload("patterns.gd")
5
+ const GdhRead = preload("reading.gd")
6
+ const GdhLog = preload("logger.gd")
7
7
 
8
- var _log: Log
8
+ var _log: GdhLog
9
9
  ## Kept for the call, so the project's class names are read once however many bases are walked.
10
- var _dependencies: Dependencies = null
10
+ var _dependencies: GdhDependencies = null
11
11
 
12
12
 
13
- func _init(p_log: Log) -> void:
13
+ func _init(p_log: GdhLog) -> void:
14
14
  _log = p_log
15
15
 
16
16
 
@@ -62,7 +62,7 @@ func get_gdscript_info(params: Dictionary) -> Dictionary:
62
62
  if _triple_quotes(raw) % 2 == 1:
63
63
  in_multiline_string = false
64
64
  continue
65
- var stripped: String = Patterns.without_comment(raw)
65
+ var stripped: String = GdhPatterns.without_comment(raw)
66
66
 
67
67
  if stripped.is_empty():
68
68
  continue
@@ -85,7 +85,7 @@ func get_gdscript_info(params: Dictionary) -> Dictionary:
85
85
  # on the line with them off. `@abstract class_name X` was reported as no declared class,
86
86
  # and `@abstract func x()` and `@warning_ignore("...") func x()` as no function at all:
87
87
  # gdUnit4 alone declares 248 methods that way and this answered with none of them.
88
- var header: String = Patterns.without_annotations(stripped)
88
+ var header: String = GdhPatterns.without_annotations(stripped)
89
89
 
90
90
  var at: int = i
91
91
  # A declaration wrapped across lines is one declaration, and reading the first line alone
@@ -104,9 +104,9 @@ func get_gdscript_info(params: Dictionary) -> Dictionary:
104
104
  if _can_wrap(header):
105
105
  while _bracket_depth(stripped) > 0 and i + 1 < lines.size():
106
106
  i += 1
107
- stripped = (stripped + " " + Patterns.without_comment(lines[i])).strip_edges()
108
- header = Patterns.without_annotations(stripped)
109
- var declaration: RegExMatch = Patterns.declared_class(header)
107
+ stripped = (stripped + " " + GdhPatterns.without_comment(lines[i])).strip_edges()
108
+ header = GdhPatterns.without_annotations(stripped)
109
+ var declaration: RegExMatch = GdhPatterns.declared_class(header)
110
110
  if declaration != null:
111
111
  declared_class_name = declaration.get_string(1)
112
112
  # `class_name X extends Y` is also one line, and taking the rest of it as the name
@@ -144,9 +144,9 @@ func get_gdscript_info(params: Dictionary) -> Dictionary:
144
144
  # named it. The same reading as that tool's, so the two agree.
145
145
  var classes_used: Array[Dictionary] = []
146
146
  if _dependencies == null:
147
- _dependencies = Dependencies.new(_log)
147
+ _dependencies = GdhDependencies.new(_log)
148
148
  var declared_by: Dictionary = _dependencies.classes_declared()
149
- for used: String in Dependencies.classes_used_in(content, declared_by):
149
+ for used: String in GdhDependencies.classes_used_in(content, declared_by):
150
150
  var declaring: String = str(declared_by[used])
151
151
  if declaring == full_script_path:
152
152
  continue
@@ -165,7 +165,7 @@ func get_gdscript_info(params: Dictionary) -> Dictionary:
165
165
  if value.begins_with("*"):
166
166
  global_autoloads[setting.trim_prefix("autoload/")] = value.trim_prefix("*")
167
167
  var autoloads_used: Array[Dictionary] = []
168
- for used: String in Dependencies.classes_used_in(content, global_autoloads):
168
+ for used: String in GdhDependencies.classes_used_in(content, global_autoloads):
169
169
  if str(global_autoloads[used]) != full_script_path:
170
170
  autoloads_used.append({"name": used, "path": str(global_autoloads[used])})
171
171
 
@@ -185,7 +185,7 @@ func get_gdscript_info(params: Dictionary) -> Dictionary:
185
185
  "autoloads_used": autoloads_used,
186
186
  "line_count": lines.size()
187
187
  }
188
- if Read.as_bool(params.get("include_inherited", false)):
188
+ if GdhRead.as_bool(params.get("include_inherited", false)):
189
189
  _add_inherited(answer, [full_script_path])
190
190
  return answer
191
191
 
@@ -202,7 +202,7 @@ func _mark_virtuals(functions: Array[Dictionary], extends_name: String, from_pat
202
202
  var virtuals: Dictionary = {}
203
203
  if not native.is_empty():
204
204
  for m: Dictionary in ClassDB.class_get_method_list(native, false):
205
- var flags: int = Read.as_int(m.get("flags", 0))
205
+ var flags: int = GdhRead.as_int(m.get("flags", 0))
206
206
  if flags & METHOD_FLAG_VIRTUAL:
207
207
  virtuals[str(m.get("name", ""))] = true
208
208
  for one: Dictionary in functions:
@@ -366,7 +366,7 @@ func _parse_variable(line: String, line_num: int) -> Dictionary:
366
366
 
367
367
  # From the annotations themselves rather than from where they sit. `@onready @export var x` put
368
368
  # the export second and so read as not exported, and a hint with a space in it was cut at it.
369
- for one: String in Patterns.annotations_on(line):
369
+ for one: String in GdhPatterns.annotations_on(line):
370
370
  if one.begins_with("export"):
371
371
  is_export = true
372
372
  var after: String = one.substr(6)
@@ -457,7 +457,7 @@ func _parse_function(line: String, line_num: int, annotated: String = "") -> Dic
457
457
  "is_static": is_static,
458
458
  # Said rather than left to be inferred: an abstract method has no body, so a caller that
459
459
  # saw only the declaration would otherwise take it for one whose body it failed to read.
460
- "is_abstract": "abstract" in Patterns.annotations_on(annotated),
460
+ "is_abstract": "abstract" in GdhPatterns.annotations_on(annotated),
461
461
  "line": line_num
462
462
  }
463
463
 
@@ -624,7 +624,7 @@ static func _triple_quotes(line: String) -> int:
624
624
 
625
625
  func _extract_dependencies(line: String) -> Array[String]:
626
626
  var deps: Array[String] = []
627
- var regex: RegEx = Patterns.compiled("(?:preload|load)\\s*\\(\\s*[\"']([^\"']+)[\"']\\s*\\)")
627
+ var regex: RegEx = GdhPatterns.compiled("(?:preload|load)\\s*\\(\\s*[\"']([^\"']+)[\"']\\s*\\)")
628
628
 
629
629
  for m: RegExMatch in regex.search_all(line):
630
630
  deps.append(m.get_string(1))