koro 2.0.0rc2__py3-none-any.whl → 2.0.1__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.
- koro/__init__.py +8 -8
- koro/slot/__init__.py +21 -21
- koro/slot/bin.py +158 -155
- koro/slot/file.py +70 -67
- koro/slot/save.py +138 -135
- koro/slot/xml.py +845 -783
- koro/stage/__init__.py +99 -98
- koro/stage/model.py +288 -284
- koro/stage/part.py +1754 -1704
- {koro-2.0.0rc2.dist-info → koro-2.0.1.dist-info}/LICENSE +24 -24
- koro-2.0.1.dist-info/METADATA +52 -0
- koro-2.0.1.dist-info/RECORD +14 -0
- {koro-2.0.0rc2.dist-info → koro-2.0.1.dist-info}/WHEEL +1 -1
- koro-2.0.0rc2.dist-info/METADATA +0 -21
- koro-2.0.0rc2.dist-info/RECORD +0 -14
- {koro-2.0.0rc2.dist-info → koro-2.0.1.dist-info}/top_level.txt +0 -0
koro/__init__.py
CHANGED
@@ -1,8 +1,8 @@
|
|
1
|
-
from .slot import *
|
2
|
-
from .slot.bin import *
|
3
|
-
from .slot.file import *
|
4
|
-
from .slot.save import *
|
5
|
-
from .slot.xml import *
|
6
|
-
from .stage import *
|
7
|
-
from .stage.model import *
|
8
|
-
from .stage.part import *
|
1
|
+
from .slot import *
|
2
|
+
from .slot.bin import *
|
3
|
+
from .slot.file import *
|
4
|
+
from .slot.save import *
|
5
|
+
from .slot.xml import *
|
6
|
+
from .stage import *
|
7
|
+
from .stage.model import *
|
8
|
+
from .stage.part import *
|
koro/slot/__init__.py
CHANGED
@@ -1,21 +1,21 @@
|
|
1
|
-
from abc import ABC, abstractmethod
|
2
|
-
|
3
|
-
from ..stage import Stage
|
4
|
-
|
5
|
-
__all__ = ["Slot"]
|
6
|
-
|
7
|
-
|
8
|
-
class Slot(ABC):
|
9
|
-
__slots__ = ()
|
10
|
-
|
11
|
-
def __bool__(self) -> bool:
|
12
|
-
"""Return whether this slot is filled."""
|
13
|
-
return self.load() is not None
|
14
|
-
|
15
|
-
@abstractmethod
|
16
|
-
def load(self) -> Stage | None:
|
17
|
-
pass
|
18
|
-
|
19
|
-
@abstractmethod
|
20
|
-
def save(self, data: Stage | None, /) -> None:
|
21
|
-
pass
|
1
|
+
from abc import ABC, abstractmethod
|
2
|
+
|
3
|
+
from ..stage import Stage
|
4
|
+
|
5
|
+
__all__ = ["Slot"]
|
6
|
+
|
7
|
+
|
8
|
+
class Slot(ABC):
|
9
|
+
__slots__ = ()
|
10
|
+
|
11
|
+
def __bool__(self) -> bool:
|
12
|
+
"""Return whether this slot is filled."""
|
13
|
+
return self.load() is not None
|
14
|
+
|
15
|
+
@abstractmethod
|
16
|
+
def load(self) -> Stage | None:
|
17
|
+
pass
|
18
|
+
|
19
|
+
@abstractmethod
|
20
|
+
def save(self, data: Stage | None, /) -> None:
|
21
|
+
pass
|
koro/slot/bin.py
CHANGED
@@ -1,155 +1,158 @@
|
|
1
|
-
from itertools import chain
|
2
|
-
from typing import Final
|
3
|
-
|
4
|
-
from ..stage import Stage
|
5
|
-
from .file import FileSlot
|
6
|
-
from .xml import XmlSlot
|
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
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
buffer[:
|
99
|
-
data_index + 1024 - buffer_index
|
100
|
-
]
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
1
|
+
from itertools import chain
|
2
|
+
from typing import Final
|
3
|
+
|
4
|
+
from ..stage import Stage
|
5
|
+
from .file import FileSlot
|
6
|
+
from .xml import XmlSlot
|
7
|
+
|
8
|
+
|
9
|
+
__all__ = ["BinSlot"]
|
10
|
+
|
11
|
+
|
12
|
+
class BinSlot(FileSlot):
|
13
|
+
__slots__ = ()
|
14
|
+
|
15
|
+
@staticmethod
|
16
|
+
def compress(data: bytes, /) -> bytes:
|
17
|
+
buffer: bytearray = bytearray(1024)
|
18
|
+
buffer_index: int = 958
|
19
|
+
chunk: bytearray
|
20
|
+
data_index: int = 0
|
21
|
+
output: Final[bytearray] = bytearray(
|
22
|
+
b"\x00\x00\x00\x01\x00\x00\x00\x08"
|
23
|
+
+ len(data).to_bytes(4, byteorder="big")
|
24
|
+
+ b"\x00\x00\x00\x01"
|
25
|
+
)
|
26
|
+
reference_indices: list[int]
|
27
|
+
test_buffer: bytearray
|
28
|
+
test_length: int
|
29
|
+
test_reference_indicies: list[int]
|
30
|
+
while data_index < len(data):
|
31
|
+
chunk = bytearray(b"\x00")
|
32
|
+
for bit in range(8):
|
33
|
+
if data_index >= len(data):
|
34
|
+
chunk[0] >>= 8 - bit
|
35
|
+
output.extend(chunk)
|
36
|
+
return output + b"\x00" * (len(output) & 1)
|
37
|
+
if len(data) - data_index <= 2:
|
38
|
+
buffer[buffer_index] = data[data_index]
|
39
|
+
buffer_index = buffer_index + 1 & 1023
|
40
|
+
chunk[0] = chunk[0] >> 1 | 128
|
41
|
+
chunk.append(data[data_index])
|
42
|
+
data_index += 1
|
43
|
+
continue
|
44
|
+
reference_indices = []
|
45
|
+
for i in chain(range(buffer_index, 1024), range(buffer_index)):
|
46
|
+
if data[data_index] == buffer[i]:
|
47
|
+
reference_indices.append(i)
|
48
|
+
if not reference_indices:
|
49
|
+
buffer[buffer_index] = data[data_index]
|
50
|
+
buffer_index = buffer_index + 1 & 1023
|
51
|
+
chunk[0] = chunk[0] >> 1 | 128
|
52
|
+
chunk.append(data[data_index])
|
53
|
+
data_index += 1
|
54
|
+
continue
|
55
|
+
test_buffer = buffer.copy()
|
56
|
+
test_buffer[buffer_index] = data[data_index]
|
57
|
+
for i in reference_indices.copy():
|
58
|
+
if data[data_index + 1] != test_buffer[i - 1023]:
|
59
|
+
reference_indices.remove(i)
|
60
|
+
if not reference_indices:
|
61
|
+
buffer[buffer_index] = data[data_index]
|
62
|
+
buffer_index = buffer_index + 1 & 1023
|
63
|
+
chunk[0] = chunk[0] >> 1 | 128
|
64
|
+
chunk.append(data[data_index])
|
65
|
+
data_index += 1
|
66
|
+
continue
|
67
|
+
test_buffer[buffer_index - 1023] = data[data_index + 1]
|
68
|
+
for i in reference_indices.copy():
|
69
|
+
if data[data_index + 2] != test_buffer[i - 1022]:
|
70
|
+
reference_indices.remove(i)
|
71
|
+
if not reference_indices:
|
72
|
+
buffer[buffer_index] = data[data_index]
|
73
|
+
buffer_index = buffer_index + 1 & 1023
|
74
|
+
chunk[0] = chunk[0] >> 1 | 128
|
75
|
+
chunk.append(data[data_index])
|
76
|
+
data_index += 1
|
77
|
+
continue
|
78
|
+
test_length = 4
|
79
|
+
test_reference_indicies = reference_indices.copy()
|
80
|
+
while test_length <= min(66, len(data) - data_index):
|
81
|
+
test_buffer[buffer_index + test_length - 1026] = data[
|
82
|
+
data_index + test_length - 2
|
83
|
+
]
|
84
|
+
for i in test_reference_indicies.copy():
|
85
|
+
if (
|
86
|
+
data[data_index + test_length - 1]
|
87
|
+
!= test_buffer[i + test_length - 1025]
|
88
|
+
):
|
89
|
+
test_reference_indicies.remove(i)
|
90
|
+
if test_reference_indicies:
|
91
|
+
reference_indices = test_reference_indicies.copy()
|
92
|
+
else:
|
93
|
+
break
|
94
|
+
test_length += 1
|
95
|
+
chunk[0] >>= 1
|
96
|
+
test_length -= 1
|
97
|
+
if buffer_index + test_length >= 1024:
|
98
|
+
buffer[buffer_index:] = data[
|
99
|
+
data_index : data_index + 1024 - buffer_index
|
100
|
+
]
|
101
|
+
buffer[: buffer_index + test_length - 1024] = data[
|
102
|
+
data_index + 1024 - buffer_index : data_index + test_length
|
103
|
+
]
|
104
|
+
else:
|
105
|
+
buffer[buffer_index : buffer_index + test_length] = data[
|
106
|
+
data_index : data_index + test_length
|
107
|
+
]
|
108
|
+
buffer_index = buffer_index + test_length & 1023
|
109
|
+
chunk.extend(
|
110
|
+
(
|
111
|
+
reference_indices[0] & 255,
|
112
|
+
reference_indices[0] >> 2 & 192 | test_length - 3,
|
113
|
+
)
|
114
|
+
)
|
115
|
+
data_index += test_length
|
116
|
+
output.extend(chunk)
|
117
|
+
return bytes(output)
|
118
|
+
|
119
|
+
@staticmethod
|
120
|
+
def decompress(data: bytes, /) -> bytes:
|
121
|
+
buffer: Final[bytearray] = bytearray(1024)
|
122
|
+
buffer_index: int = 958
|
123
|
+
handle: int | bytearray
|
124
|
+
flags: int
|
125
|
+
offset: int
|
126
|
+
raw: Final[bytearray] = bytearray(data[:15:-1])
|
127
|
+
ref: bytes
|
128
|
+
result: Final[bytearray] = bytearray()
|
129
|
+
result_size: Final[int] = int.from_bytes(data[8:12], byteorder="big")
|
130
|
+
while len(result) < result_size:
|
131
|
+
flags = raw.pop()
|
132
|
+
for _ in range(8):
|
133
|
+
if flags & 1:
|
134
|
+
handle = raw.pop()
|
135
|
+
buffer[buffer_index] = handle
|
136
|
+
buffer_index = buffer_index + 1 & 1023
|
137
|
+
result.append(handle)
|
138
|
+
else:
|
139
|
+
if len(raw) < 2:
|
140
|
+
return result
|
141
|
+
ref = bytes((raw.pop() for _ in range(2)))
|
142
|
+
offset = (ref[1] << 2 & 768) + ref[0]
|
143
|
+
handle = bytearray()
|
144
|
+
for i in range((ref[1] & 63) + 3):
|
145
|
+
handle.append(buffer[offset + i - 1024])
|
146
|
+
buffer[buffer_index] = handle[-1]
|
147
|
+
buffer_index = buffer_index + 1 & 1023
|
148
|
+
result.extend(handle)
|
149
|
+
flags >>= 1
|
150
|
+
return bytes(result)
|
151
|
+
|
152
|
+
@staticmethod
|
153
|
+
def deserialize(data: bytes, /) -> Stage:
|
154
|
+
return XmlSlot.deserialize(BinSlot.decompress(data))
|
155
|
+
|
156
|
+
@staticmethod
|
157
|
+
def serialize(stage: Stage, /) -> bytes:
|
158
|
+
return BinSlot.compress(XmlSlot.serialize(stage))
|
koro/slot/file.py
CHANGED
@@ -1,67 +1,70 @@
|
|
1
|
-
from abc import ABC, abstractmethod
|
2
|
-
from os import remove
|
3
|
-
from os.path import isfile
|
4
|
-
from typing import TYPE_CHECKING, Any
|
5
|
-
|
6
|
-
from ..stage import Stage
|
7
|
-
from . import Slot
|
8
|
-
|
9
|
-
if TYPE_CHECKING:
|
10
|
-
from _typeshed import StrOrBytesPath
|
11
|
-
else:
|
12
|
-
StrOrBytesPath = Any
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
def
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
def
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
def
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
def
|
55
|
-
return
|
56
|
-
|
57
|
-
def
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
1
|
+
from abc import ABC, abstractmethod
|
2
|
+
from os import remove
|
3
|
+
from os.path import isfile
|
4
|
+
from typing import TYPE_CHECKING, Any
|
5
|
+
|
6
|
+
from ..stage import Stage
|
7
|
+
from . import Slot
|
8
|
+
|
9
|
+
if TYPE_CHECKING:
|
10
|
+
from _typeshed import StrOrBytesPath
|
11
|
+
else:
|
12
|
+
StrOrBytesPath = Any
|
13
|
+
|
14
|
+
|
15
|
+
__all__ = ["FileSlot"]
|
16
|
+
|
17
|
+
|
18
|
+
class FileSlot(Slot, ABC):
|
19
|
+
__match_args__ = ("path",)
|
20
|
+
__slots__ = ("_path",)
|
21
|
+
|
22
|
+
_path: StrOrBytesPath
|
23
|
+
|
24
|
+
def __init__(self, path: StrOrBytesPath, /) -> None:
|
25
|
+
self._path = path
|
26
|
+
|
27
|
+
def __bool__(self) -> bool:
|
28
|
+
return isfile(self.path)
|
29
|
+
|
30
|
+
@staticmethod
|
31
|
+
@abstractmethod
|
32
|
+
def deserialize(data: bytes, /) -> Stage:
|
33
|
+
pass
|
34
|
+
|
35
|
+
def __eq__(self, other: object, /) -> bool:
|
36
|
+
if isinstance(other, FileSlot) and (
|
37
|
+
isinstance(other, type(self)) or isinstance(self, type(other))
|
38
|
+
):
|
39
|
+
return self.path == other.path
|
40
|
+
else:
|
41
|
+
return NotImplemented
|
42
|
+
|
43
|
+
def __hash__(self) -> int:
|
44
|
+
return hash(self.path)
|
45
|
+
|
46
|
+
def load(self) -> Stage | None:
|
47
|
+
try:
|
48
|
+
with open(self.path, "rb") as f:
|
49
|
+
return self.deserialize(f.read())
|
50
|
+
except FileNotFoundError:
|
51
|
+
return None
|
52
|
+
|
53
|
+
@property
|
54
|
+
def path(self) -> StrOrBytesPath:
|
55
|
+
return self._path
|
56
|
+
|
57
|
+
def __repr__(self) -> str:
|
58
|
+
return f"{type(self).__name__}({self.path!r})"
|
59
|
+
|
60
|
+
def save(self, data: Stage | None) -> None:
|
61
|
+
if data is None:
|
62
|
+
remove(self.path)
|
63
|
+
else:
|
64
|
+
with open(self.path, "wb") as f:
|
65
|
+
f.write(self.serialize(data))
|
66
|
+
|
67
|
+
@staticmethod
|
68
|
+
@abstractmethod
|
69
|
+
def serialize(stage: Stage, /) -> bytes:
|
70
|
+
pass
|