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  }