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 }