gdharness 1.3.41 → 1.3.43
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/cli.js +131 -47
- package/build/godot/addons/auto_reload/auto_reload.gd +18 -19
- package/build/godot/addons/gdharness_editor/animation_reads.gd +84 -35
- package/build/godot/addons/gdharness_editor/bridge_client.gd +53 -58
- package/build/godot/addons/gdharness_editor/declared_values.gd +68 -32
- package/build/godot/addons/gdharness_editor/file_reads.gd +114 -39
- package/build/godot/addons/gdharness_editor/file_walk.gd +12 -5
- package/build/godot/addons/gdharness_editor/headless_check.gd +94 -41
- package/build/godot/addons/gdharness_editor/plugin.gd +2 -3
- package/build/godot/addons/gdharness_editor/property_paths.gd +22 -12
- package/build/godot/addons/gdharness_editor/property_values.gd +143 -78
- package/build/godot/addons/gdharness_editor/reading.gd +37 -12
- package/build/godot/addons/gdharness_editor/same_values.gd +4 -4
- package/build/godot/addons/gdharness_editor/scene_file.gd +136 -79
- package/build/godot/addons/gdharness_editor/scene_uids.gd +12 -6
- package/build/godot/addons/gdharness_editor/serialisation.gd +75 -51
- package/build/godot/addons/gdharness_editor/setting_reads.gd +7 -6
- package/build/godot/addons/gdharness_editor/tool_executor.gd +3 -6
- package/build/godot/addons/gdharness_editor/tools/animation_states.gd +41 -28
- package/build/godot/addons/gdharness_editor/tools/animation_tools.gd +84 -50
- package/build/godot/addons/gdharness_editor/tools/animation_tracks.gd +70 -40
- package/build/godot/addons/gdharness_editor/tools/class_tools.gd +34 -30
- package/build/godot/addons/gdharness_editor/tools/filesystem_tools.gd +24 -22
- package/build/godot/addons/gdharness_editor/tools/navigation_bake.gd +49 -29
- package/build/godot/addons/gdharness_editor/tools/play_tools.gd +26 -21
- package/build/godot/addons/gdharness_editor/tools/profile_tools.gd +40 -40
- package/build/godot/addons/gdharness_editor/tools/resource_tools.gd +181 -107
- package/build/godot/addons/gdharness_editor/tools/scene_groups.gd +16 -9
- package/build/godot/addons/gdharness_editor/tools/scene_kin.gd +75 -43
- package/build/godot/addons/gdharness_editor/tools/scene_paths.gd +35 -20
- package/build/godot/addons/gdharness_editor/tools/scene_placing.gd +28 -13
- package/build/godot/addons/gdharness_editor/tools/scene_sharing.gd +66 -0
- package/build/godot/addons/gdharness_editor/tools/scene_sharing.gd.uid +1 -0
- package/build/godot/addons/gdharness_editor/tools/scene_tools.gd +171 -62
- package/build/godot/addons/gdharness_editor/tools/script_paths.gd +20 -8
- package/build/godot/addons/gdharness_editor/tools/settings_tools.gd +11 -8
- package/build/godot/addons/gdharness_editor/tools/signal_tools.gd +47 -34
- package/build/godot/addons/gdharness_editor/tools/tileset_tools.gd +77 -50
- package/build/godot/addons/gdharness_editor/value_shapes.gd +5 -6
- package/build/godot/addons/gdharness_runtime/call_errors.gd +1 -2
- package/build/godot/addons/gdharness_runtime/declared_values.gd +68 -32
- package/build/godot/addons/gdharness_runtime/error_report.gd +1 -2
- package/build/godot/addons/gdharness_runtime/reading.gd +37 -12
- package/build/godot/addons/gdharness_runtime/runtime_accessibility.gd +84 -63
- package/build/godot/addons/gdharness_runtime/runtime_autoload.gd +153 -148
- package/build/godot/addons/gdharness_runtime/runtime_capture.gd +64 -42
- package/build/godot/addons/gdharness_runtime/runtime_changes.gd +184 -122
- package/build/godot/addons/gdharness_runtime/runtime_frames.gd +2 -2
- package/build/godot/addons/gdharness_runtime/runtime_hover.gd +24 -20
- package/build/godot/addons/gdharness_runtime/runtime_input.gd +134 -92
- package/build/godot/addons/gdharness_runtime/runtime_items.gd +29 -13
- package/build/godot/addons/gdharness_runtime/runtime_menus.gd +0 -1
- package/build/godot/addons/gdharness_runtime/runtime_mouse.gd +38 -20
- package/build/godot/addons/gdharness_runtime/runtime_pad.gd +17 -13
- package/build/godot/addons/gdharness_runtime/runtime_paths.gd +84 -46
- package/build/godot/addons/gdharness_runtime/runtime_queries.gd +117 -79
- package/build/godot/addons/gdharness_runtime/runtime_says.gd +14 -17
- package/build/godot/addons/gdharness_runtime/runtime_screen.gd +23 -16
- package/build/godot/addons/gdharness_runtime/runtime_targets.gd +117 -66
- package/build/godot/addons/gdharness_runtime/runtime_touch.gd +211 -0
- package/build/godot/addons/gdharness_runtime/runtime_touch.gd.uid +1 -0
- package/build/godot/addons/gdharness_runtime/runtime_typing.gd +44 -27
- package/build/godot/addons/gdharness_runtime/runtime_values.gd +108 -73
- package/build/godot/addons/gdharness_runtime/runtime_waits.gd +111 -84
- package/build/godot/addons/gdharness_runtime/runtime_words.gd +10 -7
- package/build/godot/addons/gdharness_runtime/serialisation.gd +75 -51
- package/build/godot/addons/gdharness_runtime/value_shapes.gd +5 -6
- package/build/godot/addons/gdharness_tests/bench.gd +4 -5
- package/build/godot/addons/gdharness_tests/event.gd +10 -5
- package/build/godot/addons/gdharness_tests/share.gd +38 -18
- package/build/godot/addons/gdharness_tests/suites.gd +3 -3
- package/build/godot/addons/gdharness_tests/wait.gd +3 -2
- package/build/godot/loader/loader.gd +9 -9
- package/build/godot/operations/animation_reads.gd +84 -35
- package/build/godot/operations/audio_buses.gd +48 -19
- package/build/godot/operations/class_cache.gd +16 -11
- package/build/godot/operations/classdb_queries.gd +89 -44
- package/build/godot/operations/declared_values.gd +68 -32
- package/build/godot/operations/dependencies.gd +139 -93
- package/build/godot/operations/editor_tools.gd +9 -10
- package/build/godot/operations/export_presets.gd +100 -54
- package/build/godot/operations/file_reads.gd +114 -39
- package/build/godot/operations/file_walk.gd +12 -5
- package/build/godot/operations/gdscript_analysis.gd +105 -66
- package/build/godot/operations/gdscript_authoring.gd +138 -39
- package/build/godot/operations/godot_operations.gd +17 -11
- package/build/godot/operations/import_pipeline.gd +110 -59
- package/build/godot/operations/input_actions.gd +63 -39
- package/build/godot/operations/patterns.gd +3 -1
- package/build/godot/operations/plugins.gd +36 -16
- package/build/godot/operations/project_config.gd +144 -27
- package/build/godot/operations/project_diagnostics.gd +24 -11
- package/build/godot/operations/project_file.gd +16 -16
- package/build/godot/operations/project_validation.gd +71 -44
- package/build/godot/operations/property_paths.gd +22 -12
- package/build/godot/operations/property_values.gd +143 -78
- package/build/godot/operations/reading.gd +37 -12
- package/build/godot/operations/rename_checks.gd +2 -2
- package/build/godot/operations/resource_files.gd +11 -9
- package/build/godot/operations/same_values.gd +4 -4
- package/build/godot/operations/scene_connections.gd +27 -13
- package/build/godot/operations/script_check.gd +52 -49
- package/build/godot/operations/serialisation.gd +75 -51
- package/build/godot/operations/setting_reads.gd +7 -6
- package/build/godot/operations/value_shapes.gd +5 -6
- package/build/godot/tests/listed.gd +321 -268
- package/package.json +1 -1
|
@@ -22,7 +22,7 @@ func list_plugins(_params: Dictionary) -> Dictionary:
|
|
|
22
22
|
"addons_directory_exists": false,
|
|
23
23
|
"enabled_count": 0,
|
|
24
24
|
"disabled_count": 0,
|
|
25
|
-
"message": "No addons directory found in the project"
|
|
25
|
+
"message": "No addons directory found in the project",
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
var enabled_paths: Array[String] = _enabled_paths()
|
|
@@ -34,7 +34,7 @@ func list_plugins(_params: Dictionary) -> Dictionary:
|
|
|
34
34
|
var plugin_cfg_path: String = folder.path_join("plugin.cfg")
|
|
35
35
|
var name: String = folder.trim_prefix(addons_path)
|
|
36
36
|
var enabled: bool = plugin_cfg_path in enabled_paths
|
|
37
|
-
var plugin_info: Dictionary = {"name": name, "path": folder, "enabled": enabled}
|
|
37
|
+
var plugin_info: Dictionary = { "name": name, "path": folder, "enabled": enabled }
|
|
38
38
|
|
|
39
39
|
var plugin_config: ConfigFile = ConfigFile.new()
|
|
40
40
|
if plugin_config.load(plugin_cfg_path) == OK:
|
|
@@ -52,7 +52,7 @@ func list_plugins(_params: Dictionary) -> Dictionary:
|
|
|
52
52
|
"plugins": plugins,
|
|
53
53
|
"addons_directory_exists": true,
|
|
54
54
|
"enabled_count": enabled_count,
|
|
55
|
-
"disabled_count": plugins.size() - enabled_count
|
|
55
|
+
"disabled_count": plugins.size() - enabled_count,
|
|
56
56
|
}
|
|
57
57
|
# Enabled in the project with no plugin.cfg where the entry points: the editor reports each one
|
|
58
58
|
# as an error at every start and loads nothing for it.
|
|
@@ -99,7 +99,9 @@ func enable_plugin(params: Dictionary) -> Dictionary:
|
|
|
99
99
|
|
|
100
100
|
if plugin_cfg_path in enabled_paths:
|
|
101
101
|
return {
|
|
102
|
-
"plugin_name": plugin_name,
|
|
102
|
+
"plugin_name": plugin_name,
|
|
103
|
+
"action": "already_enabled",
|
|
104
|
+
"message": "Plugin is already enabled",
|
|
103
105
|
}
|
|
104
106
|
|
|
105
107
|
enabled_paths.append(plugin_cfg_path)
|
|
@@ -108,7 +110,9 @@ func enable_plugin(params: Dictionary) -> Dictionary:
|
|
|
108
110
|
return _log.failure("Failed to save project.godot: " + error_string(err))
|
|
109
111
|
|
|
110
112
|
var answer: Dictionary = {
|
|
111
|
-
"plugin_name": plugin_name,
|
|
113
|
+
"plugin_name": plugin_name,
|
|
114
|
+
"action": "enabled",
|
|
115
|
+
"enabled_plugins": _names_of(enabled_paths),
|
|
112
116
|
}
|
|
113
117
|
var unloadable: String = _why_not_loaded(plugin_cfg_path)
|
|
114
118
|
if not unloadable.is_empty():
|
|
@@ -116,9 +120,9 @@ func enable_plugin(params: Dictionary) -> Dictionary:
|
|
|
116
120
|
return answer
|
|
117
121
|
|
|
118
122
|
|
|
119
|
-
# What the editor finds wrong with the plugin at [param plugin_cfg_path] when it loads it, the
|
|
120
|
-
# EditorNode::set_addon_plugin_enabled makes, or "" when it would load. Written all the same,
|
|
121
|
-
# the script can be put right before the editor next starts.
|
|
123
|
+
# What the editor finds wrong with the plugin at [param plugin_cfg_path] when it loads it, the
|
|
124
|
+
# checks EditorNode::set_addon_plugin_enabled makes, or "" when it would load. Written all the same,
|
|
125
|
+
# since the script can be put right before the editor next starts.
|
|
122
126
|
static func _why_not_loaded(plugin_cfg_path: String) -> String:
|
|
123
127
|
var config: ConfigFile = ConfigFile.new()
|
|
124
128
|
if config.load(plugin_cfg_path) != OK:
|
|
@@ -134,11 +138,16 @@ static func _why_not_loaded(plugin_cfg_path: String) -> String:
|
|
|
134
138
|
# Loaded only to learn whether the editor could; what the engine says of a script that does not
|
|
135
139
|
# parse is about a file the answer does not describe.
|
|
136
140
|
printerr(GdhLog.ASIDE)
|
|
137
|
-
var script: Script = ResourceLoader.load(
|
|
141
|
+
var script: Script = ResourceLoader.load(
|
|
142
|
+
script_path, "Script", ResourceLoader.CACHE_MODE_IGNORE
|
|
143
|
+
)
|
|
138
144
|
printerr(GdhLog.RESUMED)
|
|
139
145
|
if script == null or script.get_instance_base_type() == &"":
|
|
140
146
|
return (
|
|
141
|
-
|
|
147
|
+
(
|
|
148
|
+
"%s does not compile, so the editor disables the plugin again at its next start."
|
|
149
|
+
% script_path
|
|
150
|
+
)
|
|
142
151
|
+ " script_diagnostics names the error."
|
|
143
152
|
)
|
|
144
153
|
if not ClassDB.is_parent_class(script.get_instance_base_type(), "EditorPlugin"):
|
|
@@ -161,7 +170,7 @@ func disable_plugin(params: Dictionary) -> Dictionary:
|
|
|
161
170
|
return {
|
|
162
171
|
"plugin_name": plugin_name,
|
|
163
172
|
"action": "already_disabled",
|
|
164
|
-
"message": "Plugin is not currently enabled"
|
|
173
|
+
"message": "Plugin is not currently enabled",
|
|
165
174
|
}
|
|
166
175
|
|
|
167
176
|
enabled_paths.erase(plugin_cfg_path)
|
|
@@ -169,7 +178,11 @@ func disable_plugin(params: Dictionary) -> Dictionary:
|
|
|
169
178
|
if err != OK:
|
|
170
179
|
return _log.failure("Failed to save project.godot: " + error_string(err))
|
|
171
180
|
|
|
172
|
-
return {
|
|
181
|
+
return {
|
|
182
|
+
"plugin_name": plugin_name,
|
|
183
|
+
"action": "disabled",
|
|
184
|
+
"enabled_plugins": _names_of(enabled_paths),
|
|
185
|
+
}
|
|
173
186
|
|
|
174
187
|
|
|
175
188
|
# Every entry in [editor_plugins], as the plugin.cfg path the editor stores. Paths rather than
|
|
@@ -180,13 +193,17 @@ func disable_plugin(params: Dictionary) -> Dictionary:
|
|
|
180
193
|
# hand-edited file can carry.
|
|
181
194
|
func _enabled_paths() -> Array[String]:
|
|
182
195
|
var paths: Array[String] = []
|
|
183
|
-
var enabled_value: Variant = ProjectSettings.get_setting(
|
|
196
|
+
var enabled_value: Variant = ProjectSettings.get_setting(
|
|
197
|
+
"editor_plugins/enabled", PackedStringArray()
|
|
198
|
+
)
|
|
184
199
|
if enabled_value is PackedStringArray:
|
|
185
200
|
var packed: PackedStringArray = enabled_value
|
|
186
201
|
paths.assign(packed)
|
|
187
202
|
elif enabled_value is String:
|
|
188
203
|
var listed: String = enabled_value
|
|
189
|
-
for m: RegExMatch in GdhPatterns.compiled("res://[^\"',)]+?plugin\\.cfg").search_all(
|
|
204
|
+
for m: RegExMatch in GdhPatterns.compiled("res://[^\"',)]+?plugin\\.cfg").search_all(
|
|
205
|
+
listed
|
|
206
|
+
):
|
|
190
207
|
paths.append(m.get_string())
|
|
191
208
|
return paths
|
|
192
209
|
|
|
@@ -197,7 +214,9 @@ static func _names_of(paths: Array[String]) -> Array[String]:
|
|
|
197
214
|
var names: Array[String] = []
|
|
198
215
|
for path: String in paths:
|
|
199
216
|
var inside: bool = path.begins_with("res://addons/") and path.ends_with("/plugin.cfg")
|
|
200
|
-
names.append(
|
|
217
|
+
names.append(
|
|
218
|
+
path.trim_prefix("res://addons/").trim_suffix("/plugin.cfg") if inside else path
|
|
219
|
+
)
|
|
201
220
|
return names
|
|
202
221
|
|
|
203
222
|
|
|
@@ -215,6 +234,7 @@ func _save_enabled_paths(paths: Array[String]) -> Error:
|
|
|
215
234
|
# Kept in an Array[String] and converted here, because appending to a packed array answers with
|
|
216
235
|
# whether it worked and a project holding return_value_discarded at error level will not compile
|
|
217
236
|
# a script that drops that answer. The conversion is what matters: an Array[String] handed to
|
|
218
|
-
# set_setting is written as Array[String]([...]), which the editor does not read as a plugin
|
|
237
|
+
# set_setting is written as Array[String]([...]), which the editor does not read as a plugin
|
|
238
|
+
# list.
|
|
219
239
|
ProjectSettings.set_setting("editor_plugins/enabled", PackedStringArray(paths))
|
|
220
240
|
return GdhProjectFile.save(["editor_plugins/enabled"])
|
|
@@ -7,6 +7,52 @@ const GdhProjectFile = preload("project_file.gd")
|
|
|
7
7
|
const GdhSerialisation = preload("serialisation.gd")
|
|
8
8
|
const GdhSettingReads = preload("setting_reads.gd")
|
|
9
9
|
const GdhShapes = preload("value_shapes.gd")
|
|
10
|
+
## The words GDScript reserves, which no script could reach an autoload by.
|
|
11
|
+
const GDSCRIPT_KEYWORDS: Array[String] = [
|
|
12
|
+
"and",
|
|
13
|
+
"as",
|
|
14
|
+
"assert",
|
|
15
|
+
"await",
|
|
16
|
+
"break",
|
|
17
|
+
"breakpoint",
|
|
18
|
+
"class",
|
|
19
|
+
"class_name",
|
|
20
|
+
"const",
|
|
21
|
+
"continue",
|
|
22
|
+
"elif",
|
|
23
|
+
"else",
|
|
24
|
+
"enum",
|
|
25
|
+
"extends",
|
|
26
|
+
"false",
|
|
27
|
+
"for",
|
|
28
|
+
"func",
|
|
29
|
+
"if",
|
|
30
|
+
"in",
|
|
31
|
+
"INF",
|
|
32
|
+
"is",
|
|
33
|
+
"match",
|
|
34
|
+
"NAN",
|
|
35
|
+
"namespace",
|
|
36
|
+
"not",
|
|
37
|
+
"null",
|
|
38
|
+
"or",
|
|
39
|
+
"pass",
|
|
40
|
+
"PI",
|
|
41
|
+
"preload",
|
|
42
|
+
"return",
|
|
43
|
+
"self",
|
|
44
|
+
"signal",
|
|
45
|
+
"static",
|
|
46
|
+
"super",
|
|
47
|
+
"TAU",
|
|
48
|
+
"trait",
|
|
49
|
+
"true",
|
|
50
|
+
"var",
|
|
51
|
+
"void",
|
|
52
|
+
"when",
|
|
53
|
+
"while",
|
|
54
|
+
"yield",
|
|
55
|
+
]
|
|
10
56
|
|
|
11
57
|
var _log: GdhLog
|
|
12
58
|
var _values: GdhSerialisation = GdhSerialisation.new()
|
|
@@ -54,9 +100,9 @@ func set_project_setting(params: Dictionary) -> Dictionary:
|
|
|
54
100
|
var in_file: bool = GdhProjectFile.sets(setting_path)
|
|
55
101
|
|
|
56
102
|
var wanted: int = _wanted_type(setting_path, old_value if had_value else null)
|
|
57
|
-
# A value of fields or a colour's html code is fitted as a property write fits it, so a value
|
|
58
|
-
# of them takes the other takes too. A number is left to the check below, which refuses one
|
|
59
|
-
# setting would cut where fitting it would cut it.
|
|
103
|
+
# A value of fields or a colour's html code is fitted as a property write fits it, so a value
|
|
104
|
+
# one of them takes the other takes too. A number is left to the check below, which refuses one
|
|
105
|
+
# the setting would cut where fitting it would cut it.
|
|
60
106
|
var final_value: Variant = _values.deserialize_value(value)
|
|
61
107
|
if final_value is Dictionary or (wanted == TYPE_COLOR and final_value is String):
|
|
62
108
|
final_value = _values.fitted(value, wanted)
|
|
@@ -64,8 +110,8 @@ func set_project_setting(params: Dictionary) -> Dictionary:
|
|
|
64
110
|
if not _converts_faithfully(final_value, wanted):
|
|
65
111
|
return _log.failure(_cannot_hold(setting_path, wanted, final_value))
|
|
66
112
|
final_value = type_convert(final_value, wanted)
|
|
67
|
-
# JSON carries every number as a float and a new setting has no declared type to fit one to, so
|
|
68
|
-
# whole number was saved as 800.0 beside an answer saying 800.
|
|
113
|
+
# JSON carries every number as a float and a new setting has no declared type to fit one to, so
|
|
114
|
+
# a whole number was saved as 800.0 beside an answer saying 800.
|
|
69
115
|
if not had_value and wanted == TYPE_NIL and final_value is float:
|
|
70
116
|
var number: float = final_value
|
|
71
117
|
if number == floorf(number) and absf(number) < 9.0e15:
|
|
@@ -86,7 +132,8 @@ func set_project_setting(params: Dictionary) -> Dictionary:
|
|
|
86
132
|
"saved": true,
|
|
87
133
|
}
|
|
88
134
|
# The same value in the same type is no change, and the file is left alone: a save rewrites it
|
|
89
|
-
# whole. The type is compared as well, so an int setting the file holds as 50.0 is still put
|
|
135
|
+
# whole. The type is compared as well, so an int setting the file holds as 50.0 is still put
|
|
136
|
+
# right.
|
|
90
137
|
if had_value and typeof(old_value) == typeof(final_value) and old_value == final_value:
|
|
91
138
|
answer["saved"] = false
|
|
92
139
|
answer["unchanged"] = true
|
|
@@ -127,12 +174,34 @@ func set_project_setting(params: Dictionary) -> Dictionary:
|
|
|
127
174
|
)
|
|
128
175
|
var made: Array[String] = _translations_made_by(missing)
|
|
129
176
|
if not made.is_empty():
|
|
130
|
-
said += (
|
|
177
|
+
said += (
|
|
178
|
+
"; project_import reimport makes the translation files from " + ", ".join(made)
|
|
179
|
+
)
|
|
131
180
|
answer["note"] = (str(answer["note"]) + " " if answer.has("note") else "") + said + "."
|
|
132
181
|
ProjectSettings.set_setting(setting_path, final_value)
|
|
133
182
|
var err: Error = GdhProjectFile.save([setting_path])
|
|
134
183
|
if err != OK:
|
|
135
184
|
return _log.failure("Failed to save project.godot: " + error_string(err))
|
|
185
|
+
# An override still replaces what was written wherever its feature is, and a write of the base
|
|
186
|
+
# value read as if it reached every build.
|
|
187
|
+
var overrides: Dictionary = GdhSettingReads.overrides_of(setting_path, _values)
|
|
188
|
+
if not overrides.is_empty():
|
|
189
|
+
answer["overrides"] = overrides
|
|
190
|
+
var parts: Array[String] = []
|
|
191
|
+
for feature: Variant in overrides:
|
|
192
|
+
parts.append(
|
|
193
|
+
(
|
|
194
|
+
"one with the feature %s reads %s from %s.%s"
|
|
195
|
+
% [feature, JSON.stringify(overrides[feature]), setting_path, feature]
|
|
196
|
+
)
|
|
197
|
+
)
|
|
198
|
+
answer["overridesNote"] = (
|
|
199
|
+
(
|
|
200
|
+
"Not every build reads what was written: %s. Set or unset an override to change "
|
|
201
|
+
+ "what its builds read."
|
|
202
|
+
)
|
|
203
|
+
% "; ".join(parts)
|
|
204
|
+
)
|
|
136
205
|
return answer
|
|
137
206
|
|
|
138
207
|
|
|
@@ -175,8 +244,9 @@ static func _translations_made_by(missing: Array[String]) -> Array[String]:
|
|
|
175
244
|
return sources
|
|
176
245
|
|
|
177
246
|
|
|
178
|
-
# Takes a setting's line out of project.godot, so the engine's default holds again, or nothing at
|
|
179
|
-
# for one the engine does not declare: the way back from a set, which writes a misspelt name as
|
|
247
|
+
# Takes a setting's line out of project.godot, so the engine's default holds again, or nothing at
|
|
248
|
+
# all for one the engine does not declare: the way back from a set, which writes a misspelt name as
|
|
249
|
+
# given.
|
|
180
250
|
func unset_project_setting(params: Dictionary) -> Dictionary:
|
|
181
251
|
var setting_path: String = str(params.get("setting", ""))
|
|
182
252
|
if setting_path.is_empty():
|
|
@@ -193,7 +263,9 @@ func unset_project_setting(params: Dictionary) -> Dictionary:
|
|
|
193
263
|
if not held or not GdhProjectFile.sets(setting_path):
|
|
194
264
|
answer["removed"] = false
|
|
195
265
|
answer["saved"] = false
|
|
196
|
-
answer["note"] =
|
|
266
|
+
answer["note"] = (
|
|
267
|
+
"project.godot does not set %s, so there was nothing to take out." % setting_path
|
|
268
|
+
)
|
|
197
269
|
var near: PackedStringArray = PackedStringArray() if held else _settings_near(setting_path)
|
|
198
270
|
if not near.is_empty():
|
|
199
271
|
answer["note"] += (
|
|
@@ -202,8 +274,8 @@ func unset_project_setting(params: Dictionary) -> Dictionary:
|
|
|
202
274
|
)
|
|
203
275
|
return _with_what_holds(answer, setting_path)
|
|
204
276
|
answer["old_value"] = _values.serialize_value(value)
|
|
205
|
-
# The default rather than null for a setting the engine declares: null takes the declaration
|
|
206
|
-
# as well, and a value at its default is left out of the file all the same.
|
|
277
|
+
# The default rather than null for a setting the engine declares: null takes the declaration
|
|
278
|
+
# away as well, and a value at its default is left out of the file all the same.
|
|
207
279
|
ProjectSettings.set_setting(setting_path, default_value)
|
|
208
280
|
var err: Error = GdhProjectFile.save([setting_path])
|
|
209
281
|
if err != OK:
|
|
@@ -277,7 +349,10 @@ static func _cannot_hold(setting_path: String, wanted: int, value: Variant) -> S
|
|
|
277
349
|
var declared: String = "%s is declared as %s" % [setting_path, type_string(wanted)]
|
|
278
350
|
var cost: String = " Setting it would save a different value than the one asked for."
|
|
279
351
|
if wanted == TYPE_INT and typeof(value) == TYPE_FLOAT:
|
|
280
|
-
return
|
|
352
|
+
return (
|
|
353
|
+
"%s, which holds whole numbers, and %s would be cut to fit.%s"
|
|
354
|
+
% [declared, str(value), cost]
|
|
355
|
+
)
|
|
281
356
|
if wanted in numbers and typeof(value) in numbers:
|
|
282
357
|
return "%s, and %s is not the same number as one.%s" % [declared, str(value), cost]
|
|
283
358
|
var given: String = type_string(typeof(value))
|
|
@@ -285,8 +360,8 @@ static func _cannot_hold(setting_path: String, wanted: int, value: Variant) -> S
|
|
|
285
360
|
return "%s, and %s (%s) cannot become one.%s" % [declared, JSON.stringify(value), given, remedy]
|
|
286
361
|
|
|
287
362
|
|
|
288
|
-
## Why [param tagged], a value whose tag could not be built, was not written to
|
|
289
|
-
## and how a value of that tag is written.
|
|
363
|
+
## Why [param tagged], a value whose tag could not be built, was not written to
|
|
364
|
+
## [param setting_path], and how a value of that tag is written.
|
|
290
365
|
static func _unbuilt(setting_path: String, tagged: Dictionary) -> String:
|
|
291
366
|
var tag: String = str(tagged["_type"])
|
|
292
367
|
var refused: String = (
|
|
@@ -299,7 +374,9 @@ static func _unbuilt(setting_path: String, tagged: Dictionary) -> String:
|
|
|
299
374
|
var keys: Array = shapes[0]
|
|
300
375
|
var parts: Array[String] = ['"_type": "%s"' % tag]
|
|
301
376
|
for key: Variant in keys:
|
|
302
|
-
parts.append(
|
|
377
|
+
parts.append(
|
|
378
|
+
'"%s": %s' % [key, "[...]" if tag.begins_with("Packed") and key == "value" else "..."]
|
|
379
|
+
)
|
|
303
380
|
return refused + " One is written as {%s}." % ", ".join(parts)
|
|
304
381
|
|
|
305
382
|
|
|
@@ -313,14 +390,21 @@ func add_autoload(params: Dictionary) -> Dictionary:
|
|
|
313
390
|
# every start. `enabled: false` used to write the entry without its asterisk and answer that it
|
|
314
391
|
# was disabled, and the autoload ran all the same.
|
|
315
392
|
if not GdhRead.as_bool(params.get("enabled", true), true):
|
|
316
|
-
return
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
393
|
+
return (
|
|
394
|
+
_log
|
|
395
|
+
. failure(
|
|
396
|
+
(
|
|
397
|
+
"An autoload cannot be added disabled: Godot loads every autoload in the list "
|
|
398
|
+
+ "at every start. global: false keeps its name out of the global scope and "
|
|
399
|
+
+ "still loads it; remove_autoload is what stops it loading"
|
|
400
|
+
)
|
|
321
401
|
)
|
|
322
402
|
)
|
|
323
403
|
|
|
404
|
+
var clash: String = _autoload_name_refusal(name)
|
|
405
|
+
if not clash.is_empty():
|
|
406
|
+
return _log.failure(clash)
|
|
407
|
+
|
|
324
408
|
if not path.begins_with("res://"):
|
|
325
409
|
path = "res://" + path
|
|
326
410
|
|
|
@@ -347,7 +431,7 @@ func add_autoload(params: Dictionary) -> Dictionary:
|
|
|
347
431
|
"enabled": true,
|
|
348
432
|
"global": global,
|
|
349
433
|
"action": "unchanged",
|
|
350
|
-
"saved": false
|
|
434
|
+
"saved": false,
|
|
351
435
|
}
|
|
352
436
|
ProjectSettings.set_setting(setting, entry)
|
|
353
437
|
var err: Error = GdhProjectFile.save([setting])
|
|
@@ -364,6 +448,35 @@ func add_autoload(params: Dictionary) -> Dictionary:
|
|
|
364
448
|
}
|
|
365
449
|
|
|
366
450
|
|
|
451
|
+
## Why [param name] cannot name an autoload, or empty when it can. The editor's Autoload tab refuses
|
|
452
|
+
## the same names: an autoload named after an engine class or a built-in type takes that name in
|
|
453
|
+
## every script, so an autoload called Input made every Input.is_action_pressed call the autoload.
|
|
454
|
+
static func _autoload_name_refusal(name: String) -> String:
|
|
455
|
+
var reason: String = ""
|
|
456
|
+
if not name.is_valid_ascii_identifier():
|
|
457
|
+
reason = "it is not an identifier, which a script could name"
|
|
458
|
+
elif name in GDSCRIPT_KEYWORDS:
|
|
459
|
+
reason = "it is a GDScript keyword"
|
|
460
|
+
elif ClassDB.class_exists(name):
|
|
461
|
+
reason = (
|
|
462
|
+
"it is the engine class %s, and every script naming %s would reach the autoload instead"
|
|
463
|
+
% [name, name]
|
|
464
|
+
)
|
|
465
|
+
else:
|
|
466
|
+
for type: int in TYPE_MAX:
|
|
467
|
+
if type_string(type) == name:
|
|
468
|
+
reason = "it is the built-in type %s" % name
|
|
469
|
+
if reason.is_empty():
|
|
470
|
+
return ""
|
|
471
|
+
return (
|
|
472
|
+
(
|
|
473
|
+
"%s cannot name an autoload: %s. Nothing was written; the editor's Autoload tab "
|
|
474
|
+
+ "refuses it too."
|
|
475
|
+
)
|
|
476
|
+
% [JSON.stringify(name), reason]
|
|
477
|
+
)
|
|
478
|
+
|
|
479
|
+
|
|
367
480
|
# Remove an autoload singleton
|
|
368
481
|
func remove_autoload(params: Dictionary) -> Dictionary:
|
|
369
482
|
var name: String = str(params.get("name", ""))
|
|
@@ -380,7 +493,7 @@ func remove_autoload(params: Dictionary) -> Dictionary:
|
|
|
380
493
|
if err != OK:
|
|
381
494
|
return _log.failure("Failed to save project.godot: " + error_string(err))
|
|
382
495
|
|
|
383
|
-
return {"name": name, "removed": true, "old_path": old_value.trim_prefix("*")}
|
|
496
|
+
return { "name": name, "removed": true, "old_path": old_value.trim_prefix("*") }
|
|
384
497
|
|
|
385
498
|
|
|
386
499
|
# List all autoload singletons
|
|
@@ -407,7 +520,7 @@ func list_autoloads(_params: Dictionary) -> Dictionary:
|
|
|
407
520
|
}
|
|
408
521
|
autoloads.append(autoload_info)
|
|
409
522
|
|
|
410
|
-
return {"autoloads": autoloads, "count": autoloads.size()}
|
|
523
|
+
return { "autoloads": autoloads, "count": autoloads.size() }
|
|
411
524
|
|
|
412
525
|
|
|
413
526
|
# Set the main scene
|
|
@@ -421,7 +534,9 @@ func set_main_scene(params: Dictionary) -> Dictionary:
|
|
|
421
534
|
|
|
422
535
|
if not FileAccess.file_exists(scene_path):
|
|
423
536
|
return _log.failure(
|
|
424
|
-
GdhFileWalk.new().with_nearest_files(
|
|
537
|
+
GdhFileWalk.new().with_nearest_files(
|
|
538
|
+
"Scene file does not exist: " + scene_path, scene_path
|
|
539
|
+
)
|
|
425
540
|
)
|
|
426
541
|
|
|
427
542
|
# The engine starts the game by instancing the main scene, so a script or any other resource set
|
|
@@ -433,7 +548,9 @@ func set_main_scene(params: Dictionary) -> Dictionary:
|
|
|
433
548
|
"%s is %s, not a scene, and the main scene has to be one"
|
|
434
549
|
% [scene_path, "a " + kind if not kind.is_empty() else "no resource the engine loads"]
|
|
435
550
|
)
|
|
436
|
-
return _log.failure(
|
|
551
|
+
return _log.failure(
|
|
552
|
+
GdhFileWalk.new().with_nearest_files(said, scene_path.get_basename() + ".tscn")
|
|
553
|
+
)
|
|
437
554
|
|
|
438
555
|
var old_main_scene: String = str(ProjectSettings.get_setting("application/run/main_scene", ""))
|
|
439
556
|
|
|
@@ -443,4 +560,4 @@ func set_main_scene(params: Dictionary) -> Dictionary:
|
|
|
443
560
|
if err != OK:
|
|
444
561
|
return _log.failure("Failed to save project settings: " + str(err))
|
|
445
562
|
|
|
446
|
-
return {"old_main_scene": old_main_scene, "new_main_scene": scene_path, "saved": true}
|
|
563
|
+
return { "old_main_scene": old_main_scene, "new_main_scene": scene_path, "saved": true }
|
|
@@ -40,14 +40,17 @@ func get_project_health(params: Dictionary) -> Dictionary:
|
|
|
40
40
|
# failed this check and lost points, while Godot's own guidance is to keep a scene's files
|
|
41
41
|
# together by feature, so the check failed projects for following the engine's advice.
|
|
42
42
|
|
|
43
|
-
checks["structure"] = {
|
|
43
|
+
checks["structure"] = {
|
|
44
|
+
"name": "Project Structure", "passed": details.is_empty(), "details": details,
|
|
45
|
+
}
|
|
44
46
|
|
|
45
47
|
# 2. Resource Check
|
|
46
48
|
if "resources" in check_categories:
|
|
47
49
|
var details: Array[String] = []
|
|
48
50
|
|
|
49
|
-
# Every format the engine imports, the list the import status reads: five of them were
|
|
50
|
-
# here, so a project of glTF models, fonts and WebP textures had nothing to
|
|
51
|
+
# Every format the engine imports, the list the import status reads: five of them were
|
|
52
|
+
# checked here, so a project of glTF models, fonts and WebP textures had nothing to
|
|
53
|
+
# reimport.
|
|
51
54
|
var resources: Array[String] = _files.find_files_with_extensions(
|
|
52
55
|
"res://", GdhImportPipeline.IMPORTABLE_EXTENSIONS
|
|
53
56
|
)
|
|
@@ -68,7 +71,7 @@ func get_project_health(params: Dictionary) -> Dictionary:
|
|
|
68
71
|
"details": details,
|
|
69
72
|
# What the check read: files the engine imports. A .tres is a resource and was counted
|
|
70
73
|
# under this name as none, beside a project holding one.
|
|
71
|
-
"importable_files": resources.size()
|
|
74
|
+
"importable_files": resources.size(),
|
|
72
75
|
}
|
|
73
76
|
|
|
74
77
|
# 3. Scripts Check
|
|
@@ -106,7 +109,7 @@ func get_project_health(params: Dictionary) -> Dictionary:
|
|
|
106
109
|
"name": "Scripts",
|
|
107
110
|
"passed": details.is_empty(),
|
|
108
111
|
"details": details,
|
|
109
|
-
"total_scripts": script_files.size()
|
|
112
|
+
"total_scripts": script_files.size(),
|
|
110
113
|
}
|
|
111
114
|
if compiled.has("checked"):
|
|
112
115
|
scripts["compiled"] = GdhRead.as_int(compiled["checked"])
|
|
@@ -123,14 +126,18 @@ func get_project_health(params: Dictionary) -> Dictionary:
|
|
|
123
126
|
for entry: Dictionary in GdhInputActions.new(_log).undefined_in_scripts():
|
|
124
127
|
details.append(GdhInputActions.undefined_line(entry))
|
|
125
128
|
deductions += 5
|
|
126
|
-
checks["references"] = {
|
|
129
|
+
checks["references"] = {
|
|
130
|
+
"name": "References", "passed": details.is_empty(), "details": details,
|
|
131
|
+
}
|
|
127
132
|
|
|
128
133
|
# 4. Scenes Check
|
|
129
134
|
if "scenes" in check_categories:
|
|
130
135
|
var details: Array[String] = []
|
|
131
136
|
|
|
132
137
|
# Binary scenes too, which a project may save instead and which counted as none.
|
|
133
|
-
var scene_files: Array[String] = _files.find_files_with_extensions(
|
|
138
|
+
var scene_files: Array[String] = _files.find_files_with_extensions(
|
|
139
|
+
"res://", ["tscn", "scn"]
|
|
140
|
+
)
|
|
134
141
|
if scene_files.size() == 0:
|
|
135
142
|
details.append("No scene files found in project")
|
|
136
143
|
deductions += 5
|
|
@@ -142,7 +149,7 @@ func get_project_health(params: Dictionary) -> Dictionary:
|
|
|
142
149
|
"name": "Scenes",
|
|
143
150
|
"passed": details.is_empty(),
|
|
144
151
|
"details": details,
|
|
145
|
-
"total_scenes": scene_files.size()
|
|
152
|
+
"total_scenes": scene_files.size(),
|
|
146
153
|
}
|
|
147
154
|
|
|
148
155
|
# 5. Configuration Check
|
|
@@ -162,7 +169,9 @@ func get_project_health(params: Dictionary) -> Dictionary:
|
|
|
162
169
|
details.append("No project name set")
|
|
163
170
|
deductions += 2
|
|
164
171
|
|
|
165
|
-
checks["config"] = {
|
|
172
|
+
checks["config"] = {
|
|
173
|
+
"name": "Configuration", "passed": details.is_empty(), "details": details,
|
|
174
|
+
}
|
|
166
175
|
|
|
167
176
|
var score: int = maxi(0, 100 - deductions)
|
|
168
177
|
|
|
@@ -193,7 +202,11 @@ func get_project_health(params: Dictionary) -> Dictionary:
|
|
|
193
202
|
var check: Dictionary = checks[category]
|
|
194
203
|
var found: Array[String] = check["details"]
|
|
195
204
|
for detail: String in found:
|
|
196
|
-
issues.append({"check": category, "detail": detail})
|
|
205
|
+
issues.append({ "check": category, "detail": detail })
|
|
197
206
|
return {
|
|
198
|
-
"score": score,
|
|
207
|
+
"score": score,
|
|
208
|
+
"grade": grade,
|
|
209
|
+
"checks": checks,
|
|
210
|
+
"issues": issues,
|
|
211
|
+
"recommendations": recommendations,
|
|
199
212
|
}
|
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
extends RefCounted
|
|
2
|
-
|
|
3
2
|
## project.godot written through the engine's own save, in the order the file already held.
|
|
4
3
|
##
|
|
5
4
|
## The engine writes each section's keys in the order it registered them, and an engine running a
|
|
6
5
|
## script registers them in another order than the editor does, so a one-setting change moved lines
|
|
7
|
-
## nobody touched across several sections and a commit of it read as a rewrite. The save is still
|
|
8
|
-
## engine's, for what only it gets right: the header and the text of each value the call
|
|
9
|
-
## The order is taken from the file as it was, and so is every line of a key the call did
|
|
10
|
-
## because the engine writes its settings as it holds them rather than as the file had
|
|
11
|
-
## leaves out each key at its default, so a project holding `unsafe_call_argument=0` on
|
|
12
|
-
## the line to a change of another key. It writes a dictionary with its keys sorted, so
|
|
13
|
-
## session hooks, which run in the order listed, changed order. And it writes what a
|
|
6
|
+
## nobody touched across several sections and a commit of it read as a rewrite. The save is still
|
|
7
|
+
## the engine's, for what only it gets right: the header and the text of each value the call
|
|
8
|
+
## changed. The order is taken from the file as it was, and so is every line of a key the call did
|
|
9
|
+
## not change, because the engine writes its settings as it holds them rather than as the file had
|
|
10
|
+
## them. It leaves out each key at its default, so a project holding `unsafe_call_argument=0` on
|
|
11
|
+
## purpose lost the line to a change of another key. It writes a dictionary with its keys sorted, so
|
|
12
|
+
## gdUnit4's session hooks, which run in the order listed, changed order. And it writes what a
|
|
14
13
|
## `project_settings_override` file laid over the project, so a player's window mode in the user
|
|
15
14
|
## directory became the game's default.
|
|
16
15
|
|
|
@@ -42,8 +41,8 @@ static func save(changed: PackedStringArray) -> Error:
|
|
|
42
41
|
## [param before] with the lines of the settings in [param changed] as [param written] has them: one
|
|
43
42
|
## the engine wrote goes in its place, or where the engine put it relative to its neighbours when it
|
|
44
43
|
## is new, and one the engine left out leaves the file. Every other line is [param before]'s, in its
|
|
45
|
-
## place, and a key the engine wrote that the file did not hold and the call did not change stays
|
|
46
|
-
## The header is [param written]'s.
|
|
44
|
+
## place, and a key the engine wrote that the file did not hold and the call did not change stays
|
|
45
|
+
## out. The header is [param written]'s.
|
|
47
46
|
static func in_order_of(before: String, written: String, changed: PackedStringArray) -> String:
|
|
48
47
|
var old: Dictionary = _parsed(before)
|
|
49
48
|
var fresh: Dictionary = _parsed(written)
|
|
@@ -113,8 +112,8 @@ static func in_order_of(before: String, written: String, changed: PackedStringAr
|
|
|
113
112
|
fresh_keys[name].append(str(entry["key"]))
|
|
114
113
|
|
|
115
114
|
# The blank lines closing a section belong to its place in the file rather than to the section,
|
|
116
|
-
# so the last one keeps the ending the engine gave the file wherever it moved from. A section
|
|
117
|
-
# back sits between two others, which the engine parts with a blank line.
|
|
115
|
+
# so the last one keeps the ending the engine gave the file wherever it moved from. A section
|
|
116
|
+
# put back sits between two others, which the engine parts with a blank line.
|
|
118
117
|
var names: Array[String] = _merged(old_names, fresh_names)
|
|
119
118
|
var tails: Array[String] = []
|
|
120
119
|
for section: Dictionary in fresh_sections:
|
|
@@ -181,7 +180,8 @@ static func _merged(held: Array[String], wanted: Array[String]) -> Array[String]
|
|
|
181
180
|
|
|
182
181
|
|
|
183
182
|
## [param text] as a preamble (the header and anything before the first section) and its sections,
|
|
184
|
-
## each with its header line and the blank lines after it, its entries, and the blank lines closing
|
|
183
|
+
## each with its header line and the blank lines after it, its entries, and the blank lines closing
|
|
184
|
+
## it.
|
|
185
185
|
static func _parsed(text: String) -> Dictionary:
|
|
186
186
|
var preamble: String = ""
|
|
187
187
|
var sections: Array[Dictionary] = []
|
|
@@ -193,7 +193,7 @@ static func _parsed(text: String) -> Dictionary:
|
|
|
193
193
|
if not current.is_empty():
|
|
194
194
|
_close(current, body)
|
|
195
195
|
sections.append(current)
|
|
196
|
-
current = {"name": stripped.substr(1, stripped.length() - 2), "head": line}
|
|
196
|
+
current = { "name": stripped.substr(1, stripped.length() - 2), "head": line }
|
|
197
197
|
body = []
|
|
198
198
|
elif current.is_empty():
|
|
199
199
|
preamble += line
|
|
@@ -204,7 +204,7 @@ static func _parsed(text: String) -> Dictionary:
|
|
|
204
204
|
if not current.is_empty():
|
|
205
205
|
_close(current, body)
|
|
206
206
|
sections.append(current)
|
|
207
|
-
return {"preamble": preamble, "sections": sections}
|
|
207
|
+
return { "preamble": preamble, "sections": sections }
|
|
208
208
|
|
|
209
209
|
|
|
210
210
|
## Fills in [param section]'s entries and closing blank lines from the lines after its head. A blank
|
|
@@ -218,7 +218,7 @@ static func _close(section: Dictionary, body: Array[String]) -> void:
|
|
|
218
218
|
for line: String in body:
|
|
219
219
|
var stripped: String = line.strip_edges()
|
|
220
220
|
if entries.is_empty() or not (stripped.is_empty() or stripped.begins_with(";")):
|
|
221
|
-
entries.append({"key": _key_of(line), "text": line})
|
|
221
|
+
entries.append({ "key": _key_of(line), "text": line })
|
|
222
222
|
else:
|
|
223
223
|
var last: Dictionary = entries[entries.size() - 1]
|
|
224
224
|
last["text"] = str(last["text"]) + line
|