From fa873e2eed179e3bb650ebe0d36bc91978429ac1 Mon Sep 17 00:00:00 2001 From: komuw Date: Thu, 22 Feb 2024 22:10:21 +0300 Subject: [PATCH 1/8] m --- diff.go | 82 ++++++++++++++++++++++++++++++++++++++++++++++++++++ diff_test.go | 45 ++++++++++++++++++++++++++++ 2 files changed, 127 insertions(+) create mode 100644 diff.go create mode 100644 diff_test.go diff --git a/diff.go b/diff.go new file mode 100644 index 0000000..7a28264 --- /dev/null +++ b/diff.go @@ -0,0 +1,82 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package diff provides basic text comparison (like Unix's diff(1)). +package kama + +import ( + "fmt" + "strings" +) + +// Most of the code here is insipired by(or taken from): +// (a) https://github.com/rsc/diff whose license(BSD 3-Clause "New" or "Revised" License) can be found here: https://github.com/rsc/diff/blob/master/LICENSE + +// Format returns a formatted diff of the two texts, +// showing the entire text and the minimum line-level +// additions and removals to turn text1 into text2. +// (That is, lines only in text1 appear with a leading -, +// and lines only in text2 appear with a leading +.) +func Format(text1, text2 string) string { + if text1 != "" && !strings.HasSuffix(text1, "\n") { + text1 += "(missing final newline)" + } + lines1 := strings.Split(text1, "\n") + lines1 = lines1[:len(lines1)-1] // remove empty string after final line + if text2 != "" && !strings.HasSuffix(text2, "\n") { + text2 += "(missing final newline)" + } + lines2 := strings.Split(text2, "\n") + lines2 = lines2[:len(lines2)-1] // remove empty string after final line + + // Naive dynamic programming algorithm for edit distance. + // https://en.wikipedia.org/wiki/Wagner–Fischer_algorithm + // dist[i][j] = edit distance between lines1[:len(lines1)-i] and lines2[:len(lines2)-j] + // (The reversed indices make following the minimum cost path + // visit lines in the same order as in the text.) + dist := make([][]int, len(lines1)+1) + for i := range dist { + dist[i] = make([]int, len(lines2)+1) + if i == 0 { + for j := range dist[0] { + dist[0][j] = j + } + continue + } + for j := range dist[i] { + if j == 0 { + dist[i][0] = i + continue + } + cost := dist[i][j-1] + 1 + if cost > dist[i-1][j]+1 { + cost = dist[i-1][j] + 1 + } + if lines1[len(lines1)-i] == lines2[len(lines2)-j] { + if cost > dist[i-1][j-1] { + cost = dist[i-1][j-1] + } + } + dist[i][j] = cost + } + } + + var buf strings.Builder + i, j := len(lines1), len(lines2) + for i > 0 || j > 0 { + cost := dist[i][j] + if i > 0 && j > 0 && cost == dist[i-1][j-1] && lines1[len(lines1)-i] == lines2[len(lines2)-j] { + fmt.Fprintf(&buf, " %s\n", lines1[len(lines1)-i]) + i-- + j-- + } else if i > 0 && cost == dist[i-1][j]+1 { + fmt.Fprintf(&buf, "-%s\n", lines1[len(lines1)-i]) + i-- + } else { + fmt.Fprintf(&buf, "+%s\n", lines2[len(lines2)-j]) + j-- + } + } + return buf.String() +} diff --git a/diff_test.go b/diff_test.go new file mode 100644 index 0000000..8c59f01 --- /dev/null +++ b/diff_test.go @@ -0,0 +1,45 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package kama + +import ( + "strings" + "testing" +) + +func TestFormat(t *testing.T) { + t.Parallel() + + formatTests := []struct { + text1 string + text2 string + diff string + }{ + {"a b c", "a b d e f", "a b -c +d +e +f"}, + {"", "a b c", "+a +b +c"}, + {"a b c", "", "-a -b -c"}, + {"a b c", "d e f", "-a -b -c +d +e +f"}, + {"a b c d e f", "a b d e f", "a b -c d e f"}, + {"a b c e f", "a b c d e f", "a b c +d e f"}, + } + + for _, tt := range formatTests { + // Turn spaces into \n. + text1 := strings.ReplaceAll(tt.text1, " ", "\n") + if text1 != "" { + text1 += "\n" + } + text2 := strings.ReplaceAll(tt.text2, " ", "\n") + if text2 != "" { + text2 += "\n" + } + out := Format(text1, text2) + // Cut final \n, cut spaces, turn remaining \n into spaces. + out = strings.ReplaceAll(strings.ReplaceAll(strings.TrimSuffix(out, "\n"), " ", ""), "\n", " ") + if out != tt.diff { + t.Errorf("diff(%q, %q) = %q, want %q", text1, text2, out, tt.diff) + } + } +} From 3ad21c0fd5a4e347566afbb0f6047647cbb34fa3 Mon Sep 17 00:00:00 2001 From: komuw Date: Thu, 22 Feb 2024 22:21:05 +0300 Subject: [PATCH 2/8] m --- diff.go | 4 ++-- diff_test.go | 18 ++++++++++++++---- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/diff.go b/diff.go index 7a28264..0ac5f95 100644 --- a/diff.go +++ b/diff.go @@ -13,12 +13,12 @@ import ( // Most of the code here is insipired by(or taken from): // (a) https://github.com/rsc/diff whose license(BSD 3-Clause "New" or "Revised" License) can be found here: https://github.com/rsc/diff/blob/master/LICENSE -// Format returns a formatted diff of the two texts, +// diff returns a formatted diff of the two texts, // showing the entire text and the minimum line-level // additions and removals to turn text1 into text2. // (That is, lines only in text1 appear with a leading -, // and lines only in text2 appear with a leading +.) -func Format(text1, text2 string) string { +func diff(text1, text2 string) string { if text1 != "" && !strings.HasSuffix(text1, "\n") { text1 += "(missing final newline)" } diff --git a/diff_test.go b/diff_test.go index 8c59f01..80cf882 100644 --- a/diff_test.go +++ b/diff_test.go @@ -5,14 +5,16 @@ package kama import ( + "fmt" + "net/http" "strings" "testing" ) -func TestFormat(t *testing.T) { +func TestSmallDiff(t *testing.T) { t.Parallel() - formatTests := []struct { + tests := []struct { text1 string text2 string diff string @@ -25,7 +27,7 @@ func TestFormat(t *testing.T) { {"a b c e f", "a b c d e f", "a b c +d e f"}, } - for _, tt := range formatTests { + for _, tt := range tests { // Turn spaces into \n. text1 := strings.ReplaceAll(tt.text1, " ", "\n") if text1 != "" { @@ -35,7 +37,7 @@ func TestFormat(t *testing.T) { if text2 != "" { text2 += "\n" } - out := Format(text1, text2) + out := diff(text1, text2) // Cut final \n, cut spaces, turn remaining \n into spaces. out = strings.ReplaceAll(strings.ReplaceAll(strings.TrimSuffix(out, "\n"), " ", ""), "\n", " ") if out != tt.diff { @@ -43,3 +45,11 @@ func TestFormat(t *testing.T) { } } } + +func TestOya(t *testing.T) { + a := Dir(http.Request{Method: "GET"}) + b := Dir(http.Request{Method: "POST"}) + + x := diff(a, b) + fmt.Println("x: ", x) +} From aa59f9a17564583f0726b8fbd0e90dab11163238 Mon Sep 17 00:00:00 2001 From: komuw Date: Thu, 22 Feb 2024 22:23:50 +0300 Subject: [PATCH 3/8] m --- kama.go | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/kama.go b/kama.go index 37e52de..e0b7c9e 100644 --- a/kama.go +++ b/kama.go @@ -135,3 +135,18 @@ func Dir(i interface{}, c ...Config) string { func Stackp() { stackp() } + +// TODO: docs +func Diffp(old, new interface{}, c ...Config) { + fmt.Println( + Diff(old, new, c...), + ) +} + +// TODO: docs +func Diff(old, new interface{}, c ...Config) string { + return diff( + Dir(old, c...), + Dir(new, c...), + ) +} From 0c9d3184292473182de26a7c13f778c088eff94f Mon Sep 17 00:00:00 2001 From: komuw Date: Thu, 22 Feb 2024 22:30:00 +0300 Subject: [PATCH 4/8] m --- diff_test.go | 10 --- kama_test.go | 43 +++++++++++ testdata/kama_test/TestDiff-http_Request.txt | 76 +++++++++++++++++++ .../TestDiff-package_compress_flate.txt | 36 +++++++++ 4 files changed, 155 insertions(+), 10 deletions(-) create mode 100644 testdata/kama_test/TestDiff-http_Request.txt create mode 100644 testdata/kama_test/TestDiff-package_compress_flate.txt diff --git a/diff_test.go b/diff_test.go index 80cf882..06d4c5c 100644 --- a/diff_test.go +++ b/diff_test.go @@ -5,8 +5,6 @@ package kama import ( - "fmt" - "net/http" "strings" "testing" ) @@ -45,11 +43,3 @@ func TestSmallDiff(t *testing.T) { } } } - -func TestOya(t *testing.T) { - a := Dir(http.Request{Method: "GET"}) - b := Dir(http.Request{Method: "POST"}) - - x := diff(a, b) - fmt.Println("x: ", x) -} diff --git a/kama_test.go b/kama_test.go index f8f2506..e3e2598 100644 --- a/kama_test.go +++ b/kama_test.go @@ -248,3 +248,46 @@ func TestReadmeExamples(t *testing.T) { }) } } + +func TestDiff(t *testing.T) { + t.Parallel() + + tt := []struct { + tName string + old interface{} + new interface{} + }{ + { + tName: "package compress/flate", + old: "compress/flate", + new: "compress/flate", + }, + // { + // tName: "package github.com/pkg/errors", + // item: "github.com/pkg/errors", + // }, + // { + // tName: "http request", + // item: req, + // }, + { + tName: "http Request", + old: http.Request{Method: "GET"}, + new: http.Request{Method: "POST"}, + }, + } + + for _, v := range tt { + v := v + tName := fmt.Sprintf("TestDiff-%s", v.tName) + + t.Run(tName, func(t *testing.T) { + t.Parallel() + + res := Diff(v.old, v.new) + + path := getDataPath(t, "kama_test.go", tName) + dealWithTestData(t, path, res) + }) + } +} diff --git a/testdata/kama_test/TestDiff-http_Request.txt b/testdata/kama_test/TestDiff-http_Request.txt new file mode 100644 index 0000000..7b1695d --- /dev/null +++ b/testdata/kama_test/TestDiff-http_Request.txt @@ -0,0 +1,76 @@ + + [ + NAME: net/http.Request + KIND: struct + SIGNATURE: [http.Request *http.Request] + FIELDS: [ + Method string + URL *url.URL + Proto string + ProtoMajor int + ProtoMinor int + Header http.Header + Body io.ReadCloser + GetBody func() (io.ReadCloser, error) + ContentLength int64 + TransferEncoding []string + Close bool + Host string + Form url.Values + PostForm url.Values + MultipartForm *multipart.Form + Trailer http.Header + RemoteAddr string + RequestURI string + TLS *tls.ConnectionState + Cancel <-chan struct {} + Response *http.Response + ] + METHODS: [ + AddCookie func(*http.Request, *http.Cookie) + BasicAuth func(*http.Request) (string, string, bool) + Clone func(*http.Request, context.Context) *http.Request + Context func(*http.Request) context.Context + Cookie func(*http.Request, string) (*http.Cookie, error) + Cookies func(*http.Request) []*http.Cookie + FormFile func(*http.Request, string) (multipart.File, *multipart.FileHeader, error) + FormValue func(*http.Request, string) string + MultipartReader func(*http.Request) (*multipart.Reader, error) + ParseForm func(*http.Request) error + ParseMultipartForm func(*http.Request, int64) error + PathValue func(*http.Request, string) string + PostFormValue func(*http.Request, string) string + ProtoAtLeast func(*http.Request, int, int) bool + Referer func(*http.Request) string + SetBasicAuth func(*http.Request, string, string) + SetPathValue func(*http.Request, string, string) + UserAgent func(*http.Request) string + WithContext func(*http.Request, context.Context) *http.Request + Write func(*http.Request, io.Writer) error + WriteProxy func(*http.Request, io.Writer) error + ] + SNIPPET: Request{ +- Method: "GET", ++ Method: "POST", + URL: *url.URL(nil), + Proto: "", + ProtoMajor: int(0), + ProtoMinor: int(0), + Header: http.Header{(nil)}, + Body: io.ReadCloser nil, + GetBody: func() (io.ReadCloser, error), + ContentLength: int64(0), + TransferEncoding: []string{(nil)}, + Close: false, + Host: "", + Form: url.Values{(nil)}, + PostForm: url.Values{(nil)}, + MultipartForm: *multipart.Form(nil), + Trailer: http.Header{(nil)}, + RemoteAddr: "", + RequestURI: "", + TLS: *tls.ConnectionState(nil), + Cancel: <-chan struct {} (len=0, cap=0), + Response: *http.Response(nil), + } + ] diff --git a/testdata/kama_test/TestDiff-package_compress_flate.txt b/testdata/kama_test/TestDiff-package_compress_flate.txt new file mode 100644 index 0000000..fe48bbd --- /dev/null +++ b/testdata/kama_test/TestDiff-package_compress_flate.txt @@ -0,0 +1,36 @@ + + [ + NAME: compress/flate + CONSTANTS: [ + BestCompression untyped int + BestSpeed untyped int + DefaultCompression untyped int + HuffmanOnly untyped int + NoCompression untyped int + ] + VARIABLES: [] + FUNCTIONS: [ + NewReader(r io.Reader) io.ReadCloser + NewReaderDict(r io.Reader, dict []byte) io.ReadCloser + NewWriter(w io.Writer, level int) (*Writer, error) + NewWriterDict(w io.Writer, level int, dict []byte) (*Writer, error) + ] + TYPES: [ + CorruptInputError int64 + (CorruptInputError) Error() string + InternalError string + (InternalError) Error() string + ReadError struct + (*ReadError) Error() string + Reader interface + (Reader) Read(p []byte) (n int, err error) + (Reader) ReadByte() (byte, error) + Resetter interface + (Resetter) Reset(r io.Reader, dict []byte) error + WriteError struct + (*WriteError) Error() string + Writer struct + (*Writer) Close() error + (*Writer) Flush() error + (*Writer) Reset(dst io.Writer) + (*Writer) Write(data []byte) (n int, err error)] From d4f8b5bacb061e78f674537ce565dbd45f873edc Mon Sep 17 00:00:00 2001 From: komuw Date: Thu, 22 Feb 2024 22:31:31 +0300 Subject: [PATCH 5/8] m --- kama_test.go | 13 +++++------- testdata/kama_test/TestDiff-errors.txt | 29 ++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 8 deletions(-) create mode 100644 testdata/kama_test/TestDiff-errors.txt diff --git a/kama_test.go b/kama_test.go index e3e2598..75129f3 100644 --- a/kama_test.go +++ b/kama_test.go @@ -262,14 +262,11 @@ func TestDiff(t *testing.T) { old: "compress/flate", new: "compress/flate", }, - // { - // tName: "package github.com/pkg/errors", - // item: "github.com/pkg/errors", - // }, - // { - // tName: "http request", - // item: req, - // }, + { + tName: "errors", + old: "errors", + new: "github.com/pkg/errors", + }, { tName: "http Request", old: http.Request{Method: "GET"}, diff --git a/testdata/kama_test/TestDiff-errors.txt b/testdata/kama_test/TestDiff-errors.txt new file mode 100644 index 0000000..bb14abf --- /dev/null +++ b/testdata/kama_test/TestDiff-errors.txt @@ -0,0 +1,29 @@ + + [ +-NAME: errors ++NAME: github.com/pkg/errors + CONSTANTS: [] + VARIABLES: [] + FUNCTIONS: [ +- As(err error, target any) bool ++ As(err error, target interface{}) bool ++ Cause(err error) error ++ Errorf(format string, args ...interface{}) error + Is(err error, target error) bool +- Join(errs ...error) error +- New(text string) error ++ New(message string) error + Unwrap(err error) error ++ WithMessage(err error, message string) error ++ WithMessagef(err error, format string, args ...interface{}) error ++ WithStack(err error) error ++ Wrap(err error, message string) error ++ Wrapf(err error, format string, args ...interface{}) error + ] +-TYPES: [] ++TYPES: [ ++ Frame uintptr ++ (Frame) Format(s fmt.State, verb rune) ++ (Frame) MarshalText() ([]byte, error) ++ StackTrace []Frame ++ (StackTrace) Format(s fmt.State, verb rune)] From 45ab615c03f03f176658670711ea357b5474cc67 Mon Sep 17 00:00:00 2001 From: komuw Date: Thu, 22 Feb 2024 22:34:02 +0300 Subject: [PATCH 6/8] m --- kama.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kama.go b/kama.go index e0b7c9e..0b4effa 100644 --- a/kama.go +++ b/kama.go @@ -136,14 +136,14 @@ func Stackp() { stackp() } -// TODO: docs +// Diffp prints a formatted diff showing the minimum line-level additions and removals that would turn old into new. func Diffp(old, new interface{}, c ...Config) { fmt.Println( Diff(old, new, c...), ) } -// TODO: docs +// Diff returns a formatted diff showing the minimum line-level additions and removals that would turn old into new. func Diff(old, new interface{}, c ...Config) string { return diff( Dir(old, c...), From 826b3fc2d8a2b25b1845a3b14a447951676ffc4f Mon Sep 17 00:00:00 2001 From: komuw Date: Thu, 22 Feb 2024 22:35:38 +0300 Subject: [PATCH 7/8] m --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f4c14d8..bb053a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,9 @@ Most recent version is listed first. +# v0.0.18 +- Add ability to diff items: https://github.com/komuw/kama/pull/67 + # v0.0.17 - Add ability to dump items with circular references: https://github.com/komuw/kama/pull/66 From 49b2f7b8f67e391ba8ec8521ca4b5b553471d381 Mon Sep 17 00:00:00 2001 From: komuw Date: Thu, 22 Feb 2024 22:38:24 +0300 Subject: [PATCH 8/8] m --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 74f61c0..a822767 100644 --- a/README.md +++ b/README.md @@ -216,3 +216,4 @@ go test -race ./... -count=1 5. https://github.com/alecthomas/repr 6. https://github.com/k0kubun/pp 7. https://github.com/jba/printsrc +8. https://github.com/kylelemons/godebug