Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 55 additions & 2 deletions godot_parser/objects.py
Original file line number Diff line number Diff line change
@@ -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",
Expand All @@ -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):
"""
Expand Down Expand Up @@ -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]),
)
Expand All @@ -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)
Expand Down
6 changes: 3 additions & 3 deletions godot_parser/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()]
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of inlining this logic, can we do

['%s: %s' % (stringify_object(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)

Expand Down
24 changes: 21 additions & 3 deletions godot_parser/values.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
common,
)

from .objects import GDObject
from .objects import GDObject, GDArray, StringName

boolean = (
(Keyword("true") | Keyword("false"))
Expand Down Expand Up @@ -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
Expand All @@ -59,4 +77,4 @@

# Exports

value <<= primitive | list_ | dict_ | obj_type
value <<= primitive | list_ | dict_ | typed_array | obj_type
4 changes: 2 additions & 2 deletions tests/test_gdfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -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\\")"
Expand Down
12 changes: 6 additions & 6 deletions tests/test_objects.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -89,20 +89,20 @@ 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"""
r = SubResource(1)
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
Expand Down
36 changes: 35 additions & 1 deletion tests/test_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -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__)

Expand Down Expand Up @@ -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"),
}
}
)
),
),
]


Expand Down
Loading