changelog generator & diff tool stormlightlabs.github.io/git-storm/
changelog changeset markdown golang git
at main 4.8 kB view raw
1package diff 2 3import "io" 4 5// DiffViewKind enumerates the supported diff views. 6type DiffViewKind int 7 8const ( 9 ViewUnified DiffViewKind = iota 10 ViewSplit 11 ViewHunk 12 ViewInline 13 ViewRich 14 ViewSource 15) 16 17// String returns a human-readable name for the view kind. 18func (k DiffViewKind) String() string { 19 switch k { 20 case ViewUnified: 21 return "Unified" 22 case ViewSplit: 23 return "Split" 24 case ViewHunk: 25 return "Hunk" 26 case ViewInline: 27 return "Inline" 28 case ViewRich: 29 return "Rich" 30 case ViewSource: 31 return "Source" 32 default: 33 return "Unknown" 34 } 35} 36 37// DiffResult holds the output of a diff tool. 38type DiffResult struct { 39 // Content is the rendered diff output (text, ANSI, HTML, etc.) 40 Content string 41 42 // View is the kind of view used to render the diff. 43 View DiffViewKind 44 45 // Metadata like file paths, commit hashes, timestamps (optional). 46 // You may extend as needed. 47 OldPath string 48 NewPath string 49 FromHash string 50 ToHash string 51} 52 53// DiffTool is the interface for generating diffs between two versions. 54type DiffTool interface { 55 // Diff takes two blobs (old and new versions) and returns a DiffResult. 56 // The reader parameters may be full file contents or other abstraction. 57 // The viewKind parameter selects which view implementation (Unified/Split/...). 58 Diff(oldContent io.Reader, newContent io.Reader, viewKind DiffViewKind) (DiffResult, error) 59} 60 61// UnifiedDiff implements unified view (single linear view with additions & deletions). 62// 63// TODO: Support pluggable diff algorithms beyond Myers. 64type UnifiedDiff struct { 65 // TerminalWidth is the total available width for rendering 66 TerminalWidth int 67 // ShowLineNumbers controls whether line numbers are displayed 68 ShowLineNumbers bool 69 // Expanded controls whether to show all unchanged lines or compress them 70 Expanded bool 71 // EnableWordWrap enables word wrapping for long lines 72 EnableWordWrap bool 73} 74 75// Diff generates a unified diff view from two content readers. 76func (u *UnifiedDiff) Diff(oldContent io.Reader, newContent io.Reader, viewKind DiffViewKind) (DiffResult, error) { 77 oldBytes, err := io.ReadAll(oldContent) 78 if err != nil { 79 return DiffResult{}, err 80 } 81 newBytes, err := io.ReadAll(newContent) 82 if err != nil { 83 return DiffResult{}, err 84 } 85 86 oldLines := splitLines(string(oldBytes)) 87 newLines := splitLines(string(newBytes)) 88 89 myers := &Myers{} 90 edits, err := myers.Compute(oldLines, newLines) 91 if err != nil { 92 return DiffResult{}, err 93 } 94 95 formatter := &UnifiedFormatter{ 96 TerminalWidth: u.TerminalWidth, 97 ShowLineNumbers: u.ShowLineNumbers, 98 Expanded: u.Expanded, 99 EnableWordWrap: u.EnableWordWrap, 100 } 101 102 content := formatter.Format(edits) 103 104 return DiffResult{ 105 Content: content, 106 View: ViewUnified, 107 }, nil 108} 109 110// SplitDiff implements side-by-side view (old on left, new on right). 111// 112// TODO: Support pluggable diff algorithms beyond Myers. 113type SplitDiff struct { 114 // TerminalWidth is the total available width for rendering 115 TerminalWidth int 116 // ShowLineNumbers controls whether line numbers are displayed 117 ShowLineNumbers bool 118 // Expanded controls whether to show all unchanged lines or compress them 119 Expanded bool 120 // EnableWordWrap enables word wrapping for long lines 121 EnableWordWrap bool 122} 123 124// Diff generates a side-by-side diff view from two content readers. 125func (s *SplitDiff) Diff(oldContent io.Reader, newContent io.Reader, viewKind DiffViewKind) (DiffResult, error) { 126 oldBytes, err := io.ReadAll(oldContent) 127 if err != nil { 128 return DiffResult{}, err 129 } 130 newBytes, err := io.ReadAll(newContent) 131 if err != nil { 132 return DiffResult{}, err 133 } 134 135 oldLines := splitLines(string(oldBytes)) 136 newLines := splitLines(string(newBytes)) 137 138 myers := &Myers{} 139 edits, err := myers.Compute(oldLines, newLines) 140 if err != nil { 141 return DiffResult{}, err 142 } 143 144 formatter := &SideBySideFormatter{ 145 TerminalWidth: s.TerminalWidth, 146 ShowLineNumbers: s.ShowLineNumbers, 147 Expanded: s.Expanded, 148 EnableWordWrap: s.EnableWordWrap, 149 } 150 151 content := formatter.Format(edits) 152 153 return DiffResult{ 154 Content: content, 155 View: ViewSplit, 156 }, nil 157} 158 159// splitLines splits a string into lines, preserving empty lines. 160func splitLines(s string) []string { 161 if s == "" { 162 return []string{} 163 } 164 lines := make([]string, 0) 165 start := 0 166 for i := 0; i < len(s); i++ { 167 if s[i] == '\n' { 168 lines = append(lines, s[start:i]) 169 start = i + 1 170 } 171 } 172 173 if start < len(s) { 174 lines = append(lines, s[start:]) 175 } 176 return lines 177} 178 179// HunkDiff focuses on changed blocks, minimal context. 180type HunkDiff struct{} 181 182// InlineDiff renders changes inline in full file flow. 183type InlineDiff struct{} 184 185// RichDiff renders changes for formatted preview (for example, markdown or html previews). 186type RichDiff struct{} 187 188// SourceDiff simply returns raw diff data or patch format without special formatting. 189type SourceDiff struct{}