github.com/Ilhicas/nomad@v1.0.4-0.20210304152020-e86851182bc3/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 if rb[0] == '\n' || rb[0] == '\r' { 97 state = sLookEscapeChar 98 } 99 } 100 } 101 } 102 } 103 104 if err != nil { 105 // write ~ if it's the last thing 106 if state == sLookChar { 107 bw.WriteByte(r.escapeChar) 108 } 109 bw.Flush() 110 r.pw.CloseWithError(err) 111 break 112 } 113 } 114 } 115 116 // processBuf process buffer and emits all output to writer 117 // if the last part of buffer is a new line followed by sequnce, it writes 118 // all output until the new line and returns sLookChar 119 func (r *reader) processBuf(bw io.Writer, buf []byte, n int, s lookState) lookState { 120 i := 0 121 122 wi := 0 123 124 START: 125 if s == sLookEscapeChar && buf[i] == r.escapeChar { 126 if i+1 >= n { 127 // buf terminates with ~ - write all before 128 bw.Write(buf[wi:i]) 129 return sLookChar 130 } 131 132 nc := buf[i+1] 133 if nc == r.escapeChar { 134 // skip one escape char 135 bw.Write(buf[wi:i]) 136 i++ 137 wi = i 138 } else if r.handler(nc) { 139 // skip both characters 140 bw.Write(buf[wi:i]) 141 i = i + 2 142 wi = i 143 } else if nc == '\n' || nc == '\r' { 144 i = i + 2 145 s = sLookEscapeChar 146 goto START 147 } else { 148 i = i + 2 149 // need to write everything keep going 150 } 151 } 152 153 // search until we get \n~, or buf terminates 154 for { 155 if i >= n { 156 // got to end without new line, write and return 157 bw.Write(buf[wi:n]) 158 return sLookNewLine 159 } 160 161 if buf[i] == '\n' || buf[i] == '\r' { 162 // buf terminated at new line 163 if i+1 >= n { 164 bw.Write(buf[wi:n]) 165 return sLookEscapeChar 166 } 167 168 // peek to see escape character go back to START if so 169 if buf[i+1] == r.escapeChar { 170 s = sLookEscapeChar 171 i++ 172 goto START 173 } 174 } 175 176 i++ 177 } 178 }