amulet-core 1.9.19__py3-none-any.whl → 1.9.20__py3-none-any.whl
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.
Potentially problematic release.
This version of amulet-core might be problematic. Click here for more details.
- amulet/__init__.py +27 -27
- amulet/__pyinstaller/__init__.py +2 -2
- amulet/__pyinstaller/hook-amulet.py +4 -4
- amulet/_version.py +21 -21
- amulet/api/__init__.py +2 -2
- amulet/api/abstract_base_entity.py +128 -128
- amulet/api/block.py +630 -630
- amulet/api/block_entity.py +71 -71
- amulet/api/cache.py +107 -107
- amulet/api/chunk/__init__.py +6 -6
- amulet/api/chunk/biomes.py +207 -207
- amulet/api/chunk/block_entity_dict.py +175 -175
- amulet/api/chunk/blocks.py +46 -46
- amulet/api/chunk/chunk.py +389 -389
- amulet/api/chunk/entity_list.py +75 -75
- amulet/api/chunk/status.py +167 -167
- amulet/api/data_types/__init__.py +4 -4
- amulet/api/data_types/generic_types.py +4 -4
- amulet/api/data_types/operation_types.py +16 -16
- amulet/api/data_types/world_types.py +49 -49
- amulet/api/data_types/wrapper_types.py +71 -71
- amulet/api/entity.py +74 -74
- amulet/api/errors.py +119 -119
- amulet/api/history/__init__.py +36 -36
- amulet/api/history/base/__init__.py +3 -3
- amulet/api/history/base/base_history.py +26 -26
- amulet/api/history/base/history_manager.py +63 -63
- amulet/api/history/base/revision_manager.py +73 -73
- amulet/api/history/changeable.py +15 -15
- amulet/api/history/data_types.py +7 -7
- amulet/api/history/history_manager/__init__.py +3 -3
- amulet/api/history/history_manager/container.py +102 -102
- amulet/api/history/history_manager/database.py +279 -279
- amulet/api/history/history_manager/meta.py +93 -93
- amulet/api/history/history_manager/object.py +116 -116
- amulet/api/history/revision_manager/__init__.py +2 -2
- amulet/api/history/revision_manager/disk.py +33 -33
- amulet/api/history/revision_manager/ram.py +12 -12
- amulet/api/item.py +75 -75
- amulet/api/level/__init__.py +4 -4
- amulet/api/level/base_level/__init__.py +1 -1
- amulet/api/level/base_level/base_level.py +1035 -1026
- amulet/api/level/base_level/chunk_manager.py +227 -227
- amulet/api/level/base_level/clone.py +389 -389
- amulet/api/level/base_level/player_manager.py +101 -101
- amulet/api/level/immutable_structure/__init__.py +1 -1
- amulet/api/level/immutable_structure/immutable_structure.py +94 -94
- amulet/api/level/immutable_structure/void_format_wrapper.py +117 -117
- amulet/api/level/structure.py +22 -22
- amulet/api/level/world.py +19 -19
- amulet/api/partial_3d_array/__init__.py +2 -2
- amulet/api/partial_3d_array/base_partial_3d_array.py +263 -263
- amulet/api/partial_3d_array/bounded_partial_3d_array.py +528 -528
- amulet/api/partial_3d_array/data_types.py +15 -15
- amulet/api/partial_3d_array/unbounded_partial_3d_array.py +229 -229
- amulet/api/partial_3d_array/util.py +152 -152
- amulet/api/player.py +65 -65
- amulet/api/registry/__init__.py +2 -2
- amulet/api/registry/base_registry.py +34 -34
- amulet/api/registry/biome_manager.py +153 -153
- amulet/api/registry/block_manager.py +156 -156
- amulet/api/selection/__init__.py +2 -2
- amulet/api/selection/abstract_selection.py +315 -315
- amulet/api/selection/box.py +805 -805
- amulet/api/selection/group.py +488 -488
- amulet/api/structure.py +37 -37
- amulet/api/wrapper/__init__.py +8 -8
- amulet/api/wrapper/chunk/interface.py +441 -441
- amulet/api/wrapper/chunk/translator.py +567 -567
- amulet/api/wrapper/format_wrapper.py +772 -772
- amulet/api/wrapper/structure_format_wrapper.py +116 -116
- amulet/api/wrapper/world_format_wrapper.py +63 -63
- amulet/level/__init__.py +1 -1
- amulet/level/formats/anvil_forge_world.py +40 -40
- amulet/level/formats/anvil_world/__init__.py +3 -3
- amulet/level/formats/anvil_world/_sector_manager.py +291 -384
- amulet/level/formats/anvil_world/data_pack/__init__.py +2 -2
- amulet/level/formats/anvil_world/data_pack/data_pack.py +224 -224
- amulet/level/formats/anvil_world/data_pack/data_pack_manager.py +77 -77
- amulet/level/formats/anvil_world/dimension.py +177 -177
- amulet/level/formats/anvil_world/format.py +769 -769
- amulet/level/formats/anvil_world/region.py +384 -384
- amulet/level/formats/construction/__init__.py +3 -3
- amulet/level/formats/construction/format_wrapper.py +515 -515
- amulet/level/formats/construction/interface.py +134 -134
- amulet/level/formats/construction/section.py +60 -60
- amulet/level/formats/construction/util.py +165 -165
- amulet/level/formats/leveldb_world/__init__.py +3 -3
- amulet/level/formats/leveldb_world/chunk.py +33 -33
- amulet/level/formats/leveldb_world/dimension.py +385 -419
- amulet/level/formats/leveldb_world/format.py +659 -641
- amulet/level/formats/leveldb_world/interface/chunk/__init__.py +36 -36
- amulet/level/formats/leveldb_world/interface/chunk/base_leveldb_interface.py +836 -836
- amulet/level/formats/leveldb_world/interface/chunk/generate_interface.py +31 -31
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_0.py +30 -30
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_1.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_10.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_11.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_12.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_13.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_14.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_15.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_16.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_17.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_18.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_19.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_2.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_20.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_21.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_22.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_23.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_24.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_25.py +24 -24
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_26.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_27.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_28.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_29.py +33 -33
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_3.py +57 -57
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_30.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_31.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_32.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_33.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_34.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_35.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_36.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_37.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_38.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_39.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_4.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_40.py +16 -16
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_5.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_6.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_7.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_8.py +180 -180
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_9.py +18 -18
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_chunk_versions.py +79 -79
- amulet/level/formats/mcstructure/__init__.py +3 -3
- amulet/level/formats/mcstructure/chunk.py +50 -50
- amulet/level/formats/mcstructure/format_wrapper.py +408 -408
- amulet/level/formats/mcstructure/interface.py +175 -175
- amulet/level/formats/schematic/__init__.py +3 -3
- amulet/level/formats/schematic/chunk.py +55 -55
- amulet/level/formats/schematic/data_types.py +4 -4
- amulet/level/formats/schematic/format_wrapper.py +373 -373
- amulet/level/formats/schematic/interface.py +142 -142
- amulet/level/formats/sponge_schem/__init__.py +4 -4
- amulet/level/formats/sponge_schem/chunk.py +62 -62
- amulet/level/formats/sponge_schem/format_wrapper.py +463 -463
- amulet/level/formats/sponge_schem/interface.py +118 -118
- amulet/level/formats/sponge_schem/varint/__init__.py +1 -1
- amulet/level/formats/sponge_schem/varint/varint.py +87 -87
- amulet/level/interfaces/chunk/anvil/anvil_0.py +72 -72
- amulet/level/interfaces/chunk/anvil/anvil_1444.py +336 -336
- amulet/level/interfaces/chunk/anvil/anvil_1466.py +94 -94
- amulet/level/interfaces/chunk/anvil/anvil_1467.py +37 -37
- amulet/level/interfaces/chunk/anvil/anvil_1484.py +20 -20
- amulet/level/interfaces/chunk/anvil/anvil_1503.py +20 -20
- amulet/level/interfaces/chunk/anvil/anvil_1519.py +34 -34
- amulet/level/interfaces/chunk/anvil/anvil_1901.py +20 -20
- amulet/level/interfaces/chunk/anvil/anvil_1908.py +20 -20
- amulet/level/interfaces/chunk/anvil/anvil_1912.py +21 -21
- amulet/level/interfaces/chunk/anvil/anvil_1934.py +20 -20
- amulet/level/interfaces/chunk/anvil/anvil_2203.py +69 -69
- amulet/level/interfaces/chunk/anvil/anvil_2529.py +19 -19
- amulet/level/interfaces/chunk/anvil/anvil_2681.py +76 -76
- amulet/level/interfaces/chunk/anvil/anvil_2709.py +19 -19
- amulet/level/interfaces/chunk/anvil/anvil_2844.py +267 -267
- amulet/level/interfaces/chunk/anvil/anvil_3463.py +19 -19
- amulet/level/interfaces/chunk/anvil/anvil_na.py +607 -607
- amulet/level/interfaces/chunk/anvil/base_anvil_interface.py +326 -326
- amulet/level/load.py +59 -59
- amulet/level/loader.py +95 -95
- amulet/level/translators/chunk/bedrock/__init__.py +267 -267
- amulet/level/translators/chunk/bedrock/bedrock_nbt_blockstate_translator.py +46 -46
- amulet/level/translators/chunk/bedrock/bedrock_numerical_translator.py +39 -39
- amulet/level/translators/chunk/bedrock/bedrock_psudo_numerical_translator.py +37 -37
- amulet/level/translators/chunk/java/java_1_18_translator.py +40 -40
- amulet/level/translators/chunk/java/java_blockstate_translator.py +94 -94
- amulet/level/translators/chunk/java/java_numerical_translator.py +62 -62
- amulet/libs/leveldb/__init__.py +7 -7
- amulet/operations/__init__.py +5 -5
- amulet/operations/clone.py +18 -18
- amulet/operations/delete_chunk.py +32 -32
- amulet/operations/fill.py +30 -30
- amulet/operations/paste.py +65 -65
- amulet/operations/replace.py +58 -58
- amulet/utils/__init__.py +14 -14
- amulet/utils/format_utils.py +41 -41
- amulet/utils/generator.py +15 -15
- amulet/utils/matrix.py +243 -243
- amulet/utils/numpy_helpers.py +46 -46
- amulet/utils/world_utils.py +349 -349
- {amulet_core-1.9.19.dist-info → amulet_core-1.9.20.dist-info}/METADATA +97 -97
- amulet_core-1.9.20.dist-info/RECORD +208 -0
- amulet_core-1.9.19.dist-info/RECORD +0 -208
- {amulet_core-1.9.19.dist-info → amulet_core-1.9.20.dist-info}/WHEEL +0 -0
- {amulet_core-1.9.19.dist-info → amulet_core-1.9.20.dist-info}/entry_points.txt +0 -0
- {amulet_core-1.9.19.dist-info → amulet_core-1.9.20.dist-info}/top_level.txt +0 -0
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
from .data_pack import DataPack
|
|
2
|
-
from .data_pack_manager import DataPackManager
|
|
1
|
+
from .data_pack import DataPack
|
|
2
|
+
from .data_pack_manager import DataPackManager
|
|
@@ -1,224 +1,224 @@
|
|
|
1
|
-
from abc import ABC, abstractmethod
|
|
2
|
-
from typing import Iterable, BinaryIO
|
|
3
|
-
import os
|
|
4
|
-
import json
|
|
5
|
-
from zipfile import ZipFile
|
|
6
|
-
import re
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
class BaseWrapper(ABC):
|
|
10
|
-
def __init__(self, path: str):
|
|
11
|
-
self._path = path
|
|
12
|
-
|
|
13
|
-
@staticmethod
|
|
14
|
-
@abstractmethod
|
|
15
|
-
def is_valid(path: str) -> bool:
|
|
16
|
-
"""
|
|
17
|
-
Is the given path valid for this class.
|
|
18
|
-
|
|
19
|
-
:param path: The path to test.
|
|
20
|
-
"""
|
|
21
|
-
|
|
22
|
-
@property
|
|
23
|
-
def path(self) -> str:
|
|
24
|
-
"""The path to the data."""
|
|
25
|
-
return self._path
|
|
26
|
-
|
|
27
|
-
@property
|
|
28
|
-
@abstractmethod
|
|
29
|
-
def all_files(self) -> Iterable[str]:
|
|
30
|
-
"""
|
|
31
|
-
The relative paths of all files contained within.
|
|
32
|
-
|
|
33
|
-
:return: An iterable of paths.
|
|
34
|
-
"""
|
|
35
|
-
raise NotImplementedError
|
|
36
|
-
|
|
37
|
-
def all_files_match(self, match: str) -> Iterable[str]:
|
|
38
|
-
"""
|
|
39
|
-
The relative paths of all files contained within that match the given regex.
|
|
40
|
-
|
|
41
|
-
:param match: The regex string to match again.
|
|
42
|
-
:return: An iterable of file paths that match the given regex.
|
|
43
|
-
"""
|
|
44
|
-
re_match = re.compile(match)
|
|
45
|
-
return tuple(path for path in self.all_files if re_match.fullmatch(path))
|
|
46
|
-
|
|
47
|
-
@abstractmethod
|
|
48
|
-
def has_file(self, relative_path: str) -> bool:
|
|
49
|
-
"""
|
|
50
|
-
Does the requested file exist.
|
|
51
|
-
|
|
52
|
-
:param relative_path:
|
|
53
|
-
:return:
|
|
54
|
-
"""
|
|
55
|
-
raise NotImplementedError
|
|
56
|
-
|
|
57
|
-
@abstractmethod
|
|
58
|
-
def open(self, relative_path: str, **kwargs) -> BinaryIO:
|
|
59
|
-
"""
|
|
60
|
-
Get the contents of the file.
|
|
61
|
-
|
|
62
|
-
:param relative_path:
|
|
63
|
-
:return:
|
|
64
|
-
"""
|
|
65
|
-
raise NotImplementedError
|
|
66
|
-
|
|
67
|
-
@abstractmethod
|
|
68
|
-
def close(self):
|
|
69
|
-
"""Close the contents."""
|
|
70
|
-
raise NotImplementedError
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
class ZipWrapper(BaseWrapper, ZipFile):
|
|
74
|
-
def __init__(self, path: str):
|
|
75
|
-
BaseWrapper.__init__(self, path)
|
|
76
|
-
ZipFile.__init__(self, path)
|
|
77
|
-
|
|
78
|
-
@staticmethod
|
|
79
|
-
def is_valid(path: str) -> bool:
|
|
80
|
-
return os.path.isfile(path) and path.endswith(".zip")
|
|
81
|
-
|
|
82
|
-
@property
|
|
83
|
-
def all_files(self) -> Iterable[str]:
|
|
84
|
-
for path in self.NameToInfo:
|
|
85
|
-
if not path.endswith("/"):
|
|
86
|
-
yield path
|
|
87
|
-
|
|
88
|
-
def has_file(self, relative_path: str) -> bool:
|
|
89
|
-
if relative_path.endswith("/"):
|
|
90
|
-
# is a directory
|
|
91
|
-
return False
|
|
92
|
-
else:
|
|
93
|
-
return relative_path in self.NameToInfo
|
|
94
|
-
|
|
95
|
-
open = ZipFile.open
|
|
96
|
-
close = ZipFile.close
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
class DirWrapper(BaseWrapper):
|
|
100
|
-
def __init__(self, path: str):
|
|
101
|
-
BaseWrapper.__init__(self, path)
|
|
102
|
-
|
|
103
|
-
@staticmethod
|
|
104
|
-
def is_valid(path: str) -> bool:
|
|
105
|
-
return os.path.isdir(path)
|
|
106
|
-
|
|
107
|
-
@property
|
|
108
|
-
def all_files(self) -> Iterable[str]:
|
|
109
|
-
for abs_path, _, files in os.walk(self.path):
|
|
110
|
-
rel_path = os.path.normpath(os.path.relpath(abs_path, self.path)).replace(
|
|
111
|
-
os.sep, "/"
|
|
112
|
-
)
|
|
113
|
-
if rel_path == ".":
|
|
114
|
-
yield from files
|
|
115
|
-
else:
|
|
116
|
-
for f in files:
|
|
117
|
-
yield f"{rel_path}/{f}"
|
|
118
|
-
|
|
119
|
-
def has_file(self, relative_path: str) -> bool:
|
|
120
|
-
return os.path.isfile(os.path.join(self.path, relative_path))
|
|
121
|
-
|
|
122
|
-
def open(self, relative_path: str, **kwargs) -> BinaryIO:
|
|
123
|
-
return open(os.path.join(self.path, relative_path), "rb", **kwargs)
|
|
124
|
-
|
|
125
|
-
def close(self):
|
|
126
|
-
pass
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
def _open_wrapper(path: str) -> BaseWrapper:
|
|
130
|
-
for cls in (ZipWrapper, DirWrapper):
|
|
131
|
-
if cls.is_valid(path):
|
|
132
|
-
return cls(path)
|
|
133
|
-
raise FileNotFoundError(f"The given path {path} is not valid.")
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
class DataPack:
|
|
137
|
-
"""The DataPack class wraps a single data pack."""
|
|
138
|
-
|
|
139
|
-
def __init__(self, path: str):
|
|
140
|
-
self._path = path
|
|
141
|
-
self._wrapper = _open_wrapper(path)
|
|
142
|
-
self._is_valid = self.is_wrapper_valid(self._wrapper)
|
|
143
|
-
|
|
144
|
-
@property
|
|
145
|
-
def path(self) -> str:
|
|
146
|
-
"""The path to the data."""
|
|
147
|
-
return self._path
|
|
148
|
-
|
|
149
|
-
@staticmethod
|
|
150
|
-
def is_path_valid(path: str) -> bool:
|
|
151
|
-
"""
|
|
152
|
-
Check if the given path is a valid data pack.
|
|
153
|
-
|
|
154
|
-
:param path: The path to the data pack. Can be a zip file or directory.
|
|
155
|
-
:return: True if the path is a valid data pack, False otherwise.
|
|
156
|
-
"""
|
|
157
|
-
try:
|
|
158
|
-
wrapper = _open_wrapper(path)
|
|
159
|
-
except FileNotFoundError:
|
|
160
|
-
return False
|
|
161
|
-
is_valid = DataPack.is_wrapper_valid(wrapper)
|
|
162
|
-
wrapper.close()
|
|
163
|
-
return is_valid
|
|
164
|
-
|
|
165
|
-
@property
|
|
166
|
-
def is_valid(self) -> bool:
|
|
167
|
-
return self._is_valid
|
|
168
|
-
|
|
169
|
-
@staticmethod
|
|
170
|
-
def is_wrapper_valid(wrapper: BaseWrapper):
|
|
171
|
-
if wrapper.has_file("pack.mcmeta"):
|
|
172
|
-
with wrapper.open("pack.mcmeta") as m:
|
|
173
|
-
try:
|
|
174
|
-
meta_file = json.load(m)
|
|
175
|
-
except json.JSONDecodeError:
|
|
176
|
-
pass
|
|
177
|
-
else:
|
|
178
|
-
if isinstance(meta_file, dict) and isinstance(
|
|
179
|
-
meta_file.get("pack", {}).get("pack_format", None), int
|
|
180
|
-
):
|
|
181
|
-
# TODO: check the actual value
|
|
182
|
-
return True
|
|
183
|
-
return False
|
|
184
|
-
|
|
185
|
-
@property
|
|
186
|
-
def all_files(self) -> Iterable[str]:
|
|
187
|
-
"""
|
|
188
|
-
The relative paths of all files contained within.
|
|
189
|
-
|
|
190
|
-
:return: An iterable of paths.
|
|
191
|
-
"""
|
|
192
|
-
return self._wrapper.all_files
|
|
193
|
-
|
|
194
|
-
def all_files_match(self, match: str) -> Iterable[str]:
|
|
195
|
-
"""
|
|
196
|
-
The relative paths of all files contained within that match the given regex.
|
|
197
|
-
|
|
198
|
-
:param match: The regex string to match again.
|
|
199
|
-
:return: An iterable of file paths that match the given regex.
|
|
200
|
-
"""
|
|
201
|
-
return self._wrapper.all_files_match(match)
|
|
202
|
-
|
|
203
|
-
def has_file(self, relative_path: str) -> bool:
|
|
204
|
-
"""
|
|
205
|
-
Does the requested file exist.
|
|
206
|
-
|
|
207
|
-
:param relative_path:
|
|
208
|
-
:return:
|
|
209
|
-
"""
|
|
210
|
-
return self._wrapper.has_file(relative_path)
|
|
211
|
-
|
|
212
|
-
def open(self, relative_path: str) -> BinaryIO:
|
|
213
|
-
"""
|
|
214
|
-
Get the contents of the file.
|
|
215
|
-
|
|
216
|
-
:param relative_path:
|
|
217
|
-
:return:
|
|
218
|
-
"""
|
|
219
|
-
return self._wrapper.open(relative_path)
|
|
220
|
-
|
|
221
|
-
@abstractmethod
|
|
222
|
-
def close(self):
|
|
223
|
-
"""Close the contents."""
|
|
224
|
-
self._wrapper.close()
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from typing import Iterable, BinaryIO
|
|
3
|
+
import os
|
|
4
|
+
import json
|
|
5
|
+
from zipfile import ZipFile
|
|
6
|
+
import re
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class BaseWrapper(ABC):
|
|
10
|
+
def __init__(self, path: str):
|
|
11
|
+
self._path = path
|
|
12
|
+
|
|
13
|
+
@staticmethod
|
|
14
|
+
@abstractmethod
|
|
15
|
+
def is_valid(path: str) -> bool:
|
|
16
|
+
"""
|
|
17
|
+
Is the given path valid for this class.
|
|
18
|
+
|
|
19
|
+
:param path: The path to test.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
@property
|
|
23
|
+
def path(self) -> str:
|
|
24
|
+
"""The path to the data."""
|
|
25
|
+
return self._path
|
|
26
|
+
|
|
27
|
+
@property
|
|
28
|
+
@abstractmethod
|
|
29
|
+
def all_files(self) -> Iterable[str]:
|
|
30
|
+
"""
|
|
31
|
+
The relative paths of all files contained within.
|
|
32
|
+
|
|
33
|
+
:return: An iterable of paths.
|
|
34
|
+
"""
|
|
35
|
+
raise NotImplementedError
|
|
36
|
+
|
|
37
|
+
def all_files_match(self, match: str) -> Iterable[str]:
|
|
38
|
+
"""
|
|
39
|
+
The relative paths of all files contained within that match the given regex.
|
|
40
|
+
|
|
41
|
+
:param match: The regex string to match again.
|
|
42
|
+
:return: An iterable of file paths that match the given regex.
|
|
43
|
+
"""
|
|
44
|
+
re_match = re.compile(match)
|
|
45
|
+
return tuple(path for path in self.all_files if re_match.fullmatch(path))
|
|
46
|
+
|
|
47
|
+
@abstractmethod
|
|
48
|
+
def has_file(self, relative_path: str) -> bool:
|
|
49
|
+
"""
|
|
50
|
+
Does the requested file exist.
|
|
51
|
+
|
|
52
|
+
:param relative_path:
|
|
53
|
+
:return:
|
|
54
|
+
"""
|
|
55
|
+
raise NotImplementedError
|
|
56
|
+
|
|
57
|
+
@abstractmethod
|
|
58
|
+
def open(self, relative_path: str, **kwargs) -> BinaryIO:
|
|
59
|
+
"""
|
|
60
|
+
Get the contents of the file.
|
|
61
|
+
|
|
62
|
+
:param relative_path:
|
|
63
|
+
:return:
|
|
64
|
+
"""
|
|
65
|
+
raise NotImplementedError
|
|
66
|
+
|
|
67
|
+
@abstractmethod
|
|
68
|
+
def close(self):
|
|
69
|
+
"""Close the contents."""
|
|
70
|
+
raise NotImplementedError
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class ZipWrapper(BaseWrapper, ZipFile):
|
|
74
|
+
def __init__(self, path: str):
|
|
75
|
+
BaseWrapper.__init__(self, path)
|
|
76
|
+
ZipFile.__init__(self, path)
|
|
77
|
+
|
|
78
|
+
@staticmethod
|
|
79
|
+
def is_valid(path: str) -> bool:
|
|
80
|
+
return os.path.isfile(path) and path.endswith(".zip")
|
|
81
|
+
|
|
82
|
+
@property
|
|
83
|
+
def all_files(self) -> Iterable[str]:
|
|
84
|
+
for path in self.NameToInfo:
|
|
85
|
+
if not path.endswith("/"):
|
|
86
|
+
yield path
|
|
87
|
+
|
|
88
|
+
def has_file(self, relative_path: str) -> bool:
|
|
89
|
+
if relative_path.endswith("/"):
|
|
90
|
+
# is a directory
|
|
91
|
+
return False
|
|
92
|
+
else:
|
|
93
|
+
return relative_path in self.NameToInfo
|
|
94
|
+
|
|
95
|
+
open = ZipFile.open
|
|
96
|
+
close = ZipFile.close
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class DirWrapper(BaseWrapper):
|
|
100
|
+
def __init__(self, path: str):
|
|
101
|
+
BaseWrapper.__init__(self, path)
|
|
102
|
+
|
|
103
|
+
@staticmethod
|
|
104
|
+
def is_valid(path: str) -> bool:
|
|
105
|
+
return os.path.isdir(path)
|
|
106
|
+
|
|
107
|
+
@property
|
|
108
|
+
def all_files(self) -> Iterable[str]:
|
|
109
|
+
for abs_path, _, files in os.walk(self.path):
|
|
110
|
+
rel_path = os.path.normpath(os.path.relpath(abs_path, self.path)).replace(
|
|
111
|
+
os.sep, "/"
|
|
112
|
+
)
|
|
113
|
+
if rel_path == ".":
|
|
114
|
+
yield from files
|
|
115
|
+
else:
|
|
116
|
+
for f in files:
|
|
117
|
+
yield f"{rel_path}/{f}"
|
|
118
|
+
|
|
119
|
+
def has_file(self, relative_path: str) -> bool:
|
|
120
|
+
return os.path.isfile(os.path.join(self.path, relative_path))
|
|
121
|
+
|
|
122
|
+
def open(self, relative_path: str, **kwargs) -> BinaryIO:
|
|
123
|
+
return open(os.path.join(self.path, relative_path), "rb", **kwargs)
|
|
124
|
+
|
|
125
|
+
def close(self):
|
|
126
|
+
pass
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def _open_wrapper(path: str) -> BaseWrapper:
|
|
130
|
+
for cls in (ZipWrapper, DirWrapper):
|
|
131
|
+
if cls.is_valid(path):
|
|
132
|
+
return cls(path)
|
|
133
|
+
raise FileNotFoundError(f"The given path {path} is not valid.")
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
class DataPack:
|
|
137
|
+
"""The DataPack class wraps a single data pack."""
|
|
138
|
+
|
|
139
|
+
def __init__(self, path: str):
|
|
140
|
+
self._path = path
|
|
141
|
+
self._wrapper = _open_wrapper(path)
|
|
142
|
+
self._is_valid = self.is_wrapper_valid(self._wrapper)
|
|
143
|
+
|
|
144
|
+
@property
|
|
145
|
+
def path(self) -> str:
|
|
146
|
+
"""The path to the data."""
|
|
147
|
+
return self._path
|
|
148
|
+
|
|
149
|
+
@staticmethod
|
|
150
|
+
def is_path_valid(path: str) -> bool:
|
|
151
|
+
"""
|
|
152
|
+
Check if the given path is a valid data pack.
|
|
153
|
+
|
|
154
|
+
:param path: The path to the data pack. Can be a zip file or directory.
|
|
155
|
+
:return: True if the path is a valid data pack, False otherwise.
|
|
156
|
+
"""
|
|
157
|
+
try:
|
|
158
|
+
wrapper = _open_wrapper(path)
|
|
159
|
+
except FileNotFoundError:
|
|
160
|
+
return False
|
|
161
|
+
is_valid = DataPack.is_wrapper_valid(wrapper)
|
|
162
|
+
wrapper.close()
|
|
163
|
+
return is_valid
|
|
164
|
+
|
|
165
|
+
@property
|
|
166
|
+
def is_valid(self) -> bool:
|
|
167
|
+
return self._is_valid
|
|
168
|
+
|
|
169
|
+
@staticmethod
|
|
170
|
+
def is_wrapper_valid(wrapper: BaseWrapper):
|
|
171
|
+
if wrapper.has_file("pack.mcmeta"):
|
|
172
|
+
with wrapper.open("pack.mcmeta") as m:
|
|
173
|
+
try:
|
|
174
|
+
meta_file = json.load(m)
|
|
175
|
+
except json.JSONDecodeError:
|
|
176
|
+
pass
|
|
177
|
+
else:
|
|
178
|
+
if isinstance(meta_file, dict) and isinstance(
|
|
179
|
+
meta_file.get("pack", {}).get("pack_format", None), int
|
|
180
|
+
):
|
|
181
|
+
# TODO: check the actual value
|
|
182
|
+
return True
|
|
183
|
+
return False
|
|
184
|
+
|
|
185
|
+
@property
|
|
186
|
+
def all_files(self) -> Iterable[str]:
|
|
187
|
+
"""
|
|
188
|
+
The relative paths of all files contained within.
|
|
189
|
+
|
|
190
|
+
:return: An iterable of paths.
|
|
191
|
+
"""
|
|
192
|
+
return self._wrapper.all_files
|
|
193
|
+
|
|
194
|
+
def all_files_match(self, match: str) -> Iterable[str]:
|
|
195
|
+
"""
|
|
196
|
+
The relative paths of all files contained within that match the given regex.
|
|
197
|
+
|
|
198
|
+
:param match: The regex string to match again.
|
|
199
|
+
:return: An iterable of file paths that match the given regex.
|
|
200
|
+
"""
|
|
201
|
+
return self._wrapper.all_files_match(match)
|
|
202
|
+
|
|
203
|
+
def has_file(self, relative_path: str) -> bool:
|
|
204
|
+
"""
|
|
205
|
+
Does the requested file exist.
|
|
206
|
+
|
|
207
|
+
:param relative_path:
|
|
208
|
+
:return:
|
|
209
|
+
"""
|
|
210
|
+
return self._wrapper.has_file(relative_path)
|
|
211
|
+
|
|
212
|
+
def open(self, relative_path: str) -> BinaryIO:
|
|
213
|
+
"""
|
|
214
|
+
Get the contents of the file.
|
|
215
|
+
|
|
216
|
+
:param relative_path:
|
|
217
|
+
:return:
|
|
218
|
+
"""
|
|
219
|
+
return self._wrapper.open(relative_path)
|
|
220
|
+
|
|
221
|
+
@abstractmethod
|
|
222
|
+
def close(self):
|
|
223
|
+
"""Close the contents."""
|
|
224
|
+
self._wrapper.close()
|
|
@@ -1,77 +1,77 @@
|
|
|
1
|
-
from typing import Iterable, BinaryIO
|
|
2
|
-
from .data_pack import DataPack
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
class DataPackManager:
|
|
6
|
-
"""
|
|
7
|
-
The DataPackManager class contains one or more data packs.
|
|
8
|
-
It manages loading them so that the stacking order is maintained.
|
|
9
|
-
"""
|
|
10
|
-
|
|
11
|
-
def __init__(self, data_packs: Iterable[DataPack]):
|
|
12
|
-
"""
|
|
13
|
-
Construct a new DataPackManager class.
|
|
14
|
-
|
|
15
|
-
:param data_packs: The data packs to load from. Later in the list get higher priority.
|
|
16
|
-
"""
|
|
17
|
-
self._data_packs = tuple(
|
|
18
|
-
reversed(
|
|
19
|
-
tuple(
|
|
20
|
-
pack
|
|
21
|
-
for pack in data_packs
|
|
22
|
-
if isinstance(pack, DataPack) and pack.is_valid
|
|
23
|
-
)
|
|
24
|
-
)
|
|
25
|
-
)
|
|
26
|
-
|
|
27
|
-
@property
|
|
28
|
-
def all_files(self) -> Iterable[str]:
|
|
29
|
-
"""
|
|
30
|
-
The relative paths of all files contained within.
|
|
31
|
-
|
|
32
|
-
:return: An iterable of paths.
|
|
33
|
-
"""
|
|
34
|
-
all_files = set()
|
|
35
|
-
for pack in self._data_packs:
|
|
36
|
-
all_files.update(pack.all_files)
|
|
37
|
-
return all_files
|
|
38
|
-
|
|
39
|
-
def all_files_match(self, match: str) -> Iterable[str]:
|
|
40
|
-
"""
|
|
41
|
-
The relative paths of all files contained within that match the given regex.
|
|
42
|
-
|
|
43
|
-
:param match: The regex string to match again.
|
|
44
|
-
:return: An iterable of file paths that match the given regex.
|
|
45
|
-
"""
|
|
46
|
-
all_files = set()
|
|
47
|
-
for pack in self._data_packs:
|
|
48
|
-
all_files.update(pack.all_files_match(match))
|
|
49
|
-
return all_files
|
|
50
|
-
|
|
51
|
-
def has_file(self, relative_path: str) -> bool:
|
|
52
|
-
"""
|
|
53
|
-
Does the requested file exist.
|
|
54
|
-
|
|
55
|
-
:param relative_path:
|
|
56
|
-
:return:
|
|
57
|
-
"""
|
|
58
|
-
return any(pack.has_file(relative_path) for pack in self._data_packs)
|
|
59
|
-
|
|
60
|
-
def open(self, relative_path: str) -> BinaryIO:
|
|
61
|
-
"""
|
|
62
|
-
Get the contents of the file.
|
|
63
|
-
|
|
64
|
-
:param relative_path:
|
|
65
|
-
:return:
|
|
66
|
-
"""
|
|
67
|
-
for pack in self._data_packs:
|
|
68
|
-
if pack.has_file(relative_path):
|
|
69
|
-
return pack.open(relative_path)
|
|
70
|
-
raise FileNotFoundError(
|
|
71
|
-
f"The requested path {relative_path} could not be found."
|
|
72
|
-
)
|
|
73
|
-
|
|
74
|
-
def close(self):
|
|
75
|
-
"""Close the contents."""
|
|
76
|
-
for pack in self._data_packs:
|
|
77
|
-
pack.close()
|
|
1
|
+
from typing import Iterable, BinaryIO
|
|
2
|
+
from .data_pack import DataPack
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class DataPackManager:
|
|
6
|
+
"""
|
|
7
|
+
The DataPackManager class contains one or more data packs.
|
|
8
|
+
It manages loading them so that the stacking order is maintained.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
def __init__(self, data_packs: Iterable[DataPack]):
|
|
12
|
+
"""
|
|
13
|
+
Construct a new DataPackManager class.
|
|
14
|
+
|
|
15
|
+
:param data_packs: The data packs to load from. Later in the list get higher priority.
|
|
16
|
+
"""
|
|
17
|
+
self._data_packs = tuple(
|
|
18
|
+
reversed(
|
|
19
|
+
tuple(
|
|
20
|
+
pack
|
|
21
|
+
for pack in data_packs
|
|
22
|
+
if isinstance(pack, DataPack) and pack.is_valid
|
|
23
|
+
)
|
|
24
|
+
)
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
@property
|
|
28
|
+
def all_files(self) -> Iterable[str]:
|
|
29
|
+
"""
|
|
30
|
+
The relative paths of all files contained within.
|
|
31
|
+
|
|
32
|
+
:return: An iterable of paths.
|
|
33
|
+
"""
|
|
34
|
+
all_files = set()
|
|
35
|
+
for pack in self._data_packs:
|
|
36
|
+
all_files.update(pack.all_files)
|
|
37
|
+
return all_files
|
|
38
|
+
|
|
39
|
+
def all_files_match(self, match: str) -> Iterable[str]:
|
|
40
|
+
"""
|
|
41
|
+
The relative paths of all files contained within that match the given regex.
|
|
42
|
+
|
|
43
|
+
:param match: The regex string to match again.
|
|
44
|
+
:return: An iterable of file paths that match the given regex.
|
|
45
|
+
"""
|
|
46
|
+
all_files = set()
|
|
47
|
+
for pack in self._data_packs:
|
|
48
|
+
all_files.update(pack.all_files_match(match))
|
|
49
|
+
return all_files
|
|
50
|
+
|
|
51
|
+
def has_file(self, relative_path: str) -> bool:
|
|
52
|
+
"""
|
|
53
|
+
Does the requested file exist.
|
|
54
|
+
|
|
55
|
+
:param relative_path:
|
|
56
|
+
:return:
|
|
57
|
+
"""
|
|
58
|
+
return any(pack.has_file(relative_path) for pack in self._data_packs)
|
|
59
|
+
|
|
60
|
+
def open(self, relative_path: str) -> BinaryIO:
|
|
61
|
+
"""
|
|
62
|
+
Get the contents of the file.
|
|
63
|
+
|
|
64
|
+
:param relative_path:
|
|
65
|
+
:return:
|
|
66
|
+
"""
|
|
67
|
+
for pack in self._data_packs:
|
|
68
|
+
if pack.has_file(relative_path):
|
|
69
|
+
return pack.open(relative_path)
|
|
70
|
+
raise FileNotFoundError(
|
|
71
|
+
f"The requested path {relative_path} could not be found."
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
def close(self):
|
|
75
|
+
"""Close the contents."""
|
|
76
|
+
for pack in self._data_packs:
|
|
77
|
+
pack.close()
|