commit: PR desc parser and writer

This commit is contained in:
Adam Majer 2024-12-07 14:35:34 +01:00
parent 77751ecc46
commit db766bacc3
2 changed files with 157 additions and 176 deletions

View File

@ -6,6 +6,7 @@ import (
"fmt" "fmt"
"io" "io"
"regexp" "regexp"
"slices"
"strconv" "strconv"
"strings" "strings"
) )
@ -54,7 +55,7 @@ func parsePrLine(line string) (BasicPR, error) {
return ret, nil return ret, nil
} }
func ExtractAssociatedDescriptionAndPRs(data *bufio.Scanner) (string, []BasicPR) { func ExtractPRsFromDescription(data *bufio.Scanner) (string, []BasicPR) {
prs := make([]BasicPR, 0, 1) prs := make([]BasicPR, 0, 1)
var desc strings.Builder var desc strings.Builder
@ -74,15 +75,40 @@ func ExtractAssociatedDescriptionAndPRs(data *bufio.Scanner) (string, []BasicPR)
} }
func prToLine(writer io.Writer, pr BasicPR) { func prToLine(writer io.Writer, pr BasicPR) {
fmt.Fprintf(writer, PrPattern, pr.org, pr.repo, pr.num)
writer.Write([]byte("\n")) writer.Write([]byte("\n"))
fmt.Fprintf(writer, PrPattern, pr.org, pr.repo, pr.num)
}
// returns:
// <0 for a<b
// >0 for a>b
// =0 when equal
func compareBasicPRs(a BasicPR, b BasicPR) int {
if c := strings.Compare(a.org, b.org); c != 0 {
return c
}
if c := strings.Compare(a.repo, b.repo); c != 0 {
return c
}
if a.num > b.num {
return 1
}
if a.num < b.num {
return -1
}
return 0
} }
func AppendPRsToDescription(desc string, prs []BasicPR) string { func AppendPRsToDescription(desc string, prs []BasicPR) string {
var out strings.Builder var out strings.Builder
out.WriteString(strings.TrimSpace(desc)) out.WriteString(strings.TrimSpace(desc))
out.WriteString("\n\n") out.WriteString("\n")
slices.SortFunc(prs, compareBasicPRs)
prs = slices.Compact(prs)
for _, pr := range prs { for _, pr := range prs {
prToLine(&out, pr) prToLine(&out, pr)

View File

@ -12,181 +12,136 @@ func newStringScanner(s string) *bufio.Scanner {
} }
func TestAssociatedPRScanner(t *testing.T) { func TestAssociatedPRScanner(t *testing.T) {
t.Run("No PRs", func(t *testing.T) { testTable := []struct {
if _, out := ExtractAssociatedDescriptionAndPRs(newStringScanner("")); len(out) != 0 { name string
t.Error("Unexpected output", out) input string
} prs []BasicPR
}) desc string
}{
t.Run("Single PR", func(t *testing.T) { {
const singlePRText = `Some header of the issue "No PRs",
"",
Followed by some description []BasicPR{},
PR: test/foo#4 "",
` },
_, out := ExtractAssociatedDescriptionAndPRs(newStringScanner(singlePRText)) {
if len(out) != 1 { "Single PRs",
t.Error("Unexpected output", out) "Some header of the issue\n\nFollowed by some description\n\nPR: test/foo#4\n",
return []BasicPR{{org: "test", repo: "foo", num: 4}},
} "Some header of the issue\n\nFollowed by some description",
},
expected := BasicPR{ {
"Multiple PRs",
"Some header of the issue\n\nFollowed by some description\nPR: test/foo#4\n\nPR: test/goo#5\n",
[]BasicPR{
{org: "test", repo: "foo", num: 4},
{org: "test", repo: "goo", num: 5},
},
"Some header of the issue\n\nFollowed by some description",
},
{
"Multiple PRs with whitespace",
"Some header of the issue\n\n\tPR: test/goo#5\n\n Followed by some description\n \t PR: test/foo#4\n",
[]BasicPR{
{org: "test", repo: "foo", num: 4},
{org: "test", repo: "goo", num: 5},
},
"Some header of the issue\n\n\n Followed by some description",
},
{
"Multiple PRs with missing names and other special cases to ignore",
"Some header of the issue\n\n\n\t PR: foobar#5 \n\t PR: rd/goo5 \n\t PR: test/#5 \n" +
"\t PR: /goo#5 \n\t PR: test/goo# \n\t PR: test / goo # 10 \n\tPR: test/gool# 10 \n" +
"\t PR: test/goo#5 \n\t\n Followed by some description\n\t PR: test/foo#4 \n\t\n\n",
[]BasicPR{
{
org: "test", org: "test",
repo: "foo", repo: "foo",
num: 4, num: 4,
} },
if out[0] != expected { {
t.Error("Unexpected", out, "Expected", expected)
}
})
t.Run("Multiple PRs", func(t *testing.T) {
const multiplePRText = `Some header of the issue
Followed by some description
PR: test/foo#4
PR: test/goo#5
`
_, out := ExtractAssociatedDescriptionAndPRs(newStringScanner(multiplePRText))
if len(out) != 2 {
t.Error("Unexpected output", out)
return
}
expected1 := BasicPR{
org: "test",
repo: "foo",
num: 4,
}
expected2 := BasicPR{
org: "test", org: "test",
repo: "goo", repo: "goo",
num: 5, num: 5,
},
},
"Some header of the issue\n\n\n\t PR: foobar#5 \n\t PR: rd/goo5 \n\t PR: test/#5 \n" +
"\t PR: /goo#5 \n\t PR: test/goo# \n\t PR: test / goo # 10 \n\tPR: test/gool# 10 \n" +
"\t\n Followed by some description",
},
} }
if !slices.Contains(out, expected1) { for _, test := range testTable {
t.Error("Unexpected", out, "Expected", expected1) t.Run(test.name, func(t *testing.T) {
} desc, prs := ExtractPRsFromDescription(newStringScanner(test.input))
if !slices.Contains(out, expected2) { if len(prs) != len(test.prs) {
t.Error("Unexpected", out, "Expected", expected2) t.Error("Unexpected length:", len(prs), "expected:", len(test.prs))
}
})
t.Run("Multiple PRs with whitespace", func(t *testing.T) {
const whitespacePRText = `Some header of the issue
PR: test/goo#5
Followed by some description
PR: test/foo#4
`
desc, out := ExtractAssociatedDescriptionAndPRs(newStringScanner(whitespacePRText))
if len(out) != 2 {
t.Error("Unexpected output", out)
return return
} }
const expectedDesc = `Some header of the issue for _, p := range test.prs {
if !slices.Contains(prs, p) {
t.Error("missing expected PR", p)
Followed by some description`
expected1 := BasicPR{
org: "test",
repo: "foo",
num: 4,
} }
expected2 := BasicPR{
org: "test",
repo: "goo",
num: 5,
} }
if !slices.Contains(out, expected1) { if desc != test.desc {
t.Error("Unexpected", out, "Expected", expected1) t.Error("Desc output", len(desc), "!=", len(test.desc), ":", desc)
}
if !slices.Contains(out, expected2) {
t.Error("Unexpected", out, "Expected", expected2)
}
if desc != expectedDesc {
t.Error("unexpected desc", desc)
} }
}) })
}
}
t.Run("Multiple PRs with missing names and other special cases to ignore", func(t *testing.T) { func TestAppendingPRsToDescription(t *testing.T) {
const whitespacePRText = `Some header of the issue testTable := []struct {
name string
PR: foobar#5 desc string
PR: rd/goo5 PRs []BasicPR
PR: test/#5 output string
PR: /goo#5 }{
PR: test/goo# {
PR: test / goo # 10 "Append single PR to end of description",
PR: test/gool# 10 "something",
PR: test/goo#5 []BasicPR{
{org: "a", repo: "b", num: 100},
Followed by some description },
PR: test/foo#4 "something\n\nPR: a/b#100",
},
{
` "Append multiple PR to end of description",
"something",
desc, out := ExtractAssociatedDescriptionAndPRs(newStringScanner(whitespacePRText)) []BasicPR{
if len(out) != 2 { {org: "a1", repo: "b", num: 100},
t.Error("Unexpected output", out) {org: "a1", repo: "c", num: 100},
return {org: "a1", repo: "c", num: 101},
{org: "b", repo: "b", num: 100},
{org: "c", repo: "b", num: 100},
},
"something\n\nPR: a1/b#100\nPR: a1/c#100\nPR: a1/c#101\nPR: b/b#100\nPR: c/b#100",
},
{
"Append multiple sorted PR to end of description and remove dups",
"something",
[]BasicPR{
{org: "a1", repo: "c", num: 101},
{org: "a1", repo: "c", num: 100},
{org: "c", repo: "b", num: 100},
{org: "b", repo: "b", num: 100},
{org: "a1", repo: "c", num: 101},
{org: "a1", repo: "c", num: 101},
{org: "a1", repo: "b", num: 100},
},
"something\n\nPR: a1/b#100\nPR: a1/c#100\nPR: a1/c#101\nPR: b/b#100\nPR: c/b#100",
},
} }
const expectedDesc = `Some header of the issue for _, test := range testTable {
t.Run(test.name, func(t *testing.T) {
PR: foobar#5 d := AppendPRsToDescription(test.desc, test.PRs)
PR: rd/goo5 if d != test.output {
PR: test/#5 t.Error(len(d), "vs", len(test.output))
PR: /goo#5
PR: test/goo#
PR: test / goo # 10
PR: test/gool# 10
Followed by some description`
if desc != expectedDesc {
t.Error(len(desc), "vs", len(expectedDesc))
t.Error("description doesn't match expected. ", desc)
}
expected1 := BasicPR{
org: "test",
repo: "foo",
num: 4,
}
expected2 := BasicPR{
org: "test",
repo: "goo",
num: 5,
}
if !slices.Contains(out, expected1) {
t.Error("Unexpected", out, "Expected", expected1)
}
if !slices.Contains(out, expected2) {
t.Error("Unexpected", out, "Expected", expected2)
}
})
t.Run("Append PRs to end of description", func(t *testing.T) {
d := AppendPRsToDescription("something", []BasicPR{
BasicPR{org: "a", repo: "b", num: 100},
})
const expectedDesc = `something
PR: a/b#100
`
if d != expectedDesc {
t.Error(len(d), "vs", len(expectedDesc))
t.Error("unpected output", d) t.Error("unpected output", d)
} }
}) })
}
} }