diff --git a/godot_parser/objects.py b/godot_parser/objects.py index 42d5004..efcfeee 100644 --- a/godot_parser/objects.py +++ b/godot_parser/objects.py @@ -1,12 +1,14 @@ """Wrappers for Godot's non-primitive object types""" from functools import partial -from typing import Type, TypeVar +from typing import Any, Type, TypeVar from .util import stringify_object __all__ = [ + "StringName", "GDObject", + "GDArray", "Vector2", "Vector3", "Color", @@ -17,6 +19,25 @@ GD_OBJECT_REGISTRY = {} +class StringName(object): + + def __init__(self, value: str): + self.value = value + + def __str__(self) -> str: + return "&" + stringify_object(self.value) + + def __eq__(self, value): + if isinstance(value, StringName): + return self.value == value.value + return self.value == value + + def __ne__(self, value): + return not self.__eq__(value) + + def __hash__(self): + return hash(self.value) + class GDObjectMeta(type): """ @@ -55,7 +76,7 @@ def from_parser(cls: Type[GDObjectType], parse_result) -> GDObjectType: return factory(*parse_result[1:]) def __str__(self) -> str: - return "%s( %s )" % ( + return "%s(%s)" % ( self.name, ", ".join([stringify_object(v) for v in self.args]), ) @@ -72,6 +93,38 @@ def __ne__(self, other) -> bool: return not self.__eq__(other) +class GDArray: + + def __init__(self, type=None, items: list[Any]=[]): + self.type = type + self.args = items + + @classmethod + def from_parser(cls, parse_result) -> "GDArray": + type = parse_result[0] + return GDArray(type, parse_result[1]) + + def __str__(self): + return "Array[%s](%s)" % ( + self.type, + stringify_object(self.args), + ) + + def __getitem__(self, idx: int): + return self.args[idx] + + def __setitem__(self, idx: int, value): + self.args[idx] = value + + def __eq__(self, other) -> bool: + if not isinstance(other, GDArray): + return False + return self.type == other.type and self.args == other.args + + def __ne__(self, other) -> bool: + return not self.__eq__(other) + + class Vector2(GDObject): def __init__(self, x: float, y: float) -> None: super().__init__("Vector2", x, y) diff --git a/godot_parser/util.py b/godot_parser/util.py index e945b3e..db0ac85 100644 --- a/godot_parser/util.py +++ b/godot_parser/util.py @@ -10,19 +10,19 @@ def stringify_object(value): if value is None: return "null" elif isinstance(value, str): - return json.dumps(value) + return json.dumps(value, ensure_ascii=False) elif isinstance(value, bool): return "true" if value else "false" elif isinstance(value, dict): return ( "{\n" + ",\n".join( - ['"%s": %s' % (k, stringify_object(v)) for k, v in value.items()] + ['%s: %s' % (json.dumps(k, ensure_ascii=False) if isinstance(k, str) else str(k), stringify_object(v)) for k, v in value.items()] ) + "\n}" ) elif isinstance(value, list): - return "[ " + ", ".join([stringify_object(v) for v in value]) + " ]" + return "[" + ", ".join([stringify_object(v) for v in value]) + "]" else: return str(value) diff --git a/godot_parser/values.py b/godot_parser/values.py index 1b43031..a430d1c 100644 --- a/godot_parser/values.py +++ b/godot_parser/values.py @@ -14,7 +14,7 @@ common, ) -from .objects import GDObject +from .objects import GDObject, GDArray, StringName boolean = ( (Keyword("true") | Keyword("false")) @@ -46,7 +46,25 @@ .set_name("list") .set_parse_action(lambda p: p.as_list()) ) -key_val = Group(QuotedString('"', escChar="\\") + Suppress(":") + value) + +# Array[SomeType]( [ 1, 2 ] ) +typed_array = ( + ( + Suppress(Keyword("Array")) + + Suppress("[") + Word(alphas, alphanums).set_results_name("array_type") + Suppress("]") + + Suppress("(") + list_ + Suppress(")") + ) + .set_name("typed_list") + .set_parse_action(GDArray.from_parser) +) + +string_name = Suppress("&") + QuotedString('"', escChar="\\").set_parse_action(lambda p: StringName(p[0])) + +key = string_name | QuotedString('"', escChar="\\") + +key_val = Group( + key + Suppress(":") + value +) # { # "_edit_use_anchors_": false @@ -59,4 +77,4 @@ # Exports -value <<= primitive | list_ | dict_ | obj_type +value <<= primitive | list_ | dict_ | typed_array | obj_type diff --git a/tests/test_gdfile.py b/tests/test_gdfile.py index 7706e0f..8715664 100644 --- a/tests/test_gdfile.py +++ b/tests/test_gdfile.py @@ -27,9 +27,9 @@ def test_all_data_types(self): """[gd_resource load_steps=1 format=2] [resource] -list = [ 1, 2.0, "string" ] +list = [1, 2.0, "string"] map = { -"key": [ "nested", Vector2( 1, 1 ) ] +"key": ["nested", Vector2(1, 1)] } empty = null escaped = "foo(\\"bar\\")" diff --git a/tests/test_objects.py b/tests/test_objects.py index 89598dc..434fe6d 100644 --- a/tests/test_objects.py +++ b/tests/test_objects.py @@ -13,7 +13,7 @@ def test_vector2(self): self.assertEqual(v[1], 2) self.assertEqual(v.x, 1) self.assertEqual(v.y, 2) - self.assertEqual(str(v), "Vector2( 1, 2 )") + self.assertEqual(str(v), "Vector2(1, 2)") v.x = 2 v.y = 3 self.assertEqual(v.x, 2) @@ -32,7 +32,7 @@ def test_vector3(self): self.assertEqual(v.x, 1) self.assertEqual(v.y, 2) self.assertEqual(v.z, 3) - self.assertEqual(str(v), "Vector3( 1, 2, 3 )") + self.assertEqual(str(v), "Vector3(1, 2, 3)") v.x = 2 v.y = 3 v.z = 4 @@ -57,7 +57,7 @@ def test_color(self): self.assertEqual(c.g, 0.2) self.assertEqual(c.b, 0.3) self.assertEqual(c.a, 0.4) - self.assertEqual(str(c), "Color( 0.1, 0.2, 0.3, 0.4 )") + self.assertEqual(str(c), "Color(0.1, 0.2, 0.3, 0.4)") c.r = 0.2 c.g = 0.3 c.b = 0.4 @@ -89,7 +89,7 @@ def test_ext_resource(self): self.assertEqual(r.id, 1) r.id = 2 self.assertEqual(r.id, 2) - self.assertEqual(str(r), "ExtResource( 2 )") + self.assertEqual(str(r), "ExtResource(2)") def test_sub_resource(self): """Test for SubResource""" @@ -97,12 +97,12 @@ def test_sub_resource(self): self.assertEqual(r.id, 1) r.id = 2 self.assertEqual(r.id, 2) - self.assertEqual(str(r), "SubResource( 2 )") + self.assertEqual(str(r), "SubResource(2)") def test_dunder(self): """Test the __magic__ methods on GDObject""" v = Vector2(1, 2) - self.assertEqual(repr(v), "Vector2( 1, 2 )") + self.assertEqual(repr(v), "Vector2(1, 2)") v2 = Vector2(1, 2) self.assertEqual(v, v2) v2.x = 10 diff --git a/tests/test_parser.py b/tests/test_parser.py index 975166f..08612e4 100644 --- a/tests/test_parser.py +++ b/tests/test_parser.py @@ -3,7 +3,7 @@ from pyparsing import ParseException -from godot_parser import GDFile, GDObject, GDSection, GDSectionHeader, Vector2, parse +from godot_parser import StringName, GDFile, GDObject, GDArray, GDSection, GDSubResourceSection, GDSectionHeader, Vector2, parse HERE = os.path.dirname(__file__) @@ -110,6 +110,40 @@ ) ), ), + ( + """[sub_resource type="Compositor" id="3"] + compositor_effects = Array[CompositorEffect]([ExtResource("1")]) + """, + GDFile( + GDSection( + GDSectionHeader("sub_resource", type="Compositor", id="3"), + compositor_effects=GDArray( + "CompositorEffect", + [GDObject("ExtResource", "1")], + ) + ) + ), + ), + ( + """[sub_resource type="AnimationLibrary" id="1"] + _data = { + &"RESET": SubResource("2"), + &"sprint": SubResource("3"), + &"walk": SubResource("4") + }""", + GDFile( + GDSection( + GDSectionHeader("sub_resource", type="AnimationLibrary", id="1"), + **{ + "_data": { + StringName("RESET"): GDObject("SubResource", "2"), + StringName("sprint"): GDObject("SubResource", "3"), + StringName("walk"): GDObject("SubResource", "4"), + } + } + ) + ), + ), ]