sq/cli/diff/diff.go
Neil O'Toole 2f2dfd6e47
#229: More diff (#233)
- Implement `sq diff --data`.
2023-05-22 09:08:14 -06:00

164 lines
3.5 KiB
Go

// Package diff contains the CLI's diff implementation.
//
// Reference:
// - https://github.com/aymanbagabas/go-udiff
// - https://www.gnu.org/software/diffutils/manual/html_node/Hunks.html
// - https://www.cloudbees.com/blog/git-diff-a-complete-comparison-tutorial-for-git
package diff
import (
"fmt"
"io"
"strings"
"github.com/neilotoole/sq/libsq/source"
"github.com/neilotoole/sq/cli/output"
"github.com/neilotoole/sq/libsq/core/stringz"
"github.com/neilotoole/sq/libsq/core/errz"
)
// Config contains parameters to control diff behavior.
type Config struct {
// Lines specifies the number of lines of context surrounding a diff.
Lines int
// RecordWriterFn is a factory function that returns
// an output.RecordWriter used to generate diff text
// when comparing table data.
RecordWriterFn output.NewRecordWriterFunc
}
// Elements determines what source elements to compare.
type Elements struct {
// Summary compares a summary of the sources.
Summary bool
// DBProperties compares DB properties.
DBProperties bool
// Table compares table structure.
Table bool
// RowCount compares table row count.
RowCount bool
// Data compares each row in a table. Caution: this can be slow.
Data bool
}
// sourceData encapsulates data about a source.
type sourceData struct {
handle string
src *source.Source
srcMeta *source.Metadata
}
func (sd *sourceData) clone() *sourceData { //nolint:unused // REVISIT: no longer needed?
if sd == nil {
return nil
}
return &sourceData{
handle: sd.handle,
src: sd.src.Clone(),
srcMeta: sd.srcMeta.Clone(),
}
}
// tableData encapsulates data about a table.
type tableData struct {
tblName string
tblMeta *source.TableMetadata
src *source.Source
srcMeta *source.Metadata
}
func (td *tableData) clone() *tableData { //nolint:unused // REVISIT: no longer needed?
if td == nil {
return nil
}
return &tableData{
tblName: td.tblName,
tblMeta: td.tblMeta.Clone(),
src: td.src.Clone(),
srcMeta: td.srcMeta.Clone(),
}
}
// sourceDiff is a container for a source diff.
type sourceDiff struct {
sd1, sd2 *sourceData
header string
diff string
}
// tableDiff is a container for a table diff.
type tableDiff struct {
td1, td2 *tableData
header string
diff string
}
// dbPropsDiff is a container for a DB properties diff.
type dbPropsDiff struct {
sd1, sd2 *sourceData
header string
diff string
}
// tableDataDiff is a container for a table's data diff.
type tableDataDiff struct {
td1, td2 *tableData
// recMeta1, recMeta2 record.Meta
header string
diff string
}
// Print prints dif to w. If pr is nil, printing is in monochrome.
func Print(w io.Writer, pr *output.Printing, header, dif string) error {
if dif == "" {
return nil
}
if pr == nil || pr.IsMonochrome() {
if header != "" {
dif = header + "\n" + dif
}
_, err := fmt.Fprintln(w, dif)
return errz.Err(err)
}
after := stringz.VisitLines(dif, func(i int, line string) string {
if i == 0 && strings.HasPrefix(line, "---") {
return pr.DiffHeader.Sprint(line)
}
if i == 1 && strings.HasPrefix(line, "+++") {
return pr.DiffHeader.Sprint(line)
}
if strings.HasPrefix(line, "@@") {
return pr.DiffSection.Sprint(line)
}
if strings.HasPrefix(line, "-") {
return pr.DiffMinus.Sprint(line)
}
if strings.HasPrefix(line, "+") {
return pr.DiffPlus.Sprint(line)
}
return pr.DiffNormal.Sprint(line)
})
if header != "" {
after = pr.DiffHeader.Sprint(header) + "\n" + after
}
_, err := fmt.Fprintln(w, after)
return errz.Err(err)
}