Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -44,5 +44,5 @@ require (
golang.org/x/sys v0.44.0 // indirect
golang.org/x/term v0.43.0 // indirect
golang.org/x/text v0.37.0 // indirect
golang.org/x/tools v0.44.0 // indirect
golang.org/x/tools v0.45.0 // indirect
)
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -118,8 +118,8 @@ golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4=
golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk=
golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=
golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38=
golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c=
golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI=
golang.org/x/tools v0.45.0 h1:18qN3FAooORvApf5XjCXgsuayZOEtXf6JK18I3+ONa8=
golang.org/x/tools v0.45.0/go.mod h1:LuUGqqaXcXMEFEruIVJVm5mgDD8vww/z/SR1gQ4uE/0=
google.golang.org/protobuf v1.36.7 h1:IgrO7UwFQGJdRNXH/sQux4R1Dj1WAKcLElzeeRaXV2A=
google.golang.org/protobuf v1.36.7/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
Expand Down
6 changes: 6 additions & 0 deletions internal/cmd/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ type reportConfig struct {
UseGoPatchPaths bool `envDefault:"false"`
MinorChangeThreshold float64 `envDefault:"0.1"`
MultilineContextLines int `envDefault:"4"`
IncludeLineNumbers bool `envDefault:"false"`

KubernetesEntityDetection bool `envDefault:"true"`
AdditionalIdentifiers []string
Expand Down Expand Up @@ -108,6 +109,7 @@ func reportOptionsFlags() []*pflag.FlagSet {
fs.BoolVarP(&reportOptions.UseGoPatchPaths, "use-go-patch-style", "g", reportOptions.UseGoPatchPaths, "use Go-Patch style paths in outputs")
fs.Float64VarP(&reportOptions.MinorChangeThreshold, "minor-change-threshold", "", reportOptions.MinorChangeThreshold, "minor change threshold")
fs.IntVarP(&reportOptions.MultilineContextLines, "multi-line-context-lines", "", reportOptions.MultilineContextLines, "multi-line context lines")
fs.BoolVarP(&reportOptions.IncludeLineNumbers, "line-numbers", "n", reportOptions.IncludeLineNumbers, "include line numbers in diff output")
}),

flagSet("Filter Options", func(fs *pflag.FlagSet) {
Expand Down Expand Up @@ -256,6 +258,7 @@ func writeReport(cmd *cobra.Command, report dyff.Report) error {
reportWriter = &dyff.HumanReport{
Report: report,
Indent: 2,
IncludeLineNumbers: reportOptions.IncludeLineNumbers,
UseIndentLines: reportOptions.UseIndentLines,
DoNotInspectCerts: reportOptions.DoNotInspectCerts,
NoTableStyle: reportOptions.NoTableStyle,
Expand All @@ -274,6 +277,7 @@ func writeReport(cmd *cobra.Command, report dyff.Report) error {
HumanReport: dyff.HumanReport{
Report: report,
Indent: 0,
IncludeLineNumbers: reportOptions.IncludeLineNumbers,
UseIndentLines: reportOptions.UseIndentLines,
DoNotInspectCerts: reportOptions.DoNotInspectCerts,
NoTableStyle: true,
Expand All @@ -293,6 +297,7 @@ func writeReport(cmd *cobra.Command, report dyff.Report) error {
HumanReport: dyff.HumanReport{
Report: report,
Indent: 0,
IncludeLineNumbers: reportOptions.IncludeLineNumbers,
UseIndentLines: reportOptions.UseIndentLines,
DoNotInspectCerts: reportOptions.DoNotInspectCerts,
NoTableStyle: true,
Expand All @@ -312,6 +317,7 @@ func writeReport(cmd *cobra.Command, report dyff.Report) error {
HumanReport: dyff.HumanReport{
Report: report,
Indent: 0,
IncludeLineNumbers: reportOptions.IncludeLineNumbers,
UseIndentLines: reportOptions.UseIndentLines,
DoNotInspectCerts: reportOptions.DoNotInspectCerts,
NoTableStyle: true,
Expand Down
19 changes: 19 additions & 0 deletions pkg/dyff/core_suite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,25 @@ func humanDiff(diff dyff.Diff) string {
return buf.String()
}

func humanDiffWithLineNumbers(diff dyff.Diff) string {
reporter := dyff.HumanReport{
Report: dyff.Report{Diffs: []dyff.Diff{diff}},
Indent: 2,
UseIndentLines: true,
DoNotInspectCerts: false,
NoTableStyle: false,
OmitHeader: true,
IncludeLineNumbers: true,
}

var buf bytes.Buffer
if err := reporter.WriteReport(&buf); err != nil {
Fail(err.Error())
}

return buf.String()
}

func diffSyntaxDiff(diff dyff.Diff) string {
reporter := dyff.DiffSyntaxReport{
PathPrefix: "@@",
Expand Down
40 changes: 40 additions & 0 deletions pkg/dyff/output_human.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ type stringWriter interface {
type HumanReport struct {
Report
Indent int
IncludeLineNumbers bool
UseIndentLines bool
MinorChangeThreshold float64
MultilineContextLines int
Expand Down Expand Up @@ -112,6 +113,14 @@ func (report *HumanReport) WriteReport(out io.Writer) error {
func (report *HumanReport) generateHumanDiffOutput(output stringWriter, diff Diff, useGoPatchPaths bool, showPathRoot bool) error {
_, _ = output.WriteString("\n")
_, _ = output.WriteString(pathToString(diff.Path, useGoPatchPaths, showPathRoot))

if report.IncludeLineNumbers {
if lineInfo := report.lineNumberInfo(diff); lineInfo != "" {
_, _ = output.WriteString(" ")
_, _ = output.WriteString(report.colorizer().DimGray(lineInfo))
}
}

_, _ = output.WriteString("\n")

blocks := make([]string, len(diff.Details))
Expand All @@ -135,6 +144,37 @@ func (report *HumanReport) generateHumanDiffOutput(output stringWriter, diff Dif
return nil
}

// lineNumberInfo returns a string describing the line numbers of the change
// based on the From and To nodes of the first detail in the diff.
func (report *HumanReport) lineNumberInfo(diff Diff) string {
if len(diff.Details) == 0 {
return ""
}

detail := diff.Details[0]
fromLine := 0
toLine := 0

if detail.From != nil && detail.From.Line > 0 {
fromLine = detail.From.Line
}

if detail.To != nil && detail.To.Line > 0 {
toLine = detail.To.Line
}

switch {
case fromLine > 0 && toLine > 0:
return fmt.Sprintf("(line %d -> %d)", fromLine, toLine)
case fromLine > 0:
return fmt.Sprintf("(line %d)", fromLine)
case toLine > 0:
return fmt.Sprintf("(line %d)", toLine)
default:
return ""
}
}

// generateHumanDetailOutput only serves as a dispatcher to call the correct sub function for the respective type of change
func (report *HumanReport) generateHumanDetailOutput(detail Detail) (string, error) {
switch detail.Kind {
Expand Down
127 changes: 127 additions & 0 deletions pkg/dyff/output_human_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
package dyff_test

import (
"bytes"
"fmt"

. "github.com/onsi/ginkgo/v2"
Expand All @@ -29,6 +30,7 @@ import (
. "github.com/gonvenience/bunt"

"github.com/gonvenience/ytbx"
yamlv3 "go.yaml.in/yaml/v3"

"github.com/homeport/dyff/pkg/dyff"
)
Expand Down Expand Up @@ -265,4 +267,129 @@ variables.ROUTER_TLS_PEM.options
)
})
})

Context("line number output", func() {
BeforeEach(func() {
SetColorSettings(OFF, OFF)
})

AfterEach(func() {
SetColorSettings(AUTO, AUTO)
})

It("should show both from and to line numbers for modifications", func() {
from := &yamlv3.Node{Kind: yamlv3.ScalarNode, Tag: "!!str", Value: "old-value", Line: 5}
to := &yamlv3.Node{Kind: yamlv3.ScalarNode, Tag: "!!str", Value: "new-value", Line: 8}

diff := dyff.Diff{
Path: path("/some/key"),
Details: []dyff.Detail{{
Kind: dyff.MODIFICATION,
From: from,
To: to,
}},
}

output := humanDiffWithLineNumbers(diff)
Expect(output).To(ContainSubstring("(line 5 -> 8)"))
Expect(output).To(ContainSubstring("± value change"))
})

It("should show only to-line number for additions", func() {
to := &yamlv3.Node{
Kind: yamlv3.SequenceNode,
Tag: "!!seq",
Line: 10,
Content: []*yamlv3.Node{
{Kind: yamlv3.ScalarNode, Tag: "!!str", Value: "new-entry"},
},
}

diff := dyff.Diff{
Path: path("/some/list"),
Details: []dyff.Detail{{
Kind: dyff.ADDITION,
From: nil,
To: to,
}},
}

output := humanDiffWithLineNumbers(diff)
Expect(output).To(ContainSubstring("(line 10)"))
Expect(output).NotTo(ContainSubstring("->"))
})

It("should show only from-line number for removals", func() {
from := &yamlv3.Node{
Kind: yamlv3.SequenceNode,
Tag: "!!seq",
Line: 15,
Content: []*yamlv3.Node{
{Kind: yamlv3.ScalarNode, Tag: "!!str", Value: "old-entry"},
},
}

diff := dyff.Diff{
Path: path("/some/list"),
Details: []dyff.Detail{{
Kind: dyff.REMOVAL,
From: from,
To: nil,
}},
}

output := humanDiffWithLineNumbers(diff)
Expect(output).To(ContainSubstring("(line 15)"))
Expect(output).NotTo(ContainSubstring("->"))
})

It("should not show line numbers when nodes have no line info", func() {
diff := singleDiff("/some/key", dyff.MODIFICATION, "old", "new")

output := humanDiffWithLineNumbers(diff)
Expect(output).NotTo(ContainSubstring("(line"))
})

It("should not show line numbers when IncludeLineNumbers is disabled", func() {
from := &yamlv3.Node{Kind: yamlv3.ScalarNode, Tag: "!!str", Value: "old", Line: 5}
to := &yamlv3.Node{Kind: yamlv3.ScalarNode, Tag: "!!str", Value: "new", Line: 8}

diff := dyff.Diff{
Path: path("/some/key"),
Details: []dyff.Detail{{
Kind: dyff.MODIFICATION,
From: from,
To: to,
}},
}

// humanDiff does NOT set IncludeLineNumbers
output := humanDiff(diff)
Expect(output).NotTo(ContainSubstring("(line"))
})

It("should show line numbers from real parsed YAML files", func() {
from, to := loadFiles("../../assets/testbed/from.yml", "../../assets/testbed/to.yml")

report, err := dyff.CompareInputFiles(from, to)
Expect(err).To(BeNil())
Expect(len(report.Diffs)).To(BeNumerically(">", 0))

reporter := &dyff.HumanReport{
Report: report,
Indent: 2,
OmitHeader: true,
IncludeLineNumbers: true,
}

var buf bytes.Buffer
Expect(reporter.WriteReport(&buf)).To(BeNil())

output := buf.String()
// The testbed files are real parsed YAML, so go-yaml should
// populate Line fields and we should see at least one "(line"
Expect(output).To(ContainSubstring("(line"))
})
})
})