diff --git a/extractar.py b/extractar.py index 52e65d3..1621f65 100644 --- a/extractar.py +++ b/extractar.py @@ -9,7 +9,8 @@ # list of conditions and the following disclaimer. # # 2. Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. +# this list of conditions and the following disclaimer in the documentation and/or other materials provided with the +# distribution. # # 3. Neither the name of the copyright holder nor the names of its contributors # may be used to endorse or promote products derived from this software @@ -30,6 +31,8 @@ import os import struct import textwrap import pathlib +from typing import Any +import attr __version__ = '1.0' @@ -56,8 +59,8 @@ class ArchiveBufferReadError(IndexError): Debian binary package entry information. Exception is of type IndexError. """ - -class ArchiveInfo: +@attr.s(slots=True) +class ArchiveInfo(object): """ Information on a file in an archive. @@ -76,19 +79,13 @@ class ArchiveInfo: the same name are present; if you change the `name` attribute, the initial file will be extracted with the new name (and new metadata). """ - def __init__(self, name: bytes, size: int, mtime: int, perms: int, uid: int, gid: int): - self._name = name - self.size = size - self.mtime = mtime - self.perms = perms - self.uid = uid - self.gid = gid - self.offset: int = 0 - - - @property - def name(self): - return self._name + name: bytes = attr.ib(converter=lambda s: _utf8(s).rstrip(b' '), on_setattr=attr.setters.NO_OP) + size: int = attr.ib(converter=int) + mtime: int = attr.ib(converter=int) + perms: int = attr.ib(converter=lambda x: int(x, 8)) + uid: int = attr.ib(converter=int) + gid: int = attr.ib(converter=int) + offset: int = attr.ib(converter=int, default=0) @classmethod @@ -111,19 +108,30 @@ class ArchiveInfo: if magic != b'\x60\n': raise ValueError("Invalid file signature") - return cls(_utf8(name).rstrip(b' '), int(size, 10), int(mtime, 10), int(perms, 8), int(uid, 10), int(gid, 10)) + return cls(name=name, size=size, mtime=mtime, perms=perms, uid=uid, gid=gid) + + + def __setattr__(self, __name: str, __value: Any) -> None: + """ + Custom setattr dunder method to create strict immutability over certain attributes of a class. Any attributes + which may be mutable should be handled appropriately. + """ + # |------ immutable ------| |---- mutable ----| + if not hasattr(self, __name) or __name == "offset": + object.__setattr__(self, __name, __value) def __repr__(self) -> str: return textwrap.dedent('''\ - ArchiveInfo: ({0}) + {7}: ({0}) _name: {0} size: {1} mtime: {2} perms: {3} uid: {4} gid: {5} - offset: {6}\n'''.format(self._name, self.size, self.mtime, self.perms, self.uid, self.gid, self.offset)) + offset: {6}\n'''.format(self.name, self.size, self.mtime, self.perms, self.uid, self.gid, self.offset, + self.__class__)) class Archive: @@ -265,9 +273,9 @@ class Archive: # write out the specific content of an archive member pos = member.offset + 60 + # @TODO: put in try cus IO be IO lmao with open(path, 'wb') as fp: - data = self.__ar_contents[pos:pos+member.size] - fp.write(data) + fp.write(self.__ar_contents[pos:pos+member.size]) def extractall(self, path: str | bytes = ''): @@ -287,4 +295,3 @@ class Archive: # iterate over all members in the mapping for name in self._mapping.keys(): self.extract(name, os.path.join(_utf8(path), name)) -