Documentation
¶
Overview ¶
Package diffutils provides canonical types and algorithms for computing and representing text diffs in Go.
The core diff engine is based on pmezard/go-difflib (BSD 3-Clause licensed; see LICENSE), which implements a port of Python's difflib SequenceMatcher. This package absorbs that code and adds:
- Canonical interchange types (DiffContent, DiffChange, LineRange)
- A high-level DiffLines function for comparing string slices
- Interfaces (DiffSource, InlineHighlighter) for extensibility
Adapter sub-packages bridge major Go diff libraries:
- github.com/mikeschinkel/go-diffutils/sgdiff — sourcegraph/go-diff adapter
- github.com/mikeschinkel/go-diffutils/dmp — sergi/go-diff inline highlighter
Stability ¶
This package is provisional as of v0.1.0. The public API may change in minor releases until promoted to stable.
Index ¶
- Variables
- func AppendErr(errs []error, err error) []error
- func CombineErrs(errs []error) error
- func ErrValue[T any](err error, key string) (T, bool)
- func Errors(err error) []error
- func FindErr[T error](err error) (out T, ok bool)
- func GetContextDiffString(diff ContextDiff) (string, error)
- func GetUnifiedDiffString(diff UnifiedDiff) (string, error)
- func MsgErr(msg any) error
- func NewErr(parts ...any) error
- func SplitLines(s string) []string
- func WithErr(parts ...any) error
- func WriteContextDiff(writer io.Writer, diff ContextDiff) error
- func WriteUnifiedDiff(writer io.Writer, diff UnifiedDiff) (err error)
- type ChangeType
- type ContextDiff
- type DiffChange
- type DiffContent
- type DiffSource
- type ErrKV
- func AnyKV(key string, value any) ErrKV
- func AppendKV(kvs []ErrKV, parts ...any) []ErrKV
- func BoolKV(key string, value bool) ErrKV
- func ErrMeta(err error) []ErrKV
- func ErrorKV(key string, value error) ErrKV
- func Float64KV(key string, value float64) ErrKV
- func Int64KV(key string, value int64) ErrKV
- func IntKV(key string, value int) ErrKV
- func StringKV(key, value string) ErrKV
- type InlineHighlighter
- type IsDotErrEntry
- type LineRange
- type Match
- type OpCode
- type SequenceMatcher
- func (m *SequenceMatcher) GetGroupedOpCodes(n int) [][]OpCode
- func (m *SequenceMatcher) GetMatchingBlocks() []Match
- func (m *SequenceMatcher) GetOpCodes() []OpCode
- func (m *SequenceMatcher) QuickRatio() float64
- func (m *SequenceMatcher) Ratio() float64
- func (m *SequenceMatcher) RealQuickRatio() float64
- func (m *SequenceMatcher) SetSeq1(a []string)
- func (m *SequenceMatcher) SetSeq2(b []string)
- func (m *SequenceMatcher) SetSeqs(a, b []string)
- type UnifiedDiff
Constants ¶
This section is empty.
Variables ¶
var ( ErrMissingSentinel = errors.New("missing required sentinel error") ErrTrailingKey = errors.New("trailing key without value") ErrMisplacedError = errors.New("error in wrong position") ErrInvalidArgumentType = errors.New("invalid argument type") ErrOddKeyValueCount = errors.New("odd number of key-value arguments") ErrCrossPackageError = errors.New("error from different doterr package") )
Sentinel errors for validation failures
var ( // ErrDiffUtils is the layer sentinel for all diffutils errors. ErrDiffUtils = errors.New("diffutils") // ErrEmptyInput indicates that both old and new inputs are empty. ErrEmptyInput = errors.New("empty input") // ErrNilContent indicates a nil DiffContent was passed. ErrNilContent = errors.New("nil content") )
Functions ¶
func CombineErrs ¶
CombineErrs bundles a slice of errors into a single composite error that unwraps to its members. Order is preserved and nils are skipped. Returns nil for an empty/fully-nil slice, or the sole error when there is exactly one.
func ErrValue ¶
ErrValue extracts a single metadata value by key with type safety. Returns the value and true if found and the value is of type T. Returns the zero value of T and false if not found or type mismatch.
Example:
status, ok := ErrValue[int](err, "http_status")
if ok {
fmt.Printf("Status: %d\n", status)
}
name, ok := ErrValue[string](err, "parameter_name")
func Errors ¶
Errors returns the errors stored on a doterr entry. If err is a doterr entry, returns its errors. If err is a joined error (has Unwrap() []error), scans immediate children left-to-right and returns errors from the first doterr entry found. Otherwise returns nil. The returned slice preserves insertion order and is a copy.
Note: These errors may be sentinel errors (e.g., ErrRepo), custom error types (e.g., *rfc9457.Error), or any other error type stored in the entry.
func FindErr ¶
FindErr walks an error tree (including errors.Join trees) and returns the first match for target (via errors.As).
func GetContextDiffString ¶
func GetContextDiffString(diff ContextDiff) (string, error)
GetContextDiffString returns a context diff as a string.
func GetUnifiedDiffString ¶
func GetUnifiedDiffString(diff UnifiedDiff) (string, error)
GetUnifiedDiffString returns a unified diff as a string.
func MsgErr ¶
MsgErr creates an ad-hoc error message without requiring a sentinel error. This is a convenience for rapid development - use sentinels for production code.
Accepts two forms:
- MsgErr("message") - creates error with the given message
- MsgErr(err) - wraps existing error, preserving error chain for errors.Is()
Use this when you don't want to define a sentinel immediately. Future tooling can detect MsgErr usage and suggest/generate appropriate sentinel errors.
For errors with metadata, use NewErr with a sentinel, or wrap with WithErr:
err := MsgErr("config invalid")
err = WithErr(err, "path", configPath)
func NewErr ¶
NewErr builds a standalone structured entry (no primary cause inside). Accepted parts:
- error — sentinel/tag (required: at least one, must be first)
- KV{Key,Value} — explicit key/value
- "key", value — implicit pair (value can be any type, including error)
- error — optional trailing cause (joined last via errors.Join)
Pattern: one or more sentinels (error), then zero or more key-value pairs, then optional trailing cause (error). After the first string key, all remaining args must form valid pairs, except for an optional final error. Returns nil if no meaningful parts are provided after validation. Returns a validation error joined with the partial entry if validation fails.
func SplitLines ¶
SplitLines splits a string on "\n" while preserving them. The output can be used as input for UnifiedDiff and ContextDiff.
func WithErr ¶
WithErr is a flexible enrichment helper. Typical uses:
// Enrich an existing composite error (err may be an errors.Join tree):
err = WithErr(err, "Foo", 10)
// Build an entry and join a trailing cause in one shot:
err = WithErr("endpoint", ep, ErrTemplate, cause) // 'cause' is last
Behavior:
If the FIRST arg is an error, it is treated as the base error to enrich: • If it is a doterr entry, merge KVs/sentinels into that entry. • If it is a multi-unwrap (errors.Join tree), find the RIGHTMOST doterr entry, merge into it, and rebuild preserving order. • If no doterr entry is found, a new entry will be joined in (see step 3).
After consuming the base (if present), if the LAST remaining arg is an error, it is treated as the CAUSE and joined LAST.
The remaining middle args (if any) are collected into an entry. If we enriched an existing doterr entry in step 1, that merged entry is used; otherwise, a fresh entry is created. If there is a trailing CAUSE from step 2, the result is errors.Join(entry, cause). If there is no cause, the entry is returned.
Note: For inter-function composition, prefer New() with trailing cause:
return NewErr(ErrRepo, "key", val, cause) // cause last
func WriteContextDiff ¶
func WriteContextDiff(writer io.Writer, diff ContextDiff) error
WriteContextDiff writes a context diff to the given writer.
func WriteUnifiedDiff ¶
func WriteUnifiedDiff(writer io.Writer, diff UnifiedDiff) (err error)
WriteUnifiedDiff writes a unified diff to the given writer.
Types ¶
type ChangeType ¶
type ChangeType int
ChangeType indicates whether lines were added or deleted in a diff.
const ( // LinesAdded means lines exist in new but not old. LinesAdded ChangeType = iota // LinesDeleted means lines exist in old but not new. LinesDeleted )
func (ChangeType) String ¶
func (ct ChangeType) String() string
String returns a human-readable representation of the change type.
type ContextDiff ¶
type ContextDiff UnifiedDiff
ContextDiff holds parameters for generating a context diff.
type DiffChange ¶
type DiffChange struct {
Type ChangeType
OldRange LineRange
NewRange LineRange
}
DiffChange represents a contiguous block of added or deleted lines.
type DiffContent ¶
type DiffContent struct {
OldLines []string // Full "before" content, one entry per line
NewLines []string // Full "after" content, one entry per line
Changes []DiffChange // Change blocks (additions and deletions)
Label string // Display label (filename, path, etc.)
}
DiffContent holds diff data for two versions of text. It is the canonical interchange type for all diff operations in this package.
type DiffSource ¶
type DiffSource interface {
DiffContent() (*DiffContent, error)
}
DiffSource produces diff content from some origin. Implementations might read from Git, files, databases, etc.
type ErrKV ¶
ErrKV represents a key/value metadata pair. Keys are preserved in insertion order, and values may be of any type.
func AnyKV ¶
AnyKV creates a KV with any value type. Use this for custom types or when the specific type constructor doesn't exist.
func AppendKV ¶
AppendKV appends key-value pairs to a slice of KV values. It accepts variadic arguments in three forms:
- Individual KV values: AppendKV(kvs, StringKV("foo", "bar"))
- String key-value pairs: AppendKV(kvs, "foo", "bar")
- Lazy ErrKV functions: AppendKV(kvs, func()ErrKV{ return StringKV("expensive", compute()) })
This function is useful for accumulating metadata throughout a function before creating an error:
var kvs []ErrKV
kvs = AppendKV(kvs, "user_id", userID)
if complexCondition {
kvs = AppendKV(kvs, "reason", "complex")
}
return NewErr(ErrFailed, kvs, cause)
String key-value pairs must come in even counts (odd counts panic in debug mode). Lazy functions (func()ErrKV) are wrapped and not evaluated until error creation.
func ErrMeta ¶
ErrMeta returns the key/value pairs stored on all doterr entries in the error tree. If err is a doterr entry, returns its metadata. If err is a joined error (has Unwrap() []error), scans all children recursively and collects metadata from all doterr entries found, preserving order. Otherwise returns nil. The returned slice preserves insertion order across all entries.
func ErrorKV ¶
ErrorKV creates a KV with an error value. Use this when you want to include an error as metadata (not as a cause).
type InlineHighlighter ¶
type InlineHighlighter interface {
HighlightInline(oldLine, newLine string) (oldResult, newResult string)
}
InlineHighlighter provides character-level highlighting within changed lines. Implementations typically use diff-match-patch or similar algorithms to identify which characters changed between two versions of a line.
type IsDotErrEntry ¶
type LineRange ¶
type LineRange struct {
Start int // 1-based line number where range starts
Count int // Number of lines in the range
}
LineRange is a 1-based line range.
type OpCode ¶
OpCode describes how to turn a[I1:I2] into b[J1:J2]. Tag is one of: 'r' (replace), 'd' (delete), 'i' (insert), 'e' (equal).
type SequenceMatcher ¶
SequenceMatcher compares two sequences of strings using a "gestalt pattern matching" algorithm. It finds the longest contiguous matching subsequence that contains no "junk" elements, then recursively applies the same idea to the pieces on each side.
func NewMatcher ¶
func NewMatcher(a, b []string) *SequenceMatcher
NewMatcher creates a new SequenceMatcher comparing a and b.
func NewMatcherWithJunk ¶
func NewMatcherWithJunk(a, b []string, autoJunk bool, isJunk func(string) bool) *SequenceMatcher
NewMatcherWithJunk creates a SequenceMatcher with custom junk detection.
func (*SequenceMatcher) GetGroupedOpCodes ¶
func (m *SequenceMatcher) GetGroupedOpCodes(n int) [][]OpCode
GetGroupedOpCodes isolates change clusters by eliminating ranges with no changes. Returns groups with up to n lines of context.
func (*SequenceMatcher) GetMatchingBlocks ¶
func (m *SequenceMatcher) GetMatchingBlocks() []Match
GetMatchingBlocks returns a list of triples describing matching subsequences. Each Match means a[A:A+Size] == b[B:B+Size]. The last triple is a dummy (len(a), len(b), 0).
func (*SequenceMatcher) GetOpCodes ¶
func (m *SequenceMatcher) GetOpCodes() []OpCode
GetOpCodes returns a list of OpCodes describing how to turn a into b. Tag meanings: 'r' replace, 'd' delete, 'i' insert, 'e' equal.
func (*SequenceMatcher) QuickRatio ¶
func (m *SequenceMatcher) QuickRatio() float64
QuickRatio returns an upper bound on Ratio() relatively quickly.
func (*SequenceMatcher) Ratio ¶
func (m *SequenceMatcher) Ratio() float64
Ratio returns a measure of the sequences' similarity (float in [0,1]).
func (*SequenceMatcher) RealQuickRatio ¶
func (m *SequenceMatcher) RealQuickRatio() float64
RealQuickRatio returns an upper bound on Ratio() very quickly.
func (*SequenceMatcher) SetSeq1 ¶
func (m *SequenceMatcher) SetSeq1(a []string)
SetSeq1 sets the first sequence. The second is not changed.
func (*SequenceMatcher) SetSeq2 ¶
func (m *SequenceMatcher) SetSeq2(b []string)
SetSeq2 sets the second sequence. The first is not changed.
func (*SequenceMatcher) SetSeqs ¶
func (m *SequenceMatcher) SetSeqs(a, b []string)
SetSeqs sets both sequences to be compared.
type UnifiedDiff ¶
type UnifiedDiff struct {
A []string // First sequence lines
FromFile string // First file name
FromDate string // First file time
B []string // Second sequence lines
ToFile string // Second file name
ToDate string // Second file time
Eol string // Headers end of line, defaults to LF
Context int // Number of context lines
}
UnifiedDiff holds parameters for generating a unified diff.