aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorShulhan <ms@kilabit.info>2026-04-12 17:38:32 +0700
committerShulhan <ms@kilabit.info>2026-04-12 19:01:45 +0700
commitbfa79cf52d62418f622289cee7c1fe5807c73be6 (patch)
tree240547182a2448ffa79feec94c9787f2c4466d3c
parent8604b86d6e18bfdc9d2f8123a19bc8ed4dbfddb2 (diff)
downloadpakakeh.go-bfa79cf52d62418f622289cee7c1fe5807c73be6.tar.xz
text/diff: add example for Files, Lines, Text, and Unified
While at it, - reorganize the order of functions alphabetically, following the order from doc. - changes the [Line.String] to print text in double quoted to help human compare the changes for non-printable characters.
-rw-r--r--lib/text/diff/diff.go95
-rw-r--r--lib/text/diff/diff_example_test.go125
-rw-r--r--lib/text/diff/line.go14
-rw-r--r--lib/text/diff/testdata/lao.txt11
-rw-r--r--lib/text/diff/testdata/tzu.txt13
-rw-r--r--lib/text/diff/unified_example_test.go65
6 files changed, 287 insertions, 36 deletions
diff --git a/lib/text/diff/diff.go b/lib/text/diff/diff.go
index 7a2844c9..2e4102c0 100644
--- a/lib/text/diff/diff.go
+++ b/lib/text/diff/diff.go
@@ -52,16 +52,6 @@ func Files(oldfile, newfile string, level int) (diff *Data, err error) {
return diff, nil
}
-// Text returns the difference between old and new text.
-func Text(old, new []byte, level int) (diff *Data) {
- oldlines := ParseLines(old)
- newlines := ParseLines(new)
- diff = Lines(oldlines, newlines, level)
- diff.OldName = `old`
- diff.NewName = `new`
- return diff
-}
-
// Lines returns the difference between old and new lines.
func Lines(oldlines, newlines []Line, level int) (diff *Data) {
diff = &Data{}
@@ -209,6 +199,16 @@ func Lines(oldlines, newlines []Line, level int) (diff *Data) {
return diff
}
+// Text returns the difference between old and new text.
+func Text(old, new []byte, level int) (diff *Data) {
+ oldlines := ParseLines(old)
+ newlines := ParseLines(new)
+ diff = Lines(oldlines, newlines, level)
+ diff.OldName = `old`
+ diff.NewName = `new`
+ return diff
+}
+
// checkIsMatched set the IsMatched to true if no changes found.
func (diff *Data) checkIsMatched() {
if len(diff.Adds) != 0 {
@@ -223,19 +223,32 @@ func (diff *Data) checkIsMatched() {
diff.IsMatched = true
}
-// PushAdd will add new line to diff set.
+// GetAllAdds returns chunks of additions including the line changes.
+func (diff Data) GetAllAdds() (chunks text.Chunks) {
+ for _, add := range diff.Adds {
+ chunks = append(chunks, text.Chunk{StartAt: 0, V: add.Val})
+ }
+ chunks = append(chunks, diff.Changes.GetAllAdds()...)
+ return
+}
+
+// GetAllDels returns chunks of deletions including the line changes.
+func (diff Data) GetAllDels() (chunks text.Chunks) {
+ for _, del := range diff.Dels {
+ chunks = append(chunks, text.Chunk{StartAt: 0, V: del.Val})
+ }
+ chunks = append(chunks, diff.Changes.GetAllDels()...)
+ return
+}
+
+// PushAdd adds new line to slice of Adds.
func (diff *Data) PushAdd(new Line) {
new.Kind = LineKindAdd
diff.Adds = append(diff.Adds, new)
}
-// PushDel will add deletion line to diff set.
-func (diff *Data) PushDel(old Line) {
- old.Kind = LineKindDel
- diff.Dels = append(diff.Dels, old)
-}
-
-// PushChange set to diff data.
+// PushChange adds the old and new line to Dels, Adds, and [Data.Changes]
+// respectively.
func (diff *Data) PushChange(old, new Line) {
if old.Num != new.Num {
old.NumOther, new.NumOther = new.Num, old.Num
@@ -246,27 +259,39 @@ func (diff *Data) PushChange(old, new Line) {
diff.PushAdd(new)
}
-// GetAllAdds return chunks of additions including in line changes.
-func (diff Data) GetAllAdds() (chunks text.Chunks) {
- for _, add := range diff.Adds {
- chunks = append(chunks, text.Chunk{StartAt: 0, V: add.Val})
- }
- chunks = append(chunks, diff.Changes.GetAllAdds()...)
- return
-}
-
-// GetAllDels return chunks of deletions including in line changes.
-func (diff Data) GetAllDels() (chunks text.Chunks) {
- for _, del := range diff.Dels {
- chunks = append(chunks, text.Chunk{StartAt: 0, V: del.Val})
- }
- chunks = append(chunks, diff.Changes.GetAllDels()...)
- return
+// PushDel adds deletion line to slice of Dels.
+func (diff *Data) PushDel(old Line) {
+ old.Kind = LineKindDel
+ diff.Dels = append(diff.Dels, old)
}
-// String return formatted data.
+// String returns the additions and deletions with custom format.
+// The custom format as follow,
+//
+// --- <OldName>
+// +++ <NewName>
+// ----
+// <NumOther>/ <Num>: <Kind><QuotedVal>
+// ++++
+// <NumOther>/ <Num>: <Kind><Val>
+//
+// The lines after "----" is the deleted lines from the old text.
+// The lines after "++++" is the added lines from the new text.
+//
+// The NumOther is the line number from the other text.
+// The Num is the line number where line is deleted in old text, or added in
+// new text.
+//
+// The Kind is '-' for deletion or '+' for addition.
+//
+// The QuotedVal value is the line text printed inside double quotes.
func (diff Data) String() (s string) {
+ if diff.IsMatched {
+ return ``
+ }
var sb strings.Builder
+ fmt.Fprintf(&sb, "--- %s\n", diff.OldName)
+ fmt.Fprintf(&sb, "+++ %s\n", diff.NewName)
if len(diff.Dels) > 0 {
sb.WriteString("----\n")
for _, line := range diff.Dels {
diff --git a/lib/text/diff/diff_example_test.go b/lib/text/diff/diff_example_test.go
new file mode 100644
index 00000000..2b3709d3
--- /dev/null
+++ b/lib/text/diff/diff_example_test.go
@@ -0,0 +1,125 @@
+// SPDX-License-Identifier: BSD-3-Clause
+// SPDX-FileCopyrightText: 2026 M. Shulhan <ms@kilabit.info>
+
+package diff_test
+
+import (
+ "fmt"
+ "log"
+
+ "git.sr.ht/~shulhan/pakakeh.go/lib/text/diff"
+)
+
+func ExampleFiles() {
+ diff, err := diff.Files(`testdata/lao.txt`, `testdata/tzu.txt`, diff.LevelLines)
+ if err != nil {
+ log.Fatal(err)
+ }
+ fmt.Println(diff.String())
+
+ // Output:
+ // --- testdata/lao.txt
+ // +++ testdata/tzu.txt
+ // ----
+ // 1/ 1: -"The Way that can be told of is not the eternal Way;"
+ // 2/ 2: -"The name that can be named is not the eternal name."
+ // 2/ 4: -"The Named is the mother of all things."
+ // ++++
+ // 4/ 2: +"The named is the mother of all things."
+ // 3/ 3: +""
+ // 11/ 11: +"They both may be called deep and profound."
+ // 12/ 12: +"Deeper and more profound,"
+ // 13/ 13: +"The door of all subtleties!"
+}
+
+func ExampleLines() {
+ lao := `The Way that can be told of is not the eternal Way;
+The name that can be named is not the eternal name.
+The Nameless is the origin of Heaven and Earth;
+The Named is the mother of all things.
+Therefore let there always be non-being,
+ so we may see their subtlety,
+And let there always be being,
+ so we may see their outcome.
+The two are the same,
+But after they are produced,
+ they have different names.`
+
+ tzu := `The Nameless is the origin of Heaven and Earth;
+The named is the mother of all things.
+
+Therefore let there always be non-being,
+ so we may see their subtlety,
+And let there always be being,
+ so we may see their outcome.
+The two are the same,
+But after they are produced,
+ they have different names.
+They both may be called deep and profound.
+Deeper and more profound,
+The door of all subtleties!
+`
+ oldlines := diff.ParseLines([]byte(lao))
+ newlines := diff.ParseLines([]byte(tzu))
+ diff := diff.Lines(oldlines, newlines, diff.LevelLines)
+ fmt.Println(diff.String())
+
+ // Output:
+ // --- oldlines
+ // +++ newlines
+ // ----
+ // 1/ 1: -"The Way that can be told of is not the eternal Way;"
+ // 2/ 2: -"The name that can be named is not the eternal name."
+ // 2/ 4: -"The Named is the mother of all things."
+ // ++++
+ // 4/ 2: +"The named is the mother of all things."
+ // 3/ 3: +""
+ // 11/ 11: +"They both may be called deep and profound."
+ // 12/ 12: +"Deeper and more profound,"
+ // 13/ 13: +"The door of all subtleties!"
+}
+
+func ExampleText() {
+ lao := `The Way that can be told of is not the eternal Way;
+The name that can be named is not the eternal name.
+The Nameless is the origin of Heaven and Earth;
+The Named is the mother of all things.
+Therefore let there always be non-being,
+ so we may see their subtlety,
+And let there always be being,
+ so we may see their outcome.
+The two are the same,
+But after they are produced,
+ they have different names.`
+
+ tzu := `The Nameless is the origin of Heaven and Earth;
+The named is the mother of all things.
+
+Therefore let there always be non-being,
+ so we may see their subtlety,
+And let there always be being,
+ so we may see their outcome.
+The two are the same,
+But after they are produced,
+ they have different names.
+They both may be called deep and profound.
+Deeper and more profound,
+The door of all subtleties!
+`
+ diff := diff.Text([]byte(lao), []byte(tzu), diff.LevelLines)
+ fmt.Println(diff.String())
+
+ // Output:
+ // --- old
+ // +++ new
+ // ----
+ // 1/ 1: -"The Way that can be told of is not the eternal Way;"
+ // 2/ 2: -"The name that can be named is not the eternal name."
+ // 2/ 4: -"The Named is the mother of all things."
+ // ++++
+ // 4/ 2: +"The named is the mother of all things."
+ // 3/ 3: +""
+ // 11/ 11: +"They both may be called deep and profound."
+ // 12/ 12: +"Deeper and more profound,"
+ // 13/ 13: +"The door of all subtleties!"
+}
diff --git a/lib/text/diff/line.go b/lib/text/diff/line.go
index e7a4825c..b7d428c7 100644
--- a/lib/text/diff/line.go
+++ b/lib/text/diff/line.go
@@ -54,12 +54,24 @@ func ReadLines(file string) (lines []Line, err error) {
return lines, nil
}
+// WriteLines writes each line in lines into w using the [Line.String] return
+// value.
func WriteLines(w io.Writer, lines []Line) {
for _, line := range lines {
fmt.Fprintln(w, line.String())
}
}
+// Strings returns the formatted line data.
+// The format is as follow,
+//
+// <NumOther>/<Num>: <Kind><QuotedVal>
+//
+// The NumOther is line number from other text being compared.
+// The Num is line number.
+// The Kind is '-' for deletion or '+' for addition.
+// The QuotedVal is double quoted line text, to help human compare and see the
+// changes for non-printable characters.
func (line Line) String() string {
- return fmt.Sprintf("%4d/%4d: %c%s", line.NumOther, line.Num, line.Kind, line.Val)
+ return fmt.Sprintf("%4d/%4d: %c%q", line.NumOther, line.Num, line.Kind, line.Val)
}
diff --git a/lib/text/diff/testdata/lao.txt b/lib/text/diff/testdata/lao.txt
new file mode 100644
index 00000000..635ef2c4
--- /dev/null
+++ b/lib/text/diff/testdata/lao.txt
@@ -0,0 +1,11 @@
+The Way that can be told of is not the eternal Way;
+The name that can be named is not the eternal name.
+The Nameless is the origin of Heaven and Earth;
+The Named is the mother of all things.
+Therefore let there always be non-being,
+ so we may see their subtlety,
+And let there always be being,
+ so we may see their outcome.
+The two are the same,
+But after they are produced,
+ they have different names.
diff --git a/lib/text/diff/testdata/tzu.txt b/lib/text/diff/testdata/tzu.txt
new file mode 100644
index 00000000..5af88a8f
--- /dev/null
+++ b/lib/text/diff/testdata/tzu.txt
@@ -0,0 +1,13 @@
+The Nameless is the origin of Heaven and Earth;
+The named is the mother of all things.
+
+Therefore let there always be non-being,
+ so we may see their subtlety,
+And let there always be being,
+ so we may see their outcome.
+The two are the same,
+But after they are produced,
+ they have different names.
+They both may be called deep and profound.
+Deeper and more profound,
+The door of all subtleties!
diff --git a/lib/text/diff/unified_example_test.go b/lib/text/diff/unified_example_test.go
new file mode 100644
index 00000000..20cb971a
--- /dev/null
+++ b/lib/text/diff/unified_example_test.go
@@ -0,0 +1,65 @@
+// SPDX-License-Identifier: BSD-3-Clause
+// SPDX-FileCopyrightText: 2026 M. Shulhan <ms@kilabit.info>
+
+package diff_test
+
+import (
+ "fmt"
+ "strings"
+
+ "git.sr.ht/~shulhan/pakakeh.go/lib/text/diff"
+)
+
+func ExampleUnified() {
+ lao := `The Way that can be told of is not the eternal Way;
+The name that can be named is not the eternal name.
+The Nameless is the origin of Heaven and Earth;
+The Named is the mother of all things.
+Therefore let there always be non-being,
+ so we may see their subtlety,
+And let there always be being,
+ so we may see their outcome.
+The two are the same,
+But after they are produced,
+ they have different names.`
+
+ tzu := `The Nameless is the origin of Heaven and Earth;
+The named is the mother of all things.
+
+Therefore let there always be non-being,
+ so we may see their subtlety,
+And let there always be being,
+ so we may see their outcome.
+The two are the same,
+But after they are produced,
+ they have different names.
+They both may be called deep and profound.
+Deeper and more profound,
+The door of all subtleties!
+`
+ dif := diff.Unified([]byte(lao), []byte(tzu))
+ var sb strings.Builder
+ dif.WriteUnified(&sb, 3)
+ fmt.Println(sb.String())
+
+ // Output:
+ // --- old
+ // +++ new
+ // @@ -1,7 +1,6 @@
+ // -The Way that can be told of is not the eternal Way;
+ // -The name that can be named is not the eternal name.
+ // The Nameless is the origin of Heaven and Earth;
+ // -The Named is the mother of all things.
+ // +The named is the mother of all things.
+ // +
+ // Therefore let there always be non-being,
+ // so we may see their subtlety,
+ // And let there always be being,
+ // @@ -9,3 +8,6 @@
+ // The two are the same,
+ // But after they are produced,
+ // they have different names.
+ // +They both may be called deep and profound.
+ // +Deeper and more profound,
+ // +The door of all subtleties!
+}