mdiff

package
v0.14.7 Latest Latest
Warning

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

Go to latest
Published: May 7, 2024 License: BSD-3-Clause Imports: 9 Imported by: 2

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

View Source
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

func Format(w io.Writer, d *Diff, _ *FileInfo) error

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

func FormatContext(w io.Writer, d *Diff, fi *FileInfo) error

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

func FormatUnified(w io.Writer, d *Diff, fi *FileInfo) error

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

func New(lhs, rhs []string) *Diff

New constructs a Diff from the specified string slices. A diff constructed by New has 0 lines of context.

func (*Diff) AddContext

func (d *Diff) AddContext(n int) *Diff

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

func (d *Diff) Unify() *Diff

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

type Edit = slice.Edit[string]

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

type FormatFunc func(w io.Writer, d *Diff, fi *FileInfo) error

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

type Patch struct {
	FileInfo *FileInfo // nil if no file header was present
	Chunks   []*Chunk
}

A Patch is the parsed representation of a diff read from text format.

func Read added in v0.14.0

func Read(r io.Reader) (*Patch, error)

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

func ReadGitPatch(r io.Reader) ([]*Patch, error)

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

func ReadUnified(r io.Reader) (*Patch, error)

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]

Jump to

Keyboard shortcuts

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