github.hscsec.cn/u-root/u-root@v7.0.0+incompatible/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  	"flag"
    27  	"fmt"
    28  	"os"
    29  )
    30  
    31  // flags
    32  var (
    33  	fSuppress = flag.Bool("s", false, "suppress counts")
    34  	fPrompt   = flag.String("p", "*", "specify a command prompt")
    35  )
    36  
    37  // current FileBuffer
    38  var buffer *FileBuffer
    39  
    40  // current ed state
    41  var state struct {
    42  	fileName string // current filename
    43  	lastErr  error
    44  	printErr bool
    45  	prompt   bool
    46  	winSize  int
    47  	lastRep  string
    48  	lastSub  string
    49  }
    50  
    51  // Parse input and run command
    52  func run(cmd string) (e error) {
    53  	ctx := &Context{
    54  		cmd: cmd,
    55  	}
    56  	if ctx.addrs, ctx.cmdOffset, e = buffer.ResolveAddrs(cmd); e != nil {
    57  		return
    58  	}
    59  	if len(cmd) <= ctx.cmdOffset {
    60  		// no command, default to print
    61  		ctx.cmd += "p"
    62  	}
    63  	if exe, ok := cmds[ctx.cmd[ctx.cmdOffset]]; ok {
    64  		buffer.Start()
    65  		if e = exe(ctx); e != nil {
    66  			return
    67  		}
    68  		buffer.End()
    69  	} else {
    70  		return fmt.Errorf("invalid command: %v", cmd[ctx.cmdOffset])
    71  	}
    72  	return
    73  }
    74  
    75  // Entry point
    76  func main() {
    77  	var e error
    78  	flag.Usage = func() {
    79  		fmt.Fprintf(flag.CommandLine.Output(), "Usage: %s [-s] [-p <prompt>] [file]\n", os.Args[0])
    80  		flag.PrintDefaults()
    81  	}
    82  	flag.Parse()
    83  	flag.Visit(func(f *flag.Flag) {
    84  		if f.Name == "p" {
    85  			state.prompt = true
    86  		}
    87  	})
    88  	args := flag.Args()
    89  	if len(args) > 1 { // we only accept one additional argument
    90  		flag.Usage()
    91  		os.Exit(1)
    92  	}
    93  	buffer = NewFileBuffer(nil)
    94  	if len(args) == 1 { // we were given a file name
    95  		state.fileName = args[0]
    96  		// try to read in the file
    97  		if _, e = os.Stat(state.fileName); os.IsNotExist(e) && !*fSuppress {
    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  				fmt.Fprintln(os.Stderr, e)
   103  				os.Exit(1)
   104  			}
   105  			if !*fSuppress {
   106  				fmt.Println(buffer.Size())
   107  			}
   108  		}
   109  	}
   110  	state.winSize = 22 // we don't actually support getting the real window size
   111  	inScan := bufio.NewScanner(os.Stdin)
   112  	if state.prompt {
   113  		fmt.Printf("%s", *fPrompt)
   114  	}
   115  	for inScan.Scan() {
   116  		cmd := inScan.Text()
   117  		e = run(cmd)
   118  		if e != nil {
   119  			state.lastErr = e
   120  			if !*fSuppress && state.printErr {
   121  				fmt.Println(e)
   122  			} else {
   123  				fmt.Println("?")
   124  			}
   125  		}
   126  		if state.prompt {
   127  			fmt.Printf("%s", *fPrompt)
   128  		}
   129  	}
   130  	if inScan.Err() != nil {
   131  		fmt.Fprintf(os.Stderr, "error reading stdin: %v", inScan.Err())
   132  		os.Exit(1)
   133  	}
   134  }