Documentation
¶
Overview ¶
Package mdiff supports creating textual diffs.
To create a diff between two slices of strings, call:
diff := mdiff.New(lhs, rhs)
The diff.Chunks field contains the disjoint chunks of the input where edits have been applied. The complete edit sequence is in diff.Edits.
By default, a diff does not include any context lines. To add up to n lines of context, call:
diff.AddContext(n)
This adds additional edits to the head and tail of each chunk containing the context lines, if any, that were found in the input. Adding context may cause chunks to overlap. To remove the overlap, call:
diff.Unify()
This modifies the diff in-place to merge adjacent and overlapping chunks, so that their contexts are not repeated.
These operations can be chained to produce a (unified) diff with context:
diff := mdiff.New(lhs, rhs).AddContext(3).Unify()
Output ¶
To write a diff in textual format, use the Diff.Format or Patch.Format method with a formatting function. For example, use Normal to write an old-style Unix diff output to stdout:
diff.Format(os.Stdout, mdiff.Normal, nil)
The Context and Unified functions allow rendering a diff in those formats instead. Use FileInfo to tell the formatter the names and timestamps to use for their file headers:
diff.Format(os.Stdout, mdiff.Unified, &mdiff.FileInfo{ Left: "dir/original.go", Right: "dir/patched.go", })
If the options are omitted, the formatters defined by this package provide default placeholders. You can also implement your own function using the same signature. It is up to the implementation how to handle defaults.
Reading Patches ¶
To read patches formatted as text, use the Read, ReadUnified, and ReadGitPatch functions. These functions consume the contents of an io.Reader and return (on successe) one or more Patch values containing the diff chunks encoded in the patch. For example, to read a Unix diff:
p, err := mdiff.Read(input)
Unlike a Diff, a patch does not contain the complete text of the input, nor any edit operations apart from those described in the patch itself.
The ReadGitPatch function reads a concatenated sequence of patches in the format generated by Git commands like "git diff -p". Git metadata such as commit tags, headers, and so on, are discarded. Note also that this function does not support the "combined" Git patch format.
Index ¶
Examples ¶
Constants ¶
const TimeFormat = "2006-01-02 15:04:05.999999 -0700"
TimeFormat is the default format string used to render timestamps in context and unified diff outputs. It is based on the RFC 2822 time format.
Variables ¶
This section is empty.
Functions ¶
func Context ¶ added in v0.20.0
Context is a FormatFunc that renders ch in the context diff format introduced by BSD diff. If fi == nil, the file header is omitted.
Example ¶
package main import ( "os" "time" "github.com/creachadair/mds/mdiff" ) func main() { diff := mdiff.New( []string{"I", "saw", "three", "mice", "running", "away"}, []string{"three", "blind", "mice", "ran", "home"}, ).AddContext(3).Unify() ts := time.Date(2024, 3, 18, 22, 30, 35, 0, time.UTC) diff.Format(os.Stdout, mdiff.Context, &mdiff.FileInfo{ Left: "old", LeftTime: ts, Right: "new", RightTime: ts.Add(3 * time.Second), TimeFormat: time.ANSIC, }) }
Output: *** old Mon Mar 18 22:30:35 2024 --- new Mon Mar 18 22:30:38 2024 *************** *** 1,6 **** - I - saw three mice ! running ! away --- 1,5 ---- three + blind mice ! ran ! home
func Normal ¶ added in v0.20.0
Normal is a FormatFunc that renders ch in the "normal" Unix diff format. This format does not include a file header, so the FileInfo is ignored.
Example ¶
package main import ( "os" "github.com/creachadair/mds/mdiff" ) func main() { diff := mdiff.New( []string{"I", "saw", "three", "mice", "running", "away"}, []string{"three", "blind", "mice", "ran", "home"}, ) diff.Format(os.Stdout, mdiff.Normal, nil) }
Output: 1,2d0 < I < saw 3a2 > blind 5,6c4,5 < running < away --- > ran > home
func Unified ¶ added in v0.20.0
Unified is a FormatFunc that renders ch in the unified diff format introduced by GNU diff. If fi == nil, the file header is omitted.
Example ¶
package main import ( "os" "github.com/creachadair/mds/mdiff" ) func main() { diff := mdiff.New( []string{"I", "saw", "three", "mice", "running", "away"}, []string{"three", "blind", "mice", "ran", "home"}, ).AddContext(3).Unify() diff.Format(os.Stdout, mdiff.Unified, nil) // nil means "no header" }
Output: @@ -1,6 +1,5 @@ -I -saw three +blind mice -running -away +ran +home
Types ¶
type Chunk ¶
type Chunk struct { // The edits applied within this chunk, in order. Edits []Edit // The starting and ending lines of this chunk in the left input. // Lines are 1-based, and the range includes start but excludes end. LStart, LEnd int // The starting and ending lines of this chunk in the right input. // Lines are 1-based and the range includes start but excludes end. RStart, REnd int }
A Chunk is a contiguous region within a diff covered by one or more consecutive edit operations.
func UnifyChunks ¶ added in v0.15.0
UnifyChunks modifies the chunks in cs to merge adjoining or overlapping chunks, and returns a slice of the modified chunks.
type Diff ¶
type Diff struct {
// The left and right inputs. These fields alias the slices passed to New.
Left, Right []string
// The diff chunks, in order. If the inputs are identical, Chunks is empty.
Chunks []*Chunk
// The sequence of edits, in order, applied to transform Left into Right.
Edits []Edit
}
A Diff represents the difference between two slices of strings.
func New ¶
New constructs a Diff from the specified string slices. A diff constructed by New has 0 lines of context.
func (*Diff) AddContext ¶
AddContext updates d so that each chunk has up to n lines of context before and after, to the extent possible. Context lines are those that are equal on both sides of the diff. AddContext returns d.
Adding context may result in overlapping chunks. Call Unify to merge overlapping chunks.
func (*Diff) Format ¶ added in v0.20.0
Format renders a diff in textual format using the specified format function. If fi == nil, no file header is generated.
func (*Diff) Unify ¶
Unify updates d in-place to merge chunks that adjoin or overlap. For a Diff returned by New, this is a no-op; however AddContext may cause chunks to abut or to overlap. Unify returns d.
Unify updates the edits of any merged chunks, but does not modify the original edit sequence in d.Edits.
type Edit ¶
Edit is an edit operation on strings. It is exported here so the caller does not need to import the slice package directly.
type FileInfo ¶ added in v0.14.0
type FileInfo struct { // Left is the filename to use for the left-hand input. Left string // Right is the filename to use for the right-hand argument. Right string // LeftTime is the timestamp to use for the left-hand input. LeftTime time.Time // RightTime is the timestamp to use for the right-hand input. RightTime time.Time // TimeFormat specifies the time format to use for timestamps. // Any format string accepted by time.Format is permitted. // If omitted, it uses the TimeFormat constant. TimeFormat string }
FileInfo specifies file metadata to use when formatting a diff.
type FormatFunc ¶
FormatFunc is a function that renders diff chunks as text to an io.Writer.
A FormatFunc should accept a nil info pointer, and should skip or supply default values for missing fields.
type Patch ¶ added in v0.14.0
A Patch is the parsed representation of a diff read from text format.
func Read ¶ added in v0.14.0
Read reads an old-style "normal" Unix diff patch from r.
Example ¶
package main import ( "fmt" "log" "strings" "github.com/creachadair/mds/mdiff" ) func main() { const textDiff = `1,2d0 < I < saw 3a2 > blind 5,6c4,5 < running < away --- > ran > home` p, err := mdiff.Read(strings.NewReader(textDiff)) if err != nil { log.Fatalf("Read: %v", err) } printChunks(p.Chunks) } func printChunks(cs []*mdiff.Chunk) { for i, c := range cs { fmt.Printf("Chunk %d: left %d:%d, right %d:%d\n", i+1, c.LStart, c.LEnd, c.RStart, c.REnd) for j, e := range c.Edits { fmt.Printf(" edit %d.%d: %v\n", i+1, j+1, e) } } }
Output: Chunk 1: left 1:3, right 1:1 edit 1.1: -[I saw] Chunk 2: left 4:4, right 2:3 edit 2.1: +[blind] Chunk 3: left 5:7, right 4:6 edit 3.1: ![running away:ran home]
func ReadGitPatch ¶ added in v0.14.3
ReadGitPatch reads a sequence of unified diff patches in the format produced by "git diff -p" with default settings. The commit metadata and header lines are ignored.
func ReadUnified ¶ added in v0.14.0
ReadUnified reads a unified diff patch from r.
Example ¶
package main import ( "fmt" "log" "strings" "github.com/creachadair/mds/mdiff" ) func main() { const textDiff = `@@ -1,3 +1 @@ -I -saw three @@ -3,2 +1,3 @@ three +blind mice @@ -4,3 +3,3 @@ mice -running -away +ran +home` p, err := mdiff.ReadUnified(strings.NewReader(textDiff)) if err != nil { log.Fatalf("ReadUnified: %v", err) } printChunks(p.Chunks) } func printChunks(cs []*mdiff.Chunk) { for i, c := range cs { fmt.Printf("Chunk %d: left %d:%d, right %d:%d\n", i+1, c.LStart, c.LEnd, c.RStart, c.REnd) for j, e := range c.Edits { fmt.Printf(" edit %d.%d: %v\n", i+1, j+1, e) } } }
Output: Chunk 1: left 1:4, right 1:1 edit 1.1: -[I saw] edit 1.2: =[three] Chunk 2: left 3:5, right 1:4 edit 2.1: =[three] edit 2.2: +[blind] edit 2.3: =[mice] Chunk 3: left 4:7, right 3:6 edit 3.1: =[mice] edit 3.2: -[running away] edit 3.3: +[ran home]