summary refs log tree commit diff stats
path: root/lib/pure/streamwrapper.nim
blob: b99982f1b33626247fbf12318fd607c24a7f2496 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
#
#
#            Nim's Runtime Library
#        (c) Copyright 2020 Andreas Rumpf
#
#    See the file "copying.txt", included in this
#    distribution, for details about the copyright.
#

## This module implements stream wrapper.
##
## **Since** version 1.2.

import deques, streams

type
  PipeOutStream*[T] = ref object of T
    # When stream peek operation is called, it reads from base stream
    # type using `baseReadDataImpl` and stores the content to this buffer.
    # Next stream read operation returns data in the buffer so that previus peek
    # operation looks like didn't changed read positon.
    # When stream read operation that returns N byte data is called and the size is smaller than buffer size,
    # first N elements are removed from buffer.
    # Deque type can do such operation more efficiently than seq type.
    buffer: Deque[char]
    baseReadLineImpl: typeof(StreamObj.readLineImpl)
    baseReadDataImpl: typeof(StreamObj.readDataImpl)

proc posReadLine[T](s: Stream, line: var TaintedString): bool =
  var s = PipeOutStream[T](s)
  assert s.baseReadLineImpl != nil

  let n = s.buffer.len
  line.string.setLen(0)
  for i in 0..<n:
    var c = s.buffer.popFirst
    if c == '\c':
      c = readChar(s)
      return true
    elif c == '\L': return true
    elif c == '\0':
      return line.len > 0
    line.string.add(c)

  var line2: string
  result = s.baseReadLineImpl(s, line2)
  line.add line2

proc posReadData[T](s: Stream, buffer: pointer, bufLen: int): int =
  var s = PipeOutStream[T](s)
  assert s.baseReadDataImpl != nil

  let
    dest = cast[ptr UncheckedArray[char]](buffer)
    n = min(s.buffer.len, bufLen)
  result = n
  for i in 0..<n:
    dest[i] = s.buffer.popFirst
  if bufLen > n:
    result += s.baseReadDataImpl(s, addr dest[n], bufLen - n)

proc posReadDataStr[T](s: Stream, buffer: var string, slice: Slice[int]): int =
  posReadData[T](s, addr buffer[slice.a], slice.len)

proc posPeekData[T](s: Stream, buffer: pointer, bufLen: int): int =
  var s = PipeOutStream[T](s)
  assert s.baseReadDataImpl != nil

  let
    dest = cast[ptr UncheckedArray[char]](buffer)
    n = min(s.buffer.len, bufLen)

  result = n
  for i in 0..<n:
    dest[i] = s.buffer[i]

  if bufLen > n:
    let
      newDataNeeded = bufLen - n
      numRead = s.baseReadDataImpl(s, addr dest[n], newDataNeeded)
    result += numRead
    for i in 0..<numRead:
      s.buffer.addLast dest[n + i]

proc newPipeOutStream*[T](s: sink (ref T)): owned PipeOutStream[T] =
  ## Wrap pipe for reading with PipeOutStream so that you can use peek* procs and generate runtime error
  ## when setPosition/getPosition is called or write operation is performed.
  ##
  ## Example:
  ##
  ## .. code-block:: Nim
  ##   import osproc, streamwrapper
  ##   var
  ##     p = startProcess(exePath)
  ##     outStream = p.outputStream().newPipeOutStream()
  ##   echo outStream.peekChar
  ##   p.close()

  assert s.readDataImpl != nil

  new(result)
  for dest, src in fields((ref T)(result)[], s[]):
    dest = src
  wasMoved(s[])
  if result.readLineImpl != nil:
    result.baseReadLineImpl = result.readLineImpl
    result.readLineImpl = posReadLine[T]
  result.baseReadDataImpl = result.readDataImpl
  result.readDataImpl = posReadData[T]
  result.readDataStrImpl = posReadDataStr[T]
  result.peekDataImpl = posPeekData[T]

  # Set nil to anything you may not call.
  result.setPositionImpl = nil
  result.getPositionImpl = nil
  result.writeDataImpl = nil
  result.flushImpl = nil