Make buffer more stylistically consistent

We're beginning to develop some consistent styling in blocksfree/cppo-ng, but
the buffer code predates most of that.  Let's fix that.
This commit is contained in:
T. Joseph Carter 2017-07-20 04:19:09 -07:00
parent d3c3e1479c
commit 7c169b2053
2 changed files with 144 additions and 94 deletions

View File

@ -16,32 +16,34 @@
# with this program; if not, write to the Free Software Foundation, Inc., # with this program; if not, write to the Free Software Foundation, Inc.,
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
"""Abstract base class for disk image buffers
The buffers we will work with most commonly represent 140k disk images. For
that, it'd make a lot of sense to simply use a bytearray. It's built in to
the language and pretty efficient for an interpreted OO construct. But even
ProDOS disk images can be 32M in size.
Well that's fine, because most systems have 1G or more of RAM, plus swap, you
might say. And that's true, they do have that much RAM. Only on an embedded
system like the Raspberry Pi, they may not have much or any swap. And while
ProDOS volumes will never be larger than 32M, GS/OS supports other
filesystems
HFS is even commonly used. It's true that HFS support has not been something
you could expect from AppleCommander, to say nothing of cppo, but we ought to
consider the possibility for the future. As of this writing, we cannot
really guarantee that your host operating system can handle HFS completely.
How do you access resource fork on Linux? How do you access any of it on
Windows? Can Apple OSes officially carrying the designation "macOS" even
open old HFS volumes read-only anymore? They haven't had read-write access
for some time now.
"""
from abc import ABCMeta, abstractmethod from abc import ABCMeta, abstractmethod
from typing import Optional from typing import Optional
# Rationale:
#
# The buffers we will work with most commonly represent 140k disk images. For
# that, it'd make a lot of sense to simply use a bytearray. It's built in to
# the language and pretty efficient for an interpreted OO construct. But even
# ProDOS disk images can be 32M in size.
#
# Well that's fine, because most systems have 1G or more of RAM, plus swap, you
# might say. And that's true, they do have that much RAM. Only on an embedded
# system like the Raspberry Pi, they may not have much or any swap. And while
# ProDOS volumes will never be larger than 32M, GS/OS supports other
# filesystems
#
# HFS is even commonly used. It's true that HFS support has not been something
# you could expect from AppleCommander, to say nothing of cppo, but we ought to
# consider the possibility for the future. As of this writing, we cannot
# really guarantee that your host operating system can handle HFS completely.
# How do you access resource fork on Linux? How do you access any of it on
# Windows? Can Apple OSes officially carrying the designation "macOS" even
# open old HFS volumes read-only anymore? They haven't had read-write access
# for some time now.
class BufferType(metaclass=ABCMeta): class BufferType(object, metaclass=ABCMeta):
"""Abstract class that describes a BufferType. """Abstract class that describes a BufferType.
Read-only BufferType subclasses must implement read and __len__, as well as Read-only BufferType subclasses must implement read and __len__, as well as
@ -61,41 +63,49 @@ class BufferType(metaclass=ABCMeta):
@abstractmethod @abstractmethod
def __len__(self) -> int: def __len__(self) -> int:
"""Return len(self)""" """Implement len(self)
Subclasses must provide an implementation for this method.
"""
pass pass
@property @property
def changed(self): def changed(self):
"""Returns True if buffer has been altered """Return True if buffer has been altered
Always returns false for read-only buffers Returns:
Always False for read-only buffers
""" """
return False return False
@abstractmethod @abstractmethod
def read( def read(self, start: int, count: int) -> bytes:
self, """Return count bytes from buffer beginning at start
start: int = 0,
count: Optional[int] = None,
limit: bool = True
) -> bytearray:
"""Return bytearray of count bytes from buffer beginning at start
Should raise IndexError if an attempt to read past the end of the Args:
buffer is made. start: Starting position of bytes to return
count: Number of bytes to return
Returns:
bytes object of the requested length copied from buffer
Raises:
IndexError if attempt to read outside the buffer is made
""" """
pass pass
@abstractmethod @abstractmethod
def read1( def read1(self, offset: int) -> int:
self, """Return single byte from buffer as int
offset: int = 0,
limit: bool = True
) -> int:
"""Return int of single byte from buffer at offset
Should raise IndexError if an attempt to read past the end of the Args:
buffer is made. offset: The position of the requested byte in the buffer
Returns:
int value of the requested byte
Raises:
IndexError if attempt to read outside the buffer is made
""" """
pass pass
@ -103,26 +113,43 @@ class BufferType(metaclass=ABCMeta):
self, self,
buf: bytes, buf: bytes,
start: int, start: int,
count: Optional[int] = None, count: Optional[int] = None
limit: bool = True
) -> None: ) -> None:
"""Writes bytes to buffer beginning at start """Write given bytes-like object to buffer at start
Raises NotImplementedError unless implemented by subclass Subclasses should raise IndexError if an attempt to write outside the
buffer is made.
Args:
buf: The bytes-like object to write
start: Offset to where in buffer it should be written
count: Length to write (default: length of buf)
Raises:
NotImplementedEror unless implemented by subclass
""" """
raise NotImplementedError('buffer does not support writing') raise NotImplementedError('buffer does not support writing')
def resize(self, size: int) -> None: def resize(self, size: int) -> None:
"""Resize buffer to size r"""Resize a given buffer
Raises NotImplementedError unless implemented by subclass Resizes the current buffer in place. If size < len(self), the buffer
will be truncated. If size > len(self), the buffer will be extended.
The newly added bytes will be b'\x00'
Args:
size: New size of buffer
Raises:
NotImplementedError unless implemented by subclass
""" """
raise NotImplementedError('buffer does not support writing') raise NotImplementedError('buffer does not support writing')
@property @property
def locked(self) -> bool: def locked(self) -> bool:
"""Returns True for read-only buffers. """Determine writability of buffer
Returns True unless writing is implemented by subclass Returns:
True unless writing is implemented by subclass
""" """
return True return True

View File

@ -15,6 +15,8 @@
# You should have received a copy of the GNU General Public License along # You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc., # with this program; if not, write to the Free Software Foundation, Inc.,
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
"""Read/Write BufferType that lives in memory"""
from typing import Dict, List, Optional, Union from typing import Dict, List, Optional, Union
from .buffertype import BufferType from .buffertype import BufferType
@ -30,7 +32,7 @@ class ByteBuffer(BufferType):
def __init__( def __init__(
self, self,
bytes_or_int: Union[bytes,int], bytes_or_int: Union[bytes, int],
changed: bool = False, changed: bool = False,
locked: bool = False locked: bool = False
) -> None: ) -> None:
@ -39,45 +41,53 @@ class ByteBuffer(BufferType):
self._locked = locked self._locked = locked
def __len__(self) -> int: def __len__(self) -> int:
"""Return len(self)""" """Implement len(self)"""
return len(self._buf) return len(self._buf)
def read( @property
self, def changed(self):
start: int = 0, """Return True if buffer has been altered
count: int = 1,
limit: bool = True
) -> bytearray:
"""Return bytearray of count bytes from buffer beginning at start
By default, an IndexError will be raised if you read past the end of Returns:
the buffer. Pass limit=False if reads outside the buffer should just Always False for read-only buffers
return trncated or empty results as with python slicing """
return self._changed
def read(self, start: int, count: int) -> bytes:
"""Return count bytes from buffer beginning at start
Args:
start: Starting position of bytes to return
count: Number of bytes to return
Returns:
bytes object of the requested length copied from buffer
Raises:
IndexError if attempt to read outside the buffer is made
""" """
if count is None:
count = len(self) - start
try: try:
assert(start >= 0) assert start >= 0
assert(count >= 0) assert count >= 0
if limit == True: assert start + count <= len(self._buf)
assert(start + count <= len(self._buf))
except AssertionError: except AssertionError:
raise IndexError('buffer read with index out of range') raise IndexError('buffer read with index out of range')
return bytes(self._buf[start:start + count]) return bytes(self._buf[start:start + count])
def read1( def read1(self, offset: int) -> int:
self, """Return single byte from buffer as int
offset: int = 0,
limit: bool = True
) -> int:
"""Return int of single byte from buffer at offset
Should raise IndexError if an attempt to read past the end of the Args:
buffer is made. offset: The position of the requested byte in the buffer
Returns:
int value of the requested byte
Raises:
IndexError if attempt to read outside the buffer is made
""" """
try: try:
if limit == True: assert 0 <= offset <= len(self._buf)
assert 0 <= offset <= len(self._buf)
except AssertionError: except AssertionError:
raise IndexError('buffer read with index out of range') raise IndexError('buffer read with index out of range')
return self._buf[offset] return self._buf[offset]
@ -86,14 +96,17 @@ class ByteBuffer(BufferType):
self, self,
buf: bytes, buf: bytes,
start: int, start: int,
count: Optional[int] = None, count: Optional[int] = None
limit: bool = True
) -> None: ) -> None:
"""Writes bytes to buffer beginning at start """Write given bytes-like object to buffer at start
If count is not supplied, the entire bytes-like object will be written Args:
to the buffer. An IndexError will be raised if the write would extend buf: The bytes-like object to write
past the end of the buffer. Pass limit=False start: Offset to where in buffer it should be written
count: Length to write (default: length of buf)
Raises:
IndexError if attempt to read outside the buffer is made
""" """
if self.locked: if self.locked:
raise BufferError('cannot write to locked buffer') raise BufferError('cannot write to locked buffer')
@ -101,21 +114,27 @@ class ByteBuffer(BufferType):
if not count: if not count:
count = len(buf) count = len(buf)
try: try:
assert(start >= 0) assert start >= 0
assert(count >= 0) assert count >= 0
if limit == True: assert start + count <= len(self._buf)
assert(start + count <= len(self._buf))
except AssertionError: except AssertionError:
raise IndexError('buffer write with index out of range') raise IndexError('buffer write with index out of range')
self._buf[start:start+count] = buf self._buf[start:start+count] = buf
self._changed = True self._changed = True
def resize(self, size): def resize(self, size: int) -> None:
"""Resize buffer to size r"""Resize a given buffer
If size is larger than len(self), the buffer is appended with zero Resizes the current buffer in place. If size < len(self), the buffer
bytes. If it is smaller, the buffer will be truncated. will be truncated. If size > len(self), the buffer will be extended.
The newly added bytes will be b'\x00'
Args:
size: New size of buffer
Raises:
BufferError if buffer is locked
""" """
if self.locked: if self.locked:
raise BufferError('cannot write to locked buffer') raise BufferError('cannot write to locked buffer')
@ -127,8 +146,12 @@ class ByteBuffer(BufferType):
self._changed = True self._changed = True
@property @property
def locked(self): def locked(self) -> bool:
"""Returns True for read-only buffers.""" """Determine writability of buffer
Returns:
True if buffer has been locked to prevent writing
"""
return self._locked return self._locked
@locked.setter @locked.setter