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.
- package/build/cli.js +14 -13
- package/build/godot/addons/gdharness_editor/animation_reads.gd +7 -19
- package/build/godot/addons/gdharness_editor/bridge_client.gd +8 -8
- package/build/godot/addons/gdharness_editor/file_reads.gd +60 -25
- package/build/godot/addons/gdharness_editor/headless_check.gd +7 -7
- package/build/godot/addons/gdharness_editor/plugin.gd +6 -6
- package/build/godot/addons/gdharness_editor/property_values.gd +120 -53
- package/build/godot/addons/gdharness_editor/reading.gd +28 -0
- package/build/godot/addons/gdharness_editor/scene_file.gd +28 -23
- package/build/godot/addons/gdharness_editor/serialisation.gd +45 -94
- package/build/godot/addons/gdharness_editor/tool_executor.gd +24 -24
- package/build/godot/addons/gdharness_editor/tools/animation_tools.gd +52 -41
- package/build/godot/addons/gdharness_editor/tools/filesystem_tools.gd +4 -4
- package/build/godot/addons/gdharness_editor/tools/play_tools.gd +2 -2
- package/build/godot/addons/gdharness_editor/tools/profile_tools.gd +14 -14
- package/build/godot/addons/gdharness_editor/tools/resource_tools.gd +48 -41
- package/build/godot/addons/gdharness_editor/tools/scene_kin.gd +17 -17
- package/build/godot/addons/gdharness_editor/tools/scene_paths.gd +14 -12
- package/build/godot/addons/gdharness_editor/tools/scene_placing.gd +7 -7
- package/build/godot/addons/gdharness_editor/tools/scene_tools.gd +81 -77
- package/build/godot/addons/gdharness_editor/tools/settings_tools.gd +4 -4
- package/build/godot/addons/gdharness_editor/value_shapes.gd +111 -0
- package/build/godot/addons/gdharness_editor/value_shapes.gd.uid +1 -0
- package/build/godot/addons/gdharness_runtime/reading.gd +28 -0
- package/build/godot/addons/gdharness_runtime/runtime_accessibility.gd +18 -18
- package/build/godot/addons/gdharness_runtime/runtime_autoload.gd +29 -29
- package/build/godot/addons/gdharness_runtime/runtime_capture.gd +21 -18
- package/build/godot/addons/gdharness_runtime/runtime_changes.gd +86 -80
- package/build/godot/addons/gdharness_runtime/runtime_input.gd +65 -64
- package/build/godot/addons/gdharness_runtime/runtime_items.gd +6 -6
- package/build/godot/addons/gdharness_runtime/runtime_menus.gd +8 -8
- package/build/godot/addons/gdharness_runtime/runtime_mouse.gd +6 -6
- package/build/godot/addons/gdharness_runtime/runtime_pad.gd +10 -10
- package/build/godot/addons/gdharness_runtime/runtime_paths.gd +7 -7
- package/build/godot/addons/gdharness_runtime/runtime_queries.gd +41 -41
- package/build/godot/addons/gdharness_runtime/runtime_says.gd +3 -3
- package/build/godot/addons/gdharness_runtime/runtime_targets.gd +33 -33
- package/build/godot/addons/gdharness_runtime/runtime_typing.gd +20 -20
- package/build/godot/addons/gdharness_runtime/runtime_values.gd +25 -24
- package/build/godot/addons/gdharness_runtime/runtime_waits.gd +56 -50
- package/build/godot/addons/gdharness_runtime/serialisation.gd +45 -94
- package/build/godot/addons/gdharness_runtime/value_shapes.gd +111 -0
- package/build/godot/addons/gdharness_runtime/value_shapes.gd.uid +1 -0
- package/build/godot/operations/animation_reads.gd +7 -19
- package/build/godot/operations/audio_buses.gd +10 -10
- package/build/godot/operations/class_cache.gd +16 -16
- package/build/godot/operations/classdb_queries.gd +12 -12
- package/build/godot/operations/dependencies.gd +33 -29
- package/build/godot/operations/editor_tools.gd +12 -12
- package/build/godot/operations/export_presets.gd +10 -8
- package/build/godot/operations/file_reads.gd +60 -25
- package/build/godot/operations/gdscript_analysis.gd +20 -20
- package/build/godot/operations/gdscript_authoring.gd +17 -15
- package/build/godot/operations/godot_operations.gd +64 -63
- package/build/godot/operations/import_pipeline.gd +20 -20
- package/build/godot/operations/input_actions.gd +17 -17
- package/build/godot/operations/logger.gd +1 -1
- package/build/godot/operations/plugins.gd +8 -8
- package/build/godot/operations/project_config.gd +24 -17
- package/build/godot/operations/project_diagnostics.gd +9 -9
- package/build/godot/operations/property_values.gd +120 -53
- package/build/godot/operations/reading.gd +28 -0
- package/build/godot/operations/rename_checks.gd +3 -3
- package/build/godot/operations/resource_files.gd +5 -5
- package/build/godot/operations/script_check.gd +7 -7
- package/build/godot/operations/serialisation.gd +45 -94
- package/build/godot/operations/value_shapes.gd +111 -0
- package/package.json +1 -1
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
extends RefCounted
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
const
|
|
5
|
-
const
|
|
6
|
-
const
|
|
7
|
-
const
|
|
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:
|
|
25
|
-
var _files:
|
|
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
|
|
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:
|
|
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 =
|
|
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 =
|
|
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:
|
|
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:
|
|
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 =
|
|
141
|
+
by_class = GdhPatterns.compiled("\\b" + _regex_escaped(class_name_declared) + "\\b")
|
|
140
142
|
var target: Dictionary = {
|
|
141
|
-
"path": resource_path, "uid": str(
|
|
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"
|
|
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] =
|
|
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 =
|
|
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
|
|
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 =
|
|
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(
|
|
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 =
|
|
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 =
|
|
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:
|
|
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
|
|
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 =
|
|
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:
|
|
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 =
|
|
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 =
|
|
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
|
|
11
|
-
const
|
|
12
|
-
const
|
|
13
|
-
const
|
|
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 =
|
|
54
|
+
tools = GdhSceneTools.new()
|
|
55
55
|
elif command in RESOURCE_COMMANDS:
|
|
56
|
-
tools =
|
|
56
|
+
tools = GdhResourceTools.new()
|
|
57
57
|
elif command in ANIMATION_COMMANDS:
|
|
58
|
-
tools =
|
|
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
|
|
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(
|
|
76
|
-
"is" if
|
|
77
|
-
"it" if
|
|
78
|
-
"it" if
|
|
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
|
|
6
|
-
const
|
|
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:
|
|
22
|
+
var _log: GdhLog
|
|
23
23
|
|
|
24
24
|
|
|
25
|
-
func _init(p_log:
|
|
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
|
|
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 =
|
|
192
|
-
|
|
193
|
-
|
|
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
|
|
14
|
-
const
|
|
15
|
-
const
|
|
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
|
-
##
|
|
46
|
-
##
|
|
47
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
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:
|
|
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:
|
|
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 (
|
|
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
|
|
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
|
|
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
|
|
4
|
-
const
|
|
5
|
-
const
|
|
6
|
-
const
|
|
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:
|
|
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:
|
|
10
|
+
var _dependencies: GdhDependencies = null
|
|
11
11
|
|
|
12
12
|
|
|
13
|
-
func _init(p_log:
|
|
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 =
|
|
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 =
|
|
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 + " " +
|
|
108
|
-
header =
|
|
109
|
-
var declaration: RegExMatch =
|
|
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 =
|
|
147
|
+
_dependencies = GdhDependencies.new(_log)
|
|
148
148
|
var declared_by: Dictionary = _dependencies.classes_declared()
|
|
149
|
-
for used: String in
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
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 =
|
|
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))
|