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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: opalib
3
- Version: 0.2.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
@@ -0,0 +1,7 @@
1
+ # opalib
2
+ A library to do multiple things
3
+
4
+ ## New modules
5
+
6
+ - `src/physics.py`: gravity, force, kinetic energy, potential energy, and projectile motion helpers.
7
+ - `src/units.py`: length unit conversions including `studs`, meters, feet, and custom units.
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "opalib"
3
- version = "0.2.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.2.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
- # Get all .py files in this folder (excluding this file)
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 not attribute.startswith("_"):
15
- globals()[attribute] = getattr(module, attribute)
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.2.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,10 @@
1
+ __init__
2
+ enum_extender
3
+ format
4
+ ieee754
5
+ libdeflate
6
+ physics
7
+ tests
8
+ units
9
+ util
10
+ web
@@ -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,4 @@
1
+ from .. import Enums
2
+
3
+ Enums.new("HTTPMethod", ["GET", "POST", "PUT", "DELETE"])
4
+ print(Enums.HTTPMethod.GET)
@@ -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)
@@ -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,2 +0,0 @@
1
- # opalib
2
- A library to do multiple things
@@ -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
@@ -1,4 +0,0 @@
1
- __init__
2
- enum_extender
3
- tests
4
- web
@@ -1,4 +0,0 @@
1
- from ..enum_extender import Enums
2
-
3
- Enums.new("HTTPMethod", ["GET", "POST", "PUT", "DELETE"])
4
- print(Enums.HTTPMethod.GET)
File without changes
File without changes
File without changes
File without changes
File without changes