From 5b62df0409806aac3d4908c7c636399d39563d7c Mon Sep 17 00:00:00 2001 From: Benjamin Kirkbride Date: Sat, 29 Jun 2019 15:53:30 -0400 Subject: [PATCH] I'm back! --- .style.yapf | 3 - dev_requirements.txt | 1 + mypy.ini | 21 ++ pytiled_parser/objects.py | 191 +++++++----------- pytiled_parser/utilities.py | 36 +++- pytiled_parser/xml_parser.py | 111 +++++----- setup.py | 7 +- test/output.py | 20 -- test/test_attr.py | 22 ++ test/test_output.py | 178 ++++++++++++++++ test/test_tiled.py | 12 +- tests/__init__.py | 2 +- tests/{unit2 => }/test_parser.py | 59 +++++- ...bax => test_pytiled_parser_integration.py} | 10 +- 14 files changed, 454 insertions(+), 219 deletions(-) delete mode 100644 .style.yapf create mode 100644 mypy.ini delete mode 100644 test/output.py create mode 100644 test/test_attr.py create mode 100644 test/test_output.py rename tests/{unit2 => }/test_parser.py (77%) rename tests/{unit2/test_pytiled_parser_integration.py.bax => test_pytiled_parser_integration.py} (93%) diff --git a/.style.yapf b/.style.yapf deleted file mode 100644 index 5666f47..0000000 --- a/.style.yapf +++ /dev/null @@ -1,3 +0,0 @@ -[style] -based_on_style = google -column_limit = 78 diff --git a/dev_requirements.txt b/dev_requirements.txt index de3f832..0f6ddbb 100644 --- a/dev_requirements.txt +++ b/dev_requirements.txt @@ -1,4 +1,5 @@ -e . +black pytest sphinx coverage diff --git a/mypy.ini b/mypy.ini new file mode 100644 index 0000000..af3f209 --- /dev/null +++ b/mypy.ini @@ -0,0 +1,21 @@ +# Global options: + +[mypy] +python_version = 3.6 +warn_unused_configs = False +disallow_any_unimported = True +disallow_any_expr = True +disallow_any_decorated = True +disallow_any_explicit = True +disallow_any_generics = True +disallow_subclassing_any = True +disallow_untyped_calls = True +disallow_untyped_defs = True +disallow_incomplete_defs = True +check_untyped_defs = True +disallow_untyped_decorators = True +warn_return_any = True + +# Per-module options: +[mypy-tests.*] +ignore_errors = True diff --git a/pytiled_parser/objects.py b/pytiled_parser/objects.py index a805a7b..7581aa3 100644 --- a/pytiled_parser/objects.py +++ b/pytiled_parser/objects.py @@ -2,16 +2,15 @@ pytiled_parser objects for Tiled maps. """ -import dataclasses + import functools import re - +import xml.etree.ElementTree as etree from collections import OrderedDict from pathlib import Path +from typing import Dict, List, NamedTuple, Optional, Union -import xml.etree.ElementTree as etree - -from typing import NamedTuple, Union, Optional, List, Dict +import attr class Color(NamedTuple): @@ -60,7 +59,7 @@ class Template: """ -@dataclasses.dataclass +@attr.s(auto_attribs=True) class Chunk: """ Chunk object for infinite maps. @@ -81,7 +80,7 @@ class Chunk: chunk_data: List[List[int]] -@dataclasses.dataclass +@attr.s(auto_attribs=True) class Image: """ Image object. @@ -100,7 +99,7 @@ class Image: source: str size: Optional[Size] = None - trans: Optional[Color] = None + trans: Optional[str] = None Properties = Dict[str, Union[int, float, Color, Path, str]] @@ -151,7 +150,7 @@ class Frame(NamedTuple): duration: int -@dataclasses.dataclass +@attr.s(auto_attribs=True) class TileTerrain: """ Defines each corner of a tile by Terrain index in @@ -172,7 +171,7 @@ class TileTerrain: bottom_right: Optional[int] = None -@dataclasses.dataclass +@attr.s(auto_attribs=True, kw_only=True) class Layer: """Class that all layers inherret from. @@ -212,7 +211,7 @@ Either a 2 dimensional array of integers representing the global tile IDs """ -@dataclasses.dataclass +@attr.s(auto_attribs=True, kw_only=True) class TileLayer(Layer): """Tile map layer containing tiles. @@ -230,27 +229,8 @@ class TileLayer(Layer): data: LayerData -@dataclasses.dataclass -class _TiledObjectBase: - id: int - location: OrderedPair - - -@dataclasses.dataclass -class _TiledObjectDefaults: - size: Size = Size(0, 0) - rotation: int = 0 - opacity: float = 1 - - name: Optional[str] = None - type: Optional[str] = None - - properties: Optional[Properties] = None - template: Optional[Template] = None - - -@dataclasses.dataclass -class TiledObject(_TiledObjectDefaults, _TiledObjectBase): +@attr.s(auto_attribs=True, kw_only=True) +class TiledObject: """ TiledObject object. @@ -274,8 +254,21 @@ class TiledObject(_TiledObjectDefaults, _TiledObjectBase): FIXME """ + id: int + location: OrderedPair -@dataclasses.dataclass + size: Size = Size(0, 0) + rotation: int = 0 + opacity: float = 1 + + name: Optional[str] = None + type: Optional[str] = None + + properties: Optional[Properties] = None + template: Optional[Template] = None + + +@attr.s() class RectangleObject(TiledObject): """ Rectangle shape defined by a point, width, and height. @@ -286,7 +279,7 @@ class RectangleObject(TiledObject): """ -@dataclasses.dataclass +@attr.s() class ElipseObject(TiledObject): """ Elipse shape defined by a point, width, and height. @@ -295,7 +288,7 @@ class ElipseObject(TiledObject): """ -@dataclasses.dataclass +@attr.s() class PointObject(TiledObject): """ Point defined by a point (x,y). @@ -304,13 +297,8 @@ class PointObject(TiledObject): """ -@dataclasses.dataclass -class _TileImageObjectBase(_TiledObjectBase): - gid: int - - -@dataclasses.dataclass -class TileImageObject(TiledObject, _TileImageObjectBase): +@attr.s(auto_attribs=True, kw_only=True) +class TileImageObject(TiledObject): """ Polygon shape defined by a set of connections between points. @@ -320,14 +308,11 @@ class TileImageObject(TiledObject, _TileImageObjectBase): :gid (int): Refference to a global tile id. """ - -@dataclasses.dataclass -class _PointsObjectBase(_TiledObjectBase): - points: List[OrderedPair] + gid: int -@dataclasses.dataclass -class PolygonObject(TiledObject, _PointsObjectBase): +@attr.s(auto_attribs=True, kw_only=True) +class PolygonObject(TiledObject): """ Polygon shape defined by a set of connections between points. @@ -337,9 +322,11 @@ class PolygonObject(TiledObject, _PointsObjectBase): :points (List[OrderedPair]) """ + points: List[OrderedPair] -@dataclasses.dataclass -class PolylineObject(TiledObject, _PointsObjectBase): + +@attr.s(auto_attribs=True, kw_only=True) +class PolylineObject(TiledObject): """ Polyline defined by a set of connections between points. @@ -351,29 +338,11 @@ class PolylineObject(TiledObject, _PointsObjectBase): the location of the object. """ - -@dataclasses.dataclass -class _TextObjectBase(_TiledObjectBase): - text: str + points: List[OrderedPair] -@dataclasses.dataclass -class _TextObjectDefaults(_TiledObjectDefaults): - font_family: str = "sans-serif" - font_size: int = 16 - wrap: bool = False - color: str = "#000000" - bold: bool = False - italic: bool = False - underline: bool = False - strike_out: bool = False - kerning: bool = False - horizontal_align: str = "left" - vertical_align: str = "top" - - -@dataclasses.dataclass -class TextObject(TiledObject, _TextObjectDefaults, _TextObjectBase): +@attr.s(auto_attribs=True, kw_only=True) +class TextObject(TiledObject): """ Text object with associated settings. @@ -396,8 +365,21 @@ class TextObject(TiledObject, _TextObjectDefaults, _TextObjectBase): :vertical_align (str): Vertical alignment of the text (defalt: "top") """ + text: str + font_family: str = "sans-serif" + font_size: int = 16 + wrap: bool = False + color: str = "#000000" + bold: bool = False + italic: bool = False + underline: bool = False + strike_out: bool = False + kerning: bool = False + horizontal_align: str = "left" + vertical_align: str = "top" -@dataclasses.dataclass + +@attr.s(auto_attribs=True, kw_only=True) class ObjectLayer(Layer): """ TiledObject Group Object. @@ -426,7 +408,7 @@ class ObjectLayer(Layer): draw_order: Optional[str] = "topdown" -@dataclasses.dataclass +@attr.s(auto_attribs=True, kw_only=True) class LayerGroup(Layer): """ Layer Group. @@ -445,13 +427,13 @@ class LayerGroup(Layer): layers: Optional[List[Union["LayerGroup", Layer, ObjectLayer]]] -@dataclasses.dataclass +@attr.s(auto_attribs=True) class Hitbox: - """Group of hitboxes for + """Group of hitboxes for FIXME """ -@dataclasses.dataclass +@attr.s(auto_attribs=True) class Tile: """ Individual tile object. @@ -466,14 +448,15 @@ class Tile: """ id: int - type: Optional[str] - terrain: Optional[TileTerrain] - animation: Optional[List[Frame]] - image: Optional[Image] - hitboxes: Optional[List[TiledObject]] + + type: Optional[str] = None + terrain: Optional[TileTerrain] = None + animation: Optional[List[Frame]] = None + image: Optional[Image] = None + hitboxes: Optional[List[TiledObject]] = None -@dataclasses.dataclass +@attr.s(auto_attribs=True) class TileSet: """ Object for storing a TSX with all associated collision data. @@ -506,22 +489,23 @@ class TileSet: name: str max_tile_size: Size - spacing: Optional[int] - margin: Optional[int] - tile_count: Optional[int] - columns: Optional[int] - tile_offset: Optional[OrderedPair] - grid: Optional[Grid] - properties: Optional[Properties] - image: Optional[Image] - terrain_types: Optional[List[Terrain]] - tiles: Optional[Dict[int, Tile]] + + spacing: Optional[int] = None + margin: Optional[int] = None + tile_count: Optional[int] = None + columns: Optional[int] = None + tile_offset: Optional[OrderedPair] = None + grid: Optional[Grid] = None + properties: Optional[Properties] = None + image: Optional[Image] = None + terrain_types: Optional[List[Terrain]] = None + tiles: Optional[Dict[int, Tile]] = None TileSetDict = Dict[int, TileSet] -@dataclasses.dataclass +@attr.s(auto_attribs=True) class TileMap: """ Object for storing a TMX with all associated layers and properties. @@ -569,7 +553,7 @@ class TileMap: map_size: Size tile_size: Size infinite: bool - next_layer_id: int + next_layer_id: Optional[int] next_object_id: int tile_sets: TileSetDict @@ -581,22 +565,3 @@ class TileMap: background_color: Optional[str] = None properties: Optional[Properties] = None - - -""" -[22:16] <__m4ch1n3__> i would "[i for i in int_list if i < littler_then_value]" -[22:16] <__m4ch1n3__> it returns a list of integers below "littler_then_value" -[22:17] <__m4ch1n3__> !py3 [i for i in [1,2,3,4,1,2,3,4] if i < 3] -[22:17] __m4ch1n3__: [1, 2, 1, 2] -[22:17] <__m4ch1n3__> !py3 [i for i in [1,2,3,4,1,2,3,4] if i < 4] -[22:17] __m4ch1n3__: [1, 2, 3, 1, 2, 3] -[22:22] <__m4ch1n3__> !py3 max([i for i in [1,2,3,4,1,2,3,4] if i < 4]) -[22:22] __m4ch1n3__: 3 -[22:22] <__m4ch1n3__> max(...) would return the maximum of resulting list -[22:23] <__m4ch1n3__> !py3 max([i for i in [1, 10, 100] if i < 20]) -[22:23] __m4ch1n3__: 10 -[22:23] <__m4ch1n3__> !py3 max([i for i in [1, 10, 100] if i < 242]) -[22:23] __m4ch1n3__: 100 -[22:23] == markb1 [~mbiggers@45.36.35.206] has quit [Ping timeout: 245 seconds] -[22:23] <__m4ch1n3__> !py3 max(i for i in [1, 10, 100] if i < 242) -""" diff --git a/pytiled_parser/utilities.py b/pytiled_parser/utilities.py index 32d308e..71a917e 100644 --- a/pytiled_parser/utilities.py +++ b/pytiled_parser/utilities.py @@ -1,3 +1,6 @@ +import functools +from typing import Dict, List, Optional + import pytiled_parser.objects as objects @@ -27,8 +30,24 @@ def parse_color(color: str) -> objects.Color: return objects.Color(red, green, blue, alpha) -def get_tile_by_gid(tile_sets: objects.TileSetDict, gid: int) -> objects.Tile: - """Gets Tile from a global tile ID. +def _get_tile_set_key(gid: int, tile_set_keys: List[int]) -> int: + """Gets tile set key given a tile GID. + + Args: + gid: Global ID of the tile. + + Returns: + int: The key of the tile set that contains the tile for the GID. + """ + + # credit to __m4ch1n3__ on ##learnpython for this idea + return max([key for key in tile_set_keys if key <= gid]) + + +def get_tile_by_gid( + gid: int, tile_sets: objects.TileSetDict +) -> Optional[objects.Tile]: + """Gets correct Tile for a given global ID. Args: tile_sets (objects.TileSetDict): TileSetDict from TileMap. @@ -36,10 +55,13 @@ def get_tile_by_gid(tile_sets: objects.TileSetDict, gid: int) -> objects.Tile: Returns: objects.Tile: The Tile object reffered to by the global tile ID. + None: If there is no objects.Tile object in the tile_set.tiles dict + for the associated gid. """ - for tileset_key, tileset in tile_sets.items(): - for tile_key, tile in tileset.tiles.items(): - tile_gid = tile.id + tileset_key - if tile_gid == gid: - return tile + tile_set_key = _get_tile_set_key(gid, list(tile_sets.keys())) + tile_set = tile_sets[tile_set_key] + + if tile_set.tiles is not None: + return tile_set.tiles.get(gid - tile_set_key) + return None diff --git a/pytiled_parser/xml_parser.py b/pytiled_parser/xml_parser.py index 8ca4649..2282686 100644 --- a/pytiled_parser/xml_parser.py +++ b/pytiled_parser/xml_parser.py @@ -1,13 +1,11 @@ -import functools import base64 +import functools import gzip import re -import zlib - -from pathlib import Path - -from typing import Callable, Dict, List, Optional, Tuple, Union import xml.etree.ElementTree as etree +import zlib +from pathlib import Path +from typing import Callable, Dict, List, Optional, Tuple, Union import pytiled_parser.objects as objects import pytiled_parser.utilities as utilities @@ -73,7 +71,7 @@ def _decode_data( element: etree.Element, layer_width: int, encoding: str, - compression: Optional[str], + compression: Optional[str] = None, ) -> List[List[int]]: """Decodes data or chunk data. @@ -105,12 +103,10 @@ def _decode_data( if encoding == "csv": return _decode_csv_data(data_text) - return _decode_base64_data(data_text, compression, layer_width) + return _decode_base64_data(data_text, layer_width, compression) -def _parse_data( - element: etree.Element, layer_width: int -) -> objects.LayerData: +def _parse_data(element: etree.Element, layer_width: int) -> objects.LayerData: """Parses layer data. Will parse CSV, base64, gzip-base64, or zlip-base64 encoded data. @@ -226,7 +222,13 @@ def _parse_tile_layer(element: etree.Element,) -> objects.TileLayer: raise ValueError(f"{element} has no child data element.") return objects.TileLayer( - id, name, offset, opacity, properties, size, data + id=id, + name=name, + offset=offset, + opacity=opacity, + properties=properties, + size=size, + data=data, ) @@ -249,7 +251,7 @@ def _parse_objects( location_y = float(object_element.attrib["y"]) location = objects.OrderedPair(location_x, location_y) - tiled_object = objects.TiledObject(id, location) + tiled_object = objects.TiledObject(id=id, location=location) try: width = float(object_element.attrib["width"]) @@ -323,14 +325,14 @@ def _parse_object_layer(element: etree.Element,) -> objects.ObjectLayer: pass return objects.ObjectLayer( - id, - name, - offset, - opacity, - properties, - tiled_objects, - color, - draw_order, + id=id, + name=name, + offset=offset, + opacity=opacity, + properties=properties, + tiled_objects=tiled_objects, + color=color, + draw_order=draw_order, ) @@ -352,7 +354,14 @@ def _parse_layer_group(element: etree.Element,) -> objects.LayerGroup: layers = _get_layers(element) - return objects.LayerGroup(id, name, offset, opacity, properties, layers) + return objects.LayerGroup( + id=id, + name=name, + offset=offset, + opacity=opacity, + properties=properties, + layers=layers, + ) def _get_layer_parser( @@ -648,6 +657,37 @@ def _parse_tile_set(tile_set_element: etree.Element) -> objects.TileSet: ) +def _get_tile_sets( + map_element: etree.Element, parent_dir: Path +) -> objects.TileSetDict: + # parse all tilesets + tile_sets: objects.TileSetDict = {} + tile_set_element_list = map_element.findall("./tileset") + for tile_set_element in tile_set_element_list: + # tiled docs are ambiguous about the 'firstgid' attribute + # current understanding is for the purposes of mapping the layer + # data to the tile set data, add the 'firstgid' value to each + # tile 'id'; this means that the 'firstgid' is specific to each, + # tile set as they pertain to the map, not tile set specific as + # the tiled docs can make it seem + # 'firstgid' the key for each TileMap + first_gid = int(tile_set_element.attrib["firstgid"]) + try: + # check if is an external TSX + source = tile_set_element.attrib["source"] + except KeyError: + # the tile set in embedded + name = tile_set_element.attrib["name"] + tile_sets[first_gid] = _parse_tile_set(tile_set_element) + else: + # tile set is external + tile_sets[first_gid] = _parse_external_tile_set( + parent_dir, tile_set_element + ) + + return tile_sets + + def parse_tile_map(tmx_file: Union[str, Path]) -> objects.TileMap: # setting up XML parsing map_tree = etree.parse(str(tmx_file)) @@ -660,9 +700,11 @@ def parse_tile_map(tmx_file: Union[str, Path]) -> objects.TileMap: tiled_version = map_element.attrib["tiledversion"] orientation = map_element.attrib["orientation"] render_order = map_element.attrib["renderorder"] + map_width = int(map_element.attrib["width"]) map_height = int(map_element.attrib["height"]) map_size = objects.Size(map_width, map_height) + tile_width = int(map_element.attrib["tilewidth"]) tile_height = int(map_element.attrib["tileheight"]) tile_size = objects.Size(tile_width, tile_height) @@ -673,30 +715,7 @@ def parse_tile_map(tmx_file: Union[str, Path]) -> objects.TileMap: next_layer_id = int(map_element.attrib["nextlayerid"]) next_object_id = int(map_element.attrib["nextobjectid"]) - # parse all tilesets - tile_sets: Dict[int, objects.TileSet] = {} - tile_set_element_list = map_element.findall("./tileset") - for tile_set_element in tile_set_element_list: - # tiled docs are ambiguous about the 'firstgid' attribute - # current understanding is for the purposes of mapping the layer - # data to the tile set data, add the 'firstgid' value to each - # tile 'id'; this means that the 'firstgid' is specific to each, - # tile set as they pertain to the map, not tile set specific as - # the tiled docs can make it seem - # 'firstgid' is saved beside each TileMap - firstgid = int(tile_set_element.attrib["firstgid"]) - try: - # check if is an external TSX - source = tile_set_element.attrib["source"] - except KeyError: - # the tile set in embedded - name = tile_set_element.attrib["name"] - tile_sets[firstgid] = _parse_tile_set(tile_set_element) - else: - # tile set is external - tile_sets[firstgid] = _parse_external_tile_set( - parent_dir, tile_set_element - ) + tile_sets = _get_tile_sets(map_element, parent_dir) layers = _get_layers(map_element) diff --git a/setup.py b/setup.py index d8b13db..dd337da 100644 --- a/setup.py +++ b/setup.py @@ -1,6 +1,7 @@ -from os import path import sys -from setuptools import setup +from os import path + +from setuptools import setup # type: ignore BUILD = 0 VERSION = "0.0.1" @@ -21,7 +22,7 @@ if __name__ == "__main__": license="MIT", url="https://github.com/Beefy-Swain/pytiled_parser", download_url="https://github.com/Beefy-Swain/pytiled_parser", - install_requires=["dataclasses"], + install_requires=["attrs"], packages=["pytiled_parser"], classifiers=[ "Development Status :: 1 - Planning", diff --git a/test/output.py b/test/output.py deleted file mode 100644 index 7190a30..0000000 --- a/test/output.py +++ /dev/null @@ -1,20 +0,0 @@ -{ 'background_color': None, - 'hex_side_length': None, - 'infinite': False, - 'layers': [ TileLayer(id=1, name='Tile Layer 1', offset=None, opacity=None, properties=None, size=Size(width=10, height=10), data=[[1, 2, 3, 4, 5, 6, 7, 8, 30, 30], [9, 10, 11, 12, 13, 14, 15, 16, 30, 30], [17, 18, 19, 20, 21, 22, 23, 24, 30, 30], [25, 26, 27, 28, 29, 30, 31, 32, 30, 30], [33, 34, 35, 36, 37, 38, 39, 40, 30, 30], [41, 42, 43, 44, 45, 46, 47, 48, 30, 30], [30, 30, 30, 30, 30, 30, 30, 30, 30, 30], [30, 30, 30, 30, 30, 30, 30, 30, 30, 30], [30, 30, 30, 30, 30, 30, 30, 30, 30, 30], [30, 30, 30, 30, 30, 30, 30, 30, 30, 30]]), - TileLayer(id=2, name='Tile Layer 2', offset=None, opacity=0.5, properties=None, size=Size(width=10, height=10), data=[[0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 46, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 6, 7, 7, 7, 7, 7, 8, 0], [0, 0, 14, 15, 15, 15, 15, 15, 16, 0], [0, 0, 22, 23, 23, 23, 23, 23, 24, 0]]), - LayerGroup(id=3, name='Group 1', offset=None, opacity=None, properties={'bool property': True}, layers=[TileLayer(id=5, name='Tile Layer 4', offset=OrderedPair(x=49.0, y=-50.0), opacity=None, properties=None, size=Size(width=10, height=10), data=[[0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 31, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]), TileLayer(id=4, name='Tile Layer 3', offset=None, opacity=None, properties=None, size=Size(width=10, height=10), data=[[0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [1, 2, 3, 0, 0, 0, 0, 0, 0, 0], [9, 10, 11, 0, 0, 0, 0, 0, 0, 0], [17, 18, 19, 0, 0, 0, 0, 0, 0, 0]])]), - ObjectLayer(id=6, name='Object Layer 1', offset=OrderedPair(x=4.66667, y=-4.33333), opacity=0.9, properties=None, tiled_objects=[TiledObject(id=1, location=OrderedPair(x=200.25, y=210.75), size=Size(width=47.25, height=25.0), rotation=15, opacity=1, name='rectangle 1', type='rectangle type', properties=None, template=None), TiledObject(id=2, location=OrderedPair(x=252.5, y=87.75), size=Size(width=0, height=0), rotation=-21, opacity=1, name='polygon 1', type='polygon type', properties=None, template=None), TiledObject(id=3, location=OrderedPair(x=198.75, y=102.5), size=Size(width=17.75, height=14.25), rotation=0, opacity=1, name='elipse 1', type='elipse type', properties=None, template=None), TiledObject(id=4, location=OrderedPair(x=174.25, y=186.0), size=Size(width=0, height=0), rotation=0, opacity=1, name='point 1', type='point type', properties=None, template=None), TiledObject(id=7, location=OrderedPair(x=11.3958, y=48.5833), size=Size(width=107.625, height=27.25), rotation=0, opacity=1, name='insert text 1', type='insert text type', properties=None, template=None), TiledObject(id=6, location=OrderedPair(x=47.25, y=72.5), size=Size(width=47.0, height=53.0), rotation=31, opacity=1, name='inserted tile 1', type='inserted tile type', properties={'tile property bool': True}, template=None), TiledObject(id=8, location=OrderedPair(x=144.667, y=112.0), size=Size(width=0, height=0), rotation=0, opacity=1, name='polyline 1', type='polyline type', properties=None, template=None), TiledObject(id=9, location=OrderedPair(x=69.8333, y=168.333), size=Size(width=0, height=0), rotation=0, opacity=1, name='polygon 2', type='polygon type', properties=None, template=None)], color=Color(red=0, green=0, blue=0, alpha=255), draw_order='index')], - 'map_size': Size(width=10, height=10), - 'next_layer_id': 16, - 'next_object_id': 10, - 'orientation': 'orthogonal', - 'parent_dir': PosixPath('/home/benk/Projects/pytiled_parser/venv/pytiled_parser/tests/test_data'), - 'properties': None, - 'render_order': 'right-down', - 'stagger_axis': None, - 'stagger_index': None, - 'tile_sets': { 1: TileSet(name='tile_set_image', max_tile_size=Size(width=32, height=32), spacing=1, margin=1, tile_count=48, columns=8, tile_offset=None, grid=None, properties=None, image=Image(source='images/tmw_desert_spacing.png', size=Size(width=265, height=199), trans=None), terrain_types=None, tiles={})}, - 'tile_size': Size(width=32, height=32), - 'tiled_version': '1.2.3', - 'version': '1.2'} diff --git a/test/test_attr.py b/test/test_attr.py new file mode 100644 index 0000000..e3de7d1 --- /dev/null +++ b/test/test_attr.py @@ -0,0 +1,22 @@ +import attr + + +@attr.s(auto_attribs=True, kw_only=True) +class Foo: + x: int + y: int + + a: int = 5 + + +@attr.s(auto_attribs=True, kw_only=True) +class Bar(Foo): + z: int + + +foo = Foo(x=1, y=2) + +bar = Bar(x=1, y=2, z=3) + +print(foo) +print(bar) diff --git a/test/test_output.py b/test/test_output.py new file mode 100644 index 0000000..e6228d5 --- /dev/null +++ b/test/test_output.py @@ -0,0 +1,178 @@ +{ + "parent_dir": PosixPath( + "/home/ben/Projects/pytiled_parser/pytiled_parser-venv/pytiled_parser/tests/test_data" + ), + "version": "1.2", + "tiled_version": "1.2.3", + "orientation": "orthogonal", + "render_order": "right-down", + "map_size": Size(width=8, height=6), + "tile_size": Size(width=32, height=32), + "infinite": False, + "next_layer_id": 2, + "next_object_id": 1, + "tile_sets": { + 1: TileSet( + name="tile_set_image", + max_tile_size=Size(width=32, height=32), + spacing=1, + margin=1, + tile_count=48, + columns=8, + tile_offset=None, + grid=None, + properties=None, + image=Image( + source="images/tmw_desert_spacing.png", + size=Size(width=265, height=199), + trans=None, + ), + terrain_types=None, + tiles={ + 9: Tile( + id=9, + type=None, + terrain=None, + animation=None, + image=None, + hitboxes=[ + TiledObject( + id=2, + location=OrderedPair(x=1.0, y=1.0), + size=Size(width=32.0, height=32.0), + rotation=1, + opacity=1, + name="wall", + type="rectangle type", + properties=None, + template=None, + ) + ], + ), + 19: Tile( + id=19, + type=None, + terrain=None, + animation=None, + image=None, + hitboxes=[ + TiledObject( + id=1, + location=OrderedPair(x=32.0, y=1.0), + size=Size(width=0, height=0), + rotation=1, + opacity=1, + name="wall corner", + type="polygon type", + properties=None, + template=None, + ) + ], + ), + 20: Tile( + id=20, + type=None, + terrain=None, + animation=None, + image=None, + hitboxes=[ + TiledObject( + id=1, + location=OrderedPair(x=1.45455, y=1.45455), + size=Size(width=0, height=0), + rotation=1, + opacity=1, + name="polyline", + type="polyline type", + properties=None, + template=None, + ) + ], + ), + 31: Tile( + id=31, + type=None, + terrain=None, + animation=None, + image=None, + hitboxes=[ + TiledObject( + id=1, + location=OrderedPair(x=5.09091, y=2.54545), + size=Size(width=19.6364, height=19.2727), + rotation=1, + opacity=1, + name="rock 1", + type="elipse type", + properties=None, + template=None, + ), + TiledObject( + id=2, + location=OrderedPair(x=16.1818, y=22.0), + size=Size(width=8.54545, height=8.36364), + rotation=-1, + opacity=1, + name="rock 2", + type="elipse type", + properties=None, + template=None, + ), + ], + ), + 45: Tile( + id=45, + type=None, + terrain=None, + animation=None, + image=None, + hitboxes=[ + TiledObject( + id=1, + location=OrderedPair(x=14.7273, y=26.3636), + size=Size(width=0, height=0), + rotation=0, + opacity=1, + name="sign", + type="point type", + properties=None, + template=None, + ) + ], + ), + }, + ) + }, + "layers": [ + TileLayer( + id=1, + name="Tile Layer 1", + offset=None, + opacity=None, + properties=None, + size=Size(width=8, height=6), + data=[ + [1, 2, 3, 4, 5, 6, 7, 8], + [9, 10, 11, 12, 13, 14, 15, 16], + [17, 18, 19, 20, 21, 22, 23, 24], + [25, 26, 27, 28, 29, 30, 31, 32], + [33, 34, 35, 36, 37, 38, 39, 40], + [41, 42, 43, 44, 45, 46, 47, 48], + ], + ) + ], + "hex_side_length": None, + "stagger_axis": None, + "stagger_index": None, + "background_color": None, + "properties": { + "bool property - false": False, + "bool property - true": True, + "color property": "#ff49fcff", + "file property": PosixPath("../../../../../../../../var/log/syslog"), + "float property": 1.23456789, + "int property": 13, + "string property": "Hello, World!!", + }, +} +e diff --git a/test/test_tiled.py b/test/test_tiled.py index 235d112..589aa21 100644 --- a/test/test_tiled.py +++ b/test/test_tiled.py @@ -1,17 +1,11 @@ -import pprint import pickle - +import pprint from io import StringIO import pytiled_parser - -pp = pprint.PrettyPrinter(indent=4, compact=True, width=100) - -pp = pp.pprint - -MAP_NAME = "/home/benk/Projects/pytiled_parser/venv/pytiled_parser/tests/test_data/test_map_image_tile_set.tmx" +MAP_NAME = "/home/ben/Projects/pytiled_parser/pytiled_parser-venv/pytiled_parser/tests/test_data/test_map_simple_hitboxes.tmx" map = pytiled_parser.parse_tile_map(MAP_NAME) -pp(map.__dict__) +print(map.__dict__) diff --git a/tests/__init__.py b/tests/__init__.py index 0bb7ca5..3aeeee8 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1,3 +1,3 @@ import pytest -pytest.main(["-x", "tests/unit2"]) +pytest.main(["--tb=native", "-s", "tests"]) diff --git a/tests/unit2/test_parser.py b/tests/test_parser.py similarity index 77% rename from tests/unit2/test_parser.py rename to tests/test_parser.py index 3d58ab1..30c983b 100644 --- a/tests/unit2/test_parser.py +++ b/tests/test_parser.py @@ -1,11 +1,9 @@ -import pytest - import xml.etree.ElementTree as etree - from contextlib import contextmanager from typing import Callable, List, Optional, Tuple -from pytiled_parser import objects, xml_parser, utilities +import pytest +from pytiled_parser import objects, utilities, xml_parser @contextmanager @@ -19,8 +17,7 @@ def _get_root_element(xml: str) -> etree.Element: layer_data = [ ( - '' - "", + '' "", (int(1), "Tile Layer 1", None, None, None), ), ( @@ -46,7 +43,7 @@ layer_data = [ @pytest.mark.parametrize("xml,expected", layer_data) def test_parse_layer(xml, expected, monkeypatch): - def mockreturn(properties): + def mockreturn(*args): return "properties" monkeypatch.setattr(xml_parser, "_parse_properties_element", mockreturn) @@ -160,11 +157,53 @@ data_base64 = [ @pytest.mark.parametrize( "data_base64,width,compression,expected,raises", data_base64 ) -def test_decode_base64_data( - data_base64, width, compression, expected, raises -): +def test_decode_base64_data(data_base64, width, compression, expected, raises): with raises: assert ( xml_parser._decode_base64_data(data_base64, width, compression) == expected ) + + +# FIXME: use hypothesis for this +def create_tile_set(qty_of_tiles): + tile_set = objects.TileSet(None, None) + + if qty_of_tiles == 0: + return tile_set + + tiles = {} + + for tile_id in range(qty_of_tiles): + tiles[tile_id] = objects.Tile(tile_id) + + tile_set.tiles = tiles + + return tile_set + + +tile_by_gid = [ + (1, {1: create_tile_set(0)}, None), + (1, {1: create_tile_set(1)}, objects.Tile(0)), + (1, {1: create_tile_set(2)}, objects.Tile(0)), + (2, {1: create_tile_set(1)}, None), + (10, {1: create_tile_set(10)}, objects.Tile(9)), + (1, {1: create_tile_set(1), 2: create_tile_set(1)}, objects.Tile(0)), + (2, {1: create_tile_set(1), 2: create_tile_set(1)}, objects.Tile(0)), + (3, {1: create_tile_set(1), 2: create_tile_set(1)}, None), + (15, {1: create_tile_set(5), 6: create_tile_set(10)}, objects.Tile(9)), + ( + 20, + { + 1: create_tile_set(5), + 6: create_tile_set(10), + 16: create_tile_set(10), + }, + objects.Tile(4), + ), +] + + +@pytest.mark.parametrize("gid,tile_sets,expected", tile_by_gid) +def test_get_tile_by_gid(gid, tile_sets, expected): + assert utilities.get_tile_by_gid(gid, tile_sets) == expected diff --git a/tests/unit2/test_pytiled_parser_integration.py.bax b/tests/test_pytiled_parser_integration.py similarity index 93% rename from tests/unit2/test_pytiled_parser_integration.py.bax rename to tests/test_pytiled_parser_integration.py index 904548a..433dad6 100644 --- a/tests/unit2/test_pytiled_parser_integration.py.bax +++ b/tests/test_pytiled_parser_integration.py @@ -1,9 +1,7 @@ import os - from pathlib import Path import pytest - import pytiled_parser print(os.path.dirname(os.path.abspath(__file__))) @@ -15,13 +13,11 @@ def test_map_simple(): """ TMX with a very simple spritesheet tile set and some properties. """ - map = pytiled_parser.parse_tile_map( - Path("../test_data/test_map_simple.tmx") - ) + map = pytiled_parser.parse_tile_map(Path("test_data/test_map_simple.tmx")) # map # unsure how to get paths to compare propperly - assert str(map.parent_dir) == "../test_data" + assert str(map.parent_dir) == "test_data" assert map.version == "1.2" assert map.tiled_version == "1.2.3" assert map.orientation == "orthogonal" @@ -41,7 +37,7 @@ def test_map_simple(): assert map.properties == { "bool property - false": False, "bool property - true": True, - "color property": (0x49, 0xFC, 0xFF, 0xFF), + "color property": "#ff49fcff", "file property": Path("/var/log/syslog"), "float property": 1.23456789, "int property": 13,