github.com/iqoqo/nomad@v0.11.3-0.20200911112621-d7021c74d101/helper/escapingio/reader.go (about) 1 package escapingio 2 3 import ( 4 "bufio" 5 "io" 6 ) 7 8 // Handler is a callback for handling an escaped char. Reader would skip 9 // the escape char and passed char if returns true; otherwise, it preserves them 10 // in output 11 type Handler func(c byte) bool 12 13 // NewReader returns a reader that escapes the c character (following new lines), 14 // in the same manner OpenSSH handling, which defaults to `~`. 15 // 16 // For illustrative purposes, we use `~` in documentation as a shorthand for escaping character. 17 // 18 // If following a new line, reader sees: 19 // * `~~`, only one is emitted 20 // * `~.` (or any character), the handler is invoked with the character. 21 // If handler returns true, `~.` will be skipped; otherwise, it's propagated. 22 // * `~` and it's the last character in stream, it's propagated 23 // 24 // Appearances of `~` when not preceded by a new line are propagated unmodified. 25 func NewReader(r io.Reader, c byte, h Handler) io.Reader { 26 pr, pw := io.Pipe() 27 reader := &reader{ 28 impl: r, 29 escapeChar: c, 30 handler: h, 31 pr: pr, 32 pw: pw, 33 } 34 go reader.pipe() 35 return reader 36 } 37 38 // lookState represents the state of reader for what character of `\n~.` sequence 39 // reader is looking for 40 type lookState int 41 42 const ( 43 // sLookNewLine indicates that reader is looking for new line 44 sLookNewLine lookState = iota 45 46 // sLookEscapeChar indicates that reader is looking for ~ 47 sLookEscapeChar 48 49 // sLookChar indicates that reader just read `~` is waiting for next character 50 // before acting 51 sLookChar 52 ) 53 54 // to ease comments, i'll assume escape character to be `~` 55 type reader struct { 56 impl io.Reader 57 escapeChar uint8 58 handler Handler 59 60 // buffers 61 pw *io.PipeWriter 62 pr *io.PipeReader 63 } 64 65 func (r *reader) Read(buf []byte) (int, error) { 66 return r.pr.Read(buf) 67 } 68 69 func (r *reader) pipe() { 70 rb := make([]byte, 4096) 71 bw := bufio.NewWriter(r.pw) 72 73 state := sLookEscapeChar 74 75 for { 76 n, err := r.impl.Read(rb) 77 78 if n > 0 { 79 state = r.processBuf(bw, rb, n, state) 80 bw.Flush() 81 if state == sLookChar { 82 // terminated with ~ - let's read one more character 83 n, err = r.impl.Read(rb[:1]) 84 if n == 1 { 85 state = sLookNewLine 86 if rb[0] == r.escapeChar { 87 // only emit escape character once 88 bw.WriteByte(rb[0]) 89 bw.Flush() 90 } else if r.handler(rb[0]) { 91 // skip if handled 92 } else { 93 bw.WriteByte(r.escapeChar) 94 bw.WriteByte(rb[0]) 95 bw.Flush() 96 } 97 } 98 } 99 } 100 101 if err != nil { 102 // write ~ if it's the last thing 103 if state == sLookChar { 104 bw.WriteByte(r.escapeChar) 105 } 106 bw.Flush() 107 r.pw.CloseWithError(err) 108 break 109 } 110 } 111 } 112 113 // processBuf process buffer and emits all output to writer 114 // if the last part of buffer is a new line followed by sequnce, it writes 115 // all output until the new line and returns sLookChar 116 func (r *reader) processBuf(bw io.Writer, buf []byte, n int, s lookState) lookState { 117 i := 0 118 119 wi := 0 120 121 START: 122 if s == sLookEscapeChar && buf[i] == r.escapeChar { 123 if i+1 >= n { 124 // buf terminates with ~ - write all before 125 bw.Write(buf[wi:i]) 126 return sLookChar 127 } 128 129 nc := buf[i+1] 130 if nc == r.escapeChar { 131 // skip one escape char 132 bw.Write(buf[wi:i]) 133 i++ 134 wi = i 135 } else if r.handler(nc) { 136 // skip both characters 137 bw.Write(buf[wi:i]) 138 i = i + 2 139 wi = i 140 } else { 141 i = i + 2 142 // need to write everything keep going 143 } 144 } 145 146 // search until we get \n~, or buf terminates 147 for { 148 if i >= n { 149 // got to end without new line, write and return 150 bw.Write(buf[wi:n]) 151 return sLookNewLine 152 } 153 154 if buf[i] == '\n' || buf[i] == '\r' { 155 // buf terminated at new line 156 if i+1 >= n { 157 bw.Write(buf[wi:n]) 158 return sLookEscapeChar 159 } 160 161 // peek to see escape character go back to START if so 162 if buf[i+1] == r.escapeChar { 163 s = sLookEscapeChar 164 i++ 165 goto START 166 } 167 } 168 169 i++ 170 } 171 }