diff

package
v0.67.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 11, 2026 License: BSD-3-Clause Imports: 8 Imported by: 0

Documentation

Overview

Package diff implement text comparison.

Index

Examples

Constants

View Source
const (
	// LevelLines captures the differences on lines level.
	// This will fills the [Data.Adds] and [Data.Dels] only.
	LevelLines = iota

	// LevelChunks captures the differences on character level, when
	// changes is on the same line.
	// This will fills the [Data.LineChunks], [Data.Adds], and
	// [Data.Dels].
	LevelChunks
)

List of comparison levels.

View Source
const (
	LineKindAdd   = '+'
	LineKindDel   = '-'
	LineKindLabel = '@'
	LineKindNone  = 0
)

List of kind of line in Data.

View Source
const DefMatchLen = 5

DefMatchLen minimum number of bytes used for searching the next matched chunk in line.

View Source
const DefMatchRatio = 0.7

DefMatchRatio define default minimum match ratio to be considered as change.

Variables

This section is empty.

Functions

func Bytes

func Bytes(old, new []byte, oldat, newat int) (adds, dels text.Chunks)

Bytes returns the character differences (additions and deletion) between old and new bytes, start at specific position.

Example
fmt.Println(`Additions 1:`)
adds, dels := Bytes([]byte(`00000`), []byte(`00000111`), 0, 0)
fmt.Printf("  adds: %+v\n", adds)
fmt.Printf("  dels: %+v\n", dels)

fmt.Println(`Additions 2:`)
adds, dels = Bytes([]byte(`000000`), []byte(`0001000`), 0, 0)
fmt.Printf("  adds: %+v\n", adds)
fmt.Printf("  dels: %+v\n", dels)

fmt.Println(`Deletions 1:`)
adds, dels = Bytes([]byte(`00000111`), []byte(`00000`), 0, 0)
fmt.Printf("  adds: %+v\n", adds)
fmt.Printf("  dels: %+v\n", dels)

fmt.Println(`Deletions 2:`)
adds, dels = Bytes([]byte(`0001000`), []byte(`000000`), 0, 0)
fmt.Printf("  adds: %+v\n", adds)
fmt.Printf("  dels: %+v\n", dels)

fmt.Println(`Both changes 1:`)
adds, dels = Bytes([]byte(`11000`), []byte(`22000`), 0, 0)
fmt.Printf("  adds: %+v\n", adds)
fmt.Printf("  dels: %+v\n", dels)

fmt.Println(`Both changes 2:`)
adds, dels = Bytes([]byte(`0001000`), []byte(`0002000`), 0, 0)
fmt.Printf("  adds: %+v\n", adds)
fmt.Printf("  dels: %+v\n", dels)
Output:
Additions 1:
  adds: [{StartAt:5,V:111}]
  dels: []
Additions 2:
  adds: [{StartAt:3,V:1}]
  dels: []
Deletions 1:
  adds: []
  dels: [{StartAt:5,V:111}]
Deletions 2:
  adds: []
  dels: [{StartAt:3,V:1}]
Both changes 1:
  adds: [{StartAt:0,V:22}]
  dels: [{StartAt:0,V:11}]
Both changes 2:
  adds: [{StartAt:3,V:2}]
  dels: [{StartAt:3,V:1}]

func BytesRatio

func BytesRatio(old, new []byte, minTokenLen int) (ratio float32, match int, maxlen int)

BytesRatio returns the ratio of matching bytes between old and new. The ratio in in range of 0.0 to 1.0, where 1.0 when both are similar, and 0.0 when no single character matchs found. The minTokenLen define the minimum length of token for searching in both of slice.

It also return the number of characters match-ed and maximum length between old and new. The ratio is computed from match/maxlen.

Example
ratio, m, maxlen := BytesRatio([]byte(`00000`), []byte(`00000111`), 1)
fmt.Printf("Additions 1: %f %d %d\n", ratio, m, maxlen)

ratio, m, maxlen = BytesRatio([]byte(`000000`), []byte(`0001000`), 1)
fmt.Printf("Additions 2: %f %d %d\n", ratio, m, maxlen)

ratio, m, maxlen = BytesRatio([]byte(`00000111`), []byte(`00000`), 1)
fmt.Printf("Deletions 1: %f %d %d\n", ratio, m, maxlen)

ratio, m, maxlen = BytesRatio([]byte(`0001000`), []byte(`000000`), 1)
fmt.Printf("Deletions 2: %f %d %d\n", ratio, m, maxlen)

ratio, m, maxlen = BytesRatio([]byte(`11000`), []byte(`22000`), 1)
fmt.Printf("Both changes 1: %f %d %d\n", ratio, m, maxlen)

ratio, m, maxlen = BytesRatio([]byte(`0001000`), []byte(`0002000`), 1)
fmt.Printf("Both changes 2: %f %d %d\n", ratio, m, maxlen)
Output:
Additions 1: 0.625000 5 8
Additions 2: 0.857143 6 7
Deletions 1: 0.625000 5 8
Deletions 2: 0.857143 6 7
Both changes 1: 0.600000 3 5
Both changes 2: 0.857143 6 7

func WriteLines

func WriteLines(w io.Writer, lines []Line)

WriteLines writes each line in lines into w using the Line.String return value.

Types

type Data

type Data struct {
	// OldName and NewName stores the file names being compared from
	// calling the [Files].
	OldName string
	NewName string

	Adds       []Line
	Dels       []Line
	LineChunks []LineChunk

	// Unified contains the result of [Unified] diff, without the label
	// ("@@ ... @@") and context lines.
	// To print the result of unified diff with context lines use
	// [Data.WriteUnified].
	Unified []Line

	IsMatched bool
	// contains filtered or unexported fields
}

Data stores additions, deletions, and changes between two texts.

func Files

func Files(oldfile, newfile string, level int) (diff *Data, err error)

Files returns the differences between oldfile and newfile.

Example
package main

import (
	"fmt"
	"log"

	"kilabit.info/pakakeh.go/lib/text/diff"
)

func main() {
	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 Lines

func Lines(oldlines, newlines []Line, level int) (diff *Data)

Lines returns the difference between old and new lines.

Example
package main

import (
	"fmt"

	"kilabit.info/pakakeh.go/lib/text/diff"
)

func main() {
	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 Text

func Text(old, new []byte, level int) (diff *Data)

Text returns the difference between old and new text.

Example
package main

import (
	"fmt"

	"kilabit.info/pakakeh.go/lib/text/diff"
)

func main() {
	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!"

func Unified

func Unified(old, new []byte) (diff *Data)

Unified returns the unified changes between old and new bytes.

References,

Example
package main

import (
	"fmt"
	"strings"

	"kilabit.info/pakakeh.go/lib/text/diff"
)

func main() {
	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!

func (*Data) PushAdd

func (diff *Data) PushAdd(new Line)

PushAdd adds new line to slice of Adds.

func (*Data) PushChange

func (diff *Data) PushChange(old, new Line)

PushChange adds the old and new line to Dels, Adds, and Data.LineChunks respectively.

func (*Data) PushDel

func (diff *Data) PushDel(old Line)

PushDel adds deletion line to slice of Dels.

func (Data) String

func (diff Data) String() (s string)

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 (Data) WriteLineChunks

func (diff Data) WriteLineChunks(w io.Writer) (err error)

WriteLineChunks writes the formatted line changes and chunks differences into w. The format of output is,

--- <OldName>
+++ <NewName>
--++
<LineChunk>
...

The OldName and NewName is the file name being compared. See the LineChunk.String for the format of each LineChunk.

func (Data) WriteUnified

func (diff Data) WriteUnified(w io.Writer, ncontext int) (err error)

WriteUnified writes the result of unified diff into w with ncontext number of unmodified lines before and after it.

The ncontext must be 0 or positive, otherwise it will be set to 0 (no context added).

type Line

type Line struct {
	Val      []byte // The line value.
	Kind     int    // The kind of changes.
	Num      int    // The line number, started from 1.
	NumOther int    // The line number of other file.
}

Line represents single line from file or stream of bytes.

func ParseLines

func ParseLines(raw []byte) (lines []Line)

ParseLines returns lines from reading raw bytes. The line number returned in lines started from 1.

func ReadLines

func ReadLines(file string) (lines []Line, err error)

ReadLines returns lines in the file. The line number returned in lines started from 1.

func (Line) String

func (line Line) String() 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.

type LineChunk

type LineChunk struct {
	Adds text.Chunks
	Dels text.Chunks

	Old Line
	New Line
}

LineChunk represents a changes inside a line. Each chunk in Adds and Dels contains the position and value of string that is added in New or deleted in Old line.

func (LineChunk) String

func (lchunk LineChunk) String() string

String returns formatted content of LineChunk. The format is as follow,

<OldLine>
<NewLine>
^<StartAt> <Kind> <QuotedVal>

The OldLine and NewLine print the line being compared, using the format from Line.String. The StartAt print the beginning position where changes found between old and new line. The Kind print '-' for deletion or '+' for deletion. The QuoteVal print the string difference in double quote.

Source Files

  • diff.go
  • func.go
  • line.go
  • line_chunk.go
  • unified.go

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL