github.com/solo-io/cue@v0.4.7/internal/diff/print.go (about)

     1  // Copyright 2019 CUE Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package diff
    16  
    17  import (
    18  	"fmt"
    19  	"io"
    20  
    21  	"github.com/solo-io/cue/cue"
    22  	"github.com/solo-io/cue/cue/errors"
    23  )
    24  
    25  // Print the differences between two structs represented by an edit script.
    26  func Print(w io.Writer, es *EditScript) error {
    27  	p := printer{
    28  		w:       w,
    29  		margin:  2,
    30  		context: 2,
    31  	}
    32  	p.script(es)
    33  	return p.errs
    34  }
    35  
    36  type printer struct {
    37  	w         io.Writer
    38  	context   int
    39  	margin    int
    40  	indent    int
    41  	prefix    string
    42  	hasPrefix bool
    43  	hasPrint  bool
    44  	errs      errors.Error
    45  }
    46  
    47  func (p *printer) writeRaw(b []byte) {
    48  	if len(b) == 0 {
    49  		return
    50  	}
    51  	if !p.hasPrefix {
    52  		io.WriteString(p.w, p.prefix)
    53  		p.hasPrefix = true
    54  	}
    55  	if !p.hasPrint {
    56  		fmt.Fprintf(p.w, "% [1]*s", p.indent+p.margin-len(p.prefix), "")
    57  		p.hasPrint = true
    58  	}
    59  	p.w.Write(b)
    60  }
    61  
    62  func (p *printer) Write(b []byte) (n int, err error) {
    63  	i, last := 0, 0
    64  	for ; i < len(b); i++ {
    65  		if b[i] != '\n' {
    66  			continue
    67  		}
    68  		p.writeRaw(b[last:i])
    69  		last = i + 1
    70  		io.WriteString(p.w, "\n")
    71  		p.hasPrefix = false
    72  		p.hasPrint = false
    73  	}
    74  	p.writeRaw(b[last:])
    75  	return len(b), nil
    76  }
    77  
    78  func (p *printer) write(b []byte) {
    79  	_, _ = p.Write(b)
    80  }
    81  
    82  func (p *printer) printLen(align int, str string) {
    83  	fmt.Fprintf(p, "% -[1]*s", align, str)
    84  }
    85  
    86  func (p *printer) println(s string) {
    87  	fmt.Fprintln(p, s)
    88  }
    89  
    90  func (p *printer) printf(format string, args ...interface{}) {
    91  	fmt.Fprintf(p, format, args...)
    92  }
    93  
    94  func (p *printer) script(e *EditScript) {
    95  	switch e.x.Kind() {
    96  	case cue.StructKind:
    97  		p.printStruct(e)
    98  	case cue.ListKind:
    99  		p.printList(e)
   100  	default:
   101  		p.printElem("-", e.x)
   102  		p.printElem("+", e.y)
   103  	}
   104  }
   105  
   106  func (p *printer) findRun(es *EditScript, i int) (start, end int) {
   107  	lastEnd := i
   108  
   109  	for ; i < es.Len() && es.edits[i].kind == Identity; i++ {
   110  	}
   111  	start = i
   112  
   113  	// Find end of run
   114  	include := p.context
   115  	for ; i < es.Len(); i++ {
   116  		e := es.edits[i]
   117  		if e.kind != Identity {
   118  			include = p.context + 1
   119  			continue
   120  		}
   121  		if include--; include == 0 {
   122  			break
   123  		}
   124  	}
   125  
   126  	if i-start > 0 {
   127  		// Adjust start of run
   128  		if s := start - p.context; s > lastEnd {
   129  			start = s
   130  		} else {
   131  			start = lastEnd
   132  		}
   133  	}
   134  	return start, i
   135  }
   136  
   137  func (p *printer) printStruct(es *EditScript) {
   138  	// TODO: consider not printing outer curlies, or make it an option.
   139  	// if p.indent > 0 {
   140  	p.println("{")
   141  	defer p.println("}")
   142  	// }
   143  	p.indent += 4
   144  	defer func() {
   145  		p.indent -= 4
   146  	}()
   147  
   148  	var start, i int
   149  	for i < es.Len() {
   150  		lastEnd := i
   151  		// Find provisional start of run.
   152  		start, i = p.findRun(es, i)
   153  
   154  		p.printSkipped(start - lastEnd)
   155  		p.printFieldRun(es, start, i)
   156  	}
   157  	p.printSkipped(es.Len() - i)
   158  }
   159  
   160  func (p *printer) printList(es *EditScript) {
   161  	p.println("[")
   162  	p.indent += 4
   163  	defer func() {
   164  		p.indent -= 4
   165  		p.println("]")
   166  	}()
   167  
   168  	x := getElems(es.x)
   169  	y := getElems(es.y)
   170  
   171  	var start, i int
   172  	for i < es.Len() {
   173  		lastEnd := i
   174  		// Find provisional start of run.
   175  		start, i = p.findRun(es, i)
   176  
   177  		p.printSkipped(start - lastEnd)
   178  		p.printElemRun(es, x, y, start, i)
   179  	}
   180  	p.printSkipped(es.Len() - i)
   181  }
   182  
   183  func getElems(x cue.Value) (a []cue.Value) {
   184  	for i, _ := x.List(); i.Next(); {
   185  		a = append(a, i.Value())
   186  	}
   187  	return a
   188  }
   189  
   190  func (p *printer) printSkipped(n int) {
   191  	if n > 0 {
   192  		p.printf("... // %d identical elements\n", n)
   193  	}
   194  }
   195  
   196  func (p *printer) printValue(v cue.Value) {
   197  	// TODO: have indent option.
   198  	s := fmt.Sprintf("%+v", v)
   199  	io.WriteString(p, s)
   200  }
   201  
   202  func (p *printer) printFieldRun(es *EditScript, start, end int) {
   203  	// Determine max field len.
   204  	for i := start; i < end; i++ {
   205  		e := es.edits[i]
   206  
   207  		switch e.kind {
   208  		case UniqueX:
   209  			p.printField("-", es, es.LabelX(i), es.ValueX(i))
   210  
   211  		case UniqueY:
   212  			p.printField("+", es, es.LabelY(i), es.ValueY(i))
   213  
   214  		case Modified:
   215  			if e.sub != nil {
   216  				io.WriteString(p, es.LabelX(i))
   217  				io.WriteString(p, " ")
   218  				p.script(e.sub)
   219  				break
   220  			}
   221  			// TODO: show per-line differences for multiline strings.
   222  			p.printField("-", es, es.LabelX(i), es.ValueX(i))
   223  			p.printField("+", es, es.LabelY(i), es.ValueY(i))
   224  
   225  		case Identity:
   226  			// TODO: write on one line
   227  			p.printField("", es, es.LabelX(i), es.ValueX(i))
   228  		}
   229  	}
   230  }
   231  
   232  func (p *printer) printField(prefix string, es *EditScript, label string, v cue.Value) {
   233  	p.prefix = prefix
   234  	io.WriteString(p, label)
   235  	io.WriteString(p, " ")
   236  	p.printValue(v)
   237  	io.WriteString(p, "\n")
   238  	p.prefix = ""
   239  }
   240  
   241  func (p *printer) printElemRun(es *EditScript, x, y []cue.Value, start, end int) {
   242  	for _, e := range es.edits[start:end] {
   243  		switch e.kind {
   244  		case UniqueX:
   245  			p.printElem("-", x[e.XPos()])
   246  
   247  		case UniqueY:
   248  			p.printElem("+", y[e.YPos()])
   249  
   250  		case Modified:
   251  			if e.sub != nil {
   252  				p.script(e.sub)
   253  				break
   254  			}
   255  			// TODO: show per-line differences for multiline strings.
   256  			p.printElem("-", x[e.XPos()])
   257  			p.printElem("+", y[e.YPos()])
   258  
   259  		case Identity:
   260  			// TODO: write on one line
   261  			p.printElem("", x[e.XPos()])
   262  		}
   263  	}
   264  }
   265  
   266  func (p *printer) printElem(prefix string, v cue.Value) {
   267  	p.prefix = prefix
   268  	p.printValue(v)
   269  	io.WriteString(p, ",\n")
   270  	p.prefix = ""
   271  }