ContainerIO.py 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. #
  2. # The Python Imaging Library.
  3. # $Id$
  4. #
  5. # a class to read from a container file
  6. #
  7. # History:
  8. # 1995-06-18 fl Created
  9. # 1995-09-07 fl Added readline(), readlines()
  10. #
  11. # Copyright (c) 1997-2001 by Secret Labs AB
  12. # Copyright (c) 1995 by Fredrik Lundh
  13. #
  14. # See the README file for information on usage and redistribution.
  15. #
  16. from __future__ import annotations
  17. import io
  18. from collections.abc import Iterable
  19. from typing import IO, AnyStr, NoReturn
  20. class ContainerIO(IO[AnyStr]):
  21. """
  22. A file object that provides read access to a part of an existing
  23. file (for example a TAR file).
  24. """
  25. def __init__(self, file: IO[AnyStr], offset: int, length: int) -> None:
  26. """
  27. Create file object.
  28. :param file: Existing file.
  29. :param offset: Start of region, in bytes.
  30. :param length: Size of region, in bytes.
  31. """
  32. self.fh: IO[AnyStr] = file
  33. self.pos = 0
  34. self.offset = offset
  35. self.length = length
  36. self.fh.seek(offset)
  37. ##
  38. # Always false.
  39. def isatty(self) -> bool:
  40. return False
  41. def seekable(self) -> bool:
  42. return True
  43. def seek(self, offset: int, mode: int = io.SEEK_SET) -> int:
  44. """
  45. Move file pointer.
  46. :param offset: Offset in bytes.
  47. :param mode: Starting position. Use 0 for beginning of region, 1
  48. for current offset, and 2 for end of region. You cannot move
  49. the pointer outside the defined region.
  50. :returns: Offset from start of region, in bytes.
  51. """
  52. if mode == 1:
  53. self.pos = self.pos + offset
  54. elif mode == 2:
  55. self.pos = self.length + offset
  56. else:
  57. self.pos = offset
  58. # clamp
  59. self.pos = max(0, min(self.pos, self.length))
  60. self.fh.seek(self.offset + self.pos)
  61. return self.pos
  62. def tell(self) -> int:
  63. """
  64. Get current file pointer.
  65. :returns: Offset from start of region, in bytes.
  66. """
  67. return self.pos
  68. def readable(self) -> bool:
  69. return True
  70. def read(self, n: int = -1) -> AnyStr:
  71. """
  72. Read data.
  73. :param n: Number of bytes to read. If omitted, zero or negative,
  74. read until end of region.
  75. :returns: An 8-bit string.
  76. """
  77. if n > 0:
  78. n = min(n, self.length - self.pos)
  79. else:
  80. n = self.length - self.pos
  81. if n <= 0: # EOF
  82. return b"" if "b" in self.fh.mode else "" # type: ignore[return-value]
  83. self.pos = self.pos + n
  84. return self.fh.read(n)
  85. def readline(self, n: int = -1) -> AnyStr:
  86. """
  87. Read a line of text.
  88. :param n: Number of bytes to read. If omitted, zero or negative,
  89. read until end of line.
  90. :returns: An 8-bit string.
  91. """
  92. s: AnyStr = b"" if "b" in self.fh.mode else "" # type: ignore[assignment]
  93. newline_character = b"\n" if "b" in self.fh.mode else "\n"
  94. while True:
  95. c = self.read(1)
  96. if not c:
  97. break
  98. s = s + c
  99. if c == newline_character or len(s) == n:
  100. break
  101. return s
  102. def readlines(self, n: int | None = -1) -> list[AnyStr]:
  103. """
  104. Read multiple lines of text.
  105. :param n: Number of lines to read. If omitted, zero, negative or None,
  106. read until end of region.
  107. :returns: A list of 8-bit strings.
  108. """
  109. lines = []
  110. while True:
  111. s = self.readline()
  112. if not s:
  113. break
  114. lines.append(s)
  115. if len(lines) == n:
  116. break
  117. return lines
  118. def writable(self) -> bool:
  119. return False
  120. def write(self, b: AnyStr) -> NoReturn:
  121. raise NotImplementedError()
  122. def writelines(self, lines: Iterable[AnyStr]) -> NoReturn:
  123. raise NotImplementedError()
  124. def truncate(self, size: int | None = None) -> int:
  125. raise NotImplementedError()
  126. def __enter__(self) -> ContainerIO[AnyStr]:
  127. return self
  128. def __exit__(self, *args: object) -> None:
  129. self.close()
  130. def __iter__(self) -> ContainerIO[AnyStr]:
  131. return self
  132. def __next__(self) -> AnyStr:
  133. line = self.readline()
  134. if not line:
  135. msg = "end of region"
  136. raise StopIteration(msg)
  137. return line
  138. def fileno(self) -> int:
  139. return self.fh.fileno()
  140. def flush(self) -> None:
  141. self.fh.flush()
  142. def close(self) -> None:
  143. self.fh.close()