streamwrapper.nim 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118
  1. #
  2. #
  3. # Nim's Runtime Library
  4. # (c) Copyright 2020 Andreas Rumpf
  5. #
  6. # See the file "copying.txt", included in this
  7. # distribution, for details about the copyright.
  8. #
  9. ## This module implements stream wrapper.
  10. ##
  11. ## **Since** version 1.2.
  12. import deques, streams
  13. type
  14. PipeOutStream*[T] = ref object of T
  15. # When stream peek operation is called, it reads from base stream
  16. # type using `baseReadDataImpl` and stores the content to this buffer.
  17. # Next stream read operation returns data in the buffer so that previus peek
  18. # operation looks like didn't changed read positon.
  19. # When stream read operation that returns N byte data is called and the size is smaller than buffer size,
  20. # first N elements are removed from buffer.
  21. # Deque type can do such operation more efficiently than seq type.
  22. buffer: Deque[char]
  23. baseReadLineImpl: typeof(StreamObj.readLineImpl)
  24. baseReadDataImpl: typeof(StreamObj.readDataImpl)
  25. proc posReadLine[T](s: Stream, line: var TaintedString): bool =
  26. var s = PipeOutStream[T](s)
  27. assert s.baseReadLineImpl != nil
  28. let n = s.buffer.len
  29. line.string.setLen(0)
  30. for i in 0..<n:
  31. var c = s.buffer.popFirst
  32. if c == '\c':
  33. c = readChar(s)
  34. return true
  35. elif c == '\L': return true
  36. elif c == '\0':
  37. return line.len > 0
  38. line.string.add(c)
  39. var line2: string
  40. result = s.baseReadLineImpl(s, line2)
  41. line.add line2
  42. proc posReadData[T](s: Stream, buffer: pointer, bufLen: int): int =
  43. var s = PipeOutStream[T](s)
  44. assert s.baseReadDataImpl != nil
  45. let
  46. dest = cast[ptr UncheckedArray[char]](buffer)
  47. n = min(s.buffer.len, bufLen)
  48. result = n
  49. for i in 0..<n:
  50. dest[i] = s.buffer.popFirst
  51. if bufLen > n:
  52. result += s.baseReadDataImpl(s, addr dest[n], bufLen - n)
  53. proc posReadDataStr[T](s: Stream, buffer: var string, slice: Slice[int]): int =
  54. posReadData[T](s, addr buffer[slice.a], slice.len)
  55. proc posPeekData[T](s: Stream, buffer: pointer, bufLen: int): int =
  56. var s = PipeOutStream[T](s)
  57. assert s.baseReadDataImpl != nil
  58. let
  59. dest = cast[ptr UncheckedArray[char]](buffer)
  60. n = min(s.buffer.len, bufLen)
  61. result = n
  62. for i in 0..<n:
  63. dest[i] = s.buffer[i]
  64. if bufLen > n:
  65. let
  66. newDataNeeded = bufLen - n
  67. numRead = s.baseReadDataImpl(s, addr dest[n], newDataNeeded)
  68. result += numRead
  69. for i in 0..<numRead:
  70. s.buffer.addLast dest[n + i]
  71. proc newPipeOutStream*[T](s: sink (ref T)): owned PipeOutStream[T] =
  72. ## Wrap pipe for reading with PipeOutStream so that you can use peek* procs and generate runtime error
  73. ## when setPosition/getPosition is called or write operation is performed.
  74. ##
  75. ## Example:
  76. ##
  77. ## .. code-block:: Nim
  78. ## import osproc, streamwrapper
  79. ## var
  80. ## p = startProcess(exePath)
  81. ## outStream = p.outputStream().newPipeOutStream()
  82. ## echo outStream.peekChar
  83. ## p.close()
  84. assert s.readDataImpl != nil
  85. new(result)
  86. for dest, src in fields((ref T)(result)[], s[]):
  87. dest = src
  88. wasMoved(s[])
  89. if result.readLineImpl != nil:
  90. result.baseReadLineImpl = result.readLineImpl
  91. result.readLineImpl = posReadLine[T]
  92. result.baseReadDataImpl = result.readDataImpl
  93. result.readDataImpl = posReadData[T]
  94. result.readDataStrImpl = posReadDataStr[T]
  95. result.peekDataImpl = posPeekData[T]
  96. # Set nil to anything you may not call.
  97. result.setPositionImpl = nil
  98. result.getPositionImpl = nil
  99. result.writeDataImpl = nil
  100. result.flushImpl = nil