github.com/u-root/u-root@v7.0.1-0.20200915234505-ad7babab0a8e+incompatible/cmds/exp/ed/commands.go (about) 1 // Copyright 2019 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 // commands.go - defines editor commands 6 package main 7 8 import ( 9 "bufio" 10 "bytes" 11 "fmt" 12 "io" 13 "os" 14 "regexp" 15 "strconv" 16 "strings" 17 ) 18 19 // A Context is passed to an invoked command 20 type Context struct { 21 cmd string // full command string 22 cmdOffset int // start of the command after address resolution 23 addrs []int // resolved addresses 24 } 25 26 // A Command can be run with a Context and returns an error 27 type Command func(*Context) error 28 29 // The cmds map maps single byte commands to their handler functions. 30 // This is also a good way to check what commands are implemented. 31 var cmds = map[byte]Command{ 32 'q': cmdQuit, 33 'Q': cmdQuit, 34 'd': cmdDelete, 35 'l': cmdPrint, 36 'p': cmdPrint, 37 'n': cmdPrint, 38 'h': cmdErr, 39 'H': cmdErr, 40 'a': cmdInput, 41 'i': cmdInput, 42 'c': cmdInput, 43 'w': cmdWrite, 44 'W': cmdWrite, 45 'k': cmdMark, 46 'e': cmdEdit, 47 'E': cmdEdit, 48 'r': cmdEdit, 49 'f': cmdFile, 50 '=': cmdLine, 51 'j': cmdJoin, 52 'm': cmdMove, 53 't': cmdMove, 54 'y': cmdCopy, 55 'x': cmdPaste, 56 'P': cmdPrompt, 57 's': cmdSub, 58 'u': cmdUndo, 59 'D': cmdDump, // var dump the buffer for debug 60 'z': cmdScroll, 61 '!': cmdCommand, 62 '#': func(*Context) (e error) { return }, 63 } 64 65 ////////////////////// 66 // Command handlers / 67 //////////////////// 68 69 func cmdDelete(ctx *Context) (e error) { 70 var r [2]int 71 if r, e = buffer.AddrRangeOrLine(ctx.addrs); e != nil { 72 return 73 } 74 e = buffer.Delete(r) 75 return 76 } 77 78 func cmdQuit(ctx *Context) (e error) { 79 if ctx.cmd[ctx.cmdOffset] == 'q' && buffer.Dirty() { 80 return fmt.Errorf("warning: file modified") 81 } 82 os.Exit(0) 83 return 84 } 85 86 func cmdPrint(ctx *Context) (e error) { 87 var r [2]int 88 if r, e = buffer.AddrRangeOrLine(ctx.addrs); e != nil { 89 return 90 } 91 for l := r[0]; l <= r[1]; l++ { 92 if ctx.cmd[ctx.cmdOffset] == 'n' { 93 fmt.Printf("%d\t", l+1) 94 } 95 line := buffer.GetMust(l, true) 96 if ctx.cmd[ctx.cmdOffset] == 'l' { 97 line += "$" // TODO: the man pages describes more escaping, but it's not clear what GNU ed actually does. 98 } 99 fmt.Printf("%s\n", line) 100 } 101 return 102 } 103 104 func cmdScroll(ctx *Context) (e error) { 105 start, e := buffer.AddrValue(ctx.addrs) 106 if e != nil { 107 return 108 } 109 // parse win size (if there) 110 winStr := ctx.cmd[ctx.cmdOffset+1:] 111 if len(winStr) > 0 { 112 var win int 113 if win, e = strconv.Atoi(winStr); e != nil { 114 return fmt.Errorf("invalid window size: %s", winStr) 115 } 116 state.winSize = win 117 } 118 end := start + state.winSize - 1 119 if end > buffer.Len()-1 { 120 end = buffer.Len() - 1 121 } 122 var ls []string 123 if ls, e = buffer.Get([2]int{start, end}); e != nil { 124 return 125 } 126 for _, l := range ls { 127 fmt.Println(l) 128 } 129 return 130 } 131 132 func cmdErr(ctx *Context) (e error) { 133 if ctx.cmd[ctx.cmdOffset] == 'h' { 134 if state.lastErr != nil { 135 fmt.Println(state.lastErr) 136 return 137 } 138 } 139 if ctx.cmd[ctx.cmdOffset] == 'H' { 140 if state.printErr { 141 state.printErr = false 142 return 143 } 144 state.printErr = true 145 } 146 return 147 } 148 149 func cmdInput(ctx *Context) (e error) { 150 scan := bufio.NewScanner(os.Stdin) 151 nbuf := []string{} 152 if len(ctx.cmd[ctx.cmdOffset+1:]) != 0 && ctx.cmd[ctx.cmdOffset] != 'c' { 153 return fmt.Errorf("%c only takes a single line addres", ctx.cmd[ctx.cmdOffset]) 154 } 155 for scan.Scan() { 156 line := scan.Text() 157 if line == "." { 158 break 159 } 160 nbuf = append(nbuf, line) 161 } 162 if len(nbuf) == 0 { 163 return 164 } 165 switch ctx.cmd[ctx.cmdOffset] { 166 case 'i': 167 var line int 168 if line, e = buffer.AddrValue(ctx.addrs); e != nil { 169 return 170 } 171 e = buffer.Insert(line, nbuf) 172 case 'a': 173 var line int 174 if line, e = buffer.AddrValue(ctx.addrs); e != nil { 175 return 176 } 177 e = buffer.Insert(line+1, nbuf) 178 case 'c': 179 var r [2]int 180 if r, e = buffer.AddrRange(ctx.addrs); e != nil { 181 return 182 } 183 if e = buffer.Delete(r); e != nil { 184 return 185 } 186 e = buffer.Insert(r[0], nbuf) 187 } 188 return 189 } 190 191 var rxWrite = regexp.MustCompile(`^(q)?(?: )?(!)?(.*)`) 192 193 func cmdWrite(ctx *Context) (e error) { 194 file := state.fileName 195 quit := false 196 run := false 197 var r [2]int 198 if ctx.cmdOffset == 0 { 199 r[0] = 0 200 r[1] = buffer.Len() - 1 201 } else { 202 if r, e = buffer.AddrRange(ctx.addrs); e != nil { 203 return 204 } 205 } 206 m := rxWrite.FindAllStringSubmatch(ctx.cmd[ctx.cmdOffset+1:], -1) 207 if m[0][1] == "q" { 208 quit = true 209 } 210 if m[0][2] == "!" { 211 run = true 212 } 213 if len(m[0][3]) > 0 { 214 file = m[0][3] 215 } 216 var lstr []string 217 lstr, e = buffer.Get(r) 218 if e != nil { 219 return 220 } 221 if run { 222 s := System{ 223 Cmd: m[0][3], 224 Stdin: bytes.NewBuffer(nil), 225 Stdout: os.Stdout, 226 Stderr: os.Stderr, 227 } 228 go func() { 229 for _, str := range lstr { 230 if _, e = fmt.Fprintf(s.Stdin.(*bytes.Buffer), "%s\n", str); e != nil { 231 return 232 } 233 } 234 }() 235 return s.Run() 236 } 237 238 var f *os.File 239 oFlag := os.O_TRUNC 240 if ctx.cmd[ctx.cmdOffset] == 'W' { 241 oFlag = os.O_APPEND 242 } 243 if f, e = os.OpenFile(file, os.O_WRONLY|os.O_CREATE|oFlag, 0666); e != nil { 244 return e 245 } 246 defer f.Close() 247 248 for _, s := range lstr { 249 _, e = fmt.Fprintf(f, "%s\n", s) 250 if e != nil { 251 return 252 } 253 } 254 if quit { 255 if e = cmdQuit(ctx); e != nil { 256 return 257 } 258 } 259 buffer.Clean() 260 return 261 } 262 263 func cmdMark(ctx *Context) (e error) { 264 if len(ctx.cmd)-1 <= ctx.cmdOffset { 265 e = fmt.Errorf("no mark character supplied") 266 return 267 } 268 c := ctx.cmd[ctx.cmdOffset+1] 269 var l int 270 if l, e = buffer.AddrValue(ctx.addrs); e != nil { 271 return 272 } 273 e = buffer.SetMark(c, l) 274 return 275 } 276 277 func cmdEdit(ctx *Context) (e error) { 278 var addr int 279 // we do this manually because we allow addr 0 280 if len(ctx.addrs) == 0 { 281 return ErrINV 282 } 283 addr = ctx.addrs[len(ctx.addrs)-1] 284 if addr != 0 && buffer.OOB(addr) { 285 return ErrOOB 286 } 287 // cmd or filename? 288 cmd := ctx.cmd[ctx.cmdOffset] 289 force := false 290 if cmd == 'E' || cmd == 'r' { 291 force = true 292 } // else == 'e' 293 if buffer.Dirty() && !force { 294 return fmt.Errorf("warning: file modified") 295 } 296 filename := ctx.cmd[ctx.cmdOffset+1:] 297 filename = filename[wsOffset(filename):] 298 var fh io.Reader 299 if len(filename) == 0 { 300 filename = state.fileName 301 } 302 if filename[0] == '!' { // command, not filename 303 s := System{ 304 Cmd: filename[1:], 305 Stdout: bytes.NewBuffer(nil), 306 Stdin: os.Stdin, 307 Stderr: os.Stderr, 308 } 309 if e = s.Run(); e != nil { 310 return 311 } 312 fh = s.Stdout.(io.Reader) 313 } else { // filename 314 if _, e = os.Stat(filename); os.IsNotExist(e) && !*fSuppress { 315 return fmt.Errorf("%s: No such file or directory", filename) 316 // this is not fatal, we just start with an empty buffer 317 } 318 if fh, e = os.Open(filename); e != nil { 319 e = fmt.Errorf("could not read file: %v", e) 320 return 321 } 322 state.fileName = filename 323 } 324 325 if cmd != 'r' { // other commands replace 326 buffer = NewFileBuffer(nil) 327 if e = buffer.Read(0, fh); e != nil { 328 return 329 } 330 } else { 331 e = buffer.Read(addr, fh) 332 } 333 if !*fSuppress { 334 fmt.Println(buffer.Size()) 335 } 336 return 337 } 338 339 func cmdFile(ctx *Context) (e error) { 340 newFile := ctx.cmd[ctx.cmdOffset:] 341 newFile = newFile[wsOffset(newFile):] 342 if len(newFile) > 0 { 343 state.fileName = newFile 344 return 345 } 346 fmt.Println(state.fileName) 347 return 348 } 349 350 func cmdLine(ctx *Context) (e error) { 351 addr, e := buffer.AddrValue(ctx.addrs) 352 if e == nil { 353 fmt.Println(addr + 1) 354 } 355 return 356 } 357 358 func cmdJoin(ctx *Context) (e error) { 359 var r [2]int 360 if r, e = buffer.AddrRangeOrLine(ctx.addrs); e != nil { 361 return 362 } 363 // Technically only a range works, but a line isn't an error 364 if r[0] == r[1] { 365 return 366 } 367 368 joined := "" 369 for l := r[0]; l <= r[1]; l++ { 370 joined += buffer.GetMust(l, false) 371 } 372 if e = buffer.Delete(r); e != nil { 373 return 374 } 375 e = buffer.Insert(r[0], []string{joined}) 376 return 377 } 378 379 func cmdMove(ctx *Context) (e error) { 380 var r [2]int 381 var dest int 382 var lines []string 383 cmd := ctx.cmd[ctx.cmdOffset] 384 if r, e = buffer.AddrRangeOrLine(ctx.addrs); e != nil { 385 return 386 } 387 // must parse the destination 388 destStr := ctx.cmd[ctx.cmdOffset+1:] 389 var nctx Context 390 if nctx.addrs, nctx.cmdOffset, e = buffer.ResolveAddrs(destStr); e != nil { 391 return 392 } 393 // this is a bit hacky, but we're supposed to allow 0 394 append := 1 395 last := len(nctx.addrs) - 1 396 if nctx.addrs[last] == -1 { 397 nctx.addrs[last] = 0 398 append = 0 399 } 400 if dest, e = buffer.AddrValue(nctx.addrs); e != nil { 401 return 402 } 403 404 if lines, e = buffer.Get(r); e != nil { 405 return 406 } 407 delt := r[1] - r[0] + 1 408 if dest < r[0] { 409 r[0] += delt 410 r[1] += delt 411 } else if dest > r[1] { 412 //NOP 413 } else { 414 return fmt.Errorf("cannot move lines to within their own range") 415 } 416 417 // Should we throw an error if there's trailing stuff? 418 if e = buffer.Insert(dest+append, lines); e != nil { 419 return 420 } 421 if cmd == 'm' { 422 e = buffer.Delete(r) 423 } // else 't' 424 return 425 } 426 427 func cmdCopy(ctx *Context) (e error) { 428 var r [2]int 429 if r, e = buffer.AddrRangeOrLine(ctx.addrs); e != nil { 430 return 431 } 432 return buffer.Copy(r) 433 } 434 435 func cmdPaste(ctx *Context) (e error) { 436 var addr int 437 // this is a bit hacky, but we're supposed to allow 0 438 append := 1 439 last := len(ctx.addrs) - 1 440 if ctx.addrs[last] == -1 { 441 ctx.addrs[last] = 0 442 append = 0 443 } 444 if addr, e = buffer.AddrValue(ctx.addrs); e != nil { 445 return 446 } 447 return buffer.Paste(addr + append) 448 } 449 450 func cmdPrompt(ctx *Context) (e error) { 451 if state.prompt { 452 state.prompt = false 453 } else if len(*fPrompt) > 0 { 454 state.prompt = true 455 } 456 return 457 } 458 459 var rxSanitize = regexp.MustCompile(`\\.`) 460 var rxBackrefSanitize = regexp.MustCompile(`\\\\`) 461 var rxBackref = regexp.MustCompile(`\\([0-9]+)|&`) 462 var rxSubArgs = regexp.MustCompile(`g|l|n|p|\d+`) 463 464 // FIXME: this is probably more convoluted than it needs to be 465 func cmdSub(ctx *Context) (e error) { 466 cmd := ctx.cmd[ctx.cmdOffset+1:] 467 if len(cmd) == 0 { 468 if len(state.lastSub) == 0 { 469 return fmt.Errorf("invalid substitution") 470 } 471 cmd = state.lastSub 472 } 473 state.lastSub = cmd 474 del := cmd[0] 475 switch del { 476 case ' ': 477 fallthrough 478 case '\n': 479 fallthrough 480 case 'm': 481 fallthrough 482 case 'g': 483 return fmt.Errorf("Invalid pattern delimiter") 484 } 485 // we replace escapes and their escaped characters with spaces to keep indexing 486 sane := rxSanitize.ReplaceAllString(cmd, " ") 487 488 idx := [2]int{-1, -1} 489 idx[0] = strings.Index(sane[1:], string(del)) + 1 490 if idx[0] != -1 { 491 idx[1] = strings.Index(sane[idx[0]+1:], string(del)) + idx[0] + 1 492 } 493 if idx[1] == -1 { 494 idx[1] = len(cmd) - 1 495 } 496 497 mat := cmd[1:idx[0]] 498 rep := cmd[idx[0]+1 : idx[1]] 499 if rep == "%" { 500 rep = state.lastRep 501 } 502 state.lastRep = rep 503 arg := cmd[idx[1]+1:] 504 505 // arg processing 506 var count = 1 507 var printP, printL, printN, global bool 508 509 parsedArgs := rxSubArgs.FindAllStringSubmatch(arg, -1) 510 for _, m := range parsedArgs { 511 switch m[0] { 512 case "g": 513 global = true 514 case "p": 515 printP = true 516 case "l": 517 printL = true 518 case "n": 519 printN = true 520 default: 521 if count, e = strconv.Atoi(m[0]); e != nil || count < 1 { 522 return fmt.Errorf("invalid substitution argument") 523 } 524 } 525 } 526 527 repSane := rxBackrefSanitize.ReplaceAllString(rep, " ") 528 refs := rxBackref.FindAllStringSubmatchIndex(repSane, -1) 529 530 var r [2]int 531 if r, e = buffer.AddrRangeOrLine(ctx.addrs); e != nil { 532 return 533 } 534 535 var rx *regexp.Regexp 536 if rx, e = regexp.Compile(mat); e != nil { 537 return 538 } 539 540 last := "" 541 lastN := 0 542 nMatch := 0 543 b, _ := buffer.Get(r) 544 // we have to do things a bit manually because we we only have ReplaceAll, and we don't necessarily want that 545 for ln, l := range b { 546 matches := rx.FindAllStringSubmatchIndex(l, -1) 547 if !(len(matches) > 0) { 548 continue // skip the rest if we don't have matches 549 } 550 if !global { 551 if len(matches) >= count { 552 matches = [][]int{matches[count-1]} 553 } else { 554 matches = [][]int{} 555 } 556 } 557 // we have matches, deal with them 558 fLin := "" 559 oLin := 0 560 for _, m := range matches { 561 nMatch++ 562 //fRep := rep 563 //offset := 0 564 565 // Fill backrefs 566 oRep := 0 567 fRep := "" 568 for _, r := range refs { 569 if rep[r[0]:r[1]] == "&" { 570 fRep += rep[oRep:r[0]] 571 fRep += l[m[0]:m[1]] 572 oRep = r[1] 573 } else { 574 i, _ := strconv.Atoi(rep[r[2]:r[3]]) 575 if i > len(m)/2-1 { // not enough submatches for backref 576 return fmt.Errorf("invalid backref") 577 } 578 fRep += rep[oRep:r[0]] 579 fRep += l[m[2*i]:m[2*i+1]] 580 oRep = r[1] 581 } 582 } 583 fRep += rep[oRep:] 584 585 fLin += l[oLin:m[0]] 586 fLin += fRep 587 oLin = m[1] 588 } 589 fLin += l[oLin:] 590 if e = buffer.Delete([2]int{ln, ln}); e != nil { 591 return 592 } 593 if e = buffer.Insert(ln, []string{fLin}); e != nil { 594 return 595 } 596 last = fLin 597 lastN = ln 598 } 599 if nMatch == 0 { 600 e = fmt.Errorf("no match") 601 } else { 602 if printP { 603 fmt.Println(last) 604 } 605 if printL { 606 fmt.Println(last + "$") 607 } 608 if printN { 609 fmt.Printf("%d\t%s\n", lastN+1, last) 610 } 611 } 612 return 613 } 614 615 func cmdUndo(ctx *Context) (e error) { 616 buffer.Rewind() 617 return 618 } 619 620 func cmdDump(ctx *Context) (e error) { 621 fmt.Printf("%v\n", buffer) 622 return 623 } 624 625 var rxCmdSub = regexp.MustCompile(`%`) 626 627 func cmdCommand(ctx *Context) (e error) { 628 s := System{ 629 Cmd: ctx.cmd[ctx.cmdOffset+1:], 630 Stdin: os.Stdin, 631 Stdout: os.Stdout, 632 Stderr: os.Stderr, 633 } 634 e = s.Run() 635 if e != nil { 636 return 637 } 638 fmt.Println("!") 639 return 640 }