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