Skip to content

Commit 421d79c

Browse files
committed
Complete overhaul of interaction between KaitaiStream and KaitaiStruct
1 parent c6f581a commit 421d79c

File tree

1 file changed

+152
-27
lines changed

1 file changed

+152
-27
lines changed

kaitaistruct.py

+152-27
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
1+
import typing
12
import itertools
23
import sys
34
import struct
45
from io import open, BytesIO, SEEK_CUR, SEEK_END # noqa
6+
from _io import _IOBase
7+
import mmap
8+
from pathlib import Path
9+
from abc import ABC, abstractmethod
510

611
PY2 = sys.version_info[0] == 2
712

@@ -15,51 +20,171 @@
1520
__version__ = '0.9'
1621

1722

18-
class KaitaiStruct(object):
19-
def __init__(self, stream):
20-
self._io = stream
23+
class _KaitaiStruct_:
24+
__slots__ = ("_io", "_parent", "_root")
25+
def __init__(self, _io:"KaitaiStream", _parent:typing.Optiohal["_KaitaiStruct"]=None, _root:typing.Optiohal["_KaitaiStruct"]=None):
26+
self._io = _io
27+
self._parent = _parent
28+
self._root = _root if _root else self
29+
30+
31+
class KaitaiParser(ABC):
32+
__slots__ = () # in fact this class not meant to be instantiated
33+
34+
@abstractmethod
35+
@classmethod
36+
def parse(cls, struct:_KaitaiStruct_, io:"KaitaiStream"):
37+
raise NotlmplementedError()
38+
39+
40+
class _KaitaiStruct(_KaitaiStruct_):
41+
__slots__ = ()
42+
_parser = None # :KaitaiParser
43+
44+
def _read(self):
45+
self.__class__._parser.parse(self, self._io)
46+
47+
48+
class KaitaiStruct(_KaitaiStruct):
49+
__slots__ = ("_shouldExit",)
50+
def __init__(self, io: "KaitaiStream"):
51+
super.__init__(io)
52+
self._shouldExit = False
2153

2254
def __enter__(self):
55+
self._shouldExit = not stream.is_entered
56+
if self._shouldExit:
57+
self._io.__enter__()
2358
return self
2459

2560
def __exit__(self, *args, **kwargs):
26-
self.close()
27-
28-
def close(self):
29-
self._io.close()
61+
if self.shouldExit:
62+
self._io.__exit__(*args, **kwargs)
3063

3164
@classmethod
32-
def from_file(cls, filename):
33-
f = open(filename, 'rb')
34-
try:
35-
return cls(KaitaiStream(f))
36-
except Exception:
37-
# close file descriptor, then reraise the exception
38-
f.close()
39-
raise
65+
def from_any(cls, o: typing.Union[Path, str]):
66+
with KaitaiStream(o) as io:
67+
s = cls(io)
68+
s._read()
69+
return s
4070

4171
@classmethod
42-
def from_bytes(cls, buf):
43-
return cls(KaitaiStream(BytesIO(buf)))
72+
def from_file(cls, file: typing.Union[Path, str], use_mmap:bool=True):
73+
return cls.from_any(file, use_mmap=use_mmap)
4474

45-
@classmethod
46-
def from_io(cls, io):
47-
return cls(KaitaiStream(io))
4875

76+
class IKaitaiDownStream(ABC):
77+
__slots__ =("_io",)
4978

50-
class KaitaiStream(object):
51-
def __init__(self, io):
52-
self._io = io
53-
self.align_to_byte()
79+
def __init__(self, _io: typing.Any):
80+
self._io = _io
81+
82+
@abstractmethod
83+
@property
84+
def is_entered(self):
85+
raise NotImplementedError
86+
87+
@abstractmethod
88+
def __enter__(self):
89+
raise NotImplementedError()
90+
91+
def __exit__(self, *args, **kwargs):
92+
if self.is_entered():
93+
self._io.__exit__(*args, **kwargs)
94+
self._io = None
95+
96+
def __del__(self):
97+
self.__exit__(None, None)
98+
99+
100+
class KaitaiFileSyscallDownStream(IKaitaiDownStream):
101+
__slots__ = ()
102+
def __init__(self, path: typing.Union[Path, str]):
103+
super().__init__(Path(path))
104+
105+
@property
106+
def is_entered(self):
107+
return isinstance(self._io, _IOBase)
54108

55109
def __enter__(self):
110+
self._io = open(self.path).__enter__()
111+
return self
112+
113+
114+
class KaitaiFileMapDownStream(IKaitaiFileDownStream):
115+
__slots__ = ("file",)
116+
def __init__(self, path: Path):
117+
super().__init__(None)
118+
self.file = KaitaiFileSyscallDownStream(path)
119+
120+
@property
121+
def is_entered(self):
122+
return isinstance(self._io, mmap.mmap)
123+
124+
def __enter__(self):
125+
self.file = self.file.__enter__()
126+
self._io = mmap.mmap(self.file.file.fileno(), 0, access=mmap.ACCESS_READ).__enter__()
56127
return self
57128

58129
def __exit__(self, *args, **kwargs):
59-
self.close()
130+
super().__exit__(*args, **kwargs)
131+
if self.file is not None:
132+
self.file.__exit__(*args, **kwargs)
133+
self.file = None
134+
135+
136+
def getFileDownStream(path: Path, *args, use_mmap: bool=True, **kwargs) -> IKaitaiFileDownStream:
137+
if use_mmap:
138+
cls = KaitaiFileMapDownStream
139+
else:
140+
cls = KaitaiFileSyscallDownStream
141+
return cls(path, *args, **kwargs)
142+
143+
144+
class KaitaiBytesDownStream(IKaitaiBytesDownStream):
145+
__slots__ = ()
146+
def __init__(self, data: bytes):
147+
super().__init__(data)
148+
149+
@property
150+
def is_entered(self):
151+
return isinstance(self._io, BytesIO)
152+
153+
def __enter__(self):
154+
self._io = BytesIO(self._io).__enter__()
155+
return self
156+
60157

61-
def close(self):
62-
self._io.close()
158+
downstreamMapping = {
159+
bytes: KaitaiBytesDownStream,
160+
str: getFileDownStream,
161+
Path: getFileDownStream,
162+
}
163+
164+
165+
def get_downstream(x: typing.Union[bytes, str, Path], *args, **kwargs) -> IKaitaiDownStream:
166+
return downstreamMapping[type(x)](x, *args, **kwargs)
167+
168+
169+
class KaitaiStream():
170+
def __init__(self, o: typing.Union[bytes, str, Path]):
171+
self._downstream = get_downstream(o)
172+
self.align_to_byte()
173+
174+
@property
175+
def _io(self):
176+
return self.downstream._io
177+
178+
def __enter__(self):
179+
self._downstream.__enter__()
180+
return self
181+
182+
@property
183+
def is_entered(self):
184+
return self._downstream is not None and self._downstream.is_entered
185+
186+
def __exit__(self, *args, **kwargs):
187+
self._downstream.__exit__(*args, **kwargs)
63188

64189
# ========================================================================
65190
# Stream positioning

0 commit comments

Comments
 (0)