gdharness 1.2.40 → 1.2.42
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 +103 -38
- package/build/godot/addons/gdharness_editor/animation_reads.gd +14 -2
- package/build/godot/addons/gdharness_editor/bridge_client.gd +2 -1
- package/build/godot/addons/gdharness_editor/file_reads.gd +134 -10
- package/build/godot/addons/gdharness_editor/headless_check.gd +56 -15
- package/build/godot/addons/gdharness_editor/property_values.gd +1 -1
- package/build/godot/addons/gdharness_editor/reading.gd +40 -0
- package/build/godot/addons/gdharness_editor/scene_file.gd +215 -12
- package/build/godot/addons/gdharness_editor/serialisation.gd +5 -1
- package/build/godot/addons/gdharness_editor/tools/animation_tools.gd +39 -5
- package/build/godot/addons/gdharness_editor/tools/resource_tools.gd +68 -15
- package/build/godot/addons/gdharness_editor/tools/scene_kin.gd +346 -0
- package/build/godot/addons/gdharness_editor/tools/scene_kin.gd.uid +1 -0
- package/build/godot/addons/gdharness_editor/tools/scene_paths.gd +438 -0
- package/build/godot/addons/gdharness_editor/tools/scene_paths.gd.uid +1 -0
- package/build/godot/addons/gdharness_editor/tools/scene_tools.gd +102 -60
- package/build/godot/addons/gdharness_runtime/reading.gd +40 -0
- package/build/godot/addons/gdharness_runtime/runtime_autoload.gd +3 -1
- package/build/godot/addons/gdharness_runtime/runtime_changes.gd +2 -2
- package/build/godot/addons/gdharness_runtime/runtime_targets.gd +11 -6
- package/build/godot/addons/gdharness_runtime/runtime_values.gd +3 -1
- package/build/godot/addons/gdharness_runtime/serialisation.gd +5 -1
- package/build/godot/operations/animation_reads.gd +14 -2
- package/build/godot/operations/editor_tools.gd +28 -1
- package/build/godot/operations/file_reads.gd +134 -10
- package/build/godot/operations/godot_operations.gd +2 -1
- package/build/godot/operations/property_values.gd +1 -1
- package/build/godot/operations/reading.gd +40 -0
- package/build/godot/operations/serialisation.gd +5 -1
- package/build/index.js +103 -38
- package/package.json +1 -1
|
@@ -15,9 +15,10 @@ const Read = preload("reading.gd")
|
|
|
15
15
|
const Serialisation = preload("serialisation.gd")
|
|
16
16
|
|
|
17
17
|
|
|
18
|
-
## The nodes of [param scene_path] as {name, type, path, children}, with instanceOf
|
|
19
|
-
## instances another scene and every stored property when
|
|
20
|
-
## file's value or the default where it sets none.
|
|
18
|
+
## The nodes of [param scene_path] as {name, type, path, children}, with instanceOf and
|
|
19
|
+
## editableChildren on a node that instances another scene and every stored property when
|
|
20
|
+
## [param include_properties] is set, the file's value or the default where it sets none.
|
|
21
|
+
## [param depth] -1 reads every level.
|
|
21
22
|
static func scene_tree(scene_path: String, depth: int, include_properties: bool) -> Dictionary:
|
|
22
23
|
var laid: Dictionary = _laid(scene_path)
|
|
23
24
|
if not laid.get("ok", false):
|
|
@@ -41,6 +42,31 @@ static func scene_node(scene_path: String, node_path: String) -> Dictionary:
|
|
|
41
42
|
return {"ok": true, "name": found["name"], "type": found["type"], "properties": found["properties"]}
|
|
42
43
|
|
|
43
44
|
|
|
45
|
+
## [param said], the refusal for [param node_path], which [param scene_path] does not have, with
|
|
46
|
+
## where the path leaves the scene as [method Read.missing_below] says it.
|
|
47
|
+
static func missing_from(said: String, scene_path: String, node_path: String) -> String:
|
|
48
|
+
var laid: Dictionary = _laid(scene_path)
|
|
49
|
+
if not laid.get("ok", false):
|
|
50
|
+
return said
|
|
51
|
+
var nodes: Dictionary = laid["nodes"]
|
|
52
|
+
var parts: PackedStringArray = node_path.trim_prefix("./").split("/", false)
|
|
53
|
+
var at: String = "."
|
|
54
|
+
var reached: PackedStringArray = []
|
|
55
|
+
for part: String in parts:
|
|
56
|
+
var below: String = _joined(at, part)
|
|
57
|
+
if not nodes.has(below):
|
|
58
|
+
break
|
|
59
|
+
at = below
|
|
60
|
+
var _walked: bool = reached.append(part)
|
|
61
|
+
if reached.size() >= parts.size():
|
|
62
|
+
return said
|
|
63
|
+
var names: PackedStringArray = []
|
|
64
|
+
var children: Array[String] = nodes[at]["children"]
|
|
65
|
+
for child: String in children:
|
|
66
|
+
var _named: bool = names.append(str(nodes[child]["name"]))
|
|
67
|
+
return Read.missing_below(said, str(nodes["."]["name"]), reached, parts[reached.size()], names)
|
|
68
|
+
|
|
69
|
+
|
|
44
70
|
## Every node of [param scene_path] as {ok, nodes}, keyed by path, or a refusal.
|
|
45
71
|
static func _laid(scene_path: String) -> Dictionary:
|
|
46
72
|
if not FileAccess.file_exists(scene_path):
|
|
@@ -51,13 +77,11 @@ static func _laid(scene_path: String) -> Dictionary:
|
|
|
51
77
|
if loaded == null:
|
|
52
78
|
return {"ok": false, "error": "Failed to load %s as a scene" % scene_path}
|
|
53
79
|
if not loaded is PackedScene:
|
|
80
|
+
var kind: String = loaded.get_class()
|
|
81
|
+
var sort: String = ("an " if kind.left(1).to_upper() in ["A", "E", "I", "O", "U"] else "a ") + kind
|
|
54
82
|
return {
|
|
55
83
|
"ok": false,
|
|
56
|
-
"error":
|
|
57
|
-
(
|
|
58
|
-
"%s is a %s, not a scene: resource_edit read reads its properties."
|
|
59
|
-
% [scene_path, loaded.get_class()]
|
|
60
|
-
)
|
|
84
|
+
"error": "%s is %s, not a scene: resource_edit read reads its properties." % [scene_path, sort]
|
|
61
85
|
}
|
|
62
86
|
var packed: PackedScene = loaded
|
|
63
87
|
var nodes: Dictionary = {}
|
|
@@ -82,8 +106,18 @@ static func _lay(state: SceneState, prefix: String, nodes: Dictionary, within: A
|
|
|
82
106
|
var refused: String = _lay(base, prefix, nodes, within)
|
|
83
107
|
if not refused.is_empty():
|
|
84
108
|
return refused
|
|
109
|
+
var ids: Array[int] = _unique_ids(state.get_path(), state.get_node_count())
|
|
85
110
|
for index: int in state.get_node_count():
|
|
86
111
|
var path: String = _joined(prefix, str(state.get_node_path(index)))
|
|
112
|
+
var recovered: bool = false
|
|
113
|
+
if not nodes.has(path) and index > 0 and _overrides(state, index) and ids[index] >= 0:
|
|
114
|
+
# The engine finds an entry whose node was renamed or moved in the scene it comes from by
|
|
115
|
+
# the node's unique_id, and keeps the node's new name.
|
|
116
|
+
var parent: String = _joined(prefix, str(state.get_node_path(index, true)))
|
|
117
|
+
var found: String = _by_id(nodes, _owner_of(nodes, parent, prefix), ids[index])
|
|
118
|
+
recovered = not found.is_empty()
|
|
119
|
+
if recovered:
|
|
120
|
+
path = found
|
|
87
121
|
var fresh: bool = not nodes.has(path)
|
|
88
122
|
if fresh:
|
|
89
123
|
var instance: PackedScene = state.get_node_instance(index)
|
|
@@ -98,6 +132,7 @@ static func _lay(state: SceneState, prefix: String, nodes: Dictionary, within: A
|
|
|
98
132
|
if not nodes.has(path):
|
|
99
133
|
return "%s holds no nodes" % instance.resource_path
|
|
100
134
|
nodes[path]["instanceOf"] = instance.resource_path
|
|
135
|
+
nodes[path]["editableChildren"] = false
|
|
101
136
|
elif state.is_node_instance_placeholder(index):
|
|
102
137
|
nodes[path] = _made("InstancePlaceholder")
|
|
103
138
|
nodes[path]["instanceOf"] = state.get_node_instance_placeholder(index)
|
|
@@ -106,9 +141,12 @@ static func _lay(state: SceneState, prefix: String, nodes: Dictionary, within: A
|
|
|
106
141
|
continue
|
|
107
142
|
else:
|
|
108
143
|
nodes[path] = _made(str(state.get_node_type(index)))
|
|
144
|
+
if ids[index] >= 0:
|
|
145
|
+
nodes[path]["uniqueId"] = ids[index]
|
|
109
146
|
# The name is the state's even for an entry laid already, since an inherited scene names its
|
|
110
147
|
# root itself; a root instanced inside another scene is renamed by that scene once laid.
|
|
111
|
-
|
|
148
|
+
if not recovered:
|
|
149
|
+
nodes[path]["name"] = str(state.get_node_name(index))
|
|
112
150
|
if fresh:
|
|
113
151
|
# The root of what is laid here is placed by whoever instanced it.
|
|
114
152
|
if path != prefix:
|
|
@@ -123,6 +161,86 @@ static func _lay(state: SceneState, prefix: String, nodes: Dictionary, within: A
|
|
|
123
161
|
held[str(state.get_node_property_name(index, property))] = state.get_node_property_value(
|
|
124
162
|
index, property
|
|
125
163
|
)
|
|
164
|
+
# Only the scene being read and the scenes it inherits share its root, the owner whose editable
|
|
165
|
+
# instances a write here sets; a file instanced below makes its own owner's editable.
|
|
166
|
+
if prefix == ".":
|
|
167
|
+
for editable: String in _editable_in(state.get_path()):
|
|
168
|
+
var at: String = _joined(prefix, editable)
|
|
169
|
+
var marked: Dictionary = nodes.get(at, {})
|
|
170
|
+
if marked.has("editableChildren"):
|
|
171
|
+
marked["editableChildren"] = true
|
|
172
|
+
return ""
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
## The paths of the instances [param scene_path] gives editable children, from its root. SceneState
|
|
176
|
+
## does not give them to a script, so they are read off the scene's bundle.
|
|
177
|
+
static func _editable_in(scene_path: String) -> PackedStringArray:
|
|
178
|
+
var found: PackedStringArray = []
|
|
179
|
+
var loaded: Resource = ResourceLoader.load(scene_path, "", ResourceLoader.CACHE_MODE_IGNORE)
|
|
180
|
+
if not loaded is PackedScene:
|
|
181
|
+
return found
|
|
182
|
+
var given: Variant = loaded.get("_bundled")
|
|
183
|
+
if not given is Dictionary:
|
|
184
|
+
return found
|
|
185
|
+
var bundled: Dictionary = given
|
|
186
|
+
var listed: Variant = bundled.get("editable_instances", [])
|
|
187
|
+
if listed is Array:
|
|
188
|
+
for one: Variant in listed:
|
|
189
|
+
var _kept: bool = found.append(str(one))
|
|
190
|
+
return found
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
## The unique_id of each node entry in the text scene at [param scene_path], in the order its state
|
|
194
|
+
## holds them, -1 where an entry has none. All -1 for a binary scene, and for a file whose entries do
|
|
195
|
+
## not number [param count], since SceneState does not give the ids to a script.
|
|
196
|
+
static func _unique_ids(scene_path: String, count: int) -> Array[int]:
|
|
197
|
+
var ids: Array[int] = []
|
|
198
|
+
var _sized: int = ids.resize(count)
|
|
199
|
+
ids.fill(-1)
|
|
200
|
+
if not scene_path.ends_with(".tscn") or not FileAccess.file_exists(scene_path):
|
|
201
|
+
return ids
|
|
202
|
+
var read: Array[int] = []
|
|
203
|
+
var pattern: RegEx = RegEx.create_from_string(" unique_id=(\\d+)")
|
|
204
|
+
for line: String in FileAccess.get_file_as_string(scene_path).split("\n"):
|
|
205
|
+
if line.begins_with("[node "):
|
|
206
|
+
var found: RegExMatch = pattern.search(line)
|
|
207
|
+
read.append(int(found.get_string(1)) if found != null else -1)
|
|
208
|
+
return read if read.size() == count else ids
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
## Whether entry [param index] of [param state] only sets values on a node another file makes.
|
|
212
|
+
static func _overrides(state: SceneState, index: int) -> bool:
|
|
213
|
+
return (
|
|
214
|
+
state.get_node_instance(index) == null
|
|
215
|
+
and not state.is_node_instance_placeholder(index)
|
|
216
|
+
and str(state.get_node_type(index)).is_empty()
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
## The node whose file owns what sits under [param parent]: the nearest instance at or above it, or
|
|
221
|
+
## [param prefix], the root of what is being laid, as the engine searches.
|
|
222
|
+
static func _owner_of(nodes: Dictionary, parent: String, prefix: String) -> String:
|
|
223
|
+
var at: String = parent
|
|
224
|
+
while at != prefix and at != "." and nodes.has(at):
|
|
225
|
+
var entry: Dictionary = nodes[at]
|
|
226
|
+
if entry.has("instanceOf"):
|
|
227
|
+
break
|
|
228
|
+
at = "." if not at.contains("/") else at.get_base_dir()
|
|
229
|
+
return at if nodes.has(at) else prefix
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
## The path of the node [param owner] owns whose unique_id is [param id], or "": one under an instance
|
|
233
|
+
## nested below [param owner] belongs to that instance's file, whose ids are numbered apart.
|
|
234
|
+
static func _by_id(nodes: Dictionary, owner: String, id: int) -> String:
|
|
235
|
+
for path: String in nodes:
|
|
236
|
+
if path == owner or not (owner == "." or path.begins_with(owner + "/")):
|
|
237
|
+
continue
|
|
238
|
+
var entry: Dictionary = nodes[path]
|
|
239
|
+
if Read.as_int(entry.get("uniqueId", -1), -1) != id:
|
|
240
|
+
continue
|
|
241
|
+
var above: String = "." if not path.contains("/") else path.get_base_dir()
|
|
242
|
+
if _owner_of(nodes, above, owner) == owner:
|
|
243
|
+
return path
|
|
126
244
|
return ""
|
|
127
245
|
|
|
128
246
|
|
|
@@ -162,12 +280,18 @@ static func _entry(
|
|
|
162
280
|
# The root's file is the scene being read, so only another node's names a second scene.
|
|
163
281
|
if path != "." and node.has("instanceOf"):
|
|
164
282
|
data["instanceOf"] = node["instanceOf"]
|
|
283
|
+
if node.has("editableChildren"):
|
|
284
|
+
data["editableChildren"] = node["editableChildren"]
|
|
165
285
|
if include_properties:
|
|
166
286
|
var given: Dictionary = node["properties"]
|
|
167
287
|
data["properties"] = _stored(str(node["type"]), given)
|
|
288
|
+
var below: Array[String] = node["children"]
|
|
289
|
+
# Counted rather than left as an empty list, which reads as a node with no children.
|
|
168
290
|
if depth >= 0 and current_depth >= depth:
|
|
291
|
+
if not below.is_empty():
|
|
292
|
+
data["childrenNotListed"] = below.size()
|
|
169
293
|
return data
|
|
170
|
-
for child: String in
|
|
294
|
+
for child: String in below:
|
|
171
295
|
children.append(_entry(nodes, child, include_properties, depth, current_depth + 1))
|
|
172
296
|
return data
|
|
173
297
|
|
|
@@ -147,12 +147,8 @@ static func reached(object: Object, property: String) -> Dictionary:
|
|
|
147
147
|
## Whether [param object] holds, at any depth, an embedded resource with a script that is not a tool.
|
|
148
148
|
static func holds_scripted_embedded(object: Object, seen: Dictionary) -> bool:
|
|
149
149
|
for value: Variant in stored(object).values():
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
if not item is Resource or seen.has(item):
|
|
153
|
-
continue
|
|
154
|
-
var resource: Resource = item
|
|
155
|
-
if not resource.is_built_in():
|
|
150
|
+
for resource: Resource in resources_in(value):
|
|
151
|
+
if seen.has(resource) or not resource.is_built_in():
|
|
156
152
|
continue
|
|
157
153
|
seen[resource] = true
|
|
158
154
|
var script: Script = resource.get_script()
|
|
@@ -163,6 +159,25 @@ static func holds_scripted_embedded(object: Object, seen: Dictionary) -> bool:
|
|
|
163
159
|
return false
|
|
164
160
|
|
|
165
161
|
|
|
162
|
+
## The resources [param value] is or holds through arrays and dictionaries at any depth, keys
|
|
163
|
+
## included: a file embeds a resource kept as a map's value, the way an AnimationLibrary keeps its
|
|
164
|
+
## animations, as it embeds one held directly.
|
|
165
|
+
static func resources_in(value: Variant) -> Array[Resource]:
|
|
166
|
+
var found: Array[Resource] = []
|
|
167
|
+
if value is Resource:
|
|
168
|
+
found.append(value)
|
|
169
|
+
elif value is Array:
|
|
170
|
+
var items: Array = value
|
|
171
|
+
for item: Variant in items:
|
|
172
|
+
found.append_array(resources_in(item))
|
|
173
|
+
elif value is Dictionary:
|
|
174
|
+
var entries_held: Dictionary = value
|
|
175
|
+
for key: Variant in entries_held:
|
|
176
|
+
found.append_array(resources_in(key))
|
|
177
|
+
found.append_array(resources_in(entries_held[key]))
|
|
178
|
+
return found
|
|
179
|
+
|
|
180
|
+
|
|
166
181
|
## Puts the file at [param file_path] back as [param previous], or removes it when there was none,
|
|
167
182
|
## answering "" or a sentence saying that failed too.
|
|
168
183
|
static func put_back(file_path: String, previous: Variant) -> String:
|
|
@@ -233,7 +248,8 @@ static func entries(lines: PackedStringArray) -> Dictionary:
|
|
|
233
248
|
|
|
234
249
|
|
|
235
250
|
## Each property the main resource of [param after] sets differently from [param before], leaving
|
|
236
|
-
## out [param asked], as [name, whether before sets it, the text before gives it
|
|
251
|
+
## out [param asked], as [name, whether before sets it, the text before gives it, the text after
|
|
252
|
+
## gives it or null].
|
|
237
253
|
static func main_changes(before: String, after: String, asked: Dictionary) -> Array[Array]:
|
|
238
254
|
var was: Dictionary = _main_entries(before)
|
|
239
255
|
var now: Dictionary = _main_entries(after)
|
|
@@ -244,10 +260,25 @@ static func main_changes(before: String, after: String, asked: Dictionary) -> Ar
|
|
|
244
260
|
var name: String = str(key)
|
|
245
261
|
if asked.has(name) or was.get(name) == now.get(name):
|
|
246
262
|
continue
|
|
247
|
-
changed.append([name, was.has(name), str(was.get(name, ""))])
|
|
263
|
+
changed.append([name, was.has(name), str(was.get(name, "")), now.get(name)])
|
|
248
264
|
return changed
|
|
249
265
|
|
|
250
266
|
|
|
267
|
+
## Whether [param was] and [param now], the texts two files give a property or null where one sets
|
|
268
|
+
## nothing, read as the same value, nothing standing for [param default]. A file written by hand
|
|
269
|
+
## spells a value its own way, 1 for 1.0, and may set a default, which the saver leaves out.
|
|
270
|
+
static func reads_the_same(was: Variant, now: Variant, default: Variant) -> bool:
|
|
271
|
+
var values: Array = []
|
|
272
|
+
for text: Variant in [was, now]:
|
|
273
|
+
if text == null:
|
|
274
|
+
values.append(default)
|
|
275
|
+
elif "Resource(" in str(text):
|
|
276
|
+
return false
|
|
277
|
+
else:
|
|
278
|
+
values.append(str_to_var(str(text)))
|
|
279
|
+
return PropertyValues.same(values[0], values[1])
|
|
280
|
+
|
|
281
|
+
|
|
251
282
|
static func _main_entries(text: String) -> Dictionary:
|
|
252
283
|
var main: Dictionary = sections(text).get("resource", {})
|
|
253
284
|
return entries_of(main)
|
|
@@ -283,7 +314,8 @@ static func scripted_sections(text: String) -> Dictionary:
|
|
|
283
314
|
|
|
284
315
|
|
|
285
316
|
## Each property an embedded resource of [param after] sets differently from [param before], as
|
|
286
|
-
## [its id, name, whether before sets it, the text before gives it
|
|
317
|
+
## [its id, name, whether before sets it, the text before gives it, the text after gives it or
|
|
318
|
+
## null]. Only those with a script that
|
|
287
319
|
## is not a tool are compared, since only a script runs differently here, and those in
|
|
288
320
|
## [param reached_ids], which the tool changed, are left out.
|
|
289
321
|
static func embedded_changes(before: String, after: String, reached_ids: Dictionary) -> Array[Array]:
|
|
@@ -306,7 +338,7 @@ static func embedded_changes(before: String, after: String, reached_ids: Diction
|
|
|
306
338
|
for entry: Variant in all:
|
|
307
339
|
var name: String = str(entry)
|
|
308
340
|
if was.get(name) != now.get(name):
|
|
309
|
-
changed.append([id, name, was.has(name), str(was.get(name, ""))])
|
|
341
|
+
changed.append([id, name, was.has(name), str(was.get(name, "")), now.get(name)])
|
|
310
342
|
return changed
|
|
311
343
|
|
|
312
344
|
|
|
@@ -330,11 +362,15 @@ static func settle_saved(
|
|
|
330
362
|
var embedded: Array[Array] = []
|
|
331
363
|
for found: Array in embedded_changes(before, after, reached_ids):
|
|
332
364
|
var holder: Resource = by_id.get(found[0])
|
|
365
|
+
if holder != null and _unchanged(holder, found.slice(1)):
|
|
366
|
+
continue
|
|
333
367
|
if found[2] or holder == null or not at_default(holder, str(found[1])):
|
|
334
368
|
embedded.append(found)
|
|
335
369
|
var top: Array[Array] = []
|
|
336
370
|
if main != null:
|
|
337
371
|
for found: Array in main_changes(before, after, asked):
|
|
372
|
+
if _unchanged(main, found):
|
|
373
|
+
continue
|
|
338
374
|
if found[1] or not at_default(main, str(found[0])):
|
|
339
375
|
top.append(found)
|
|
340
376
|
if embedded.is_empty() and top.is_empty():
|
|
@@ -367,14 +403,19 @@ static func settle_saved(
|
|
|
367
403
|
return ""
|
|
368
404
|
|
|
369
405
|
|
|
406
|
+
## Whether the saved text gives a property of [param object] what the file did, spelt another way.
|
|
407
|
+
## [param change] is [name, whether the file set it, the text it gave, the text after or null].
|
|
408
|
+
static func _unchanged(object: Object, change: Array) -> bool:
|
|
409
|
+
var name: String = change[0]
|
|
410
|
+
var was_set: bool = change[1]
|
|
411
|
+
var was: Variant = change[2] if was_set else null
|
|
412
|
+
return reads_the_same(was, change[3], default_of(object, name))
|
|
413
|
+
|
|
414
|
+
|
|
370
415
|
## Every embedded resource [param values] reach, at any depth, by its id in the file.
|
|
371
416
|
static func embedded_in(values: Array, found: Dictionary) -> Dictionary:
|
|
372
417
|
for value: Variant in values:
|
|
373
|
-
|
|
374
|
-
for item: Variant in items:
|
|
375
|
-
if not item is Resource:
|
|
376
|
-
continue
|
|
377
|
-
var resource: Resource = item
|
|
418
|
+
for resource: Resource in resources_in(value):
|
|
378
419
|
var id: String = resource.get_scene_unique_id()
|
|
379
420
|
if not resource.is_built_in() or id.is_empty() or found.has(id):
|
|
380
421
|
continue
|
|
@@ -590,7 +590,7 @@ static func _listed(type: int) -> bool:
|
|
|
590
590
|
|
|
591
591
|
## [param value] as a caller would write it, for a refusal to quote.
|
|
592
592
|
func shown(value: Variant) -> String:
|
|
593
|
-
return JSON.stringify(_values.serialize_value(value))
|
|
593
|
+
return JSON.stringify(_values.serialize_value(value), "", false)
|
|
594
594
|
|
|
595
595
|
|
|
596
596
|
## A dictionary carrying its own type name, as the serialiser writes it.
|
|
@@ -22,6 +22,9 @@ extends RefCounted
|
|
|
22
22
|
# number was expected. Absent keys never reach here: the callers pass their own default to
|
|
23
23
|
# `Dictionary.get`, so what arrives is that default.
|
|
24
24
|
|
|
25
|
+
## How many of a node's children a refusal for a path missing below it names.
|
|
26
|
+
const NAMED_CHILDREN: int = 12
|
|
27
|
+
|
|
25
28
|
|
|
26
29
|
static func as_int(value: Variant, fallback: int = 0) -> int:
|
|
27
30
|
if value is int:
|
|
@@ -99,3 +102,40 @@ static func json_or_null(text: String) -> Variant:
|
|
|
99
102
|
if json.parse(text) != OK:
|
|
100
103
|
return null
|
|
101
104
|
return json.data
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
## [param said], the refusal for [param node_path] found nowhere in a scene whose root is called
|
|
108
|
+
## [param root_name], with how paths are read when the path starts with that name and the root has
|
|
109
|
+
## no child called that. Paths go from the root, so its own name there names a child of it, and a
|
|
110
|
+
## caller who read the root as Main in a tree wrote Main/Body and was told only that nothing is there.
|
|
111
|
+
static func from_the_root(said: String, node_path: String, root_name: String) -> String:
|
|
112
|
+
var first: String = node_path.get_slice("/", 0)
|
|
113
|
+
if root_name.is_empty() or first != root_name:
|
|
114
|
+
return said
|
|
115
|
+
var rest: String = node_path.substr(first.length() + 1)
|
|
116
|
+
var how: String = 'Paths go from the root, %s, whose own path is "."' % root_name
|
|
117
|
+
if not rest.is_empty():
|
|
118
|
+
how += ', so this one is "%s"' % rest
|
|
119
|
+
return "%s%s %s." % [said, "" if said.ends_with(".") else ".", how]
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
## [param said], the refusal for a path a scene does not have, with where the path leaves it: the
|
|
123
|
+
## last node of the path it has, [param reached] from the root, called [param root_name], when that
|
|
124
|
+
## is empty; the name below it that is missing; and [param children], the names that node has, as
|
|
125
|
+
## after a node was moved or renamed. Names up to [constant NAMED_CHILDREN] of them.
|
|
126
|
+
static func missing_below(
|
|
127
|
+
said: String, root_name: String, reached: PackedStringArray, missing: String, children: PackedStringArray
|
|
128
|
+
) -> String:
|
|
129
|
+
var shown: String = ", ".join(children.slice(0, NAMED_CHILDREN))
|
|
130
|
+
if children.size() > NAMED_CHILDREN:
|
|
131
|
+
shown += " and %d more" % (children.size() - NAMED_CHILDREN)
|
|
132
|
+
return (
|
|
133
|
+
"%s%s %s has no %s; %s."
|
|
134
|
+
% [
|
|
135
|
+
said,
|
|
136
|
+
"" if said.ends_with(".") else ".",
|
|
137
|
+
"The root, %s," % root_name if reached.is_empty() else "/".join(reached),
|
|
138
|
+
missing,
|
|
139
|
+
"its children are " + shown if not children.is_empty() else "it has no children",
|
|
140
|
+
]
|
|
141
|
+
)
|