github.com/mvdan/u-root-coreutils@v0.0.0-20230122170626-c2eef2898555/cmds/exp/ed/ed.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 // This `ed` is intended to be a feature-complete mimick of [GNU Ed](https://www.gnu.org/software/ed//). It is a close enough mimick that the [GNU Ed Man Page](https://www.gnu.org/software/ed/manual/ed_manual.html) should be a reliable source of documentation. Divergence from the man page is generally considered a bug (unless it's an added feature). 6 // 7 // There are a few known differences: 8 // 9 // - `ed` uses `go`'s `regexp` package, and as such may have a somewhat different regular expression syntax. Note, however, that backreferences follow the `ed` syntax of `\<ref>`, not the `go` syntax of `$<ref>`. 10 // - there has been little/no attempt to make particulars like error messages match `GNU Ed`. 11 // - rather than being an error, the 'g' option for 's' simply overrides any specified count. 12 // - does not support "traditional" mode 13 // 14 // The following has been implemented: 15 // - Full line address parsing (including RE and markings) 16 // - Implmented commands: !, #, =, E, H, P, Q, W, a, c, d, e, f, h, i, j, k, l, m, n, p, q, r, s, t, u, w, x, y, z 17 // 18 // The following has *not* yet been implemented, but will be eventually: 19 // - Unimplemented commands: g, G, v, V 20 // - does not (yet) support "loose" mode 21 // - does not (yet) support "restricted" mod 22 package main 23 24 import ( 25 "bufio" 26 "errors" 27 "flag" 28 "fmt" 29 "io" 30 "log" 31 "os" 32 33 "github.com/mvdan/u-root-coreutils/pkg/uroot/util" 34 ) 35 36 // flags 37 var ( 38 fsuppress bool 39 fprompt string 40 usage = "Usage: ed [-s] [-p <prompt>] [file]\n" 41 ) 42 43 func init() { 44 flag.BoolVar(&fsuppress, "s", false, "suppress counts") 45 flag.StringVar(&fprompt, "p", "*", "specify a command prompt") 46 flag.Usage = util.Usage(flag.Usage, usage) 47 } 48 49 // current FileBuffer 50 var buffer *FileBuffer 51 52 // current ed state 53 var state struct { 54 fileName string // current filename 55 lastErr error 56 printErr bool 57 prompt bool 58 winSize int 59 lastRep string 60 lastSub string 61 } 62 63 // Parse input and execute command 64 func execute(cmd string, output io.Writer) (e error) { 65 ctx := &Context{ 66 cmd: cmd, 67 out: output, 68 } 69 if ctx.addrs, ctx.cmdOffset, e = buffer.ResolveAddrs(cmd); e != nil { 70 return 71 } 72 if len(cmd) <= ctx.cmdOffset { 73 // no command, default to print 74 ctx.cmd += "p" 75 } 76 if exe, ok := cmds[ctx.cmd[ctx.cmdOffset]]; ok { 77 buffer.Start() 78 if e = exe(ctx); e != nil { 79 return 80 } 81 buffer.End() 82 } else { 83 return fmt.Errorf("invalid command: %v", cmd[ctx.cmdOffset]) 84 } 85 return 86 } 87 88 func runEd(in io.Reader, out io.Writer, suppress bool, prompt, file string) error { 89 var e error 90 if len(prompt) > 0 { 91 state.prompt = true 92 } 93 buffer = NewFileBuffer(nil) 94 if file != "" { // we were given a file name 95 state.fileName = file 96 // try to read in the file 97 if _, e = os.Stat(state.fileName); os.IsNotExist(e) && !suppress { 98 fmt.Fprintf(os.Stderr, "%s: No such file or directory", state.fileName) 99 // this is not fatal, we just start with an empty buffer 100 } else { 101 if buffer, e = FileToBuffer(state.fileName); e != nil { 102 return e 103 } 104 if !suppress { 105 fmt.Println(buffer.Size()) 106 } 107 } 108 } 109 state.winSize = 22 // we don't actually support getting the real window size 110 inScan := bufio.NewScanner(in) 111 if state.prompt { 112 fmt.Fprintf(out, "%s", prompt) 113 } 114 for inScan.Scan() { 115 cmd := inScan.Text() 116 e = execute(cmd, out) 117 if e != nil { 118 state.lastErr = e 119 if !suppress && state.printErr { 120 fmt.Fprintf(out, "%s\n", e) 121 } else { 122 fmt.Fprintf(out, "?\n") 123 } 124 if errors.Is(e, errExit) { 125 return nil 126 } 127 } 128 if state.prompt { 129 fmt.Printf("%s", prompt) 130 } 131 } 132 if inScan.Err() != nil { 133 return fmt.Errorf("error reading stdin: %v", inScan.Err()) 134 } 135 return nil 136 } 137 138 // Entry point 139 func main() { 140 flag.Parse() 141 file := "" 142 switch len(flag.Args()) { 143 case 0: 144 case 1: 145 file = flag.Args()[0] 146 default: 147 flag.Usage() 148 os.Exit(1) 149 } 150 if err := runEd(os.Stdin, os.Stdout, fsuppress, fprompt, file); err != nil { 151 log.Fatal(err) 152 } 153 }