github.com/jlowellwofford/u-root@v1.0.0/xcmds/ed/text.go (about)

     1  // Copyright 2012-2017 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  package main
     6  
     7  import (
     8  	"bytes"
     9  	"fmt"
    10  	"io"
    11  	"io/ioutil"
    12  	"os"
    13  	"reflect"
    14  	"regexp"
    15  )
    16  
    17  // start and end are used like slices.
    18  // so 1,1 defines a point. 1, 2 is line 1.
    19  // 1, $ is all the lines where $ is len(lines)+1
    20  type file struct {
    21  	dot   int
    22  	dirty bool
    23  	lines []int
    24  	data  []byte
    25  	pat   string
    26  }
    27  
    28  func makeLines(n []byte) [][]byte {
    29  	if len(n) == 0 {
    30  		return nil
    31  	}
    32  	var (
    33  		data  [][]byte
    34  		sawnl bool
    35  		pos   int
    36  		i     int
    37  	)
    38  	for i = range n {
    39  		if sawnl {
    40  			sawnl = false
    41  			l := make([]byte, i-pos)
    42  			copy(l, n[pos:i])
    43  			data = append(data, l)
    44  			pos = i
    45  		}
    46  		if n[i] == '\n' {
    47  			sawnl = true
    48  		}
    49  	}
    50  
    51  	if pos <= i {
    52  		l := make([]byte, len(n[pos:]))
    53  		copy(l, n[pos:])
    54  		data = append(data, l)
    55  	}
    56  	debug("makeLines: %v", data)
    57  	return data
    58  }
    59  
    60  func (f *file) String() string {
    61  	return fmt.Sprintf("%d %v %s", f.dot, f.lines, string(f.data))
    62  }
    63  
    64  func (f *file) fixLines() {
    65  	f.lines = nil
    66  	if len(f.data) == 0 {
    67  		return
    68  	}
    69  	f.lines = []int{0}
    70  
    71  	lines := 1
    72  	var sawnl bool
    73  	for i, v := range f.data {
    74  		if sawnl {
    75  			lines++
    76  			f.lines = append(f.lines, i)
    77  			sawnl = false
    78  		}
    79  		if v == '\n' {
    80  			sawnl = true
    81  		}
    82  	}
    83  	if f.dot < 1 {
    84  		f.dot = 1
    85  	}
    86  	if f.dot > lines {
    87  		f.dot = lines
    88  	}
    89  	//	f.start, f.end = f.dot, f.dot
    90  }
    91  
    92  // SliceXX returns half-closed indexes of the segment corresponding to [f.start, f.end]
    93  // as in the Go slice style.
    94  // The intent is that start and end act like slice indices,
    95  // i.e. the slice returned should look as though
    96  // you'd taken lines[f.start:f.end]
    97  // Note that a zero length slice is fine, and is used
    98  // for, e.g., inserting.
    99  // Also, note, the indices need to be changed from 1-relative
   100  // to 0-relative, since the API at all levels is 1-relative.
   101  func (f *file) SliceX(startLine, endLine int) (int, int) {
   102  	var end, start int
   103  	debug("SliceX: f %v", f)
   104  	defer debug("SliceX done: f %v, start %v, end %v", f, start, end)
   105  	if startLine > len(f.lines) {
   106  		//f.start = len(f.lines)
   107  		//f.end = f.start
   108  		return len(f.data), len(f.data)
   109  	} else {
   110  		if startLine > 0 {
   111  			start = f.lines[startLine-1]
   112  		}
   113  	}
   114  	end = start
   115  	if endLine < startLine {
   116  		endLine = startLine
   117  	}
   118  	if endLine != startLine {
   119  		if endLine >= len(f.lines) {
   120  			//f.end = len(f.lines)
   121  			end = len(f.data)
   122  			return start, end
   123  		}
   124  		end = f.lines[endLine-1]
   125  	}
   126  	debug("SliceX: f %v, start %v end %v\n", f, start, end)
   127  	return start, end
   128  }
   129  
   130  func (f *file) Slice(startLine, endLine int) ([]byte, int, int) {
   131  	s, e := f.SliceX(startLine, endLine)
   132  	return f.data[s:e], s, e
   133  }
   134  
   135  func (f *file) Replace(n []byte, startLine, endLine int) (int, error) {
   136  	defer debug("Replace done: f %v", f)
   137  	if f.data != nil {
   138  		start, end := f.SliceX(startLine, endLine)
   139  		debug("replace: f %v start %v end %v", f, start, end)
   140  		pre := f.data[0:start]
   141  		post := f.data[end:]
   142  		debug("replace: pre is %v, post is %v\n", pre, post)
   143  		var b bytes.Buffer
   144  		b.Write(pre)
   145  		b.Write(n)
   146  		b.Write(post)
   147  		f.data = b.Bytes()
   148  	} else {
   149  		f.data = n
   150  	}
   151  	f.fixLines()
   152  	return len(n), nil
   153  }
   154  
   155  // Read reads strings from the file after .
   156  // If there are lines already the new lines are inserted after '.'
   157  // dot is unchanged.
   158  func (f *file) Read(r io.Reader, startLine, endLine int) (int, error) {
   159  	debug("Read: r %v, startLine %v endLine %v", r, startLine, endLine)
   160  	d, err := ioutil.ReadAll(r)
   161  	debug("ReadAll returns %v, %v", d, err)
   162  	if err != nil {
   163  		return -1, err
   164  	}
   165  	// in ed, for text read, the line # is a point, not a line.
   166  	// For other commands, it's a line. Hence for reading
   167  	// line x, we need to make a point at line x+1 and
   168  	// read there.
   169  	startLine = startLine + 1
   170  	return f.Replace(d, startLine, endLine)
   171  }
   172  
   173  // Write writes the lines out from start to end, inclusive.
   174  // dot is unchanged.
   175  func (f *file) Write(w io.Writer, startLine, endLine int) (int, error) {
   176  	if endLine < startLine || startLine < 1 || endLine > len(f.lines)+1 {
   177  		return -1, fmt.Errorf("file is %d lines and [start, end] is [%d, %d]", len(f.lines), startLine, endLine)
   178  	}
   179  
   180  	start, end := f.SliceX(startLine, endLine)
   181  	amt, err := w.Write(f.data[start:end])
   182  
   183  	return amt, err
   184  }
   185  
   186  func (f *file) Print(w io.Writer, start, end int) (int, error) {
   187  	debug("Print %v %v %v", f.data, start, end)
   188  	i, err := f.Write(w, start, end)
   189  	if err != nil && start < end {
   190  		f.dot = end + 1
   191  	}
   192  	return i, err
   193  }
   194  
   195  // Write writes the lines out from start to end, inclusive.
   196  // dot is unchanged.
   197  func (f *file) WriteFile(n string, startLine, endLine int) (int, error) {
   198  	out, err := os.Create(n)
   199  	if err != nil {
   200  		return -1, err
   201  	}
   202  	defer out.Close()
   203  	return f.Write(out, startLine, endLine)
   204  }
   205  
   206  // Sub replaces the regexp with a different one.
   207  func (f *file) Sub(re, n, opt string, startLine, endLine int) error {
   208  	debug("Sub re %s n %s opt %s\n", re, n, opt)
   209  	if re == "" {
   210  		return fmt.Errorf("Empty RE")
   211  	}
   212  	r, err := regexp.Compile(re)
   213  	if err != nil {
   214  		return err
   215  	}
   216  	var opts = make(map[byte]bool)
   217  	for i := range opt {
   218  		opts[opt[i]] = true
   219  	}
   220  
   221  	o, start, end := f.Slice(startLine, endLine)
   222  	debug("Slice from [%v,%v] is [%v, %v] %v", startLine, endLine, start, end, string(o))
   223  	// Lines can turn into two lines. All kinds of stuff can happen.
   224  	// So
   225  	// Break it into lines
   226  	// make copies of those lines.
   227  	// for each line, do the sub
   228  	// paste it back together
   229  	// put it back in to f.data
   230  	b := makeLines(o)
   231  	if b == nil {
   232  		return nil
   233  	}
   234  	for i := range b {
   235  		var replaced bool
   236  		debug("Sub: before b[i] is %v", b[i])
   237  		b[i] = r.ReplaceAllFunc(b[i], func(o []byte) []byte {
   238  			debug("Sub: func called with %v", o)
   239  			if opts['g'] || !replaced {
   240  				f.dirty = true
   241  				replaced = true
   242  				return []byte(n)
   243  			}
   244  			return o
   245  		})
   246  		debug("Sub: after b[i] is %v", b[i])
   247  	}
   248  
   249  	debug("replaced o %v with n %v\n", o, b)
   250  	var repl = make([]byte, start)
   251  	copy(repl, f.data[0:start])
   252  	for _, v := range b {
   253  		repl = append(repl, v...)
   254  	}
   255  
   256  	f.data = append(repl, f.data[end:]...)
   257  	f.fixLines()
   258  	if opts['p'] {
   259  		_, err = f.Write(os.Stdout, startLine, endLine)
   260  	}
   261  	return err
   262  }
   263  
   264  func (f *file) Dirty(d bool) {
   265  	f.dirty = d
   266  }
   267  
   268  func (f *file) IsDirty() bool {
   269  	return f.dirty
   270  }
   271  
   272  // Range returns the closed range
   273  // of lines.
   274  func (f *file) Range() (int, int) {
   275  	if f.lines == nil || len(f.lines) == 0 {
   276  		return 0, 0
   277  	}
   278  	return 1, len(f.lines)
   279  }
   280  
   281  func (f *file) Equal(e Editor) error {
   282  	var errors string
   283  	g := e.(*file)
   284  	// we should verify dot but let's not do that just yet, we don't
   285  	// have it right.
   286  	if !reflect.DeepEqual(f.lines, g.lines) {
   287  		errors += fmt.Sprintf("%v vs %v: lines don't match", f.lines, g.lines)
   288  	}
   289  	if len(f.data) != len(g.data) {
   290  		errors += fmt.Sprintf("data len  differs: %v vs %v", len(f.data), len(g.data))
   291  	} else {
   292  		for i := range f.data {
   293  			if f.data[i] != g.data[i] {
   294  				errors += fmt.Sprintf("%d: %v vs %v: data doesn't match", i, f.data[i], g.data[i])
   295  			}
   296  		}
   297  	}
   298  	if errors != "" {
   299  		return fmt.Errorf(errors)
   300  	}
   301  	return nil
   302  }
   303  
   304  func (f *file) Dot() int {
   305  	return f.dot
   306  }
   307  
   308  func (f *file) Move(dot int) {
   309  	f.dot = dot
   310  }
   311  
   312  func NewTextEditor(a ...editorArg) (Editor, error) {
   313  	var f = &file{}
   314  
   315  	for _, v := range a {
   316  		if err := v(f); err != nil {
   317  			return nil, err
   318  		}
   319  	}
   320  	f.dot = 1
   321  	return f, nil
   322  }