From d65becb17eadb95242649d3f107829bfd97fb494 Mon Sep 17 00:00:00 2001 From: oldboy Date: Thu, 16 Oct 2025 20:52:36 -0500 Subject: [PATCH] First Commit On branch master Initial commit Changes to be committed: new file: .gitignore new file: .python-version new file: README.md new file: main.py new file: pyproject.toml new file: src/constructor/__init__.py new file: src/constructor/construct.py --- .gitignore | 177 ++++++++++++++++ .python-version | 1 + README.md | 0 main.py | 6 + pyproject.toml | 13 ++ src/constructor/__init__.py | 11 + src/constructor/construct.py | 377 +++++++++++++++++++++++++++++++++++ 7 files changed, 585 insertions(+) create mode 100644 .gitignore create mode 100644 .python-version create mode 100644 README.md create mode 100644 main.py create mode 100644 pyproject.toml create mode 100644 src/constructor/__init__.py create mode 100644 src/constructor/construct.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bcd6c97 --- /dev/null +++ b/.gitignore @@ -0,0 +1,177 @@ +# ---> Python +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# UV +# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +#uv.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/latest/usage/project/#working-with-version-control +.pdm.toml +.pdm-python +.pdm-build/ + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ + +# Ruff stuff: +.ruff_cache/ + +# PyPI configuration file +.pypirc + + diff --git a/.python-version b/.python-version new file mode 100644 index 0000000..e4fba21 --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.12 diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/main.py b/main.py new file mode 100644 index 0000000..e9930fe --- /dev/null +++ b/main.py @@ -0,0 +1,6 @@ +def main(): + print("Hello from constructor!") + + +if __name__ == "__main__": + main() diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..2907bae --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,13 @@ +[project] +name = "constructor" +version = "0.1.0" +description = "Custom Wrapper Around builtin Struct class" +authors = [{name = "TSD", email = "t.s.davis6291@gmail.com"}] +readme = "README.md" +license = "APACHE2.0" +requires-python = ">=3.12" +dependencies = [] + +[build-system] +requires = ["uv-build"] +build-backend = "uv_build" diff --git a/src/constructor/__init__.py b/src/constructor/__init__.py new file mode 100644 index 0000000..1aafce1 --- /dev/null +++ b/src/constructor/__init__.py @@ -0,0 +1,11 @@ +from construct import ( + struct_formatter, + structBSA, + bType + ) + +__all__ = [ + 'struct_formatter', + 'structBSA', + 'bType' +] \ No newline at end of file diff --git a/src/constructor/construct.py b/src/constructor/construct.py new file mode 100644 index 0000000..9cce9f6 --- /dev/null +++ b/src/constructor/construct.py @@ -0,0 +1,377 @@ +import struct +from dataclasses import dataclass,asdict +import sys +import platform + +class structBSA: + + _validators = '@=<>!' + _values = { + '@' : ( 'native' , 'native' , 'native' ), + '=' : ( 'native' , 'standard' , None ), + '<' : ( 'little-endian' , 'standard' , None ), + '>' : ( 'big-endian' , 'standard' , None ), + '!' : ( 'network' , 'standard' , None ) + } + + _native = sys.byteorder + _arch = platform.machine() + + if '64' in _arch: + _size = 64 + + elif '32' in _arch or '86' in _arch: + _size = 32 + + else: + _size = None + + + def __init__( + self, + char:str + ) -> None: + + if char in self._validators: + + self.char = char + + self.byte_order,\ + self.size,\ + self.alignment = self._values[char] + + def __repr__(self): + result = '' + values = { + 'Native' : self._native, + 'Architecture' : self._arch, + 'AddressSpace' : self._size, + 'Character' : self.char, + 'ByteOrder' : self.byte_order, + 'Size' : self.size, + 'Alignment' : self.alignment + } + + + ct_1,\ + ct_2,\ + cb_1,\ + cb_2 = ('\u250E','\u2512','\u2516','\u251A') + + jl,\ + jr,\ + jd,\ + ju,\ + jq = ('\u2520','\u2528','\u252F','\u2537','\u253f') + + edges = ('\u2502','\u2501') + ev,eh = edges + + lines:list[ + list[str] + ] = [[],[],[],[],[]] + + n = 0 + n_stop = len(values) - 1 + + for header,value in values.items(): + + line_1 = '' + line_2 = '' + line_3 = '' + line_4 = '' + line_5 = '' + value = str(value) + + field_length = max(len(header),len(value)) + 2 + + if len(header) %2 != 0: + header += ' ' + + if len(value) %2 != 0: + value += ' ' + + while len(header) < field_length: + + header = f' {header} ' + + if len(header) >= field_length: + break + + while len(value) < field_length: + + value = f' {value} ' + + if len(value) >= field_length: + break + + + line_2 += f'{ev}{header}' + line_4 += f'{ev}{value}' + + match n: + case 0: + + line_1 = f'{ct_1}{eh * field_length}' + + line_3 = f'{jl}{eh * field_length}' + + line_5 = f'{cb_1}{eh*field_length}' + + case _: + + line_1 = f'{jd}{eh*field_length}' + + line_3 = f'{jq}{eh*field_length}' + + line_5 = f'{ju}{eh*field_length}' + + if len(line_2) != len(line_3): + + while len(line_2) != len(line_3): + + line_1 += eh + line_3 += eh + line_5 += eh + + if len(line_2) == len(line_3): + break + + if n == n_stop: + + line_1 += ct_2 + line_2 += ev + line_3 += jr + line_4 += ev + line_5 += cb_2 + + + + lines[0].append(line_1) + lines[1].append(line_2) + lines[2].append(line_3) + lines[3].append(line_4) + lines[4].append(line_5) + n += 1 + + for line in lines: + full_line = ''.join(line) + result += f'{full_line}\n' + + return result + + def __str__(self): + + return self.char + + + +class bType(str): + + def __new__(cls,designator): + return super().__new__(cls, designator) + + _values = { + 'x' : ( '\x00' , None , 0 ), + 'c' : ( 'char' , bytes , 1 ), + 'b' : ( 'signed char' , int , 1 ), + 'B' : ( 'unsigned char' , int , 1 ), + '?' : ( '_Bool' , bool , 1 ), + 'h' : ( 'short' , int , 2 ), + 'H' : ( 'unsigned short' , int , 2 ), + 'i' : ( 'integer' , int , 4 ), + 'I' : ( 'unsigned integer' , int , 4 ), + 'l' : ( 'long' , int , 4 ), + 'L' : ( 'unsigned long' , int , 4 ), + 'q' : ( 'long long' , int , 8 ), + 'Q' : ( 'unsigned long long' , int , 8 ), + 'n' : ( 'ssize_t' , int , None ), + 'N' : ( 'size_t' , int , None ), + 'e' : ( 'f16' , float , 2 ), + 'f' : ( 'f32' , float , 4 ), + 'd' : ( 'f64' , float , 8 ), + 's' : ( 'char[]' , bytes , None ), + 'p' : ( 'char[]' , bytes , None ), + 'P' : ( 'void*' , int , None ) + } + + def __init__(self,designator:str): + self.designator = designator + self.c_type,self.type_of,self.size = self._values[designator] + + def __repr__(self): + + headers = ( + 'Symbol', + 'CType' , + 'PyType', + 'Size' + ) + + title = 'DataStructure' + + type_1 = f'{self.c_type}' + type_2 = f'{str(self.type_of).replace('class ','')}' + + size = f'{self.size}' if self.size is not None else '0x00' + + align_val = max([len(s) for s in [type_1,type_2,size,*headers]]) + 2 + + top_line = ' ' + ('\u2500' * (((align_val + 1) * 4) - 1)) + line_1 = ' \u2502' + + for header in headers: + + unused_space = (align_val - len(header)) // 2 + + if len(header) + (unused_space * 2) != align_val: + header = ' ' + header + line_1 += f'{' ' * unused_space}{header}{' ' * unused_space}\u2502' + + line_2 = ' \u251c' + + for _ in range(4): + line_2 += ('\u2500' * (align_val)) + '\u253C' + + line_2 = f'{line_2[:-1]}\u2524' + line_3 = ' \u2502' + + if self.designator == 'x': + type_1 = 'NULL' + + for data in ( + self.designator, + type_1, + type_2, + size + ): + + unused_space = (align_val - len(data)) // 2 + + if len(data) + (unused_space*2) != align_val: + data = ' ' + data + + line_3 += f'{' ' * unused_space}{data}{' ' * unused_space}\u2502' + + line_4 = top_line + title_spacer = (len(top_line) // 2) - (len(title) // 2) + title = f'{' '*title_spacer}{title}' + + top_line = f' \u250C{top_line[2:]}\u2510' + line_4 = f' \u2514{line_4[2:]}\u2518' + + return '\n'.join([title,top_line,line_1,line_2,line_3,line_4]) + + def __str__(self): + return self.designator + + @property + def c(self): + return self.designator + + +@dataclass +class struct_formatter: + + native :structBSA = structBSA('@') + native_std :structBSA = structBSA('=') + little_end :structBSA = structBSA('<') + big_end :structBSA = structBSA('>') + network :structBSA = structBSA('!') + + pad :bType = bType('x') + char :bType = bType('c') + schar :bType = bType('b') + uchar :bType = bType('B') + _Bool :bType = bType('?') + short :bType = bType('h') + ushort :bType = bType('H') + integer :bType = bType('i') + uint :bType = bType('I') + long :bType = bType('l') + ulong :bType = bType('L') + longlong :bType = bType('q') + ulonglong :bType = bType('Q') + ssize_t :bType = bType('n') + size_t :bType = bType('N') + f16 :bType = bType('e') + f32 :bType = bType('f') + f64 :bType = bType('d') + string :bType = bType('s') + pascal :bType = bType('p') + void :bType = bType('P') + + def _todict(self) -> dict[str,structBSA|bType]: + return asdict(self) + + def compile(self,*args): + str_head = '' + str_tail = '' + for arg in args: + match arg: + case structBSA(): + str_head += arg.char + case tuple(): + str_plus = ''.join([str(a) for a in arg]) + str_tail += str_plus + case bType(): + str_tail += arg + case str(): + if arg in structBSA._validators: + str_head += arg + else: + str_tail += arg + case int(): + str_tail += str(arg) + case _: + raise NotImplementedError + return struct.Struct(f'{str_head}{str_tail}') + + def __repr__(self): + result = '' + struct_formats:list[tuple[str,structBSA]] = [] + struct_types:list[tuple[str,bType]] = [] + formatter = struct_formatter()._todict() + for name,inst in formatter.items(): + match inst: + case structBSA(): + struct_formats.append((name,inst)) + case bType(): + struct_types.append((name,inst)) + splicer = '\u252D\u2502\u253d\u2502\u2535' + for fmt in struct_formats: + for s_type in struct_types: + f_name,s_fmt = fmt + s_name,s_typ = s_type + fmt_lines = repr(s_fmt).splitlines() + typ_lines = repr(s_typ).splitlines() + lengths = [] + for idx,line in enumerate(fmt_lines): + lengths.append(len(f'{typ_lines[idx]}{line}')) + new_repr = [] + + max_length = 4 + max(lengths) + title = typ_lines[0] + for idx,f_line in enumerate(fmt_lines): + t_line = splicer[idx] + typ_lines[idx+1][2:] + f_line = f_line[:-1] + spacer = max_length - len(f'\u2503 {f_line}{t_line}') + new_repr.append(f'\u2503 {f_line}{t_line}{' '*spacer}\u2503') + result += (f'\u250F{'\u2501' * (max_length - 1)}\u2513\n') + result += ( + f'\u2503 {title.lstrip()}: {f_name}+{s_name} -> [\'{s_fmt.char}{s_typ.designator}\']{' '*(max_length - len(f'\u2503 {title.lstrip()}: {f_name}+{s_name} -> [\'{s_fmt.char}{s_typ.designator}\']'))}\u2503\n' + ) + result += (f'\u2503{' '*((max_length - 1))}\u2503\n{'\n'.join(new_repr)}\n') + result += (f'\u2503{' '*(max_length - 1)}\u2503\n\u2517{'\u2501' * (max_length - 1)}\u251b\n') + return result + + +def main(): + s_dict = struct_formatter()._todict() + for key,value in s_dict.items(): + spacer = (max([len(v) for v in repr(value).splitlines()]) // 2) - (len(key) // 2) + print(f'{' ' * spacer}{key}\n{value}') + print('\n\n') + +if __name__ == '__main__': + main() \ No newline at end of file