github.com/oweisse/u-root@v0.0.0-20181109060735-d005ad25fef1/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 if startLine > 0 {
   110  		start = f.lines[startLine-1]
   111  	}
   112  	end = start
   113  	if endLine < startLine {
   114  		endLine = startLine
   115  	}
   116  	if endLine != startLine {
   117  		if endLine >= len(f.lines) {
   118  			//f.end = len(f.lines)
   119  			end = len(f.data)
   120  			return start, end
   121  		}
   122  		end = f.lines[endLine-1]
   123  	}
   124  	debug("SliceX: f %v, start %v end %v\n", f, start, end)
   125  	return start, end
   126  }
   127  
   128  func (f *file) Slice(startLine, endLine int) ([]byte, int, int) {
   129  	s, e := f.SliceX(startLine, endLine)
   130  	return f.data[s:e], s, e
   131  }
   132  
   133  func (f *file) Replace(n []byte, startLine, endLine int) (int, error) {
   134  	defer debug("Replace done: f %v", f)
   135  	if f.data != nil {
   136  		start, end := f.SliceX(startLine, endLine)
   137  		debug("replace: f %v start %v end %v", f, start, end)
   138  		pre := f.data[0:start]
   139  		post := f.data[end:]
   140  		debug("replace: pre is %v, post is %v\n", pre, post)
   141  		var b bytes.Buffer
   142  		b.Write(pre)
   143  		b.Write(n)
   144  		b.Write(post)
   145  		f.data = b.Bytes()
   146  	} else {
   147  		f.data = n
   148  	}
   149  	f.fixLines()
   150  	return len(n), nil
   151  }
   152  
   153  // Read reads strings from the file after .
   154  // If there are lines already the new lines are inserted after '.'
   155  // dot is unchanged.
   156  func (f *file) Read(r io.Reader, startLine, endLine int) (int, error) {
   157  	debug("Read: r %v, startLine %v endLine %v", r, startLine, endLine)
   158  	d, err := ioutil.ReadAll(r)
   159  	debug("ReadAll returns %v, %v", d, err)
   160  	if err != nil {
   161  		return -1, err
   162  	}
   163  	// in ed, for text read, the line # is a point, not a line.
   164  	// For other commands, it's a line. Hence for reading
   165  	// line x, we need to make a point at line x+1 and
   166  	// read there.
   167  	startLine = startLine + 1
   168  	return f.Replace(d, startLine, endLine)
   169  }
   170  
   171  // Write writes the lines out from start to end, inclusive.
   172  // dot is unchanged.
   173  func (f *file) Write(w io.Writer, startLine, endLine int) (int, error) {
   174  	if endLine < startLine || startLine < 1 || endLine > len(f.lines)+1 {
   175  		return -1, fmt.Errorf("file is %d lines and [start, end] is [%d, %d]", len(f.lines), startLine, endLine)
   176  	}
   177  
   178  	start, end := f.SliceX(startLine, endLine)
   179  	amt, err := w.Write(f.data[start:end])
   180  
   181  	return amt, err
   182  }
   183  
   184  func (f *file) Print(w io.Writer, start, end int) (int, error) {
   185  	debug("Print %v %v %v", f.data, start, end)
   186  	i, err := f.Write(w, start, end)
   187  	if err != nil && start < end {
   188  		f.dot = end + 1
   189  	}
   190  	return i, err
   191  }
   192  
   193  // Write writes the lines out from start to end, inclusive.
   194  // dot is unchanged.
   195  func (f *file) WriteFile(n string, startLine, endLine int) (int, error) {
   196  	out, err := os.Create(n)
   197  	if err != nil {
   198  		return -1, err
   199  	}
   200  	defer out.Close()
   201  	return f.Write(out, startLine, endLine)
   202  }
   203  
   204  // Sub replaces the regexp with a different one.
   205  func (f *file) Sub(re, n, opt string, startLine, endLine int) error {
   206  	debug("Sub re %s n %s opt %s\n", re, n, opt)
   207  	if re == "" {
   208  		return fmt.Errorf("Empty RE")
   209  	}
   210  	r, err := regexp.Compile(re)
   211  	if err != nil {
   212  		return err
   213  	}
   214  	var opts = make(map[byte]bool)
   215  	for i := range opt {
   216  		opts[opt[i]] = true
   217  	}
   218  
   219  	o, start, end := f.Slice(startLine, endLine)
   220  	debug("Slice from [%v,%v] is [%v, %v] %v", startLine, endLine, start, end, string(o))
   221  	// Lines can turn into two lines. All kinds of stuff can happen.
   222  	// So
   223  	// Break it into lines
   224  	// make copies of those lines.
   225  	// for each line, do the sub
   226  	// paste it back together
   227  	// put it back in to f.data
   228  	b := makeLines(o)
   229  	if b == nil {
   230  		return nil
   231  	}
   232  	for i := range b {
   233  		var replaced bool
   234  		debug("Sub: before b[i] is %v", b[i])
   235  		b[i] = r.ReplaceAllFunc(b[i], func(o []byte) []byte {
   236  			debug("Sub: func called with %v", o)
   237  			if opts['g'] || !replaced {
   238  				f.dirty = true
   239  				replaced = true
   240  				return []byte(n)
   241  			}
   242  			return o
   243  		})
   244  		debug("Sub: after b[i] is %v", b[i])
   245  	}
   246  
   247  	debug("replaced o %v with n %v\n", o, b)
   248  	var repl = make([]byte, start)
   249  	copy(repl, f.data[0:start])
   250  	for _, v := range b {
   251  		repl = append(repl, v...)
   252  	}
   253  
   254  	f.data = append(repl, f.data[end:]...)
   255  	f.fixLines()
   256  	if opts['p'] {
   257  		_, err = f.Write(os.Stdout, startLine, endLine)
   258  	}
   259  	return err
   260  }
   261  
   262  func (f *file) Dirty(d bool) {
   263  	f.dirty = d
   264  }
   265  
   266  func (f *file) IsDirty() bool {
   267  	return f.dirty
   268  }
   269  
   270  // Range returns the closed range
   271  // of lines.
   272  func (f *file) Range() (int, int) {
   273  	if f.lines == nil || len(f.lines) == 0 {
   274  		return 0, 0
   275  	}
   276  	return 1, len(f.lines)
   277  }
   278  
   279  func (f *file) Equal(e Editor) error {
   280  	var errors string
   281  	g := e.(*file)
   282  	// we should verify dot but let's not do that just yet, we don't
   283  	// have it right.
   284  	if !reflect.DeepEqual(f.lines, g.lines) {
   285  		errors += fmt.Sprintf("%v vs %v: lines don't match", f.lines, g.lines)
   286  	}
   287  	if len(f.data) != len(g.data) {
   288  		errors += fmt.Sprintf("data len  differs: %v vs %v", len(f.data), len(g.data))
   289  	} else {
   290  		for i := range f.data {
   291  			if f.data[i] != g.data[i] {
   292  				errors += fmt.Sprintf("%d: %v vs %v: data doesn't match", i, f.data[i], g.data[i])
   293  			}
   294  		}
   295  	}
   296  	if errors != "" {
   297  		return fmt.Errorf(errors)
   298  	}
   299  	return nil
   300  }
   301  
   302  func (f *file) Dot() int {
   303  	return f.dot
   304  }
   305  
   306  func (f *file) Move(dot int) {
   307  	f.dot = dot
   308  }
   309  
   310  func NewTextEditor(a ...editorArg) (Editor, error) {
   311  	var f = &file{}
   312  
   313  	for _, v := range a {
   314  		if err := v(f); err != nil {
   315  			return nil, err
   316  		}
   317  	}
   318  	f.dot = 1
   319  	return f, nil
   320  }