148 lines
		
	
	
		
			3.1 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			148 lines
		
	
	
		
			3.1 KiB
		
	
	
	
		
			Go
		
	
	
	
| package main
 | |
| 
 | |
| import (
 | |
| 	"fmt"
 | |
| 	"io"
 | |
| 	"os"
 | |
| 
 | |
| 	"strings"
 | |
| 
 | |
| 	"github.com/jessevdk/go-flags"
 | |
| 	"github.com/kylelemons/godebug/pretty"
 | |
| 	"github.com/logrusorgru/aurora"
 | |
| 	"github.com/mattn/go-isatty"
 | |
| 	"gopkg.in/yaml.v2"
 | |
| )
 | |
| 
 | |
| var version = "latest"
 | |
| 
 | |
| func main() {
 | |
| 	var opts struct {
 | |
| 		NoColor bool   `long:"no-color" description:"disable colored output" required:"false"`
 | |
| 		Version func() `long:"version" description:"print version and exit"`
 | |
| 		// Ignore  []string `long:"ignore" description:"ignore vaules by yaml path" required:"false"`
 | |
| 	}
 | |
| 
 | |
| 	opts.Version = func() {
 | |
| 		fmt.Fprintf(os.Stderr, "%v\n", version)
 | |
| 		os.Exit(0)
 | |
| 	}
 | |
| 
 | |
| 	args, err := flags.Parse(&opts)
 | |
| 	if err != nil {
 | |
| 		if err.(*flags.Error).Type == flags.ErrHelp {
 | |
| 			os.Exit(0)
 | |
| 		}
 | |
| 		os.Exit(1)
 | |
| 	}
 | |
| 	if len(args) != 2 {
 | |
| 		fmt.Println("requires exactly two arguments to diff, use `yamldiff <file1> <file2>`")
 | |
| 		os.Exit(1)
 | |
| 	}
 | |
| 
 | |
| 	formatter := newFormatter(opts.NoColor)
 | |
| 
 | |
| 	errors := stat(args[0], args[1])
 | |
| 	failOnErr(formatter, errors...)
 | |
| 
 | |
| 	yaml1, err := unmarshal(args[0])
 | |
| 	if err != nil {
 | |
| 		failOnErr(formatter, err)
 | |
| 	}
 | |
| 	yaml2, err := unmarshal(args[1])
 | |
| 	if err != nil {
 | |
| 		failOnErr(formatter, err)
 | |
| 	}
 | |
| 
 | |
| 	diff, ndiff := computeDiff(formatter, yaml1, yaml2)
 | |
| 	if ndiff > 0 {
 | |
| 		fmt.Println(diff)
 | |
| 		os.Exit(2)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func stat(filenames ...string) []error {
 | |
| 	var errs []error
 | |
| 	for _, filename := range filenames {
 | |
| 		if filename == "-" {
 | |
| 			continue
 | |
| 		}
 | |
| 		_, err := os.Stat(filename)
 | |
| 		if err != nil {
 | |
| 			errs = append(errs, fmt.Errorf("cannot find file: %v. Does it exist?", filename))
 | |
| 		}
 | |
| 	}
 | |
| 	return errs
 | |
| }
 | |
| 
 | |
| func unmarshal(filename string) (interface{}, error) {
 | |
| 	var contents []byte
 | |
| 	var err error
 | |
| 	if filename == "-" {
 | |
| 		contents, err = io.ReadAll(os.Stdin)
 | |
| 	} else {
 | |
| 		contents, err = os.ReadFile(filename)
 | |
| 	}
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	var ret interface{}
 | |
| 	err = yaml.Unmarshal(contents, &ret)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	return ret, nil
 | |
| }
 | |
| 
 | |
| func failOnErr(formatter aurora.Aurora, errs ...error) {
 | |
| 	if len(errs) == 0 {
 | |
| 		return
 | |
| 	}
 | |
| 	var errMessages []string
 | |
| 	for _, err := range errs {
 | |
| 		errMessages = append(errMessages, err.Error())
 | |
| 	}
 | |
| 	fmt.Fprintf(os.Stderr, "%v\n\n", formatter.Red(strings.Join(errMessages, "\n")))
 | |
| 	os.Exit(1)
 | |
| }
 | |
| 
 | |
| func computeDiff(formatter aurora.Aurora, a interface{}, b interface{}) (string, int) {
 | |
| 	diffs := make([]string, 0)
 | |
| 	ndiffs := 0
 | |
| 	for _, s := range strings.Split(pretty.Compare(a, b), "\n") {
 | |
| 		switch {
 | |
| 		case strings.HasPrefix(s, "+"):
 | |
| 			diffs = append(diffs, formatter.Bold(formatter.Green(s)).String())
 | |
| 			ndiffs += 1
 | |
| 		case strings.HasPrefix(s, "-"):
 | |
| 			diffs = append(diffs, formatter.Bold(formatter.Red(s)).String())
 | |
| 			ndiffs += 1
 | |
| 		default:
 | |
| 			diffs = append(diffs, s)
 | |
| 		}
 | |
| 	}
 | |
| 	return strings.Join(diffs, "\n"), ndiffs
 | |
| }
 | |
| 
 | |
| func newFormatter(noColor bool) aurora.Aurora {
 | |
| 	var formatter aurora.Aurora
 | |
| 	if noColor || !isTerminal() {
 | |
| 		formatter = aurora.NewAurora(false)
 | |
| 	} else {
 | |
| 		formatter = aurora.NewAurora(true)
 | |
| 	}
 | |
| 	return formatter
 | |
| }
 | |
| 
 | |
| func isTerminal() bool {
 | |
| 	fd := os.Stdout.Fd()
 | |
| 	switch {
 | |
| 	case isatty.IsTerminal(fd):
 | |
| 		return true
 | |
| 	case isatty.IsCygwinTerminal(fd):
 | |
| 		return true
 | |
| 	default:
 | |
| 		return false
 | |
| 	}
 | |
| }
 |