Funktionierender Prototyp des Serious Games zur Vermittlung von Wissen zu Software-Engineering-Arbeitsmodellen.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

streams.py 3.9KB

1 year ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  1. from __future__ import annotations
  2. from typing import Generator
  3. class StreamReader:
  4. """
  5. Generator-based stream reader.
  6. This class doesn't support concurrent calls to :meth:`read_line`,
  7. :meth:`read_exact`, or :meth:`read_to_eof`. Make sure calls are
  8. serialized.
  9. """
  10. def __init__(self) -> None:
  11. self.buffer = bytearray()
  12. self.eof = False
  13. def read_line(self, m: int) -> Generator[None, None, bytes]:
  14. """
  15. Read a LF-terminated line from the stream.
  16. This is a generator-based coroutine.
  17. The return value includes the LF character.
  18. Args:
  19. m: maximum number bytes to read; this is a security limit.
  20. Raises:
  21. EOFError: if the stream ends without a LF.
  22. RuntimeError: if the stream ends in more than ``m`` bytes.
  23. """
  24. n = 0 # number of bytes to read
  25. p = 0 # number of bytes without a newline
  26. while True:
  27. n = self.buffer.find(b"\n", p) + 1
  28. if n > 0:
  29. break
  30. p = len(self.buffer)
  31. if p > m:
  32. raise RuntimeError(f"read {p} bytes, expected no more than {m} bytes")
  33. if self.eof:
  34. raise EOFError(f"stream ends after {p} bytes, before end of line")
  35. yield
  36. if n > m:
  37. raise RuntimeError(f"read {n} bytes, expected no more than {m} bytes")
  38. r = self.buffer[:n]
  39. del self.buffer[:n]
  40. return r
  41. def read_exact(self, n: int) -> Generator[None, None, bytes]:
  42. """
  43. Read a given number of bytes from the stream.
  44. This is a generator-based coroutine.
  45. Args:
  46. n: how many bytes to read.
  47. Raises:
  48. EOFError: if the stream ends in less than ``n`` bytes.
  49. """
  50. assert n >= 0
  51. while len(self.buffer) < n:
  52. if self.eof:
  53. p = len(self.buffer)
  54. raise EOFError(f"stream ends after {p} bytes, expected {n} bytes")
  55. yield
  56. r = self.buffer[:n]
  57. del self.buffer[:n]
  58. return r
  59. def read_to_eof(self, m: int) -> Generator[None, None, bytes]:
  60. """
  61. Read all bytes from the stream.
  62. This is a generator-based coroutine.
  63. Args:
  64. m: maximum number bytes to read; this is a security limit.
  65. Raises:
  66. RuntimeError: if the stream ends in more than ``m`` bytes.
  67. """
  68. while not self.eof:
  69. p = len(self.buffer)
  70. if p > m:
  71. raise RuntimeError(f"read {p} bytes, expected no more than {m} bytes")
  72. yield
  73. r = self.buffer[:]
  74. del self.buffer[:]
  75. return r
  76. def at_eof(self) -> Generator[None, None, bool]:
  77. """
  78. Tell whether the stream has ended and all data was read.
  79. This is a generator-based coroutine.
  80. """
  81. while True:
  82. if self.buffer:
  83. return False
  84. if self.eof:
  85. return True
  86. # When all data was read but the stream hasn't ended, we can't
  87. # tell if until either feed_data() or feed_eof() is called.
  88. yield
  89. def feed_data(self, data: bytes) -> None:
  90. """
  91. Write data to the stream.
  92. :meth:`feed_data` cannot be called after :meth:`feed_eof`.
  93. Args:
  94. data: data to write.
  95. Raises:
  96. EOFError: if the stream has ended.
  97. """
  98. if self.eof:
  99. raise EOFError("stream ended")
  100. self.buffer += data
  101. def feed_eof(self) -> None:
  102. """
  103. End the stream.
  104. :meth:`feed_eof` cannot be called more than once.
  105. Raises:
  106. EOFError: if the stream has ended.
  107. """
  108. if self.eof:
  109. raise EOFError("stream ended")
  110. self.eof = True
  111. def discard(self) -> None:
  112. """
  113. Discard all buffered data, but don't end the stream.
  114. """
  115. del self.buffer[:]