opalib 0.2.0__tar.gz → 0.3.0__tar.gz
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.
- {opalib-0.2.0 → opalib-0.3.0}/PKG-INFO +6 -1
- opalib-0.3.0/README.md +7 -0
- {opalib-0.2.0 → opalib-0.3.0}/pyproject.toml +2 -2
- {opalib-0.2.0 → opalib-0.3.0}/src/__init__.py +11 -5
- opalib-0.3.0/src/format.py +216 -0
- opalib-0.3.0/src/ieee754.py +25 -0
- opalib-0.3.0/src/libdeflate.py +59 -0
- {opalib-0.2.0 → opalib-0.3.0}/src/opalib.egg-info/PKG-INFO +6 -1
- opalib-0.3.0/src/opalib.egg-info/SOURCES.txt +24 -0
- opalib-0.3.0/src/opalib.egg-info/top_level.txt +10 -0
- opalib-0.3.0/src/physics.py +61 -0
- opalib-0.3.0/src/tests/enum_test.py +4 -0
- opalib-0.3.0/src/tests/format_test.py +22 -0
- opalib-0.3.0/src/tests/ieee754_test.py +15 -0
- opalib-0.3.0/src/tests/libdeflate_test.py +19 -0
- opalib-0.3.0/src/tests/physics_test.py +43 -0
- opalib-0.3.0/src/tests/units_test.py +24 -0
- opalib-0.3.0/src/tests/util_test.py +37 -0
- opalib-0.3.0/src/units.py +72 -0
- opalib-0.3.0/src/util.py +469 -0
- opalib-0.2.0/README.md +0 -2
- opalib-0.2.0/src/opalib.egg-info/SOURCES.txt +0 -12
- opalib-0.2.0/src/opalib.egg-info/top_level.txt +0 -4
- opalib-0.2.0/src/tests/enum_test.py +0 -4
- {opalib-0.2.0 → opalib-0.3.0}/LICENSE +0 -0
- {opalib-0.2.0 → opalib-0.3.0}/setup.cfg +0 -0
- {opalib-0.2.0 → opalib-0.3.0}/src/enum_extender.py +0 -0
- {opalib-0.2.0 → opalib-0.3.0}/src/opalib.egg-info/dependency_links.txt +0 -0
- {opalib-0.2.0 → opalib-0.3.0}/src/tests/web_test.py +0 -0
- {opalib-0.2.0 → opalib-0.3.0}/src/web.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: opalib
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: A library to do multiple things
|
|
5
5
|
Author-email: Donovan <donovandelisle7@gmail.com>
|
|
6
6
|
Requires-Python: >=3.8
|
|
@@ -10,3 +10,8 @@ Dynamic: license-file
|
|
|
10
10
|
|
|
11
11
|
# opalib
|
|
12
12
|
A library to do multiple things
|
|
13
|
+
|
|
14
|
+
## New modules
|
|
15
|
+
|
|
16
|
+
- `src/physics.py`: gravity, force, kinetic energy, potential energy, and projectile motion helpers.
|
|
17
|
+
- `src/units.py`: length unit conversions including `studs`, meters, feet, and custom units.
|
opalib-0.3.0/README.md
ADDED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "opalib"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.3.0"
|
|
4
4
|
description = "A library to do multiple things"
|
|
5
5
|
authors = [
|
|
6
6
|
{ name="Donovan", email="donovandelisle7@gmail.com" }
|
|
@@ -13,7 +13,7 @@ requires = ["setuptools"]
|
|
|
13
13
|
build-backend = "setuptools.build_meta"
|
|
14
14
|
|
|
15
15
|
[tool.bumpver]
|
|
16
|
-
current_version = "0.
|
|
16
|
+
current_version = "0.3.0"
|
|
17
17
|
version_pattern = "MAJOR.MINOR.PATCH"
|
|
18
18
|
commit_message = "bump version {old_version} -> {new_version}"
|
|
19
19
|
tag_message = "v{new_version}"
|
|
@@ -1,15 +1,21 @@
|
|
|
1
1
|
# __init__.py
|
|
2
|
+
|
|
3
|
+
"""opalib package API."""
|
|
4
|
+
|
|
2
5
|
import os
|
|
3
6
|
import importlib
|
|
4
7
|
|
|
5
|
-
|
|
8
|
+
__all__ = []
|
|
9
|
+
|
|
6
10
|
pkg_dir = os.path.dirname(__file__)
|
|
7
11
|
for file in os.listdir(pkg_dir):
|
|
8
12
|
if file.endswith(".py") and file != "__init__.py":
|
|
9
13
|
module_name = file[:-3]
|
|
10
14
|
module = importlib.import_module(f".{module_name}", package=__name__)
|
|
11
|
-
|
|
12
|
-
# Expose everything from the module to the package level
|
|
15
|
+
|
|
13
16
|
for attribute in dir(module):
|
|
14
|
-
if
|
|
15
|
-
|
|
17
|
+
if attribute.startswith("_"):
|
|
18
|
+
continue
|
|
19
|
+
globals()[attribute] = getattr(module, attribute)
|
|
20
|
+
if attribute not in __all__:
|
|
21
|
+
__all__.append(attribute)
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
"""format - serialization format helpers for opalib."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from types import SimpleNamespace
|
|
6
|
+
from typing import Any, Callable, Dict, List, Optional, Sequence, Union
|
|
7
|
+
|
|
8
|
+
from . import util
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class RefType:
|
|
12
|
+
def __init__(self) -> None:
|
|
13
|
+
self._cache: Dict[str, Dict[str, Any]] = {}
|
|
14
|
+
|
|
15
|
+
def __getattr__(self, name: str) -> Dict[str, Any]:
|
|
16
|
+
if name not in self._cache:
|
|
17
|
+
self._cache[name] = {"type": "ref", "of": name}
|
|
18
|
+
return self._cache[name]
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
F = SimpleNamespace(
|
|
22
|
+
_VERSION=3,
|
|
23
|
+
ID={},
|
|
24
|
+
V3={},
|
|
25
|
+
Byte={},
|
|
26
|
+
Double={},
|
|
27
|
+
Int={},
|
|
28
|
+
Int64={},
|
|
29
|
+
String={},
|
|
30
|
+
Any={},
|
|
31
|
+
Bool={},
|
|
32
|
+
Ref=RefType(),
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def map_(k_format: Any, v_format: Any) -> Dict[str, Any]:
|
|
37
|
+
return {"type": "map", "k_format": k_format, "v_format": v_format}
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def list_(v_format: Any, key: Optional[str] = None) -> Dict[str, Any]:
|
|
41
|
+
return {"type": "list", "v_format": v_format, "key": key}
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def array(len_: int, v_format: Any, key: Optional[str] = None) -> Dict[str, Any]:
|
|
45
|
+
return {"type": "array", "v_format": v_format, "len": len_, "key": key}
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def union(*args: Any) -> Dict[str, Any]:
|
|
49
|
+
return {"type": "union", "formats": list(args)}
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def struct(fields: Sequence[Dict[str, Any]]) -> Dict[str, Any]:
|
|
53
|
+
return {"type": "struct", "fields": list(fields)}
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def konst(value: Any, v_format: Any, is_serialized: bool) -> Dict[str, Any]:
|
|
57
|
+
return {
|
|
58
|
+
"type": "konst",
|
|
59
|
+
"value": value,
|
|
60
|
+
"v_format": v_format,
|
|
61
|
+
"is_serialized": is_serialized,
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def save(name: str, v_format: Any) -> Dict[str, Any]:
|
|
66
|
+
return {"type": "save", "name": name, "v_format": v_format}
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def compat(func: Callable[[Any], Any]) -> Dict[str, Any]:
|
|
70
|
+
return {"type": "compat", "func": func}
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def enable_if(cond: Callable[[bytes, int, Any], bool], v_format: Any) -> Dict[str, Any]:
|
|
74
|
+
return {"type": "enable_if", "cond": cond, "v_format": v_format}
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def format_(name: str, t: Dict[str, Any]) -> Dict[str, Any]:
|
|
78
|
+
t["name"] = name
|
|
79
|
+
setattr(F, name, t)
|
|
80
|
+
return t
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def new(name: str, t: Dict[str, Any]) -> Dict[str, Any]:
|
|
84
|
+
t["name"] = name
|
|
85
|
+
setattr(F, name, t)
|
|
86
|
+
return t
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def GE_VER(ver: int, on_true: Any, on_false: Any) -> Dict[str, Any]:
|
|
90
|
+
return compat(lambda ctx: on_true if getattr(ctx, "version", 0) >= ver else on_false)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
F.map = map_
|
|
94
|
+
F.list = list_
|
|
95
|
+
F.array = array
|
|
96
|
+
F.union = union
|
|
97
|
+
F.struct = struct
|
|
98
|
+
F.konst = konst
|
|
99
|
+
F.save = save
|
|
100
|
+
F.compat = compat
|
|
101
|
+
F.enable_if = enable_if
|
|
102
|
+
F.format = format_
|
|
103
|
+
F.new = new
|
|
104
|
+
F.GE_VER = GE_VER
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _load_string(data: bytes, i: int) -> tuple[str, int]:
|
|
108
|
+
return util.read_s(data, i)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _load_bool(data: bytes, i: int) -> tuple[bool, int]:
|
|
112
|
+
b = data[i]
|
|
113
|
+
return b == 1, i + 1
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def _load_v3(data: bytes, i: int) -> tuple[util.Vector3, int]:
|
|
117
|
+
return util.read_v(data, i)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def _load_byte(data: bytes, i: int) -> tuple[int, int]:
|
|
121
|
+
return data[i], i + 1
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def _load_double(data: bytes, i: int) -> tuple[float, int]:
|
|
125
|
+
return util.read_d(data, i)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def _load_int(data: bytes, i: int) -> tuple[int, int]:
|
|
129
|
+
return util.read_i(data, i)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def _load_int64(data: bytes, i: int) -> tuple[int, int]:
|
|
133
|
+
return util.read_i64(data, i)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _load_any(data: bytes, i: int) -> tuple[Any, int]:
|
|
137
|
+
return util.read_a(data, i)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def _save_string(data: List[bytes], value: str) -> None:
|
|
141
|
+
util.a(data, util.s2b(value))
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def _save_bool(data: List[bytes], value: bool) -> None:
|
|
145
|
+
util.a(data, b"\x01" if value else b"\x00")
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def _save_v3(data: List[bytes], value: util.Vector3) -> None:
|
|
149
|
+
util.a(data, util.v2b(value))
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def _save_byte(data: List[bytes], value: int) -> None:
|
|
153
|
+
util.a(data, bytes([value]))
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def _save_double(data: List[bytes], value: float) -> None:
|
|
157
|
+
util.a(data, util.d2b(value))
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def _save_int(data: List[bytes], value: int) -> None:
|
|
161
|
+
util.a(data, util.i2b(value))
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def _save_int64(data: List[bytes], value: int) -> None:
|
|
165
|
+
util.a(data, util.i642b(value))
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def _save_any(data: List[bytes], value: Any) -> None:
|
|
169
|
+
util.a(data, util.a2b(value))
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
F.String["load"] = _load_string
|
|
173
|
+
F.Bool["load"] = _load_bool
|
|
174
|
+
F.V3["load"] = _load_v3
|
|
175
|
+
F.Byte["load"] = _load_byte
|
|
176
|
+
F.Double["load"] = _load_double
|
|
177
|
+
F.Int["load"] = _load_int
|
|
178
|
+
F.Int64["load"] = _load_int64
|
|
179
|
+
F.Any["load"] = _load_any
|
|
180
|
+
|
|
181
|
+
F.String["save"] = _save_string
|
|
182
|
+
F.Bool["save"] = _save_bool
|
|
183
|
+
F.V3["save"] = _save_v3
|
|
184
|
+
F.Byte["save"] = _save_byte
|
|
185
|
+
F.Double["save"] = _save_double
|
|
186
|
+
F.Int["save"] = _save_int
|
|
187
|
+
F.Int64["save"] = _save_int64
|
|
188
|
+
F.Any["save"] = _save_any
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
F.new(
|
|
192
|
+
"Challenge",
|
|
193
|
+
F.struct(
|
|
194
|
+
[
|
|
195
|
+
{"signature": F.array(16, F.Byte)},
|
|
196
|
+
{"issued": F.Int64},
|
|
197
|
+
{"difficulty": F.Byte},
|
|
198
|
+
{"K00": F.Int},
|
|
199
|
+
{"K01": F.Int},
|
|
200
|
+
{"K10": F.Int},
|
|
201
|
+
{"K11": F.Int},
|
|
202
|
+
]
|
|
203
|
+
),
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
F.new(
|
|
207
|
+
"Solution",
|
|
208
|
+
F.struct(
|
|
209
|
+
[
|
|
210
|
+
{"x": F.Int},
|
|
211
|
+
{"y": F.Int},
|
|
212
|
+
]
|
|
213
|
+
),
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
__all__ = ["F", "RefType"]
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""ieee754 - IEEE 754 double conversion helpers."""
|
|
2
|
+
|
|
3
|
+
import struct
|
|
4
|
+
from typing import Union
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def _to_bytes(value: Union[str, bytes]) -> bytes:
|
|
8
|
+
if isinstance(value, str):
|
|
9
|
+
return value.encode("latin1")
|
|
10
|
+
return value
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def bin2double(value: Union[str, bytes]) -> float:
|
|
14
|
+
"""Convert an 8-byte sequence to a double-precision float."""
|
|
15
|
+
raw = _to_bytes(value)
|
|
16
|
+
if len(raw) != 8:
|
|
17
|
+
raise ValueError("bin2double requires exactly 8 bytes")
|
|
18
|
+
return struct.unpack("<d", raw)[0]
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def double2bin(value: float) -> bytes:
|
|
22
|
+
"""Convert a double-precision float to an 8-byte sequence."""
|
|
23
|
+
return struct.pack("<d", value)
|
|
24
|
+
|
|
25
|
+
__all__ = ["bin2double", "double2bin"]
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""libdeflate - minimal DEFLATE wrappers for opalib."""
|
|
2
|
+
|
|
3
|
+
import zlib
|
|
4
|
+
from typing import Any, Dict, Optional, Union
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def _to_bytes(value: Union[str, bytes]) -> bytes:
|
|
8
|
+
if isinstance(value, str):
|
|
9
|
+
return value.encode("latin1")
|
|
10
|
+
return value
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class LibDeflateClass:
|
|
14
|
+
"""Minimal wrapper around Python's zlib for raw DEFLATE data."""
|
|
15
|
+
|
|
16
|
+
def CompressDeflate(
|
|
17
|
+
self,
|
|
18
|
+
data: Union[str, bytes],
|
|
19
|
+
configs: Optional[Dict[str, Any]] = None,
|
|
20
|
+
) -> bytes:
|
|
21
|
+
"""Compress raw data using DEFLATE with no zlib header."""
|
|
22
|
+
bytes_data = _to_bytes(data)
|
|
23
|
+
level = 9
|
|
24
|
+
if configs is not None and isinstance(configs, dict):
|
|
25
|
+
if "level" in configs:
|
|
26
|
+
level = int(configs["level"])
|
|
27
|
+
|
|
28
|
+
compressor = zlib.compressobj(level, zlib.DEFLATED, -zlib.MAX_WBITS)
|
|
29
|
+
return compressor.compress(bytes_data) + compressor.flush()
|
|
30
|
+
|
|
31
|
+
def DecompressDeflate(self, data: Union[str, bytes]) -> bytes:
|
|
32
|
+
"""Decompress raw DEFLATE bytes."""
|
|
33
|
+
bytes_data = _to_bytes(data)
|
|
34
|
+
decompressor = zlib.decompressobj(-zlib.MAX_WBITS)
|
|
35
|
+
return decompressor.decompress(bytes_data) + decompressor.flush()
|
|
36
|
+
|
|
37
|
+
def CompressZlib(
|
|
38
|
+
self,
|
|
39
|
+
data: Union[str, bytes],
|
|
40
|
+
configs: Optional[Dict[str, Any]] = None,
|
|
41
|
+
) -> bytes:
|
|
42
|
+
"""Compress data using zlib wrapper format."""
|
|
43
|
+
bytes_data = _to_bytes(data)
|
|
44
|
+
level = 9
|
|
45
|
+
if configs is not None and isinstance(configs, dict):
|
|
46
|
+
if "level" in configs:
|
|
47
|
+
level = int(configs["level"])
|
|
48
|
+
|
|
49
|
+
return zlib.compress(bytes_data, level)
|
|
50
|
+
|
|
51
|
+
def DecompressZlib(self, data: Union[str, bytes]) -> bytes:
|
|
52
|
+
"""Decompress zlib-wrapped compressed data."""
|
|
53
|
+
bytes_data = _to_bytes(data)
|
|
54
|
+
return zlib.decompress(bytes_data)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
LibDeflate = LibDeflateClass()
|
|
58
|
+
|
|
59
|
+
__all__ = ["LibDeflate"]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: opalib
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: A library to do multiple things
|
|
5
5
|
Author-email: Donovan <donovandelisle7@gmail.com>
|
|
6
6
|
Requires-Python: >=3.8
|
|
@@ -10,3 +10,8 @@ Dynamic: license-file
|
|
|
10
10
|
|
|
11
11
|
# opalib
|
|
12
12
|
A library to do multiple things
|
|
13
|
+
|
|
14
|
+
## New modules
|
|
15
|
+
|
|
16
|
+
- `src/physics.py`: gravity, force, kinetic energy, potential energy, and projectile motion helpers.
|
|
17
|
+
- `src/units.py`: length unit conversions including `studs`, meters, feet, and custom units.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
src/__init__.py
|
|
5
|
+
src/enum_extender.py
|
|
6
|
+
src/format.py
|
|
7
|
+
src/ieee754.py
|
|
8
|
+
src/libdeflate.py
|
|
9
|
+
src/physics.py
|
|
10
|
+
src/units.py
|
|
11
|
+
src/util.py
|
|
12
|
+
src/web.py
|
|
13
|
+
src/opalib.egg-info/PKG-INFO
|
|
14
|
+
src/opalib.egg-info/SOURCES.txt
|
|
15
|
+
src/opalib.egg-info/dependency_links.txt
|
|
16
|
+
src/opalib.egg-info/top_level.txt
|
|
17
|
+
src/tests/enum_test.py
|
|
18
|
+
src/tests/format_test.py
|
|
19
|
+
src/tests/ieee754_test.py
|
|
20
|
+
src/tests/libdeflate_test.py
|
|
21
|
+
src/tests/physics_test.py
|
|
22
|
+
src/tests/units_test.py
|
|
23
|
+
src/tests/util_test.py
|
|
24
|
+
src/tests/web_test.py
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""opalib.physics - simple physics helpers for motion and gravity calculations."""
|
|
2
|
+
|
|
3
|
+
from math import radians, sin, sqrt
|
|
4
|
+
from typing import Dict
|
|
5
|
+
|
|
6
|
+
GRAVITY_EARTH = 9.80665
|
|
7
|
+
GRAVITY_MOON = 1.62
|
|
8
|
+
GRAVITY_MARS = 3.71
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
"GRAVITY_EARTH",
|
|
12
|
+
"GRAVITY_MOON",
|
|
13
|
+
"GRAVITY_MARS",
|
|
14
|
+
"force",
|
|
15
|
+
"kinetic_energy",
|
|
16
|
+
"potential_energy",
|
|
17
|
+
"fall_time",
|
|
18
|
+
"projectile_max_height",
|
|
19
|
+
"projectile_range",
|
|
20
|
+
"projectile_time_of_flight",
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def force(mass: float, acceleration: float) -> float:
|
|
25
|
+
"""Calculate force from mass and acceleration (F = m * a)."""
|
|
26
|
+
return mass * acceleration
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def kinetic_energy(mass: float, velocity: float) -> float:
|
|
30
|
+
"""Calculate kinetic energy in joules (1/2 m v^2)."""
|
|
31
|
+
return 0.5 * mass * velocity * velocity
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def potential_energy(mass: float, height: float, gravity: float = GRAVITY_EARTH) -> float:
|
|
35
|
+
"""Calculate gravitational potential energy in joules (m * g * h)."""
|
|
36
|
+
return mass * gravity * height
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def fall_time(height: float, gravity: float = GRAVITY_EARTH) -> float:
|
|
40
|
+
"""Estimate free-fall time from a height using t = sqrt(2h/g)."""
|
|
41
|
+
if height < 0:
|
|
42
|
+
raise ValueError("Height must be non-negative")
|
|
43
|
+
return sqrt(2 * height / gravity)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def projectile_range(velocity: float, angle_deg: float, gravity: float = GRAVITY_EARTH) -> float:
|
|
47
|
+
"""Compute the horizontal range of a projectile launched at an angle."""
|
|
48
|
+
angle_rad = radians(angle_deg)
|
|
49
|
+
return (velocity * velocity * sin(2 * angle_rad)) / gravity
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def projectile_max_height(velocity: float, angle_deg: float, gravity: float = GRAVITY_EARTH) -> float:
|
|
53
|
+
"""Compute the maximum height reached by a projectile."""
|
|
54
|
+
angle_rad = radians(angle_deg)
|
|
55
|
+
return (velocity * velocity * sin(angle_rad) ** 2) / (2 * gravity)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def projectile_time_of_flight(velocity: float, angle_deg: float, gravity: float = GRAVITY_EARTH) -> float:
|
|
59
|
+
"""Compute the total flight time for a projectile."""
|
|
60
|
+
angle_rad = radians(angle_deg)
|
|
61
|
+
return (2 * velocity * sin(angle_rad)) / gravity
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""Tests for the format module."""
|
|
2
|
+
|
|
3
|
+
import unittest
|
|
4
|
+
from src import format, util
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class TestFormat(unittest.TestCase):
|
|
8
|
+
def test_bool_save_load(self):
|
|
9
|
+
data = []
|
|
10
|
+
format.F.Bool["save"](data, True)
|
|
11
|
+
result, _ = util.read_a(b"".join(data), 0)
|
|
12
|
+
self.assertTrue(result)
|
|
13
|
+
|
|
14
|
+
def test_string_save_and_read(self):
|
|
15
|
+
data = []
|
|
16
|
+
format.F.String["save"](data, "hello")
|
|
17
|
+
result, _ = util.read_s(b"".join(data), 0)
|
|
18
|
+
self.assertEqual(result, "hello")
|
|
19
|
+
|
|
20
|
+
def test_challenge_format_defined(self):
|
|
21
|
+
self.assertTrue(hasattr(format.F, "Challenge"))
|
|
22
|
+
self.assertTrue(isinstance(format.F.Challenge, dict))
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""Tests for the ieee754 module."""
|
|
2
|
+
|
|
3
|
+
import unittest
|
|
4
|
+
from src import ieee754
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class TestIEEE754(unittest.TestCase):
|
|
8
|
+
def test_double_roundtrip(self):
|
|
9
|
+
value = 3.141592653589793
|
|
10
|
+
binary = ieee754.double2bin(value)
|
|
11
|
+
self.assertEqual(ieee754.bin2double(binary), value)
|
|
12
|
+
|
|
13
|
+
def test_bin2double_requires_eight_bytes(self):
|
|
14
|
+
with self.assertRaises(ValueError):
|
|
15
|
+
ieee754.bin2double(b"123")
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""Tests for the libdeflate module."""
|
|
2
|
+
|
|
3
|
+
import unittest
|
|
4
|
+
from src import LibDeflate
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class TestLibDeflate(unittest.TestCase):
|
|
8
|
+
def test_deflate_roundtrip(self):
|
|
9
|
+
payload = b"hello world\x00\x00"
|
|
10
|
+
compressed = LibDeflate.CompressDeflate(payload, {"level": 9})
|
|
11
|
+
decompressed = LibDeflate.DecompressDeflate(compressed)
|
|
12
|
+
|
|
13
|
+
self.assertEqual(decompressed, payload)
|
|
14
|
+
|
|
15
|
+
def test_zlib_roundtrip(self):
|
|
16
|
+
payload = b"hello zlib"
|
|
17
|
+
compressed = LibDeflate.CompressZlib(payload)
|
|
18
|
+
decompressed = LibDeflate.DecompressZlib(compressed)
|
|
19
|
+
self.assertEqual(decompressed, payload)
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""Unit tests for opalib.physics."""
|
|
2
|
+
|
|
3
|
+
import unittest
|
|
4
|
+
from src import (
|
|
5
|
+
force,
|
|
6
|
+
kinetic_energy,
|
|
7
|
+
potential_energy,
|
|
8
|
+
fall_time,
|
|
9
|
+
projectile_range,
|
|
10
|
+
projectile_max_height,
|
|
11
|
+
projectile_time_of_flight,
|
|
12
|
+
GRAVITY_EARTH,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class TestPhysics(unittest.TestCase):
|
|
17
|
+
def test_force(self):
|
|
18
|
+
self.assertEqual(force(10, 9.8), 98.0)
|
|
19
|
+
|
|
20
|
+
def test_kinetic_energy(self):
|
|
21
|
+
self.assertEqual(kinetic_energy(2, 3), 9.0)
|
|
22
|
+
|
|
23
|
+
def test_potential_energy(self):
|
|
24
|
+
self.assertEqual(potential_energy(1, 2, gravity=10), 20)
|
|
25
|
+
|
|
26
|
+
def test_fall_time(self):
|
|
27
|
+
self.assertAlmostEqual(fall_time(4, gravity=GRAVITY_EARTH), 0.9035, places=4)
|
|
28
|
+
|
|
29
|
+
def test_projectile_range(self):
|
|
30
|
+
result = projectile_range(10, 45, gravity=GRAVITY_EARTH)
|
|
31
|
+
self.assertAlmostEqual(result, (10 * 10 * 1.0) / GRAVITY_EARTH, places=4)
|
|
32
|
+
|
|
33
|
+
def test_projectile_max_height(self):
|
|
34
|
+
result = projectile_max_height(10, 45, gravity=GRAVITY_EARTH)
|
|
35
|
+
self.assertAlmostEqual(result, (10 * 10 * 0.5) / (2 * GRAVITY_EARTH), places=4)
|
|
36
|
+
|
|
37
|
+
def test_projectile_time_of_flight(self):
|
|
38
|
+
result = projectile_time_of_flight(10, 45, gravity=GRAVITY_EARTH)
|
|
39
|
+
self.assertAlmostEqual(result, (2 * 10 * 0.70710678) / GRAVITY_EARTH, places=4)
|
|
40
|
+
|
|
41
|
+
def test_fall_time_negative_height(self):
|
|
42
|
+
with self.assertRaises(ValueError):
|
|
43
|
+
fall_time(-1)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""Unit tests for opalib.units."""
|
|
2
|
+
|
|
3
|
+
import unittest
|
|
4
|
+
from src import convert, STUD_TO_METER, list_units, register_unit
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class TestUnits(unittest.TestCase):
|
|
8
|
+
def test_convert_studs_to_meters(self):
|
|
9
|
+
self.assertAlmostEqual(convert(1, "studs", "meters"), STUD_TO_METER)
|
|
10
|
+
|
|
11
|
+
def test_convert_meters_to_studs(self):
|
|
12
|
+
self.assertAlmostEqual(convert(0.28, "meters", "studs"), 1.0, places=6)
|
|
13
|
+
|
|
14
|
+
def test_convert_feet_to_studs(self):
|
|
15
|
+
self.assertAlmostEqual(convert(1, "feet", "studs"), 0.3048 / STUD_TO_METER, places=6)
|
|
16
|
+
|
|
17
|
+
def test_register_custom_unit(self):
|
|
18
|
+
register_unit("test-unit", 2.5)
|
|
19
|
+
self.assertAlmostEqual(convert(2, "test-unit", "meters"), 5.0)
|
|
20
|
+
self.assertIn("test-unit", list_units())
|
|
21
|
+
|
|
22
|
+
def test_unsupported_unit(self):
|
|
23
|
+
with self.assertRaises(ValueError):
|
|
24
|
+
convert(1, "unknown", "meters")
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""Tests for the util module."""
|
|
2
|
+
|
|
3
|
+
import unittest
|
|
4
|
+
from src import util
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class TestUtil(unittest.TestCase):
|
|
8
|
+
def test_mod1(self):
|
|
9
|
+
self.assertEqual(util.mod1_dec(1, 5), 4)
|
|
10
|
+
self.assertEqual(util.mod1_inc(4, 5), 5)
|
|
11
|
+
|
|
12
|
+
def test_validate_bool(self):
|
|
13
|
+
self.assertTrue(util.validate_bool("t"))
|
|
14
|
+
self.assertFalse(util.validate_bool("false"))
|
|
15
|
+
self.assertIsNone(util.validate_bool("maybe"))
|
|
16
|
+
|
|
17
|
+
def test_b2i_and_b2i64(self):
|
|
18
|
+
self.assertEqual(util.b2i(b"\x01\x00\x00\x00"), 1)
|
|
19
|
+
self.assertEqual(util.b2i64(b"\x01\x00\x00\x00\x00\x00\x00\x00"), 1)
|
|
20
|
+
|
|
21
|
+
def test_vector_roundtrip(self):
|
|
22
|
+
vec = util.Vector3(1.0, 2.0, 3.0)
|
|
23
|
+
data = util.v2b(vec)
|
|
24
|
+
result, _ = util.read_v(data, 0)
|
|
25
|
+
self.assertEqual(result, vec)
|
|
26
|
+
|
|
27
|
+
def test_encode_decode_zeros(self):
|
|
28
|
+
payload = b"\x00\x00ABC\xff"
|
|
29
|
+
encoded = util.encode_zeros(payload)
|
|
30
|
+
decoded = util.decode_zeros(encoded)
|
|
31
|
+
self.assertEqual(decoded, payload)
|
|
32
|
+
|
|
33
|
+
def test_encode_decode_with_deflate(self):
|
|
34
|
+
payload = b"test payload\x00\x00"
|
|
35
|
+
encoded = util.encode(payload)
|
|
36
|
+
decoded = util.decode(encoded)
|
|
37
|
+
self.assertEqual(decoded, payload)
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"""opalib.units - unit conversion helpers for length and custom measures."""
|
|
2
|
+
|
|
3
|
+
from typing import Dict
|
|
4
|
+
|
|
5
|
+
STUD_TO_METER = 0.28
|
|
6
|
+
"""Conversion factor from studs to meters."""
|
|
7
|
+
|
|
8
|
+
_UNIT_FACTORS: Dict[str, float] = {
|
|
9
|
+
"meter": 1.0,
|
|
10
|
+
"meters": 1.0,
|
|
11
|
+
"m": 1.0,
|
|
12
|
+
"stud": STUD_TO_METER,
|
|
13
|
+
"studs": STUD_TO_METER,
|
|
14
|
+
"st": STUD_TO_METER,
|
|
15
|
+
"foot": 0.3048,
|
|
16
|
+
"feet": 0.3048,
|
|
17
|
+
"ft": 0.3048,
|
|
18
|
+
"inch": 0.0254,
|
|
19
|
+
"inches": 0.0254,
|
|
20
|
+
"in": 0.0254,
|
|
21
|
+
"kilometer": 1000.0,
|
|
22
|
+
"kilometers": 1000.0,
|
|
23
|
+
"km": 1000.0,
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
__all__ = [
|
|
27
|
+
"STUD_TO_METER",
|
|
28
|
+
"to_meters",
|
|
29
|
+
"from_meters",
|
|
30
|
+
"convert",
|
|
31
|
+
"register_unit",
|
|
32
|
+
"list_units",
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _normalize_unit(unit: str) -> str:
|
|
37
|
+
return unit.strip().lower()
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def to_meters(value: float, unit: str) -> float:
|
|
41
|
+
"""Convert a value from a supported unit to meters."""
|
|
42
|
+
unit_key = _normalize_unit(unit)
|
|
43
|
+
if unit_key not in _UNIT_FACTORS:
|
|
44
|
+
raise ValueError(f"Unsupported unit: {unit}")
|
|
45
|
+
return value * _UNIT_FACTORS[unit_key]
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def from_meters(value: float, unit: str) -> float:
|
|
49
|
+
"""Convert a value in meters to a supported unit."""
|
|
50
|
+
unit_key = _normalize_unit(unit)
|
|
51
|
+
if unit_key not in _UNIT_FACTORS:
|
|
52
|
+
raise ValueError(f"Unsupported unit: {unit}")
|
|
53
|
+
return value / _UNIT_FACTORS[unit_key]
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def convert(value: float, from_unit: str, to_unit: str) -> float:
|
|
57
|
+
"""Convert a value between two supported units."""
|
|
58
|
+
meters = to_meters(value, from_unit)
|
|
59
|
+
return from_meters(meters, to_unit)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def register_unit(name: str, factor_to_meters: float) -> None:
|
|
63
|
+
"""Register a custom unit conversion factor to meters."""
|
|
64
|
+
unit_key = _normalize_unit(name)
|
|
65
|
+
if factor_to_meters <= 0:
|
|
66
|
+
raise ValueError("Conversion factor must be positive")
|
|
67
|
+
_UNIT_FACTORS[unit_key] = factor_to_meters
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def list_units() -> Dict[str, float]:
|
|
71
|
+
"""Return all registered unit conversion factors."""
|
|
72
|
+
return dict(_UNIT_FACTORS)
|
opalib-0.3.0/src/util.py
ADDED
|
@@ -0,0 +1,469 @@
|
|
|
1
|
+
"""util - low-level binary and utility helpers converted from util.lua."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import math
|
|
6
|
+
import traceback
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from typing import Any, Callable, Dict, List, Optional, Tuple, Union
|
|
9
|
+
|
|
10
|
+
from . import ieee754
|
|
11
|
+
from .libdeflate import LibDeflate
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass
|
|
15
|
+
class Vector3:
|
|
16
|
+
X: float
|
|
17
|
+
Y: float
|
|
18
|
+
Z: float
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
prec = 1e-3
|
|
22
|
+
prec2 = prec ** 2
|
|
23
|
+
|
|
24
|
+
_epsilon = 1.0
|
|
25
|
+
while 1.0 + _epsilon != 1.0:
|
|
26
|
+
_epsilon *= 0.5
|
|
27
|
+
|
|
28
|
+
e = _epsilon
|
|
29
|
+
|
|
30
|
+
max_parallel_angle = 1
|
|
31
|
+
mpa_cos = math.cos(max_parallel_angle / 360 * 2 * math.pi)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class Context:
|
|
35
|
+
def __init__(self) -> None:
|
|
36
|
+
self.stack: List[Any] = []
|
|
37
|
+
self._attrs: Dict[str, Any] = {}
|
|
38
|
+
|
|
39
|
+
def __getitem__(self, key: Union[int, str]) -> Any:
|
|
40
|
+
if isinstance(key, int):
|
|
41
|
+
return self.stack[key - 1]
|
|
42
|
+
return self._attrs[key]
|
|
43
|
+
|
|
44
|
+
def __setitem__(self, key: Union[int, str], value: Any) -> None:
|
|
45
|
+
if isinstance(key, int):
|
|
46
|
+
index = key - 1
|
|
47
|
+
if index == len(self.stack):
|
|
48
|
+
self.stack.append(value)
|
|
49
|
+
elif 0 <= index < len(self.stack):
|
|
50
|
+
self.stack[index] = value
|
|
51
|
+
else:
|
|
52
|
+
raise IndexError("Context index out of range")
|
|
53
|
+
else:
|
|
54
|
+
self._attrs[key] = value
|
|
55
|
+
|
|
56
|
+
def __getattr__(self, name: str) -> Any:
|
|
57
|
+
if name in {"stack", "_attrs"}:
|
|
58
|
+
raise AttributeError
|
|
59
|
+
return self._attrs.get(name)
|
|
60
|
+
|
|
61
|
+
def __setattr__(self, name: str, value: Any) -> None:
|
|
62
|
+
if name in {"stack", "_attrs"}:
|
|
63
|
+
super().__setattr__(name, value)
|
|
64
|
+
else:
|
|
65
|
+
self._attrs[name] = value
|
|
66
|
+
|
|
67
|
+
def __len__(self) -> int:
|
|
68
|
+
return len(self.stack)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _to_bytes(value: Union[str, bytes]) -> bytes:
|
|
72
|
+
if isinstance(value, str):
|
|
73
|
+
return value.encode("latin1")
|
|
74
|
+
return value
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def mod1_dec(x: float, m: float) -> float:
|
|
78
|
+
return (x - 2) % m + 1
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def mod1_inc(x: float, m: float) -> float:
|
|
82
|
+
return x % m + 1
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def bind(f: Callable[..., Any], obj: Any) -> Callable[..., Any]:
|
|
86
|
+
def bound(*args: Any, **kwargs: Any) -> Any:
|
|
87
|
+
return f(obj, *args, **kwargs)
|
|
88
|
+
|
|
89
|
+
return bound
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def union_k(*dicts: Dict[Any, Any]) -> Dict[Any, Any]:
|
|
93
|
+
result: Dict[Any, Any] = {}
|
|
94
|
+
for source in dicts:
|
|
95
|
+
result.update(source)
|
|
96
|
+
return result
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def union_i(*iterables: List[Any]) -> List[Any]:
|
|
100
|
+
result: List[Any] = []
|
|
101
|
+
for sequence in iterables:
|
|
102
|
+
result.extend(sequence)
|
|
103
|
+
return result
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def validate_bool(txt: str) -> Optional[bool]:
|
|
107
|
+
lower = txt.lower()
|
|
108
|
+
if lower in {"true", "t"}:
|
|
109
|
+
return True
|
|
110
|
+
if lower in {"false", "f"}:
|
|
111
|
+
return False
|
|
112
|
+
return None
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def get_trace(msg: str) -> str:
|
|
116
|
+
return f"{msg}; {traceback.format_exc()}"
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def pcall(f: Callable[..., Any], *args: Any, **kwargs: Any) -> Tuple[bool, Any]:
|
|
120
|
+
try:
|
|
121
|
+
return True, f(*args, **kwargs)
|
|
122
|
+
except Exception as exc:
|
|
123
|
+
return False, get_trace(str(exc))
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
b2d = ieee754.bin2double
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def b2i(b: Union[str, bytes]) -> int:
|
|
130
|
+
raw = _to_bytes(b)
|
|
131
|
+
return int.from_bytes(raw, byteorder="little", signed=False)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def b2i64(b: Union[str, bytes]) -> int:
|
|
135
|
+
raw = _to_bytes(b)
|
|
136
|
+
return int.from_bytes(raw, byteorder="little", signed=False)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def b2v(b: Union[str, bytes]) -> Vector3:
|
|
140
|
+
raw = _to_bytes(b)
|
|
141
|
+
x = b2d(raw[0:8])
|
|
142
|
+
y = b2d(raw[8:16])
|
|
143
|
+
z = b2d(raw[16:24])
|
|
144
|
+
return Vector3(X=x, Y=y, Z=z)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def read_i(data: Union[str, bytes], i: int) -> Tuple[int, int]:
|
|
148
|
+
raw = _to_bytes(data)
|
|
149
|
+
value = int.from_bytes(raw[i : i + 4], byteorder="little", signed=False)
|
|
150
|
+
return value, i + 4
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def read_i64(data: Union[str, bytes], i: int) -> Tuple[int, int]:
|
|
154
|
+
raw = _to_bytes(data)
|
|
155
|
+
value = int.from_bytes(raw[i : i + 8], byteorder="little", signed=False)
|
|
156
|
+
return value, i + 8
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def read_d(data: Union[str, bytes], i: int) -> Tuple[float, int]:
|
|
160
|
+
raw = _to_bytes(data)
|
|
161
|
+
value = b2d(raw[i : i + 8])
|
|
162
|
+
return value, i + 8
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def read_v(data: Union[str, bytes], i: int) -> Tuple[Vector3, int]:
|
|
166
|
+
raw = _to_bytes(data)
|
|
167
|
+
vector = b2v(raw[i : i + 24])
|
|
168
|
+
return vector, i + 24
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def read_t(data: Union[str, bytes], i: int) -> Tuple[int, int]:
|
|
172
|
+
raw = _to_bytes(data)
|
|
173
|
+
return raw[i], i + 1
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def read_s(data: Union[str, bytes], i: int) -> Tuple[str, int]:
|
|
177
|
+
raw = _to_bytes(data)
|
|
178
|
+
length, i = read_i(raw, i)
|
|
179
|
+
value = raw[i : i + length].decode("latin1")
|
|
180
|
+
return value, i + length
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def read_a(data: Union[str, bytes], i: int) -> Tuple[Any, int]:
|
|
184
|
+
raw = _to_bytes(data)
|
|
185
|
+
ty, i = read_t(raw, i)
|
|
186
|
+
if ty == 0:
|
|
187
|
+
return read_s(raw, i)
|
|
188
|
+
if ty == 1:
|
|
189
|
+
return read_d(raw, i)
|
|
190
|
+
raise ValueError(f"Received unknown field type: {ty}")
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def decode(value: Union[str, bytes]) -> bytes:
|
|
194
|
+
return LibDeflate.DecompressDeflate(decode_zeros(_to_bytes(value)))
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def decode_params(data: Union[str, bytes], i: int) -> Tuple[Dict[str, Any], int]:
|
|
198
|
+
raw = _to_bytes(data)
|
|
199
|
+
fields: Dict[str, Any] = {}
|
|
200
|
+
n, i = read_i(raw, i)
|
|
201
|
+
for _ in range(n):
|
|
202
|
+
name, i = read_s(raw, i)
|
|
203
|
+
value, i = read_a(raw, i)
|
|
204
|
+
fields[name] = value
|
|
205
|
+
return fields, i
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def load(
|
|
209
|
+
data: Union[str, bytes],
|
|
210
|
+
i: int,
|
|
211
|
+
format: Optional[Dict[str, Any]],
|
|
212
|
+
context: Context,
|
|
213
|
+
) -> Tuple[Any, int]:
|
|
214
|
+
raw = _to_bytes(data)
|
|
215
|
+
if format is None:
|
|
216
|
+
return None, i
|
|
217
|
+
|
|
218
|
+
class_format = None
|
|
219
|
+
if isinstance(format, dict) and "format" in format:
|
|
220
|
+
class_format = format
|
|
221
|
+
format = format["format"]
|
|
222
|
+
|
|
223
|
+
obj: Any = None
|
|
224
|
+
if isinstance(format, dict) and not format:
|
|
225
|
+
# format.ID is represented by an empty dictionary in this conversion.
|
|
226
|
+
obj = len(context) + 1
|
|
227
|
+
elif isinstance(format, dict) and "type" in format:
|
|
228
|
+
fmt_type = format["type"]
|
|
229
|
+
if fmt_type == "compat":
|
|
230
|
+
obj, i = load(raw, i, format["func"](context), context)
|
|
231
|
+
elif fmt_type == "ref":
|
|
232
|
+
id_ = None
|
|
233
|
+
id_, i = read_i(raw, i)
|
|
234
|
+
obj = context[format["of"]][id_]
|
|
235
|
+
elif fmt_type == "konst":
|
|
236
|
+
if format.get("is_serialized"):
|
|
237
|
+
obj, i = load(raw, i, format["v_format"], context)
|
|
238
|
+
else:
|
|
239
|
+
obj = format["value"]
|
|
240
|
+
elif fmt_type == "save":
|
|
241
|
+
obj, i = load(raw, i, format["v_format"], context)
|
|
242
|
+
context[format["name"]] = obj
|
|
243
|
+
elif fmt_type == "enable_if":
|
|
244
|
+
if format["cond"](raw, i, context):
|
|
245
|
+
obj, i = load(raw, i, format["v_format"], context)
|
|
246
|
+
elif fmt_type == "union":
|
|
247
|
+
obj = {}
|
|
248
|
+
for v_format in format["formats"]:
|
|
249
|
+
context.obj = obj
|
|
250
|
+
obj, i = load(raw, i, v_format, context)
|
|
251
|
+
else:
|
|
252
|
+
obj = getattr(context, "obj", None)
|
|
253
|
+
if obj is not None:
|
|
254
|
+
context.obj = None
|
|
255
|
+
else:
|
|
256
|
+
obj = {}
|
|
257
|
+
|
|
258
|
+
if format.get("key"):
|
|
259
|
+
context[format["key"]] = obj
|
|
260
|
+
|
|
261
|
+
if fmt_type == "struct":
|
|
262
|
+
for field in format["fields"]:
|
|
263
|
+
for k, v_format in field.items():
|
|
264
|
+
obj[k], i = load(raw, i, v_format, context)
|
|
265
|
+
elif fmt_type == "array":
|
|
266
|
+
v_format = format["v_format"]
|
|
267
|
+
arr: List[Any] = []
|
|
268
|
+
for _ in range(format["len"]):
|
|
269
|
+
item, i = load(raw, i, v_format, context)
|
|
270
|
+
arr.append(item)
|
|
271
|
+
obj = arr
|
|
272
|
+
else:
|
|
273
|
+
n, i = read_i(raw, i)
|
|
274
|
+
stack_id = len(context) + 1
|
|
275
|
+
context[stack_id] = obj
|
|
276
|
+
if fmt_type == "list":
|
|
277
|
+
v_format = format["v_format"]
|
|
278
|
+
arr = []
|
|
279
|
+
for _ in range(n):
|
|
280
|
+
item, i = load(raw, i, v_format, context)
|
|
281
|
+
arr.append(item)
|
|
282
|
+
obj = arr
|
|
283
|
+
elif fmt_type == "map":
|
|
284
|
+
v_format_k = format["k_format"]
|
|
285
|
+
v_format_v = format["v_format"]
|
|
286
|
+
for _ in range(n):
|
|
287
|
+
key, i = load(raw, i, v_format_k, context)
|
|
288
|
+
if key is not None:
|
|
289
|
+
value, i = load(raw, i, v_format_v, context)
|
|
290
|
+
obj[key] = value
|
|
291
|
+
context[stack_id] = None
|
|
292
|
+
|
|
293
|
+
if class_format is not None and isinstance(class_format, dict) and class_format.get("MT"):
|
|
294
|
+
# Python does not support Lua metatables directly. Skip this behavior.
|
|
295
|
+
pass
|
|
296
|
+
|
|
297
|
+
if isinstance(format, dict) and format.get("load"):
|
|
298
|
+
obj, i = format["load"](raw, i)
|
|
299
|
+
|
|
300
|
+
return obj, i
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
def a(data: List[bytes], value: bytes) -> None:
|
|
304
|
+
data.append(value)
|
|
305
|
+
if hasattr(data, "size"):
|
|
306
|
+
data.size += len(value)
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
def i2b(x: int) -> bytes:
|
|
310
|
+
return x.to_bytes(4, byteorder="little", signed=False)
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
def i642b(x: int) -> bytes:
|
|
314
|
+
return x.to_bytes(8, byteorder="little", signed=False)
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
def v2b(v: Vector3) -> bytes:
|
|
318
|
+
return d2b(v.X) + d2b(v.Y) + d2b(v.Z)
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
def s2b(s: str) -> bytes:
|
|
322
|
+
encoded = s.encode("latin1")
|
|
323
|
+
return i2b(len(encoded)) + encoded
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
def a2b(v: Any) -> bytes:
|
|
327
|
+
if isinstance(v, str):
|
|
328
|
+
return b"\x00" + s2b(v)
|
|
329
|
+
if isinstance(v, (int, float)):
|
|
330
|
+
return b"\x01" + d2b(float(v))
|
|
331
|
+
raise TypeError("Unsupported value type for a2b")
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
def encode(value: Union[str, bytes]) -> bytes:
|
|
335
|
+
return encode_zeros(LibDeflate.CompressDeflate(_to_bytes(value), {"level": 9}))
|
|
336
|
+
|
|
337
|
+
special = bytes([255])
|
|
338
|
+
null = bytes([0])
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
def encode_zeros(value: Union[str, bytes]) -> bytes:
|
|
342
|
+
raw = _to_bytes(value)
|
|
343
|
+
result: List[bytes] = []
|
|
344
|
+
i = 0
|
|
345
|
+
n = len(raw)
|
|
346
|
+
while i < n:
|
|
347
|
+
b = raw[i]
|
|
348
|
+
if b == 0:
|
|
349
|
+
m = 1
|
|
350
|
+
while i + 1 < n and m < 254 and raw[i + 1] == 0:
|
|
351
|
+
m += 1
|
|
352
|
+
i += 1
|
|
353
|
+
result.append(special)
|
|
354
|
+
result.append(bytes([m]))
|
|
355
|
+
elif b == 255:
|
|
356
|
+
result.append(special)
|
|
357
|
+
result.append(special)
|
|
358
|
+
else:
|
|
359
|
+
result.append(bytes([b]))
|
|
360
|
+
i += 1
|
|
361
|
+
return b"".join(result)
|
|
362
|
+
|
|
363
|
+
|
|
364
|
+
def decode_zeros(value: Union[str, bytes]) -> bytes:
|
|
365
|
+
raw = _to_bytes(value)
|
|
366
|
+
result: List[bytes] = []
|
|
367
|
+
i = 0
|
|
368
|
+
n = len(raw)
|
|
369
|
+
while i < n:
|
|
370
|
+
b = raw[i]
|
|
371
|
+
if b == 255:
|
|
372
|
+
i += 1
|
|
373
|
+
b2 = raw[i]
|
|
374
|
+
if b2 == 255:
|
|
375
|
+
result.append(special)
|
|
376
|
+
else:
|
|
377
|
+
result.extend([null] * b2)
|
|
378
|
+
else:
|
|
379
|
+
result.append(bytes([b]))
|
|
380
|
+
i += 1
|
|
381
|
+
return b"".join(result)
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
def save(data: List[bytes], obj: Any, format: Optional[Dict[str, Any]], context: Context) -> None:
|
|
385
|
+
if not format:
|
|
386
|
+
return
|
|
387
|
+
|
|
388
|
+
if isinstance(format, dict) and "format" in format:
|
|
389
|
+
format = format["format"]
|
|
390
|
+
|
|
391
|
+
if isinstance(format, dict) and "type" in format:
|
|
392
|
+
fmt_type = format["type"]
|
|
393
|
+
if fmt_type == "compat":
|
|
394
|
+
save(data, obj, format["func"](context), context)
|
|
395
|
+
elif fmt_type == "ref":
|
|
396
|
+
a(data, i2b(obj.id))
|
|
397
|
+
elif fmt_type == "konst":
|
|
398
|
+
if format.get("is_serialized"):
|
|
399
|
+
save(data, format["value"], format["v_format"], context)
|
|
400
|
+
elif fmt_type == "save":
|
|
401
|
+
save(data, obj, format["v_format"], context)
|
|
402
|
+
if format["v_format"].get("type") == "konst":
|
|
403
|
+
context[format["name"]] = format["v_format"]["value"]
|
|
404
|
+
else:
|
|
405
|
+
context[format["name"]] = obj
|
|
406
|
+
elif fmt_type == "enable_if":
|
|
407
|
+
save(data, obj, format["v_format"], context)
|
|
408
|
+
elif fmt_type == "union":
|
|
409
|
+
for v_format in format["formats"]:
|
|
410
|
+
save(data, obj, v_format, context)
|
|
411
|
+
elif fmt_type == "struct":
|
|
412
|
+
for field in format["fields"]:
|
|
413
|
+
for _, v_format in field.items():
|
|
414
|
+
save(data, obj.get(next(iter(field.keys())), None), v_format, context)
|
|
415
|
+
elif fmt_type == "array":
|
|
416
|
+
for value in obj:
|
|
417
|
+
save(data, value, format["v_format"], context)
|
|
418
|
+
elif fmt_type == "list":
|
|
419
|
+
a(data, i2b(len(obj)))
|
|
420
|
+
for value in obj:
|
|
421
|
+
save(data, value, format["v_format"], context)
|
|
422
|
+
elif fmt_type == "map":
|
|
423
|
+
a(data, i2b(len(obj)))
|
|
424
|
+
for key, value in obj.items():
|
|
425
|
+
save(data, key, format["k_format"], context)
|
|
426
|
+
save(data, value, format["v_format"], context)
|
|
427
|
+
else:
|
|
428
|
+
raise ValueError(f"Unknown format type: {fmt_type}")
|
|
429
|
+
|
|
430
|
+
if isinstance(format, dict) and format.get("save"):
|
|
431
|
+
format["save"](data, obj)
|
|
432
|
+
|
|
433
|
+
|
|
434
|
+
__all__ = [
|
|
435
|
+
"Vector3",
|
|
436
|
+
"Context",
|
|
437
|
+
"mod1_dec",
|
|
438
|
+
"mod1_inc",
|
|
439
|
+
"bind",
|
|
440
|
+
"union_k",
|
|
441
|
+
"union_i",
|
|
442
|
+
"validate_bool",
|
|
443
|
+
"get_trace",
|
|
444
|
+
"pcall",
|
|
445
|
+
"b2d",
|
|
446
|
+
"b2i",
|
|
447
|
+
"b2i64",
|
|
448
|
+
"b2v",
|
|
449
|
+
"read_i",
|
|
450
|
+
"read_i64",
|
|
451
|
+
"read_d",
|
|
452
|
+
"read_v",
|
|
453
|
+
"read_t",
|
|
454
|
+
"read_s",
|
|
455
|
+
"read_a",
|
|
456
|
+
"decode",
|
|
457
|
+
"decode_params",
|
|
458
|
+
"load",
|
|
459
|
+
"a",
|
|
460
|
+
"i2b",
|
|
461
|
+
"i642b",
|
|
462
|
+
"v2b",
|
|
463
|
+
"s2b",
|
|
464
|
+
"a2b",
|
|
465
|
+
"encode",
|
|
466
|
+
"encode_zeros",
|
|
467
|
+
"decode_zeros",
|
|
468
|
+
"save",
|
|
469
|
+
]
|
opalib-0.2.0/README.md
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
LICENSE
|
|
2
|
-
README.md
|
|
3
|
-
pyproject.toml
|
|
4
|
-
src/__init__.py
|
|
5
|
-
src/enum_extender.py
|
|
6
|
-
src/web.py
|
|
7
|
-
src/opalib.egg-info/PKG-INFO
|
|
8
|
-
src/opalib.egg-info/SOURCES.txt
|
|
9
|
-
src/opalib.egg-info/dependency_links.txt
|
|
10
|
-
src/opalib.egg-info/top_level.txt
|
|
11
|
-
src/tests/enum_test.py
|
|
12
|
-
src/tests/web_test.py
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|