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 one of the formatting functions. For example, use Format to write an old-style Unix diff output to stdout:
mdiff.Format(os.Stdout, diff, nil)
The FormatContext and FormatUnified 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:
mdiff.FormatUnified(os.Stdout, diff, &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 concatenates 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 Format ¶
Format is a FormatFunc that renders d 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"}, ) mdiff.Format(os.Stdout, diff, nil) }
Output: 1,2d0 < I < saw 3a2 > blind 5,6c4,5 < running < away --- > ran > home
func FormatContext ¶
FormatContext is a FormatFunc that renders d 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) mdiff.FormatContext(os.Stdout, diff, &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 FormatUnified ¶
FormatUnified is a FormatFunc that renders d 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() mdiff.FormatUnified(os.Stdout, diff, 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.
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) 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 slice 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 a Diff 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]