github.com/hasnat/dolt/go@v0.0.0-20210628190320-9eb5d843fbb7/libraries/doltcore/diff/diff_sink.go (about)

     1  // Copyright 2019 Dolthub, Inc.
     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  	"context"
    19  	"errors"
    20  	"io"
    21  
    22  	"github.com/fatih/color"
    23  
    24  	"github.com/dolthub/dolt/go/libraries/doltcore/row"
    25  	"github.com/dolthub/dolt/go/libraries/doltcore/schema"
    26  	"github.com/dolthub/dolt/go/libraries/doltcore/table/pipeline"
    27  	"github.com/dolthub/dolt/go/libraries/doltcore/table/untyped"
    28  	"github.com/dolthub/dolt/go/libraries/doltcore/table/untyped/tabular"
    29  	"github.com/dolthub/dolt/go/store/types"
    30  )
    31  
    32  const (
    33  	colorRowProp = "color"
    34  	diffColTag   = schema.ReservedTagMin
    35  	diffColName  = "__diff__"
    36  )
    37  
    38  type ColorFunc func(...interface{}) string
    39  
    40  type ColorDiffSink struct {
    41  	sch schema.Schema
    42  	ttw *tabular.TextTableWriter
    43  }
    44  
    45  // NewColorDiffSink returns a ColorDiffSink that uses  the writer and schema given to print its output. numHeaderRows
    46  // will change how many rows of output are considered part of the table header. Use 1 for diffs where the schemas are
    47  // the same between the two table revisions, and 2 for when they differ.
    48  func NewColorDiffSink(wr io.WriteCloser, sch schema.Schema, numHeaderRows int) (*ColorDiffSink, error) {
    49  	_, additionalCols := untyped.NewUntypedSchemaWithFirstTag(diffColTag, diffColName)
    50  	outSch, err := untyped.UntypedSchemaUnion(additionalCols, sch)
    51  	if err != nil {
    52  		panic(err)
    53  	}
    54  
    55  	ttw, err := tabular.NewTextTableWriterWithNumHeaderRows(wr, outSch, numHeaderRows)
    56  
    57  	if err != nil {
    58  		return nil, err
    59  	}
    60  
    61  	return &ColorDiffSink{outSch, ttw}, nil
    62  }
    63  
    64  // GetSchema gets the schema of the rows that this writer writes
    65  func (cds *ColorDiffSink) GetSchema() schema.Schema {
    66  	return cds.sch
    67  }
    68  
    69  var colDiffColors = map[DiffChType]ColorFunc{
    70  	DiffAdded:       color.New(color.Bold, color.FgGreen).Sprint,
    71  	DiffModifiedOld: color.New(color.FgRed).Sprint,
    72  	DiffModifiedNew: color.New(color.FgGreen).Sprint,
    73  	DiffRemoved:     color.New(color.Bold, color.FgRed).Sprint,
    74  }
    75  
    76  func (cds *ColorDiffSink) ProcRowWithProps(r row.Row, props pipeline.ReadableMap) error {
    77  
    78  	taggedVals := make(row.TaggedValues)
    79  	allCols := cds.sch.GetAllCols()
    80  	colDiffs := make(map[string]DiffChType)
    81  
    82  	if prop, ok := props.Get(CollChangesProp); ok {
    83  		if convertedVal, convertedOK := prop.(map[string]DiffChType); convertedOK {
    84  			colDiffs = convertedVal
    85  		}
    86  	}
    87  
    88  	err := allCols.Iter(func(tag uint64, col schema.Column) (stop bool, err error) {
    89  		if val, ok := r.GetColVal(tag); ok {
    90  			taggedVals[tag] = val.(types.String)
    91  		}
    92  		return false, nil
    93  	})
    94  
    95  	if err != nil {
    96  		return err
    97  	}
    98  
    99  	taggedVals[diffColTag] = types.String("   ")
   100  	colorColumns := true
   101  	if prop, ok := props.Get(DiffTypeProp); ok {
   102  		if dt, convertedOK := prop.(DiffChType); convertedOK {
   103  			switch dt {
   104  			case DiffAdded:
   105  				taggedVals[diffColTag] = types.String(" + ")
   106  				colorColumns = false
   107  			case DiffRemoved:
   108  				taggedVals[diffColTag] = types.String(" - ")
   109  				colorColumns = false
   110  			case DiffModifiedOld:
   111  				taggedVals[diffColTag] = types.String(" < ")
   112  			case DiffModifiedNew:
   113  				taggedVals[diffColTag] = types.String(" > ")
   114  			}
   115  			// Treat the diff indicator string as a diff of the same type
   116  			colDiffs[diffColName] = dt
   117  		}
   118  	}
   119  
   120  	// Color the columns as appropriate. Some rows will be all colored.
   121  	err = allCols.Iter(func(tag uint64, col schema.Column) (stop bool, err error) {
   122  		var colorFunc ColorFunc
   123  		if colorColumns {
   124  			if dt, ok := colDiffs[col.Name]; ok {
   125  				if fn, ok := colDiffColors[dt]; ok {
   126  					colorFunc = fn
   127  				}
   128  			}
   129  		} else {
   130  			if prop, ok := props.Get(DiffTypeProp); ok {
   131  				if dt, convertedOK := prop.(DiffChType); convertedOK {
   132  					if fn, ok := colDiffColors[dt]; ok {
   133  						colorFunc = fn
   134  					}
   135  				}
   136  			}
   137  		}
   138  
   139  		if colorFunc != nil {
   140  			taggedVals[tag] = types.String(colorFunc(string(taggedVals[tag].(types.String))))
   141  		}
   142  
   143  		return false, nil
   144  	})
   145  
   146  	if err != nil {
   147  		return err
   148  	}
   149  
   150  	r, err = row.New(r.Format(), cds.sch, taggedVals)
   151  
   152  	if err != nil {
   153  		return err
   154  	}
   155  
   156  	return cds.ttw.WriteRow(context.TODO(), r)
   157  }
   158  
   159  // Close should release resources being held
   160  func (cds *ColorDiffSink) Close() error {
   161  	if cds.ttw != nil {
   162  		if err := cds.ttw.Close(context.TODO()); err != nil {
   163  			return err
   164  		}
   165  		cds.ttw = nil
   166  		return nil
   167  	} else {
   168  		return errors.New("Already closed.")
   169  	}
   170  }