github.com/splunk/dan1-qbec@v0.7.3/internal/diff/diff.go (about)

     1  /*
     2     Copyright 2019 Splunk Inc.
     3  
     4     Licensed under the Apache License, Version 2.0 (the "License");
     5     you may not use this file except in compliance with the License.
     6     You may obtain a copy of the License at
     7  
     8         http://www.apache.org/licenses/LICENSE-2.0
     9  
    10     Unless required by applicable law or agreed to in writing, software
    11     distributed under the License is distributed on an "AS IS" BASIS,
    12     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13     See the License for the specific language governing permissions and
    14     limitations under the License.
    15  */
    16  
    17  // Package diff contains primitives for diff-ing objects and strings.
    18  package diff
    19  
    20  import (
    21  	"strings"
    22  
    23  	"github.com/ghodss/yaml"
    24  	"github.com/pkg/errors"
    25  	godiff "github.com/pmezard/go-difflib/difflib"
    26  )
    27  
    28  const (
    29  	escGreen = "\x1b[32m"
    30  	escRed   = "\x1b[31m"
    31  	escReset = "\x1b[0m"
    32  )
    33  
    34  // Options are options for the diff. The zero-value is valid.
    35  // Use a negative number for the context if you really want 0 context lines.
    36  type Options struct {
    37  	LeftName  string // name of left side
    38  	RightName string // name of right side
    39  	Context   int    // number of context lines in the diff, defaults to 3
    40  	Colorize  bool   // added colors to the diff
    41  }
    42  
    43  // Strings diffs the left and right strings and returns
    44  // the diff. A zero-length slice is returned when there are no diffs.
    45  func Strings(left, right string, opts Options) ([]byte, error) {
    46  	if opts.Context == 0 {
    47  		opts.Context = 3
    48  	}
    49  	if opts.Context < 0 {
    50  		opts.Context = 0
    51  	}
    52  	ud := godiff.UnifiedDiff{
    53  		A:        godiff.SplitLines(left),
    54  		B:        godiff.SplitLines(right),
    55  		FromFile: opts.LeftName,
    56  		ToFile:   opts.RightName,
    57  		Context:  opts.Context,
    58  	}
    59  	s, err := godiff.GetUnifiedDiffString(ud)
    60  	if err != nil {
    61  		return nil, errors.Wrap(err, "diff error")
    62  	}
    63  	if opts.Colorize && len(s) > 0 {
    64  		lines := godiff.SplitLines(s)
    65  		var out []string
    66  		for _, l := range lines {
    67  			switch {
    68  			case strings.HasPrefix(l, "-"):
    69  				out = append(out, escRed+l+escReset)
    70  			case strings.HasPrefix(l, "+"):
    71  				out = append(out, escGreen+l+escReset)
    72  			default:
    73  				out = append(out, l)
    74  			}
    75  		}
    76  		s = strings.Join(out, "")
    77  	}
    78  	return []byte(s), nil
    79  }
    80  
    81  // Objects renders the left and right objects passed to it as YAML and returns
    82  // the diff. A zero-length slice is returned when there are no diffs.
    83  func Objects(left, right interface{}, opts Options) ([]byte, error) {
    84  	asYaml := func(data interface{}) ([]byte, error) {
    85  		if data == nil {
    86  			return []byte{}, nil
    87  		}
    88  		return yaml.Marshal(data)
    89  	}
    90  	l, err := asYaml(left)
    91  	if err != nil {
    92  		return nil, errors.Wrap(err, "marshal left")
    93  	}
    94  	r, err := asYaml(right)
    95  	if err != nil {
    96  		return nil, errors.Wrap(err, "marshal right")
    97  	}
    98  	return Strings(string(l), string(r), opts)
    99  }