github.com/oweisse/u-root@v0.0.0-20181109060735-d005ad25fef1/xcmds/ed/text.go (about) 1 // Copyright 2012-2017 the u-root Authors. All rights reserved 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package main 6 7 import ( 8 "bytes" 9 "fmt" 10 "io" 11 "io/ioutil" 12 "os" 13 "reflect" 14 "regexp" 15 ) 16 17 // start and end are used like slices. 18 // so 1,1 defines a point. 1, 2 is line 1. 19 // 1, $ is all the lines where $ is len(lines)+1 20 type file struct { 21 dot int 22 dirty bool 23 lines []int 24 data []byte 25 pat string 26 } 27 28 func makeLines(n []byte) [][]byte { 29 if len(n) == 0 { 30 return nil 31 } 32 var ( 33 data [][]byte 34 sawnl bool 35 pos int 36 i int 37 ) 38 for i = range n { 39 if sawnl { 40 sawnl = false 41 l := make([]byte, i-pos) 42 copy(l, n[pos:i]) 43 data = append(data, l) 44 pos = i 45 } 46 if n[i] == '\n' { 47 sawnl = true 48 } 49 } 50 51 if pos <= i { 52 l := make([]byte, len(n[pos:])) 53 copy(l, n[pos:]) 54 data = append(data, l) 55 } 56 debug("makeLines: %v", data) 57 return data 58 } 59 60 func (f *file) String() string { 61 return fmt.Sprintf("%d %v %s", f.dot, f.lines, string(f.data)) 62 } 63 64 func (f *file) fixLines() { 65 f.lines = nil 66 if len(f.data) == 0 { 67 return 68 } 69 f.lines = []int{0} 70 71 lines := 1 72 var sawnl bool 73 for i, v := range f.data { 74 if sawnl { 75 lines++ 76 f.lines = append(f.lines, i) 77 sawnl = false 78 } 79 if v == '\n' { 80 sawnl = true 81 } 82 } 83 if f.dot < 1 { 84 f.dot = 1 85 } 86 if f.dot > lines { 87 f.dot = lines 88 } 89 // f.start, f.end = f.dot, f.dot 90 } 91 92 // SliceXX returns half-closed indexes of the segment corresponding to [f.start, f.end] 93 // as in the Go slice style. 94 // The intent is that start and end act like slice indices, 95 // i.e. the slice returned should look as though 96 // you'd taken lines[f.start:f.end] 97 // Note that a zero length slice is fine, and is used 98 // for, e.g., inserting. 99 // Also, note, the indices need to be changed from 1-relative 100 // to 0-relative, since the API at all levels is 1-relative. 101 func (f *file) SliceX(startLine, endLine int) (int, int) { 102 var end, start int 103 debug("SliceX: f %v", f) 104 defer debug("SliceX done: f %v, start %v, end %v", f, start, end) 105 if startLine > len(f.lines) { 106 //f.start = len(f.lines) 107 //f.end = f.start 108 return len(f.data), len(f.data) 109 } else if startLine > 0 { 110 start = f.lines[startLine-1] 111 } 112 end = start 113 if endLine < startLine { 114 endLine = startLine 115 } 116 if endLine != startLine { 117 if endLine >= len(f.lines) { 118 //f.end = len(f.lines) 119 end = len(f.data) 120 return start, end 121 } 122 end = f.lines[endLine-1] 123 } 124 debug("SliceX: f %v, start %v end %v\n", f, start, end) 125 return start, end 126 } 127 128 func (f *file) Slice(startLine, endLine int) ([]byte, int, int) { 129 s, e := f.SliceX(startLine, endLine) 130 return f.data[s:e], s, e 131 } 132 133 func (f *file) Replace(n []byte, startLine, endLine int) (int, error) { 134 defer debug("Replace done: f %v", f) 135 if f.data != nil { 136 start, end := f.SliceX(startLine, endLine) 137 debug("replace: f %v start %v end %v", f, start, end) 138 pre := f.data[0:start] 139 post := f.data[end:] 140 debug("replace: pre is %v, post is %v\n", pre, post) 141 var b bytes.Buffer 142 b.Write(pre) 143 b.Write(n) 144 b.Write(post) 145 f.data = b.Bytes() 146 } else { 147 f.data = n 148 } 149 f.fixLines() 150 return len(n), nil 151 } 152 153 // Read reads strings from the file after . 154 // If there are lines already the new lines are inserted after '.' 155 // dot is unchanged. 156 func (f *file) Read(r io.Reader, startLine, endLine int) (int, error) { 157 debug("Read: r %v, startLine %v endLine %v", r, startLine, endLine) 158 d, err := ioutil.ReadAll(r) 159 debug("ReadAll returns %v, %v", d, err) 160 if err != nil { 161 return -1, err 162 } 163 // in ed, for text read, the line # is a point, not a line. 164 // For other commands, it's a line. Hence for reading 165 // line x, we need to make a point at line x+1 and 166 // read there. 167 startLine = startLine + 1 168 return f.Replace(d, startLine, endLine) 169 } 170 171 // Write writes the lines out from start to end, inclusive. 172 // dot is unchanged. 173 func (f *file) Write(w io.Writer, startLine, endLine int) (int, error) { 174 if endLine < startLine || startLine < 1 || endLine > len(f.lines)+1 { 175 return -1, fmt.Errorf("file is %d lines and [start, end] is [%d, %d]", len(f.lines), startLine, endLine) 176 } 177 178 start, end := f.SliceX(startLine, endLine) 179 amt, err := w.Write(f.data[start:end]) 180 181 return amt, err 182 } 183 184 func (f *file) Print(w io.Writer, start, end int) (int, error) { 185 debug("Print %v %v %v", f.data, start, end) 186 i, err := f.Write(w, start, end) 187 if err != nil && start < end { 188 f.dot = end + 1 189 } 190 return i, err 191 } 192 193 // Write writes the lines out from start to end, inclusive. 194 // dot is unchanged. 195 func (f *file) WriteFile(n string, startLine, endLine int) (int, error) { 196 out, err := os.Create(n) 197 if err != nil { 198 return -1, err 199 } 200 defer out.Close() 201 return f.Write(out, startLine, endLine) 202 } 203 204 // Sub replaces the regexp with a different one. 205 func (f *file) Sub(re, n, opt string, startLine, endLine int) error { 206 debug("Sub re %s n %s opt %s\n", re, n, opt) 207 if re == "" { 208 return fmt.Errorf("Empty RE") 209 } 210 r, err := regexp.Compile(re) 211 if err != nil { 212 return err 213 } 214 var opts = make(map[byte]bool) 215 for i := range opt { 216 opts[opt[i]] = true 217 } 218 219 o, start, end := f.Slice(startLine, endLine) 220 debug("Slice from [%v,%v] is [%v, %v] %v", startLine, endLine, start, end, string(o)) 221 // Lines can turn into two lines. All kinds of stuff can happen. 222 // So 223 // Break it into lines 224 // make copies of those lines. 225 // for each line, do the sub 226 // paste it back together 227 // put it back in to f.data 228 b := makeLines(o) 229 if b == nil { 230 return nil 231 } 232 for i := range b { 233 var replaced bool 234 debug("Sub: before b[i] is %v", b[i]) 235 b[i] = r.ReplaceAllFunc(b[i], func(o []byte) []byte { 236 debug("Sub: func called with %v", o) 237 if opts['g'] || !replaced { 238 f.dirty = true 239 replaced = true 240 return []byte(n) 241 } 242 return o 243 }) 244 debug("Sub: after b[i] is %v", b[i]) 245 } 246 247 debug("replaced o %v with n %v\n", o, b) 248 var repl = make([]byte, start) 249 copy(repl, f.data[0:start]) 250 for _, v := range b { 251 repl = append(repl, v...) 252 } 253 254 f.data = append(repl, f.data[end:]...) 255 f.fixLines() 256 if opts['p'] { 257 _, err = f.Write(os.Stdout, startLine, endLine) 258 } 259 return err 260 } 261 262 func (f *file) Dirty(d bool) { 263 f.dirty = d 264 } 265 266 func (f *file) IsDirty() bool { 267 return f.dirty 268 } 269 270 // Range returns the closed range 271 // of lines. 272 func (f *file) Range() (int, int) { 273 if f.lines == nil || len(f.lines) == 0 { 274 return 0, 0 275 } 276 return 1, len(f.lines) 277 } 278 279 func (f *file) Equal(e Editor) error { 280 var errors string 281 g := e.(*file) 282 // we should verify dot but let's not do that just yet, we don't 283 // have it right. 284 if !reflect.DeepEqual(f.lines, g.lines) { 285 errors += fmt.Sprintf("%v vs %v: lines don't match", f.lines, g.lines) 286 } 287 if len(f.data) != len(g.data) { 288 errors += fmt.Sprintf("data len differs: %v vs %v", len(f.data), len(g.data)) 289 } else { 290 for i := range f.data { 291 if f.data[i] != g.data[i] { 292 errors += fmt.Sprintf("%d: %v vs %v: data doesn't match", i, f.data[i], g.data[i]) 293 } 294 } 295 } 296 if errors != "" { 297 return fmt.Errorf(errors) 298 } 299 return nil 300 } 301 302 func (f *file) Dot() int { 303 return f.dot 304 } 305 306 func (f *file) Move(dot int) { 307 f.dot = dot 308 } 309 310 func NewTextEditor(a ...editorArg) (Editor, error) { 311 var f = &file{} 312 313 for _, v := range a { 314 if err := v(f); err != nil { 315 return nil, err 316 } 317 } 318 f.dot = 1 319 return f, nil 320 }