ContainerIO.py 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121
  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 typing import IO, AnyStr, Generic, Literal
  19. class ContainerIO(Generic[AnyStr]):
  20. """
  21. A file object that provides read access to a part of an existing
  22. file (for example a TAR file).
  23. """
  24. def __init__(self, file: IO[AnyStr], offset: int, length: int) -> None:
  25. """
  26. Create file object.
  27. :param file: Existing file.
  28. :param offset: Start of region, in bytes.
  29. :param length: Size of region, in bytes.
  30. """
  31. self.fh: IO[AnyStr] = file
  32. self.pos = 0
  33. self.offset = offset
  34. self.length = length
  35. self.fh.seek(offset)
  36. ##
  37. # Always false.
  38. def isatty(self) -> bool:
  39. return False
  40. def seek(self, offset: int, mode: Literal[0, 1, 2] = io.SEEK_SET) -> None:
  41. """
  42. Move file pointer.
  43. :param offset: Offset in bytes.
  44. :param mode: Starting position. Use 0 for beginning of region, 1
  45. for current offset, and 2 for end of region. You cannot move
  46. the pointer outside the defined region.
  47. """
  48. if mode == 1:
  49. self.pos = self.pos + offset
  50. elif mode == 2:
  51. self.pos = self.length + offset
  52. else:
  53. self.pos = offset
  54. # clamp
  55. self.pos = max(0, min(self.pos, self.length))
  56. self.fh.seek(self.offset + self.pos)
  57. def tell(self) -> int:
  58. """
  59. Get current file pointer.
  60. :returns: Offset from start of region, in bytes.
  61. """
  62. return self.pos
  63. def read(self, n: int = 0) -> AnyStr:
  64. """
  65. Read data.
  66. :param n: Number of bytes to read. If omitted or zero,
  67. read until end of region.
  68. :returns: An 8-bit string.
  69. """
  70. if n:
  71. n = min(n, self.length - self.pos)
  72. else:
  73. n = self.length - self.pos
  74. if not n: # EOF
  75. return b"" if "b" in self.fh.mode else "" # type: ignore[return-value]
  76. self.pos = self.pos + n
  77. return self.fh.read(n)
  78. def readline(self) -> AnyStr:
  79. """
  80. Read a line of text.
  81. :returns: An 8-bit string.
  82. """
  83. s: AnyStr = b"" if "b" in self.fh.mode else "" # type: ignore[assignment]
  84. newline_character = b"\n" if "b" in self.fh.mode else "\n"
  85. while True:
  86. c = self.read(1)
  87. if not c:
  88. break
  89. s = s + c
  90. if c == newline_character:
  91. break
  92. return s
  93. def readlines(self) -> list[AnyStr]:
  94. """
  95. Read multiple lines of text.
  96. :returns: A list of 8-bit strings.
  97. """
  98. lines = []
  99. while True:
  100. s = self.readline()
  101. if not s:
  102. break
  103. lines.append(s)
  104. return lines