Skip to content

Commit d9742df

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

File tree

1 file changed

+127
-28
lines changed

1 file changed

+127
-28
lines changed

kaitaistruct.py

+127-28
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

@@ -14,52 +19,146 @@
1419
#
1520
__version__ = '0.9'
1621

17-
18-
class KaitaiStruct(object):
19-
def __init__(self, stream):
22+
class _KaitaiStruct:
23+
__slots__ = ("_io",)
24+
def __init__(self, stream: "KaitaiStream"):
2025
self._io = stream
2126

27+
28+
class KaitaiStruct(_KaitaiStruct):
29+
__slots__ = ("_shouldExit",)
30+
def __init__(self, stream: "KaitaiStream"):
31+
super.__init__(stream)
32+
self._shouldExit = False
33+
2234
def __enter__(self):
35+
self._shouldExit = not stream.is_entered
36+
if self._shouldExit:
37+
self._io.__enter__()
2338
return self
2439

2540
def __exit__(self, *args, **kwargs):
26-
self.close()
27-
28-
def close(self):
29-
self._io.close()
41+
if self.shouldExit:
42+
self._io.__exit__(*args, **kwargs)
3043

3144
@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
45+
def from_file(cls, file: typing.Union[Path, str], use_mmap:bool=True):
46+
with cls(KaitaiStream(file, use_mmap=use_mmap)) as res:
47+
return res
4048

41-
@classmethod
42-
def from_bytes(cls, buf):
43-
return cls(KaitaiStream(BytesIO(buf)))
4449

45-
@classmethod
46-
def from_io(cls, io):
47-
return cls(KaitaiStream(io))
50+
class IKaitaiDownStream(ABC):
51+
__slots__ =("_io",)
4852

53+
def __init__(self, _io: typing.Any):
54+
self._io = _io
4955

50-
class KaitaiStream(object):
51-
def __init__(self, io):
52-
self._io = io
53-
self.align_to_byte()
56+
@abstractmethod
57+
@property
58+
def is_entered(self):
59+
raise NotImplementedError
60+
61+
@abstractmethod
62+
def __enter__(self):
63+
raise NotImplementedError()
64+
65+
def __exit__(self, *args, **kwargs):
66+
if self.is_entered():
67+
self._io.__exit__(*args, **kwargs)
68+
self._io = None
69+
70+
def __del__(self):
71+
self.__exit__(None, None)
72+
73+
74+
class KaitaiFileSyscallDownStream(IKaitaiDownStream):
75+
__slots__ = ()
76+
def __init__(self, path: typing.Union[Path, str]):
77+
super().__init__(Path(path))
78+
79+
@property
80+
def is_entered(self):
81+
return isinstance(self._io, _IOBase)
82+
83+
def __enter__(self):
84+
self._io = open(self.path).__enter__()
85+
return self
86+
87+
88+
class KaitaiFileMapDownStream(IKaitaiFileDownStream):
89+
__slots__ = ("file",)
90+
def __init__(self, path: Path):
91+
super().__init__(None)
92+
self.file = KaitaiFileSyscallDownStream(path)
93+
94+
@property
95+
def is_entered(self):
96+
return isinstance(self._io, mmap.mmap)
5497

5598
def __enter__(self):
99+
self.file = self.file.__enter__()
100+
self._io = mmap.mmap(self.file.file.fileno(), 0, access=mmap.ACCESS_READ).__enter__()
56101
return self
57102

58103
def __exit__(self, *args, **kwargs):
59-
self.close()
104+
super().__exit__(*args, **kwargs)
105+
if self.file is not None:
106+
self.file.__exit__(*args, **kwargs)
107+
self.file = None
60108

61-
def close(self):
62-
self._io.close()
109+
110+
def getFileDownStream(path: Path, *args, use_mmap: bool=True, **kwargs) -> IKaitaiFileDownStream:
111+
if use_mmap:
112+
cls = KaitaiFileMapDownStream
113+
else:
114+
cls = KaitaiFileSyscallDownStream
115+
return cls(path, *args, **kwargs)
116+
117+
118+
class KaitaiBytesDownStream(IKaitaiBytesDownStream):
119+
__slots__ = ()
120+
def __init__(self, data: bytes):
121+
super().__init__(data)
122+
123+
@property
124+
def is_entered(self):
125+
return isinstance(self._io, BytesIO)
126+
127+
def __enter__(self):
128+
self._io = BytesIO(self._io).__enter__()
129+
return self
130+
131+
132+
downstreamMapping = {
133+
bytes: KaitaiBytesDownStream,
134+
str: getFileDownStream,
135+
Path: getFileDownStream,
136+
}
137+
138+
139+
def get_downstream(x: typing.Union[bytes, str, Path], *args, **kwargs) -> IKaitaiDownStream:
140+
return downstreamMapping[type(x)](x, *args, **kwargs)
141+
142+
143+
class KaitaiStream():
144+
def __init__(self, o: typing.Union[bytes, str, Path]):
145+
self._downstream = get_downstream(o)
146+
self.align_to_byte()
147+
148+
@property
149+
def _io(self):
150+
return self.downstream._io
151+
152+
def __enter__(self):
153+
self._downstream.__enter__()
154+
return self
155+
156+
@property
157+
def is_entered(self):
158+
return self._downstream is not None and self._downstream.is_entered
159+
160+
def __exit__(self, *args, **kwargs):
161+
self._downstream.__exit__(*args, **kwargs)
63162

64163
# ========================================================================
65164
# Stream positioning

0 commit comments

Comments
 (0)