gdharness 1.2.42 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/cli.js +571 -363
- package/build/godot/addons/gdharness_editor/animation_reads.gd +7 -19
- package/build/godot/addons/gdharness_editor/bridge_client.gd +9 -9
- package/build/godot/addons/gdharness_editor/file_reads.gd +60 -25
- package/build/godot/addons/gdharness_editor/headless_check.gd +7 -7
- package/build/godot/addons/gdharness_editor/plugin.gd +6 -6
- package/build/godot/addons/gdharness_editor/property_values.gd +124 -62
- package/build/godot/addons/gdharness_editor/reading.gd +28 -0
- package/build/godot/addons/gdharness_editor/scene_file.gd +28 -23
- package/build/godot/addons/gdharness_editor/serialisation.gd +45 -94
- package/build/godot/addons/gdharness_editor/tool_executor.gd +24 -24
- package/build/godot/addons/gdharness_editor/tools/animation_tools.gd +52 -41
- package/build/godot/addons/gdharness_editor/tools/filesystem_tools.gd +4 -4
- package/build/godot/addons/gdharness_editor/tools/play_tools.gd +2 -2
- package/build/godot/addons/gdharness_editor/tools/profile_tools.gd +14 -14
- package/build/godot/addons/gdharness_editor/tools/resource_tools.gd +57 -50
- package/build/godot/addons/gdharness_editor/tools/scene_kin.gd +17 -17
- package/build/godot/addons/gdharness_editor/tools/scene_paths.gd +14 -12
- package/build/godot/addons/gdharness_editor/tools/scene_placing.gd +7 -7
- package/build/godot/addons/gdharness_editor/tools/scene_tools.gd +82 -78
- package/build/godot/addons/gdharness_editor/tools/settings_tools.gd +4 -4
- package/build/godot/addons/gdharness_editor/value_shapes.gd +111 -0
- package/build/godot/addons/gdharness_editor/value_shapes.gd.uid +1 -0
- package/build/godot/addons/gdharness_runtime/reading.gd +28 -0
- package/build/godot/addons/gdharness_runtime/runtime_accessibility.gd +18 -18
- package/build/godot/addons/gdharness_runtime/runtime_autoload.gd +32 -32
- package/build/godot/addons/gdharness_runtime/runtime_capture.gd +50 -18
- package/build/godot/addons/gdharness_runtime/runtime_changes.gd +86 -80
- package/build/godot/addons/gdharness_runtime/runtime_input.gd +65 -69
- package/build/godot/addons/gdharness_runtime/runtime_items.gd +6 -6
- package/build/godot/addons/gdharness_runtime/runtime_menus.gd +8 -8
- package/build/godot/addons/gdharness_runtime/runtime_mouse.gd +13 -21
- package/build/godot/addons/gdharness_runtime/runtime_pad.gd +10 -10
- package/build/godot/addons/gdharness_runtime/runtime_paths.gd +7 -7
- package/build/godot/addons/gdharness_runtime/runtime_queries.gd +41 -41
- package/build/godot/addons/gdharness_runtime/runtime_says.gd +3 -3
- package/build/godot/addons/gdharness_runtime/runtime_targets.gd +33 -33
- package/build/godot/addons/gdharness_runtime/runtime_typing.gd +20 -20
- package/build/godot/addons/gdharness_runtime/runtime_values.gd +25 -24
- package/build/godot/addons/gdharness_runtime/runtime_waits.gd +56 -50
- package/build/godot/addons/gdharness_runtime/serialisation.gd +45 -94
- package/build/godot/addons/gdharness_runtime/value_shapes.gd +111 -0
- package/build/godot/addons/gdharness_runtime/value_shapes.gd.uid +1 -0
- package/build/godot/operations/animation_reads.gd +7 -19
- package/build/godot/operations/audio_buses.gd +10 -10
- package/build/godot/operations/class_cache.gd +16 -16
- package/build/godot/operations/classdb_queries.gd +12 -12
- package/build/godot/operations/dependencies.gd +33 -29
- package/build/godot/operations/editor_tools.gd +12 -12
- package/build/godot/operations/export_presets.gd +10 -8
- package/build/godot/operations/file_reads.gd +60 -25
- package/build/godot/operations/gdscript_analysis.gd +20 -20
- package/build/godot/operations/gdscript_authoring.gd +17 -15
- package/build/godot/operations/godot_operations.gd +64 -63
- package/build/godot/operations/import_pipeline.gd +20 -20
- package/build/godot/operations/input_actions.gd +17 -17
- package/build/godot/operations/logger.gd +1 -1
- package/build/godot/operations/plugins.gd +8 -8
- package/build/godot/operations/project_config.gd +24 -17
- package/build/godot/operations/project_diagnostics.gd +9 -9
- package/build/godot/operations/property_values.gd +124 -62
- package/build/godot/operations/reading.gd +28 -0
- package/build/godot/operations/rename_checks.gd +3 -3
- package/build/godot/operations/resource_files.gd +5 -5
- package/build/godot/operations/script_check.gd +7 -7
- package/build/godot/operations/serialisation.gd +45 -94
- package/build/godot/operations/value_shapes.gd +111 -0
- package/build/keeper.js +10 -4
- package/package.json +1 -2
- package/build/index.js +0 -45101
|
@@ -13,17 +13,17 @@ extends RefCounted
|
|
|
13
13
|
## reaches, and every script loaded after it that depends on it compiles in silence, reload() and all.
|
|
14
14
|
## So a run ends at the first script that raised anything, and the rest go to a fresh engine.
|
|
15
15
|
|
|
16
|
-
const
|
|
17
|
-
const
|
|
16
|
+
const GdhRead = preload("reading.gd")
|
|
17
|
+
const GdhLog = preload("logger.gd")
|
|
18
18
|
|
|
19
19
|
## Where the engine raises a script's compile errors from.
|
|
20
20
|
const RELOAD: String = "GDScript::reload"
|
|
21
21
|
|
|
22
|
-
var _log:
|
|
22
|
+
var _log: GdhLog
|
|
23
23
|
|
|
24
24
|
|
|
25
25
|
## Every compile error the engine raises while it is attached, in order.
|
|
26
|
-
class
|
|
26
|
+
class GdhCaught:
|
|
27
27
|
extends Logger
|
|
28
28
|
|
|
29
29
|
var raised: Array[Dictionary] = []
|
|
@@ -50,7 +50,7 @@ class Caught:
|
|
|
50
50
|
)
|
|
51
51
|
|
|
52
52
|
|
|
53
|
-
func _init(p_log:
|
|
53
|
+
func _init(p_log: GdhLog) -> void:
|
|
54
54
|
_log = p_log
|
|
55
55
|
|
|
56
56
|
|
|
@@ -62,7 +62,7 @@ func _init(p_log: Log) -> void:
|
|
|
62
62
|
## resource cache, so a script loaded for an earlier one is compiled again for its own entry.
|
|
63
63
|
func check_scripts(params: Dictionary) -> Dictionary:
|
|
64
64
|
var asked: Array = params.get("paths", [])
|
|
65
|
-
var caught:
|
|
65
|
+
var caught: GdhCaught = GdhCaught.new()
|
|
66
66
|
OS.add_logger(caught)
|
|
67
67
|
var checked: Array[String] = []
|
|
68
68
|
var failed: Array[String] = []
|
|
@@ -80,7 +80,7 @@ func check_scripts(params: Dictionary) -> Dictionary:
|
|
|
80
80
|
if not at in others:
|
|
81
81
|
others.append(at)
|
|
82
82
|
continue
|
|
83
|
-
var found: Dictionary = {"line":
|
|
83
|
+
var found: Dictionary = {"line": GdhRead.as_int(error["line"]), "message": str(error["message"])}
|
|
84
84
|
if not found in errors:
|
|
85
85
|
errors.append(found)
|
|
86
86
|
if loaded == null and errors.is_empty():
|
|
@@ -8,7 +8,8 @@ extends RefCounted
|
|
|
8
8
|
## before: the same Vector2 came back three ways depending on which tool was asked, and a caller
|
|
9
9
|
## reading a property off the editor could not feed it back to the game.
|
|
10
10
|
|
|
11
|
-
const
|
|
11
|
+
const GdhRead = preload("reading.gd")
|
|
12
|
+
const GdhShapes = preload("value_shapes.gd")
|
|
12
13
|
|
|
13
14
|
# What the engine carries from one to another without complaint, which is what a value written to a
|
|
14
15
|
# property or handed to a parameter may arrive as. Numbers include bool because the engine counts it
|
|
@@ -120,35 +121,6 @@ const BUILDERS: Dictionary = {
|
|
|
120
121
|
"Dictionary": "_build_dictionary",
|
|
121
122
|
}
|
|
122
123
|
|
|
123
|
-
# What each tag has to carry before it is built. A tag naming a type whose keys are not there is a
|
|
124
|
-
# tag somebody wrote by mistake or wrote for a different shape, and building one out of defaults
|
|
125
|
-
# answers with a Transform2D of zeros where the caller meant something else entirely. Unbuilt, it
|
|
126
|
-
# comes back as it arrived, and whoever asked can read it against the type they were expecting.
|
|
127
|
-
const REQUIRED: Dictionary = {
|
|
128
|
-
"Vector2": [["x", "y"]],
|
|
129
|
-
"Vector2i": [["x", "y"]],
|
|
130
|
-
"Vector3": [["x", "y", "z"]],
|
|
131
|
-
"Vector3i": [["x", "y", "z"]],
|
|
132
|
-
"Vector4": [["x", "y", "z", "w"]],
|
|
133
|
-
"Vector4i": [["x", "y", "z", "w"]],
|
|
134
|
-
"Rect2": [["position", "size"], ["x", "y", "width", "height"]],
|
|
135
|
-
"Rect2i": [["position", "size"], ["x", "y", "width", "height"]],
|
|
136
|
-
"Plane": [["normal", "d"]],
|
|
137
|
-
"Quaternion": [["x", "y", "z", "w"]],
|
|
138
|
-
"AABB": [["position", "size"]],
|
|
139
|
-
"Basis": [["x", "y", "z"]],
|
|
140
|
-
"Transform2D": [["x", "y", "origin"]],
|
|
141
|
-
"Transform3D": [["basis", "origin"]],
|
|
142
|
-
"Projection": [["x", "y", "z", "w"]],
|
|
143
|
-
"Color": [["r", "g", "b"]],
|
|
144
|
-
"NodePath": [["path"]],
|
|
145
|
-
"PackedByteArray": [["base64"]],
|
|
146
|
-
"float": [["value"]],
|
|
147
|
-
"int": [["value"]],
|
|
148
|
-
"RID": [["id"]],
|
|
149
|
-
"Dictionary": [["entries"]],
|
|
150
|
-
}
|
|
151
|
-
|
|
152
124
|
|
|
153
125
|
# Converts a Godot value into something JSON can carry. A type with no entry in the table
|
|
154
126
|
# passes through as itself, which is what the JSON-native ones want.
|
|
@@ -246,8 +218,8 @@ func deserialize_value(value: Variant) -> Variant:
|
|
|
246
218
|
# under the target project's warning levels, so one such project lost every headless call.
|
|
247
219
|
var tag: String = str(fields["_type"])
|
|
248
220
|
var builder: String = BUILDERS.get(tag, "")
|
|
249
|
-
var
|
|
250
|
-
if builder.is_empty() or not
|
|
221
|
+
var spare: Array = GdhShapes.spare(fields, tag)
|
|
222
|
+
if builder.is_empty() or not spare.any(func(left: Array) -> bool: return left.is_empty()):
|
|
251
223
|
return fields
|
|
252
224
|
if tag == "float" and str(fields["value"]) not in ["inf", "-inf", "nan"]:
|
|
253
225
|
return fields
|
|
@@ -430,6 +402,11 @@ func fitted(value: Variant, type: int) -> Variant:
|
|
|
430
402
|
if rebuilt is Array and LIST_TYPES.has(type):
|
|
431
403
|
return type_convert(rebuilt, type)
|
|
432
404
|
|
|
405
|
+
# The inspector and every colour picker give a colour as its html code. Anything else stays the
|
|
406
|
+
# text it is, for the refusal to name.
|
|
407
|
+
if type == TYPE_COLOR and rebuilt is String and Color.html_is_valid(str(rebuilt)):
|
|
408
|
+
return Color.html(str(rebuilt))
|
|
409
|
+
|
|
433
410
|
var simple: Array[int] = [TYPE_BOOL, TYPE_INT, TYPE_FLOAT, TYPE_STRING]
|
|
434
411
|
if not simple.has(type) or not simple.has(typeof(rebuilt)):
|
|
435
412
|
return rebuilt
|
|
@@ -441,7 +418,7 @@ func fitted(value: Variant, type: int) -> Variant:
|
|
|
441
418
|
|
|
442
419
|
if rebuilt is String and type != TYPE_STRING:
|
|
443
420
|
var text: String = rebuilt
|
|
444
|
-
var parsed: Variant =
|
|
421
|
+
var parsed: Variant = GdhRead.json_or_null(text)
|
|
445
422
|
# A string that does not read as the type wanted is handed back as the string it is, so
|
|
446
423
|
# whoever asked can refuse it. type_convert answers 0 for "not an index" and true for any
|
|
447
424
|
# text at all, and a caller who sent a word got back a number they never sent: get_child
|
|
@@ -461,7 +438,7 @@ func fitted(value: Variant, type: int) -> Variant:
|
|
|
461
438
|
static func inexact(given: Variant, type: int) -> String:
|
|
462
439
|
if type != TYPE_INT or (typeof(given) != TYPE_FLOAT and typeof(given) != TYPE_INT):
|
|
463
440
|
return ""
|
|
464
|
-
var number: float =
|
|
441
|
+
var number: float = GdhRead.as_float(given)
|
|
465
442
|
if not is_finite(number) or absf(number) < EXACT:
|
|
466
443
|
return ""
|
|
467
444
|
return (
|
|
@@ -474,34 +451,6 @@ static func inexact(given: Variant, type: int) -> String:
|
|
|
474
451
|
)
|
|
475
452
|
|
|
476
453
|
|
|
477
|
-
## How a value of [param type] is written to be read as one, for a refusal to name, or empty for a
|
|
478
|
-
## type that is not built from fields. A refusal naming only the two types left a caller to find
|
|
479
|
-
## the shape in the description of another op.
|
|
480
|
-
static func written_as(type: int) -> String:
|
|
481
|
-
var tag: String = type_string(type)
|
|
482
|
-
var alternatives: Array = REQUIRED.get(tag, [])
|
|
483
|
-
if alternatives.is_empty() or tag in ["float", "int", "Dictionary"]:
|
|
484
|
-
return ""
|
|
485
|
-
var keys: Array = alternatives[0]
|
|
486
|
-
var parts: Array[String] = []
|
|
487
|
-
for key: Variant in keys:
|
|
488
|
-
parts.append('"%s": ...' % key)
|
|
489
|
-
return "{%s}" % ", ".join(parts)
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
## Whether [param fields] carries every key of any one of [param alternatives].
|
|
493
|
-
##
|
|
494
|
-
## More than one spelling because a rectangle has two: the two corners it is answered with, and the
|
|
495
|
-
## four numbers the editor's own tools answered with before the serialisers were made one, which a
|
|
496
|
-
## caller may have kept.
|
|
497
|
-
func _carries(fields: Dictionary, alternatives: Array) -> bool:
|
|
498
|
-
for alternative: Variant in alternatives:
|
|
499
|
-
var keys: Array = alternative
|
|
500
|
-
if keys.all(func(key: Variant) -> bool: return fields.has(key)):
|
|
501
|
-
return true
|
|
502
|
-
return false
|
|
503
|
-
|
|
504
|
-
|
|
505
454
|
func _serialize_nil(_value: Variant) -> Variant:
|
|
506
455
|
return null
|
|
507
456
|
|
|
@@ -769,42 +718,44 @@ func _describe_object(value: Object) -> Dictionary:
|
|
|
769
718
|
|
|
770
719
|
|
|
771
720
|
func _build_vector2(fields: Dictionary) -> Vector2:
|
|
772
|
-
return Vector2(
|
|
721
|
+
return Vector2(GdhRead.as_float(fields.get("x", 0)), GdhRead.as_float(fields.get("y", 0)))
|
|
773
722
|
|
|
774
723
|
|
|
775
724
|
func _build_vector2i(fields: Dictionary) -> Vector2i:
|
|
776
|
-
return Vector2i(
|
|
725
|
+
return Vector2i(GdhRead.as_int(fields.get("x", 0)), GdhRead.as_int(fields.get("y", 0)))
|
|
777
726
|
|
|
778
727
|
|
|
779
728
|
func _build_vector3(fields: Dictionary) -> Vector3:
|
|
780
729
|
return Vector3(
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
730
|
+
GdhRead.as_float(fields.get("x", 0)),
|
|
731
|
+
GdhRead.as_float(fields.get("y", 0)),
|
|
732
|
+
GdhRead.as_float(fields.get("z", 0))
|
|
784
733
|
)
|
|
785
734
|
|
|
786
735
|
|
|
787
736
|
func _build_vector3i(fields: Dictionary) -> Vector3i:
|
|
788
737
|
return Vector3i(
|
|
789
|
-
|
|
738
|
+
GdhRead.as_int(fields.get("x", 0)),
|
|
739
|
+
GdhRead.as_int(fields.get("y", 0)),
|
|
740
|
+
GdhRead.as_int(fields.get("z", 0))
|
|
790
741
|
)
|
|
791
742
|
|
|
792
743
|
|
|
793
744
|
func _build_vector4(fields: Dictionary) -> Vector4:
|
|
794
745
|
return Vector4(
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
746
|
+
GdhRead.as_float(fields.get("x", 0)),
|
|
747
|
+
GdhRead.as_float(fields.get("y", 0)),
|
|
748
|
+
GdhRead.as_float(fields.get("z", 0)),
|
|
749
|
+
GdhRead.as_float(fields.get("w", 0))
|
|
799
750
|
)
|
|
800
751
|
|
|
801
752
|
|
|
802
753
|
func _build_vector4i(fields: Dictionary) -> Vector4i:
|
|
803
754
|
return Vector4i(
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
755
|
+
GdhRead.as_int(fields.get("x", 0)),
|
|
756
|
+
GdhRead.as_int(fields.get("y", 0)),
|
|
757
|
+
GdhRead.as_int(fields.get("z", 0)),
|
|
758
|
+
GdhRead.as_int(fields.get("w", 0))
|
|
808
759
|
)
|
|
809
760
|
|
|
810
761
|
|
|
@@ -813,10 +764,10 @@ func _build_vector4i(fields: Dictionary) -> Vector4i:
|
|
|
813
764
|
func _build_rect2(fields: Dictionary) -> Rect2:
|
|
814
765
|
if not fields.has("position"):
|
|
815
766
|
return Rect2(
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
767
|
+
GdhRead.as_float(fields.get("x", 0)),
|
|
768
|
+
GdhRead.as_float(fields.get("y", 0)),
|
|
769
|
+
GdhRead.as_float(fields.get("width", 0)),
|
|
770
|
+
GdhRead.as_float(fields.get("height", 0))
|
|
820
771
|
)
|
|
821
772
|
return Rect2(_vector2_at(fields, "position"), _vector2_at(fields, "size"))
|
|
822
773
|
|
|
@@ -824,24 +775,24 @@ func _build_rect2(fields: Dictionary) -> Rect2:
|
|
|
824
775
|
func _build_rect2i(fields: Dictionary) -> Rect2i:
|
|
825
776
|
if not fields.has("position"):
|
|
826
777
|
return Rect2i(
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
778
|
+
GdhRead.as_int(fields.get("x", 0)),
|
|
779
|
+
GdhRead.as_int(fields.get("y", 0)),
|
|
780
|
+
GdhRead.as_int(fields.get("width", 0)),
|
|
781
|
+
GdhRead.as_int(fields.get("height", 0))
|
|
831
782
|
)
|
|
832
783
|
return Rect2i(_vector2i_at(fields, "position"), _vector2i_at(fields, "size"))
|
|
833
784
|
|
|
834
785
|
|
|
835
786
|
func _build_plane(fields: Dictionary) -> Plane:
|
|
836
|
-
return Plane(_vector3_at(fields, "normal"),
|
|
787
|
+
return Plane(_vector3_at(fields, "normal"), GdhRead.as_float(fields.get("d", 0)))
|
|
837
788
|
|
|
838
789
|
|
|
839
790
|
func _build_quaternion(fields: Dictionary) -> Quaternion:
|
|
840
791
|
return Quaternion(
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
792
|
+
GdhRead.as_float(fields.get("x", 0)),
|
|
793
|
+
GdhRead.as_float(fields.get("y", 0)),
|
|
794
|
+
GdhRead.as_float(fields.get("z", 0)),
|
|
795
|
+
GdhRead.as_float(fields.get("w", 0))
|
|
845
796
|
)
|
|
846
797
|
|
|
847
798
|
|
|
@@ -881,10 +832,10 @@ func _build_projection(fields: Dictionary) -> Projection:
|
|
|
881
832
|
|
|
882
833
|
func _build_color(fields: Dictionary) -> Color:
|
|
883
834
|
return Color(
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
835
|
+
GdhRead.as_float(fields.get("r", 0)),
|
|
836
|
+
GdhRead.as_float(fields.get("g", 0)),
|
|
837
|
+
GdhRead.as_float(fields.get("b", 0)),
|
|
838
|
+
GdhRead.as_float(fields.get("a", 1), 1.0)
|
|
888
839
|
)
|
|
889
840
|
|
|
890
841
|
|
|
@@ -897,7 +848,7 @@ func _build_bytes(fields: Dictionary) -> PackedByteArray:
|
|
|
897
848
|
|
|
898
849
|
|
|
899
850
|
func _build_float(fields: Dictionary) -> float:
|
|
900
|
-
return
|
|
851
|
+
return GdhRead.as_float(fields)
|
|
901
852
|
|
|
902
853
|
|
|
903
854
|
func _build_int(fields: Dictionary) -> int:
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
extends RefCounted
|
|
2
|
+
|
|
3
|
+
## How each engine type built from fields is written: the fields it needs, the ones it reads when
|
|
4
|
+
## they are there, and what a refusal says about a value that is not one.
|
|
5
|
+
##
|
|
6
|
+
## This file is the one beside the operations, and `bun run sync:gd` copies it into each addon.
|
|
7
|
+
|
|
8
|
+
# What each tag has to carry before it is built. A tag naming a type whose keys are not there is a
|
|
9
|
+
# tag somebody wrote by mistake or wrote for a different shape, and building one out of defaults
|
|
10
|
+
# answers with a Transform2D of zeros where the caller meant something else entirely. Unbuilt, it
|
|
11
|
+
# comes back as it arrived, and whoever asked can read it against the type they were expecting.
|
|
12
|
+
const REQUIRED: Dictionary = {
|
|
13
|
+
"Vector2": [["x", "y"]],
|
|
14
|
+
"Vector2i": [["x", "y"]],
|
|
15
|
+
"Vector3": [["x", "y", "z"]],
|
|
16
|
+
"Vector3i": [["x", "y", "z"]],
|
|
17
|
+
"Vector4": [["x", "y", "z", "w"]],
|
|
18
|
+
"Vector4i": [["x", "y", "z", "w"]],
|
|
19
|
+
"Rect2": [["position", "size"], ["x", "y", "width", "height"]],
|
|
20
|
+
"Rect2i": [["position", "size"], ["x", "y", "width", "height"]],
|
|
21
|
+
"Plane": [["normal", "d"]],
|
|
22
|
+
"Quaternion": [["x", "y", "z", "w"]],
|
|
23
|
+
"AABB": [["position", "size"]],
|
|
24
|
+
"Basis": [["x", "y", "z"]],
|
|
25
|
+
"Transform2D": [["x", "y", "origin"]],
|
|
26
|
+
"Transform3D": [["basis", "origin"]],
|
|
27
|
+
"Projection": [["x", "y", "z", "w"]],
|
|
28
|
+
"Color": [["r", "g", "b"]],
|
|
29
|
+
"NodePath": [["path"]],
|
|
30
|
+
"PackedByteArray": [["base64"]],
|
|
31
|
+
"float": [["value"]],
|
|
32
|
+
"int": [["value"]],
|
|
33
|
+
"RID": [["id"]],
|
|
34
|
+
"Dictionary": [["entries"]],
|
|
35
|
+
}
|
|
36
|
+
## The fields a builder reads when they are there and does without when not.
|
|
37
|
+
const OPTIONAL: Dictionary = {
|
|
38
|
+
"Color": ["a"],
|
|
39
|
+
"PackedByteArray": ["size"],
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
## How a value of [param type] is written to be read as one, for a refusal to name, or empty for a
|
|
44
|
+
## type that is not built from fields. A refusal naming only the two types leaves a caller to find
|
|
45
|
+
## the shape in the description of another op.
|
|
46
|
+
static func written_as(type: int) -> String:
|
|
47
|
+
var tag: String = type_string(type)
|
|
48
|
+
var alternatives: Array = REQUIRED.get(tag, [])
|
|
49
|
+
if alternatives.is_empty() or tag in ["float", "int", "Dictionary"]:
|
|
50
|
+
return ""
|
|
51
|
+
var keys: Array = alternatives[0]
|
|
52
|
+
var optional: Array = OPTIONAL.get(tag, [])
|
|
53
|
+
var parts: Array[String] = []
|
|
54
|
+
for key: Variant in keys + optional:
|
|
55
|
+
parts.append('"%s": ...' % key)
|
|
56
|
+
var shape: String = "{%s}" % ", ".join(parts)
|
|
57
|
+
if type == TYPE_COLOR:
|
|
58
|
+
return shape + ' or as its html code, "#ff8000"'
|
|
59
|
+
return shape
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
## The keys of [param fields] that a [param tag] built from them would not read, one list for each
|
|
63
|
+
## spelling of its fields carried whole. A rectangle has two: its corners, and four numbers.
|
|
64
|
+
##
|
|
65
|
+
## A builder drops a key it does not know without a word, so {"x": 1, "y": 2, "z": 3} for a Vector2
|
|
66
|
+
## would be written as (1, 2) and answered as written: a value is built only when a list is empty.
|
|
67
|
+
static func spare(fields: Dictionary, tag: String) -> Array:
|
|
68
|
+
var optional: Array = OPTIONAL.get(tag, [])
|
|
69
|
+
var lists: Array = []
|
|
70
|
+
var alternatives: Array = REQUIRED.get(tag, [])
|
|
71
|
+
for alternative: Variant in alternatives:
|
|
72
|
+
var keys: Array = alternative
|
|
73
|
+
if keys.all(func(key: Variant) -> bool: return fields.has(key)):
|
|
74
|
+
var over: Callable = func(key: Variant) -> bool:
|
|
75
|
+
return str(key) != "_type" and not keys.has(key) and not optional.has(key)
|
|
76
|
+
lists.append(fields.keys().filter(over))
|
|
77
|
+
return lists
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
## The sentences a refusal of [param value] where a [param type] goes ends with: which of its keys
|
|
81
|
+
## kept it from being read as one, and how one is written. Empty for a type not built from fields.
|
|
82
|
+
##
|
|
83
|
+
## The refusal names only the two types, and the key is what the caller has to take out.
|
|
84
|
+
static func remedy(value: Variant, type: int) -> String:
|
|
85
|
+
var shape: String = written_as(type)
|
|
86
|
+
if shape.is_empty():
|
|
87
|
+
return ""
|
|
88
|
+
return _unread(value, type) + " A %s is sent as %s." % [type_string(type), shape]
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
static func _unread(value: Variant, type: int) -> String:
|
|
92
|
+
if not value is Dictionary:
|
|
93
|
+
return ""
|
|
94
|
+
var fields: Dictionary = value
|
|
95
|
+
var tag: String = type_string(type)
|
|
96
|
+
var lists: Array = spare(fields, tag)
|
|
97
|
+
var built: bool = lists.any(func(over: Array) -> bool: return over.is_empty())
|
|
98
|
+
if lists.is_empty() or built or str(fields.get("_type", tag)) != tag:
|
|
99
|
+
return ""
|
|
100
|
+
lists.sort_custom(func(a: Array, b: Array) -> bool: return a.size() < b.size())
|
|
101
|
+
var left: Array = lists[0]
|
|
102
|
+
var named: PackedStringArray = []
|
|
103
|
+
for key: Variant in left:
|
|
104
|
+
var _named: bool = named.append('"%s"' % key)
|
|
105
|
+
var fields_called: String = "field called" if named.size() == 1 else "fields called"
|
|
106
|
+
var note: String = (
|
|
107
|
+
" A %s has no %s %s, so the value is not read as one" % [tag, fields_called, " or ".join(named)]
|
|
108
|
+
)
|
|
109
|
+
if left.has("type") and not fields.has("_type"):
|
|
110
|
+
return note + ': a value names its type under "_type".'
|
|
111
|
+
return note + "."
|
package/build/keeper.js
CHANGED
|
@@ -1428,17 +1428,23 @@ function recordAt(path) {
|
|
|
1428
1428
|
return null;
|
|
1429
1429
|
}
|
|
1430
1430
|
const args = fields["arguments"];
|
|
1431
|
+
const startedBy = fields["startedBy"];
|
|
1432
|
+
const projectPath = fields["projectPath"];
|
|
1433
|
+
const command = fields["command"];
|
|
1434
|
+
if (typeof startedBy !== "number" || typeof projectPath !== "string" || projectPath === "" || typeof command !== "string" || !Array.isArray(args)) {
|
|
1435
|
+
return null;
|
|
1436
|
+
}
|
|
1431
1437
|
return {
|
|
1432
1438
|
pid,
|
|
1433
1439
|
transcript,
|
|
1434
1440
|
startedAt,
|
|
1435
|
-
|
|
1436
|
-
projectPath
|
|
1441
|
+
startedBy,
|
|
1442
|
+
projectPath,
|
|
1437
1443
|
...typeof fields["servedBy"] === "string" && fields["servedBy"] !== "" ? { servedBy: fields["servedBy"] } : {},
|
|
1438
1444
|
...typeof fields["keeper"] === "string" && fields["keeper"] !== "" ? { keeper: fields["keeper"] } : {},
|
|
1439
1445
|
...typeof fields["profile"] === "string" && fields["profile"] !== "" ? { profile: fields["profile"] } : {},
|
|
1440
|
-
arguments:
|
|
1441
|
-
|
|
1446
|
+
arguments: args.filter((value) => typeof value === "string"),
|
|
1447
|
+
command,
|
|
1442
1448
|
...typeof fields["exitCode"] === "number" ? { exitCode: fields["exitCode"] } : {},
|
|
1443
1449
|
...typeof fields["exitSignal"] === "string" ? { exitSignal: fields["exitSignal"] } : {},
|
|
1444
1450
|
...typeof fields["endedAt"] === "number" ? { endedAt: fields["endedAt"] } : {}
|
package/package.json
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gdharness",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.1",
|
|
4
4
|
"mcpName": "io.github.Aureliolo/gdharness",
|
|
5
5
|
"description": "A harness for driving a Godot 4 project from an agent: editor addons, a runtime bridge into the running game, an MCP server in front of them, and a CLI that installs and diagnoses the Godot side.",
|
|
6
6
|
"type": "module",
|
|
7
|
-
"main": "./build/index.js",
|
|
8
7
|
"bin": {
|
|
9
8
|
"gdharness": "build/cli.js"
|
|
10
9
|
},
|