-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
docs: add new example to chomp for parsing a git diff (#62)
- Loading branch information
1 parent
4aebf35
commit c5ff172
Showing
4 changed files
with
278 additions
and
17 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,261 @@ | ||
package main | ||
|
||
import ( | ||
"fmt" | ||
"log" | ||
"strconv" | ||
"strings" | ||
|
||
"github.com/charmbracelet/lipgloss" | ||
"github.com/purpleclay/chomp" | ||
) | ||
|
||
const ( | ||
// git diff header delimiter > @@ ... @@ | ||
hdrDelim = "@@" | ||
// prefix for lines added | ||
addPrefix = "+" | ||
// prefix for lines removed | ||
remPrefix = "-" | ||
) | ||
|
||
var ( | ||
red = lipgloss.NewStyle().Foreground(lipgloss.Color("#b34139")) | ||
green = lipgloss.NewStyle().Foreground(lipgloss.Color("#29b337")) | ||
) | ||
|
||
type FileDiff struct { | ||
Path string | ||
Chunks []DiffChunk | ||
} | ||
|
||
func (d FileDiff) String() string { | ||
var buf strings.Builder | ||
buf.WriteString(fmt.Sprintf("path: %s\n", d.Path)) | ||
for _, chunk := range d.Chunks { | ||
buf.WriteString(chunk.String()) | ||
} | ||
|
||
return buf.String() | ||
} | ||
|
||
type DiffChunk struct { | ||
Added DiffChange | ||
Removed DiffChange | ||
} | ||
|
||
func (d DiffChunk) String() string { | ||
var buf strings.Builder | ||
|
||
buf.WriteString("(") | ||
buf.WriteString(red.Render("-")) | ||
buf.WriteString(fmt.Sprintf("%d,%d ", d.Removed.LineNo, d.Removed.Count)) | ||
buf.WriteString(green.Render("+")) | ||
buf.WriteString(fmt.Sprintf("%d,%d", d.Added.LineNo, d.Added.Count)) | ||
buf.WriteString(")\n") | ||
|
||
if d.Removed.Change != "" { | ||
buf.WriteString(red.Render(d.Removed.Change)) | ||
buf.WriteString("\n") | ||
} | ||
|
||
if d.Added.Change != "" { | ||
buf.WriteString(green.Render(d.Added.Change)) | ||
buf.WriteString("\n") | ||
} | ||
|
||
return buf.String() | ||
} | ||
|
||
type DiffChange struct { | ||
LineNo int | ||
Count int | ||
Change string | ||
} | ||
|
||
func Parse(in string) (FileDiff, error) { | ||
rem, path, err := diffPath()(in) | ||
if err != nil { | ||
return FileDiff{}, err | ||
} | ||
|
||
rem, _, err = chomp.Until(hdrDelim)(rem) | ||
if err != nil { | ||
return FileDiff{}, err | ||
} | ||
|
||
chunks, err := diffChunks(rem) | ||
if err != nil { | ||
return FileDiff{}, err | ||
} | ||
|
||
return FileDiff{ | ||
Path: path, | ||
Chunks: chunks, | ||
}, nil | ||
} | ||
|
||
func diffPath() chomp.Combinator[string] { | ||
return func(s string) (string, string, error) { | ||
// diff --git a/scan/scanner.go b/scan/scanner.go | ||
var rem string | ||
var err error | ||
|
||
if rem, _, err = chomp.Tag("diff --git ")(s); err != nil { | ||
return rem, "", err | ||
} | ||
|
||
var path string | ||
if rem, path, err = chomp.Until(" ")(rem); err != nil { | ||
return rem, "", err | ||
} | ||
path = path[strings.Index(path, "/")+1:] | ||
|
||
rem, _, err = chomp.Eol()(rem) | ||
return rem, path, err | ||
} | ||
} | ||
|
||
func diffChunks(in string) ([]DiffChunk, error) { | ||
_, chunks, err := chomp.Map(chomp.Many(diffChunk()), | ||
func(in []string) []DiffChunk { | ||
var diffChunks []DiffChunk | ||
|
||
for i := 0; i+5 < len(in); i += 6 { | ||
// 0: removed line | ||
// 1: removed count | ||
// 2: added line | ||
// 3: added count | ||
// 4: removed lines | ||
// 5: added lines | ||
chunk := DiffChunk{ | ||
Removed: DiffChange{ | ||
LineNo: mustInt(in[i]), | ||
Count: mustInt(in[i+1]), | ||
Change: in[i+4], | ||
}, | ||
Added: DiffChange{ | ||
LineNo: mustInt(in[i+2]), | ||
Count: mustInt(in[i+3]), | ||
Change: in[i+5], | ||
}, | ||
} | ||
|
||
diffChunks = append(diffChunks, chunk) | ||
} | ||
|
||
return diffChunks | ||
}, | ||
)(in) | ||
|
||
return chunks, err | ||
} | ||
|
||
func mustInt(in string) int { | ||
out, _ := strconv.Atoi(in) | ||
return out | ||
} | ||
|
||
func diffChunk() chomp.Combinator[[]string] { | ||
return func(s string) (string, []string, error) { | ||
/* | ||
@@ -25 +3,3 @@ package scan | ||
-import "bytes" | ||
+import ( | ||
+ "bytes" | ||
+) | ||
*/ | ||
var rem string | ||
var err error | ||
|
||
var changes []string | ||
rem, changes, err = chomp.Delimited( | ||
chomp.Tag(hdrDelim+" "), | ||
chomp.SepPair(diffChunkHeaderChange(remPrefix), chomp.Tag(" "), diffChunkHeaderChange(addPrefix)), | ||
chomp.Eol(), | ||
)(s) | ||
if err != nil { | ||
return rem, nil, err | ||
} | ||
|
||
var removed string | ||
rem, removed, err = chomp.Map( | ||
chomp.ManyN(chomp.Prefixed(chomp.Eol(), chomp.Tag(remPrefix)), 0), | ||
func(in []string) string { return strings.Join(in, "\n") }, | ||
)(rem) | ||
if err != nil { | ||
return rem, nil, err | ||
} | ||
|
||
var added string | ||
rem, added, err = chomp.Map( | ||
chomp.ManyN(chomp.Prefixed(chomp.Eol(), chomp.Tag(addPrefix)), 0), | ||
func(in []string) string { return strings.Join(in, "\n") }, | ||
)(rem) | ||
if err != nil { | ||
return rem, nil, err | ||
} | ||
|
||
return rem, append(changes, removed, added), nil | ||
} | ||
} | ||
|
||
func diffChunkHeaderChange(prefix string) chomp.Combinator[[]string] { | ||
return func(s string) (string, []string, error) { | ||
rem, _, err := chomp.Tag(prefix)(s) | ||
if err != nil { | ||
return rem, nil, err | ||
} | ||
|
||
return chomp.All( | ||
chomp.While(chomp.IsDigit), | ||
chomp.Opt(chomp.Prefixed(chomp.While(chomp.IsDigit), chomp.Tag(","))), | ||
)(rem) | ||
} | ||
} | ||
|
||
func main() { | ||
// generated by: git diff -U0 -- scan/scanner.go | ||
diff := `diff --git a/scan/scanner.go b/scan/scanner.go | ||
index fdf8e52..02d20e5 100644 | ||
--- a/scan/scanner.go | ||
+++ b/scan/scanner.go | ||
@@ -25 +3,3 @@ package scan | ||
-import "bytes" | ||
+import ( | ||
+ "bytes" | ||
+) | ||
@@ -57,0 +38,25 @@ func eat(prefix byte, data []byte) []byte { | ||
+ | ||
+// DiffLines is a split function for a [bufio.Scanner] that splits a git diff output | ||
+// into multiple blocks of text, each prefixed by the diff --git marker. Each block | ||
+// of text will be stripped of any leading and trailing whitespace. If the git diff | ||
+// marker isn't detected, the entire block of text is returned, with any leading and | ||
+// trailing whitespace stripped | ||
+func DiffLines() func(data []byte, atEOF bool) (advance int, token []byte, err error) { | ||
+ prefix := []byte("\ndiff --git") | ||
+ | ||
+ return func(data []byte, atEOF bool) (advance int, token []byte, err error) { | ||
+ if atEOF && len(data) == 0 { | ||
+ return 0, nil, nil | ||
+ } | ||
+ | ||
+ if i := bytes.Index(data, prefix); i >= 0 { | ||
+ return i + 1, bytes.TrimSpace(data[:i]), nil | ||
+ } | ||
+ | ||
+ if atEOF { | ||
+ return len(data), bytes.TrimSpace(data), nil | ||
+ } | ||
+ | ||
+ return 0, nil, nil | ||
+ } | ||
+}` | ||
|
||
fileDiff, err := Parse(diff) | ||
if err != nil { | ||
log.Fatal(err) | ||
} | ||
|
||
fmt.Println(fileDiff) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,25 +1,24 @@ | ||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= | ||
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= | ||
github.com/charmbracelet/lipgloss v0.9.1 h1:PNyd3jvaJbg4jRHKWXnCj1akQm4rh8dbEzN1p/u1KWg= | ||
github.com/charmbracelet/lipgloss v0.9.1/go.mod h1:1mPmG4cxScwUQALAAnacHaigiiHB9Pmr+v1VEawJl6I= | ||
github.com/charmbracelet/lipgloss v0.12.1 h1:/gmzszl+pedQpjCOH+wFkZr/N90Snz40J/NR7A0zQcs= | ||
github.com/charmbracelet/lipgloss v0.12.1/go.mod h1:V2CiwIuhx9S1S1ZlADfOj9HmxeMAORuz5izHb0zGbB8= | ||
github.com/charmbracelet/x/ansi v0.1.4 h1:IEU3D6+dWwPSgZ6HBH+v6oUuZ/nVawMiWj5831KfiLM= | ||
github.com/charmbracelet/x/ansi v0.1.4/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw= | ||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= | ||
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= | ||
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= | ||
github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98= | ||
github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= | ||
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= | ||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= | ||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= | ||
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= | ||
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= | ||
github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= | ||
github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= | ||
github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo= | ||
github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8= | ||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= | ||
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= | ||
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= | ||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= | ||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= | ||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= | ||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= | ||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= | ||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||
golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= | ||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= | ||
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= | ||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= |