gdharness 1.3.4 → 1.3.5
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 +428 -383
- package/build/godot/addons/gdharness_editor/declared_values.gd +24 -0
- package/build/godot/addons/gdharness_editor/property_values.gd +5 -18
- package/build/godot/addons/gdharness_editor/tools/class_tools.gd +20 -12
- package/build/godot/addons/gdharness_runtime/declared_values.gd +293 -0
- package/build/godot/addons/gdharness_runtime/declared_values.gd.uid +1 -0
- package/build/godot/addons/gdharness_runtime/runtime_autoload.gd +4 -0
- package/build/godot/addons/gdharness_runtime/runtime_changes.gd +29 -1
- package/build/godot/addons/gdharness_runtime/runtime_input.gd +16 -13
- package/build/godot/addons/gdharness_runtime/runtime_mouse.gd +18 -0
- package/build/godot/addons/gdharness_runtime/runtime_values.gd +32 -5
- package/build/godot/addons/gdharness_runtime/runtime_waits.gd +48 -15
- package/build/godot/operations/classdb_queries.gd +112 -46
- package/build/godot/operations/declared_values.gd +24 -0
- package/build/godot/operations/gdscript_authoring.gd +15 -2
- package/build/godot/operations/property_values.gd +5 -18
- package/build/keeper.js +83 -69
- package/package.json +1 -1
|
@@ -4,6 +4,7 @@ extends RefCounted
|
|
|
4
4
|
## happened, or when the time ran out, so the caller never sleeps for a guessed length.
|
|
5
5
|
|
|
6
6
|
const GdhChanges = preload("runtime_changes.gd")
|
|
7
|
+
const GdhDeclared = preload("declared_values.gd")
|
|
7
8
|
const GdhPaths = preload("runtime_paths.gd")
|
|
8
9
|
const GdhQueries = preload("runtime_queries.gd")
|
|
9
10
|
const GdhRead = preload("reading.gd")
|
|
@@ -51,6 +52,13 @@ static func _out_of_range(named: String, asked: int, least: int, most: int) -> D
|
|
|
51
52
|
}
|
|
52
53
|
|
|
53
54
|
|
|
55
|
+
## The refusal for [param signal_name], which [param node] lacks, naming the nearest signals it has,
|
|
56
|
+
## or every one when none is near: a name to send instead.
|
|
57
|
+
static func _no_signal(node: Node, node_path: String, signal_name: String) -> Dictionary:
|
|
58
|
+
var said: String = "%s has no signal %s" % [node_path, signal_name]
|
|
59
|
+
return {"type": "error", "message": GdhRead.offered(said, signal_name, GdhRead.signals_of(node)) + "."}
|
|
60
|
+
|
|
61
|
+
|
|
54
62
|
func wait_frames(params: Dictionary) -> Dictionary:
|
|
55
63
|
var frames: int = GdhRead.as_int(params.get("frames", 1), 1)
|
|
56
64
|
if frames < 1 or frames > MOST_FRAMES:
|
|
@@ -79,7 +87,7 @@ func wait_signal(params: Dictionary) -> Dictionary:
|
|
|
79
87
|
return standing
|
|
80
88
|
var node: Node = standing["node"]
|
|
81
89
|
if not node.has_signal(signal_name):
|
|
82
|
-
return
|
|
90
|
+
return _no_signal(node, node_path, signal_name)
|
|
83
91
|
|
|
84
92
|
var catcher: GdhSignalCatcher = GdhSignalCatcher.new()
|
|
85
93
|
var callable: Callable = catcher._on_fired
|
|
@@ -124,7 +132,7 @@ func watch_signal(params: Dictionary) -> Dictionary:
|
|
|
124
132
|
return standing
|
|
125
133
|
var node: Node = standing["node"]
|
|
126
134
|
if not node.has_signal(signal_name):
|
|
127
|
-
return
|
|
135
|
+
return _no_signal(node, node_path, signal_name)
|
|
128
136
|
var watch: GdhWatch = GdhWatch.new(node, node_path, signal_name)
|
|
129
137
|
var listening: Error = node.connect(signal_name, watch.catcher._on_fired)
|
|
130
138
|
if listening != OK:
|
|
@@ -240,7 +248,9 @@ func wait_until(params: Dictionary) -> Dictionary:
|
|
|
240
248
|
var current: Variant = watched["value"]
|
|
241
249
|
if compare in [">=", "<=", ">", "<"]:
|
|
242
250
|
return await _wait_in_order(node, params, current, timeout_ms)
|
|
243
|
-
var
|
|
251
|
+
var by_name: Array = _enum_by_name(watched, params["value"], node_path)
|
|
252
|
+
var sent: Variant = by_name[1] if not by_name.is_empty() and by_name[0] else params["value"]
|
|
253
|
+
var wanted: Variant = _values.fitted(sent, typeof(current))
|
|
244
254
|
# Read against what the property is declared as, not only against what it holds this frame: an
|
|
245
255
|
# object property holding null holds nothing typed, so any value was taken as it came and compared
|
|
246
256
|
# with null until the time ran out. #772 waited a minute for "null" that way and answered met
|
|
@@ -252,10 +262,13 @@ func wait_until(params: Dictionary) -> Dictionary:
|
|
|
252
262
|
var holds_objects: bool = declared == TYPE_OBJECT or untyped_object
|
|
253
263
|
if holds_objects and GdhChanges.names_no_object(wanted):
|
|
254
264
|
wanted = null
|
|
255
|
-
|
|
265
|
+
# A word no value of an enum is called, refused naming the values before it is compared as text.
|
|
266
|
+
var refused: String = "" if by_name.is_empty() or by_name[0] else str(by_name[1])
|
|
256
267
|
if refused.is_empty():
|
|
257
|
-
refused =
|
|
258
|
-
|
|
268
|
+
refused = _not_comparable(current, wanted, node_path, property)
|
|
269
|
+
if refused.is_empty():
|
|
270
|
+
refused = _cut_to_fit(sent, typeof(current), node_path, property)
|
|
271
|
+
var rounded: String = GdhValues.inexact(sent, typeof(current))
|
|
259
272
|
if refused.is_empty() and not rounded.is_empty():
|
|
260
273
|
refused = "%s.%s holds int: %s." % [node_path, property, rounded]
|
|
261
274
|
var never_held: bool = typeof(wanted) != TYPE_NIL and typeof(wanted) != TYPE_OBJECT
|
|
@@ -469,6 +482,20 @@ static func _not_comparable(current: Variant, wanted: Variant, node_path: String
|
|
|
469
482
|
return ""
|
|
470
483
|
|
|
471
484
|
|
|
485
|
+
## [param sent] as the number of the value an enum property names by it, as
|
|
486
|
+
## [method GdhDeclared.enum_by_name] answers, for a word waited for on what [param watched] read; []
|
|
487
|
+
## when the property is no enum or the value no word.
|
|
488
|
+
static func _enum_by_name(watched: Dictionary, sent: Variant, node_path: String) -> Array:
|
|
489
|
+
var declaration: Dictionary = watched["declaration"]
|
|
490
|
+
var enum_hint: bool = GdhRead.as_int(declaration.get("hint", PROPERTY_HINT_NONE)) == PROPERTY_HINT_ENUM
|
|
491
|
+
if watched["declared"] != TYPE_INT or not enum_hint or not sent is String:
|
|
492
|
+
return []
|
|
493
|
+
if not watched["holder"] is Object:
|
|
494
|
+
return []
|
|
495
|
+
var owner: Object = watched["holder"]
|
|
496
|
+
return GdhDeclared.enum_by_name(owner, declaration, str(sent), node_path)
|
|
497
|
+
|
|
498
|
+
|
|
472
499
|
## What [param property] reads as on [param node] right now, under `value`, or a `message` saying
|
|
473
500
|
## why it cannot be read.
|
|
474
501
|
##
|
|
@@ -486,18 +513,24 @@ static func _watched(node: Node, node_path: String, property: String) -> Diction
|
|
|
486
513
|
var missing: String = GdhPaths.nothing_under(holder, named, str(reached["called"]))
|
|
487
514
|
if not missing.is_empty():
|
|
488
515
|
return {"type": "error", "message": missing}
|
|
489
|
-
|
|
516
|
+
var slot: Dictionary = _declared_slot(holder, named)
|
|
517
|
+
return {
|
|
518
|
+
"value": GdhPaths.read_under(holder, named),
|
|
519
|
+
"declared": slot["type"],
|
|
520
|
+
"declaration": slot["declared"],
|
|
521
|
+
"holder": holder,
|
|
522
|
+
}
|
|
490
523
|
|
|
491
524
|
|
|
492
|
-
##
|
|
493
|
-
## refuse the same values: a property's declaration, or a
|
|
494
|
-
## off properties alone, an element of an Array[Node] holding
|
|
495
|
-
## on it runs out the timeout while a set of 5 on it is refused
|
|
496
|
-
## declaring none, and for a call, whose answer nothing declares.
|
|
497
|
-
static func
|
|
525
|
+
## What [param named] is declared with on [param holder], as [method GdhValues.slot_declared] answers,
|
|
526
|
+
## read the way a set reads it, so the two refuse the same values: a property's declaration, or a
|
|
527
|
+
## typed list's or map's element type. Read off properties alone, an element of an Array[Node] holding
|
|
528
|
+
## null declares nothing, and a wait for 5 on it runs out the timeout while a set of 5 on it is refused
|
|
529
|
+
## at once. TYPE_NIL for anything declaring none, and for a call, whose answer nothing declares.
|
|
530
|
+
static func _declared_slot(holder: Variant, named: String) -> Dictionary:
|
|
498
531
|
if not GdhPaths.method_of(named).is_empty():
|
|
499
|
-
return TYPE_NIL
|
|
500
|
-
return GdhValues.slot_declared(holder, named)
|
|
532
|
+
return {"type": TYPE_NIL, "class": "", "script": null, "declared": {}}
|
|
533
|
+
return GdhValues.slot_declared(holder, named)
|
|
501
534
|
|
|
502
535
|
|
|
503
536
|
## Waits until something under [param node_path] has [param said] written on it.
|
|
@@ -4,6 +4,9 @@ const GdhRead = preload("reading.gd")
|
|
|
4
4
|
const GdhLog = preload("logger.gd")
|
|
5
5
|
const GdhSerialisation = preload("serialisation.gd")
|
|
6
6
|
|
|
7
|
+
# How far below the closest name another may score and still be offered beside it.
|
|
8
|
+
const CLOSE_TO_THE_BEST: float = 0.2
|
|
9
|
+
|
|
7
10
|
# The base classes each category name stands for. Kept to the list the tool's schema offers, which
|
|
8
11
|
# a case holds: the schema offered physics3d and ui, which this refused, and physics answered the 3D
|
|
9
12
|
# bodies alone beside physics2d and physics3d.
|
|
@@ -114,20 +117,20 @@ func query_class_info(params: Dictionary) -> Dictionary:
|
|
|
114
117
|
)
|
|
115
118
|
|
|
116
119
|
if not ClassDB.class_exists(class_name_str):
|
|
117
|
-
return _not_found(class_name_str)
|
|
120
|
+
return _not_found(class_name_str, _declared_classes(params))
|
|
118
121
|
|
|
119
122
|
var member: String = str(params.get("member", ""))
|
|
120
123
|
if not member.is_empty():
|
|
121
124
|
return _member_of(class_name_str, member)
|
|
122
125
|
|
|
123
|
-
var methods: Array[Dictionary] = _methods_of(class_name_str
|
|
124
|
-
var properties: Array[Dictionary] = _properties_of(class_name_str
|
|
125
|
-
var signals: Array[Dictionary] = _signals_of(class_name_str
|
|
126
|
+
var methods: Array[Dictionary] = _methods_of(class_name_str)
|
|
127
|
+
var properties: Array[Dictionary] = _properties_of(class_name_str)
|
|
128
|
+
var signals: Array[Dictionary] = _signals_of(class_name_str)
|
|
126
129
|
|
|
127
130
|
var enums: Dictionary = {}
|
|
128
|
-
for e: String in ClassDB.class_get_enum_list(class_name_str,
|
|
131
|
+
for e: String in ClassDB.class_get_enum_list(class_name_str, true):
|
|
129
132
|
var enum_values: Dictionary = {}
|
|
130
|
-
for c: String in ClassDB.class_get_enum_constants(class_name_str, e,
|
|
133
|
+
for c: String in ClassDB.class_get_enum_constants(class_name_str, e, true):
|
|
131
134
|
enum_values[c] = ClassDB.class_get_integer_constant(class_name_str, c)
|
|
132
135
|
enums[e] = enum_values
|
|
133
136
|
|
|
@@ -143,7 +146,7 @@ func query_class_info(params: Dictionary) -> Dictionary:
|
|
|
143
146
|
)
|
|
144
147
|
)
|
|
145
148
|
|
|
146
|
-
|
|
149
|
+
var answer: Dictionary = {
|
|
147
150
|
"class_name": class_name_str,
|
|
148
151
|
"parent_class": ClassDB.get_parent_class(class_name_str),
|
|
149
152
|
"can_instantiate": ClassDB.can_instantiate(class_name_str),
|
|
@@ -156,43 +159,99 @@ func query_class_info(params: Dictionary) -> Dictionary:
|
|
|
156
159
|
"signals": signals,
|
|
157
160
|
"enums": enums
|
|
158
161
|
}
|
|
162
|
+
if include_inherited:
|
|
163
|
+
answer["inherited"] = _inherited_names(class_name_str)
|
|
164
|
+
return answer
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
# What [param class_name_str] inherits, by name under the ancestor declaring it, nearest first. In
|
|
168
|
+
# full, CharacterBody2D's inherited members ran to two hundred thousand characters, more than a
|
|
169
|
+
# client takes in one answer, and any one of them is read in full by asking for it as member.
|
|
170
|
+
func _inherited_names(class_name_str: String) -> Array[Dictionary]:
|
|
171
|
+
var inherited: Array[Dictionary] = []
|
|
172
|
+
var current: String = ClassDB.get_parent_class(class_name_str)
|
|
173
|
+
while not current.is_empty():
|
|
174
|
+
(
|
|
175
|
+
inherited
|
|
176
|
+
. append(
|
|
177
|
+
{
|
|
178
|
+
"class": current,
|
|
179
|
+
"methods": _names(_methods_of(current)),
|
|
180
|
+
"properties": _names(_properties_of(current)),
|
|
181
|
+
"signals": _names(_signals_of(current)),
|
|
182
|
+
"enums": Array(ClassDB.class_get_enum_list(current, true)),
|
|
183
|
+
}
|
|
184
|
+
)
|
|
185
|
+
)
|
|
186
|
+
current = ClassDB.get_parent_class(current)
|
|
187
|
+
return inherited
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
static func _names(members: Array[Dictionary]) -> Array[String]:
|
|
191
|
+
var names: Array[String] = []
|
|
192
|
+
for each: Dictionary in members:
|
|
193
|
+
names.append(str(each["name"]))
|
|
194
|
+
return names
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
static func _declared_classes(params: Dictionary) -> Dictionary:
|
|
198
|
+
var given: Variant = params.get("project_classes", {})
|
|
199
|
+
if given is Dictionary:
|
|
200
|
+
var declared: Dictionary = given
|
|
201
|
+
return declared
|
|
202
|
+
return {}
|
|
159
203
|
|
|
160
204
|
|
|
161
|
-
# One member of a class by name, wherever in its ancestry it is declared.
|
|
162
|
-
#
|
|
163
|
-
# The question a member is asked by name for is "does this engine have it, and what does it take":
|
|
164
|
-
# the whole class in answer to that ran to seventy thousand characters for Control, and the member
|
|
165
|
-
# is as often an ancestor's as the class's own, so the ancestry is walked without being asked. Each
|
|
166
|
-
# match says which class declares it. Nothing found names the members whose names contain the one
|
|
167
|
-
# asked for, since the usual miss is a spelling.
|
|
168
205
|
## Why [param wanted] has no answer here. A class_name a project script declares is not in ClassDB,
|
|
169
|
-
## and "Class not found" for one read as though the project had no such class.
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
)
|
|
182
|
-
% [wanted, str(entry.get("path", ""))]
|
|
183
|
-
)
|
|
206
|
+
## and "Class not found" for one read as though the project had no such class. [param declared] is
|
|
207
|
+
## what the project's scripts declare on disk, class to path, rather than the engine's list, which is
|
|
208
|
+
## the class cache: that lags the scripts, and a class written a moment ago was refused as declared
|
|
209
|
+
## nowhere.
|
|
210
|
+
func _not_found(wanted: String, declared: Dictionary) -> Dictionary:
|
|
211
|
+
if declared.has(wanted):
|
|
212
|
+
return _log.failure(
|
|
213
|
+
(
|
|
214
|
+
(
|
|
215
|
+
"%s is not an engine class: it is the class_name %s declares, and editor_classes reads "
|
|
216
|
+
+ "the engine's ClassDB only. script_info structure on that script, with "
|
|
217
|
+
+ "includeInherited, answers what it declares and what it inherits."
|
|
184
218
|
)
|
|
219
|
+
% [wanted, str(declared[wanted])]
|
|
185
220
|
)
|
|
186
|
-
|
|
187
|
-
|
|
221
|
+
)
|
|
222
|
+
# The usual miss is a spelling, one letter off a class the engine has or one the project declares.
|
|
223
|
+
# Ranked as one pool and kept near the best, since a project class a letter off came behind three
|
|
224
|
+
# engine classes that shared only "Texture" with it.
|
|
225
|
+
var pool: PackedStringArray = ClassDB.get_class_list()
|
|
226
|
+
pool.append_array(PackedStringArray(declared.keys()))
|
|
227
|
+
var close: PackedStringArray = GdhRead.nearest(wanted, pool)
|
|
228
|
+
var best: float = 0.0 if close.is_empty() else close[0].to_lower().similarity(wanted.to_lower())
|
|
229
|
+
var engine: Array[String] = []
|
|
230
|
+
var own: Array[String] = []
|
|
231
|
+
for name: String in close:
|
|
232
|
+
if name.to_lower().similarity(wanted.to_lower()) < best - CLOSE_TO_THE_BEST:
|
|
233
|
+
continue
|
|
234
|
+
if declared.has(name):
|
|
235
|
+
own.append(name)
|
|
236
|
+
else:
|
|
237
|
+
engine.append(name)
|
|
188
238
|
var offered: String = (
|
|
189
239
|
""
|
|
190
|
-
if
|
|
240
|
+
if engine.is_empty()
|
|
191
241
|
else (
|
|
192
242
|
" The nearest engine %s %s."
|
|
193
|
-
% ["class is" if
|
|
243
|
+
% ["class is" if engine.size() == 1 else "classes are", ", ".join(engine)]
|
|
194
244
|
)
|
|
195
245
|
)
|
|
246
|
+
if not own.is_empty():
|
|
247
|
+
offered += (
|
|
248
|
+
" The nearest %s a project script declares %s %s, which script_info structure reads."
|
|
249
|
+
% [
|
|
250
|
+
"class" if own.size() == 1 else "classes",
|
|
251
|
+
"is" if own.size() == 1 else "are",
|
|
252
|
+
", ".join(own),
|
|
253
|
+
]
|
|
254
|
+
)
|
|
196
255
|
return _log.failure(
|
|
197
256
|
(
|
|
198
257
|
(
|
|
@@ -204,17 +263,24 @@ func _not_found(wanted: String) -> Dictionary:
|
|
|
204
263
|
)
|
|
205
264
|
|
|
206
265
|
|
|
266
|
+
# One member of a class by name, wherever in its ancestry it is declared.
|
|
267
|
+
#
|
|
268
|
+
# The question a member is asked by name for is "does this engine have it, and what does it take":
|
|
269
|
+
# the whole class in answer to that ran to seventy thousand characters for Control, and the member
|
|
270
|
+
# is as often an ancestor's as the class's own, so the ancestry is walked without being asked. Each
|
|
271
|
+
# match says which class declares it. Nothing found names the members whose names contain the one
|
|
272
|
+
# asked for, since the usual miss is a spelling.
|
|
207
273
|
func _member_of(class_name_str: String, member: String) -> Dictionary:
|
|
208
274
|
var found: Array[Dictionary] = []
|
|
209
275
|
var current: String = class_name_str
|
|
210
276
|
while not current.is_empty():
|
|
211
|
-
for m: Dictionary in _methods_of(current
|
|
277
|
+
for m: Dictionary in _methods_of(current):
|
|
212
278
|
if m["name"] == member:
|
|
213
279
|
found.append({"kind": "method", "declared_in": current, "member": m})
|
|
214
|
-
for p: Dictionary in _properties_of(current
|
|
280
|
+
for p: Dictionary in _properties_of(current):
|
|
215
281
|
if p["name"] == member:
|
|
216
282
|
found.append({"kind": "property", "declared_in": current, "member": p})
|
|
217
|
-
for s: Dictionary in _signals_of(current
|
|
283
|
+
for s: Dictionary in _signals_of(current):
|
|
218
284
|
if s["name"] == member:
|
|
219
285
|
found.append({"kind": "signal", "declared_in": current, "member": s})
|
|
220
286
|
for e: String in ClassDB.class_get_enum_list(current, true):
|
|
@@ -251,15 +317,15 @@ func _members_alike(class_name_str: String, member: String) -> Array[String]:
|
|
|
251
317
|
var wanted: String = member.to_lower()
|
|
252
318
|
var current: String = class_name_str
|
|
253
319
|
while not current.is_empty():
|
|
254
|
-
for m: Dictionary in _methods_of(current
|
|
320
|
+
for m: Dictionary in _methods_of(current):
|
|
255
321
|
var name: String = m["name"]
|
|
256
322
|
if name.to_lower().contains(wanted) and not alike.has(name):
|
|
257
323
|
alike.append(name)
|
|
258
|
-
for p: Dictionary in _properties_of(current
|
|
324
|
+
for p: Dictionary in _properties_of(current):
|
|
259
325
|
var name: String = p["name"]
|
|
260
326
|
if name.to_lower().contains(wanted) and not alike.has(name):
|
|
261
327
|
alike.append(name)
|
|
262
|
-
for s: Dictionary in _signals_of(current
|
|
328
|
+
for s: Dictionary in _signals_of(current):
|
|
263
329
|
var name: String = s["name"]
|
|
264
330
|
if name.to_lower().contains(wanted) and not alike.has(name):
|
|
265
331
|
alike.append(name)
|
|
@@ -275,7 +341,7 @@ func inspect_inheritance(params: Dictionary) -> Dictionary:
|
|
|
275
341
|
_log.info("Inspecting inheritance for: " + class_name_str)
|
|
276
342
|
|
|
277
343
|
if not ClassDB.class_exists(class_name_str):
|
|
278
|
-
return _not_found(class_name_str)
|
|
344
|
+
return _not_found(class_name_str, _declared_classes(params))
|
|
279
345
|
|
|
280
346
|
var ancestors: Array[String] = []
|
|
281
347
|
var current: String = class_name_str
|
|
@@ -324,10 +390,10 @@ func inspect_inheritance(params: Dictionary) -> Dictionary:
|
|
|
324
390
|
}
|
|
325
391
|
|
|
326
392
|
|
|
327
|
-
func _methods_of(class_name_str: String
|
|
393
|
+
func _methods_of(class_name_str: String) -> Array[Dictionary]:
|
|
328
394
|
var methods: Array[Dictionary] = []
|
|
329
395
|
|
|
330
|
-
for m: Dictionary in ClassDB.class_get_method_list(class_name_str,
|
|
396
|
+
for m: Dictionary in ClassDB.class_get_method_list(class_name_str, true):
|
|
331
397
|
var args: Array[Dictionary] = []
|
|
332
398
|
var declared_args: Array = m.get("args", [])
|
|
333
399
|
for a: Dictionary in declared_args:
|
|
@@ -389,10 +455,10 @@ static func _type_name(info: Dictionary, nil_name: String) -> String:
|
|
|
389
455
|
return type_string(type)
|
|
390
456
|
|
|
391
457
|
|
|
392
|
-
func _properties_of(class_name_str: String
|
|
458
|
+
func _properties_of(class_name_str: String) -> Array[Dictionary]:
|
|
393
459
|
var properties: Array[Dictionary] = []
|
|
394
460
|
|
|
395
|
-
for p: Dictionary in ClassDB.class_get_property_list(class_name_str,
|
|
461
|
+
for p: Dictionary in ClassDB.class_get_property_list(class_name_str, true):
|
|
396
462
|
# A category, group or subgroup is an editor heading rather than a property.
|
|
397
463
|
var usage: int = GdhRead.as_int(p.get("usage", 0))
|
|
398
464
|
if usage & PROPERTY_USAGE_CATEGORY or usage & PROPERTY_USAGE_GROUP or usage & PROPERTY_USAGE_SUBGROUP:
|
|
@@ -412,10 +478,10 @@ func _properties_of(class_name_str: String, include_inherited: bool) -> Array[Di
|
|
|
412
478
|
return properties
|
|
413
479
|
|
|
414
480
|
|
|
415
|
-
func _signals_of(class_name_str: String
|
|
481
|
+
func _signals_of(class_name_str: String) -> Array[Dictionary]:
|
|
416
482
|
var signals: Array[Dictionary] = []
|
|
417
483
|
|
|
418
|
-
for s: Dictionary in ClassDB.class_get_signal_list(class_name_str,
|
|
484
|
+
for s: Dictionary in ClassDB.class_get_signal_list(class_name_str, true):
|
|
419
485
|
var sig_args: Array[Dictionary] = []
|
|
420
486
|
var declared_args: Array = s.get("args", [])
|
|
421
487
|
for a: Dictionary in declared_args:
|
|
@@ -85,6 +85,30 @@ static func engine_constants(target: Object, property: String) -> Dictionary:
|
|
|
85
85
|
return values
|
|
86
86
|
|
|
87
87
|
|
|
88
|
+
## [param word] as the number of the value the enum property [param declared] of [param target]
|
|
89
|
+
## names by it, as [true, number], or [false, why] naming the values, with [param holder] naming the
|
|
90
|
+
## target in the refusal. [false, ""] for a word that is a number, which converts as one.
|
|
91
|
+
##
|
|
92
|
+
## An enum is written by the name of a value as well as its number, since the name is what the script
|
|
93
|
+
## and the inspector show and the number is only where the value happens to sit. Tried as the script
|
|
94
|
+
## spells it, then as the hint does, then as the engine's constant.
|
|
95
|
+
static func enum_by_name(target: Object, declared: Dictionary, word: String, holder: String) -> Array:
|
|
96
|
+
var property: String = str(declared.get("name", ""))
|
|
97
|
+
var choices: Dictionary = enum_of(target, declared)
|
|
98
|
+
var chosen: Variant = enum_named(choices, word)
|
|
99
|
+
if chosen == null:
|
|
100
|
+
chosen = enum_named(enum_values(str(declared.get("hint_string", ""))), word)
|
|
101
|
+
var constants: Dictionary = {}
|
|
102
|
+
if chosen == null:
|
|
103
|
+
constants = engine_constants(target, property)
|
|
104
|
+
chosen = enum_named(constants, word)
|
|
105
|
+
if chosen != null:
|
|
106
|
+
return [true, chosen]
|
|
107
|
+
if word.strip_edges().is_valid_int():
|
|
108
|
+
return [false, ""]
|
|
109
|
+
return [false, enum_refusal(holder, property, word, choices, constants)]
|
|
110
|
+
|
|
111
|
+
|
|
88
112
|
static func enum_refusal(
|
|
89
113
|
holder: String, property: String, word: String, choices: Dictionary, constants: Dictionary = {}
|
|
90
114
|
) -> String:
|
|
@@ -191,9 +191,13 @@ func modify_gdscript(params: Dictionary) -> Dictionary:
|
|
|
191
191
|
|
|
192
192
|
# All of them checked before any is made, so a call is made whole or not at all: one skipped for a
|
|
193
193
|
# missing name used to leave the rest written and the answer saying success over an empty list.
|
|
194
|
-
var
|
|
194
|
+
var declared: Dictionary = _declared(lines)
|
|
195
|
+
var problems: Array[String] = _modification_problems(modifications, declared)
|
|
195
196
|
if not problems.is_empty():
|
|
196
|
-
|
|
197
|
+
var refusal: String = "Nothing was changed: " + "; ".join(problems)
|
|
198
|
+
if _redeclares(modifications, declared):
|
|
199
|
+
refusal += ". modify only adds, so a member the script declares already is changed in the file itself"
|
|
200
|
+
return _log.failure(refusal)
|
|
197
201
|
# Each placed line as a zero-based index that moves down whenever a later insertion lands above
|
|
198
202
|
# it, so the numbers answered are where the lines are in the file written, not where each went
|
|
199
203
|
# at the moment it was placed.
|
|
@@ -414,6 +418,15 @@ static func _modification_problems(modifications: Array, declared: Dictionary) -
|
|
|
414
418
|
return problems
|
|
415
419
|
|
|
416
420
|
|
|
421
|
+
static func _redeclares(modifications: Array, declared: Dictionary) -> bool:
|
|
422
|
+
for mod: Variant in modifications:
|
|
423
|
+
if mod is Dictionary:
|
|
424
|
+
var fields: Dictionary = mod
|
|
425
|
+
if declared.has(str(fields.get("name", ""))):
|
|
426
|
+
return true
|
|
427
|
+
return false
|
|
428
|
+
|
|
429
|
+
|
|
417
430
|
# The members [param lines] declares at the top level, by name, as {"kind", "line"} counted from
|
|
418
431
|
# one: variables, constants, signals, functions, enums and inner classes, which share one namespace.
|
|
419
432
|
static func _declared(lines: Array[String]) -> Dictionary:
|
|
@@ -420,25 +420,12 @@ func value_for(target: Object, declared: Dictionary, raw: Variant) -> Array:
|
|
|
420
420
|
var hint: int = GdhRead.as_int(declared.get("hint", PROPERTY_HINT_NONE))
|
|
421
421
|
if expected_type == TYPE_OBJECT and hint == PROPERTY_HINT_NODE_TYPE:
|
|
422
422
|
return _node_reference(target, property, raw)
|
|
423
|
-
# An enum is written by the name of a value as well as its number, since the name is what the
|
|
424
|
-
# script and the inspector show and the number is only where the value happens to sit.
|
|
425
423
|
if expected_type == TYPE_INT and hint == PROPERTY_HINT_ENUM and typeof(raw) == TYPE_STRING:
|
|
426
|
-
var
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
var constants: Dictionary = {}
|
|
432
|
-
if chosen == null:
|
|
433
|
-
constants = GdhDeclared.engine_constants(target, property)
|
|
434
|
-
chosen = GdhDeclared.enum_named(constants, str(raw))
|
|
435
|
-
if chosen != null:
|
|
436
|
-
return [true, chosen]
|
|
437
|
-
if not str(raw).strip_edges().is_valid_int():
|
|
438
|
-
var why: String = GdhDeclared.enum_refusal(
|
|
439
|
-
GdhPropertyPaths.class_called(target), property, str(raw), choices, constants
|
|
440
|
-
)
|
|
441
|
-
return [false, why]
|
|
424
|
+
var by_name: Array = GdhDeclared.enum_by_name(
|
|
425
|
+
target, declared, str(raw), GdhPropertyPaths.class_called(target)
|
|
426
|
+
)
|
|
427
|
+
if by_name[0] or not str(by_name[1]).is_empty():
|
|
428
|
+
return by_name
|
|
442
429
|
|
|
443
430
|
# A resource-valued property takes the path of one, which is how a caller names a TileSet,
|
|
444
431
|
# a material or a theme: there is no other way to hand a tool a Resource.
|