diff --git a/cmd/protoc-gen-go-tableau-loader/embed/templates/hub.pc.go.tpl b/cmd/protoc-gen-go-tableau-loader/embed/templates/hub.pc.go.tpl index cc623f3c..88db0b4b 100644 --- a/cmd/protoc-gen-go-tableau-loader/embed/templates/hub.pc.go.tpl +++ b/cmd/protoc-gen-go-tableau-loader/embed/templates/hub.pc.go.tpl @@ -5,6 +5,7 @@ import ( "sync/atomic" "time" + "github.com/tableauio/loader/pkg/udiff" "github.com/tableauio/tableau/format" "github.com/tableauio/tableau/load" "github.com/tableauio/tableau/store" @@ -143,7 +144,7 @@ func (h *Hub) Store(dir string, format format.Format, options ...store.Option) e return nil } -// mutableCheck checks if the messagers are mutable or not. +// mutableCheck checks if the messagers are mutated or not. func (h *Hub) mutableCheck() { interval := h.opts.MutableCheck.Interval if interval == 0 { @@ -166,7 +167,7 @@ func (h *Hub) mutableCheck() { } func (h *Hub) onMutateDefault(name string, original, current proto.Message) { - text, _ := UnifiedDiff(original, current) + text, _ := udiff.UnifiedDiff(original, current) fmt.Fprintf(os.Stderr, "==== %s DIFF BEGIN ====\n%s==== %s DIFF END ====\n", name, text, name) diff --git a/cmd/protoc-gen-go-tableau-loader/embed/templates/util.pc.go.tpl b/cmd/protoc-gen-go-tableau-loader/embed/templates/util.pc.go.tpl index 24db4c17..2b12bb92 100644 --- a/cmd/protoc-gen-go-tableau-loader/embed/templates/util.pc.go.tpl +++ b/cmd/protoc-gen-go-tableau-loader/embed/templates/util.pc.go.tpl @@ -1,9 +1,5 @@ import ( "errors" - - "github.com/pmezard/go-difflib/difflib" - "github.com/tableauio/tableau/store" - "google.golang.org/protobuf/proto" ) var ErrNotFound = errors.New("not found") @@ -22,23 +18,3 @@ func GetMessager[T Messager](messagerMap MessagerMap) T { messager, _ := messagerMap[t.Name()].(T) return messager } - -// UnifiedDiff generates the proto message delta as a unified diff. -func UnifiedDiff(original, current proto.Message) (string, error) { - originalText, err := store.MarshalToText(original, true) - if err != nil { - return "", err - } - currentText, err := store.MarshalToText(current, true) - if err != nil { - return "", err - } - diff := difflib.UnifiedDiff{ - A: difflib.SplitLines(string(originalText)), - B: difflib.SplitLines(string(currentText)), - FromFile: "Original", - ToFile: "Current", - Context: 3, - } - return difflib.GetUnifiedDiffString(diff) -} \ No newline at end of file diff --git a/go.mod b/go.mod index 85fa6d7f..cb246e6f 100644 --- a/go.mod +++ b/go.mod @@ -3,11 +3,10 @@ module github.com/tableauio/loader go 1.21 require ( + github.com/aymanbagabas/go-udiff v0.2.0 github.com/iancoleman/strcase v0.3.0 - github.com/pmezard/go-difflib v1.0.0 github.com/stretchr/testify v1.10.0 github.com/tableauio/tableau v0.15.1 - golang.org/x/exp v0.0.0-20230418202329-0354be287a23 google.golang.org/protobuf v1.34.2 ) @@ -18,6 +17,7 @@ require ( github.com/emirpasic/gods v1.18.1 // indirect github.com/mitchellh/go-wordwrap v1.0.1 // indirect github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/protocolbuffers/txtpbfmt v0.0.0-20240820135758-21b1d9897dc7 // indirect github.com/richardlehane/mscfb v1.0.4 // indirect github.com/richardlehane/msoleps v1.0.4 // indirect diff --git a/go.sum b/go.sum index 973838c6..e5396710 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ github.com/antchfx/xpath v0.0.0-20170515025933-1f3266e77307 h1:C735MoY/X+UOx6SECmHk5pVOj51h839Ph13pEoY8UmU= github.com/antchfx/xpath v0.0.0-20170515025933-1f3266e77307/go.mod h1:Yee4kTMuNiPYJ7nSNorELQMr1J33uOpXDMByNYhvtNk= +github.com/aymanbagabas/go-udiff v0.2.0 h1:TK0fH4MteXUDspT88n8CKzvK0X9O2xu9yQjWpi6yML8= +github.com/aymanbagabas/go-udiff v0.2.0/go.mod h1:RE4Ex0qsGkTAJoQdQQCA0uG+nAzJO/pI/QwceO5fgrA= github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/bufbuild/protocompile v0.10.0 h1:+jW/wnLMLxaCEG8AX9lD0bQ5v9h1RUiMKOBOT5ll9dM= @@ -56,8 +58,6 @@ go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= -golang.org/x/exp v0.0.0-20230418202329-0354be287a23 h1:4NKENAGIctmZYLK9W+X1kDK8ObBFqOSCJM6WE7CvkJY= -golang.org/x/exp v0.0.0-20230418202329-0354be287a23/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= golang.org/x/image v0.18.0 h1:jGzIakQa/ZXI1I0Fxvaa9W7yP25TqT6cHIHn+6CqvSQ= golang.org/x/image v0.18.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E= golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= diff --git a/internal/options/options.go b/internal/options/options.go index df5f801a..b9046b22 100644 --- a/internal/options/options.go +++ b/internal/options/options.go @@ -1,10 +1,10 @@ package options import ( + "slices" "strings" "github.com/tableauio/tableau/proto/tableaupb" - "golang.org/x/exp/slices" "google.golang.org/protobuf/compiler/protogen" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/reflect/protoreflect" diff --git a/pkg/udiff/udiff.go b/pkg/udiff/udiff.go new file mode 100644 index 00000000..ce4a9864 --- /dev/null +++ b/pkg/udiff/udiff.go @@ -0,0 +1,20 @@ +package udiff + +import ( + "github.com/aymanbagabas/go-udiff" + "github.com/tableauio/tableau/store" + "google.golang.org/protobuf/proto" +) + +// UnifiedDiff generates the proto message delta as a unified diff. +func UnifiedDiff(original, current proto.Message) (string, error) { + originalText, err := store.MarshalToText(original, true) + if err != nil { + return "", err + } + currentText, err := store.MarshalToText(current, true) + if err != nil { + return "", err + } + return udiff.Unified("Original", "Current", string(originalText), string(currentText)), nil +} diff --git a/pkg/udiff/udiff_test.go b/pkg/udiff/udiff_test.go new file mode 100644 index 00000000..2912cc3d --- /dev/null +++ b/pkg/udiff/udiff_test.go @@ -0,0 +1,100 @@ +package udiff + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/types/known/structpb" + "google.golang.org/protobuf/types/known/wrapperspb" +) + +func TestUnifiedDiff_NoDifference(t *testing.T) { + original := wrapperspb.String("hello") + current := wrapperspb.String("hello") + + diff, err := UnifiedDiff(original, current) + require.NoError(t, err) + assert.Empty(t, diff) +} + +func TestUnifiedDiff_WithDifference(t *testing.T) { + original := wrapperspb.String("hello") + current := wrapperspb.String("world") + + diff, err := UnifiedDiff(original, current) + require.NoError(t, err) + assert.NotEmpty(t, diff) + + // Assert full unified diff lines + assert.Contains(t, diff, "--- Original") + assert.Contains(t, diff, "+++ Current") + assert.Contains(t, diff, `-value: "hello"`) + assert.Contains(t, diff, `+value: "world"`) +} + +func TestUnifiedDiff_NilMessages(t *testing.T) { + original := wrapperspb.String("") + current := wrapperspb.String("") + + diff, err := UnifiedDiff(original, current) + require.NoError(t, err) + assert.Empty(t, diff) +} + +func TestUnifiedDiff_ComplexMessage(t *testing.T) { + original, err := structpb.NewStruct(map[string]any{ + "name": "Alice", + "age": 30, + "active": true, + "score": 99.5, + "address": map[string]any{"city": "Shanghai", "zip": "200000"}, + "tags": []any{"admin", "vip"}, + }) + require.NoError(t, err) + + current, err := structpb.NewStruct(map[string]any{ + "name": "Bob", + "age": 25, + "active": false, + "score": 88.0, + "address": map[string]any{"city": "Beijing", "zip": "100000"}, + "tags": []any{"user"}, + }) + require.NoError(t, err) + + diff, err := UnifiedDiff(original, current) + require.NoError(t, err) + assert.NotEmpty(t, diff) + + // Assert changed fields with full unified diff lines + assert.Contains(t, diff, "- bool_value: true") + assert.Contains(t, diff, "+ bool_value: false") + assert.Contains(t, diff, `- string_value: "Shanghai"`) + assert.Contains(t, diff, `+ string_value: "Beijing"`) + assert.Contains(t, diff, `- string_value: "200000"`) + assert.Contains(t, diff, `+ string_value: "100000"`) + assert.Contains(t, diff, "- number_value: 30") + assert.Contains(t, diff, "+ number_value: 25") + assert.Contains(t, diff, `- string_value: "Alice"`) + assert.Contains(t, diff, `+ string_value: "Bob"`) + assert.Contains(t, diff, "- number_value: 99.5") + assert.Contains(t, diff, "+ number_value: 88") + assert.Contains(t, diff, `- string_value: "admin"`) + assert.Contains(t, diff, `- string_value: "vip"`) + assert.Contains(t, diff, `+ string_value: "user"`) + + // Assert unchanged fields (keys) are not in diff lines (no +/- prefix) + assert.NotContains(t, diff, `- key: "active"`) + assert.NotContains(t, diff, `+ key: "active"`) + assert.NotContains(t, diff, `- key: "address"`) + assert.NotContains(t, diff, `+ key: "address"`) + assert.NotContains(t, diff, `- key: "age"`) + assert.NotContains(t, diff, `+ key: "age"`) + assert.NotContains(t, diff, `- key: "name"`) + assert.NotContains(t, diff, `+ key: "name"`) + assert.NotContains(t, diff, `- key: "score"`) + assert.NotContains(t, diff, `+ key: "score"`) + assert.NotContains(t, diff, `- key: "tags"`) + assert.NotContains(t, diff, `+ key: "tags"`) +} diff --git a/test/go-tableau-loader/protoconf/loader/hub.pc.go b/test/go-tableau-loader/protoconf/loader/hub.pc.go index a4e2e86c..6af49131 100644 --- a/test/go-tableau-loader/protoconf/loader/hub.pc.go +++ b/test/go-tableau-loader/protoconf/loader/hub.pc.go @@ -12,6 +12,7 @@ import ( "sync/atomic" "time" + "github.com/tableauio/loader/pkg/udiff" "github.com/tableauio/tableau/format" "github.com/tableauio/tableau/load" "github.com/tableauio/tableau/store" @@ -150,7 +151,7 @@ func (h *Hub) Store(dir string, format format.Format, options ...store.Option) e return nil } -// mutableCheck checks if the messagers are mutable or not. +// mutableCheck checks if the messagers are mutated or not. func (h *Hub) mutableCheck() { interval := h.opts.MutableCheck.Interval if interval == 0 { @@ -173,7 +174,7 @@ func (h *Hub) mutableCheck() { } func (h *Hub) onMutateDefault(name string, original, current proto.Message) { - text, _ := UnifiedDiff(original, current) + text, _ := udiff.UnifiedDiff(original, current) fmt.Fprintf(os.Stderr, "==== %s DIFF BEGIN ====\n%s==== %s DIFF END ====\n", name, text, name) diff --git a/test/go-tableau-loader/protoconf/loader/util.pc.go b/test/go-tableau-loader/protoconf/loader/util.pc.go index 92e9029a..6bafc42b 100644 --- a/test/go-tableau-loader/protoconf/loader/util.pc.go +++ b/test/go-tableau-loader/protoconf/loader/util.pc.go @@ -7,10 +7,6 @@ package loader import ( "errors" - - "github.com/pmezard/go-difflib/difflib" - "github.com/tableauio/tableau/store" - "google.golang.org/protobuf/proto" ) var ErrNotFound = errors.New("not found") @@ -29,23 +25,3 @@ func GetMessager[T Messager](messagerMap MessagerMap) T { messager, _ := messagerMap[t.Name()].(T) return messager } - -// UnifiedDiff generates the proto message delta as a unified diff. -func UnifiedDiff(original, current proto.Message) (string, error) { - originalText, err := store.MarshalToText(original, true) - if err != nil { - return "", err - } - currentText, err := store.MarshalToText(current, true) - if err != nil { - return "", err - } - diff := difflib.UnifiedDiff{ - A: difflib.SplitLines(string(originalText)), - B: difflib.SplitLines(string(currentText)), - FromFile: "Original", - ToFile: "Current", - Context: 3, - } - return difflib.GetUnifiedDiffString(diff) -}