Skip to content

Commit 00b91cd

Browse files
authored
feat(internal/librarian): add logic to parse pr bodies (#1767)
Updates: #1009
1 parent c2a26dc commit 00b91cd

File tree

2 files changed

+170
-0
lines changed

2 files changed

+170
-0
lines changed

internal/librarian/tag_and_release.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,19 @@ package librarian
1717
import (
1818
"context"
1919
"fmt"
20+
"log/slog"
21+
"regexp"
22+
"strings"
2023

2124
"github.com/googleapis/librarian/internal/cli"
2225
"github.com/googleapis/librarian/internal/config"
2326
)
2427

28+
var (
29+
detailsRegex = regexp.MustCompile(`(?s)<details><summary>(.*?)</summary>(.*?)</details>`)
30+
summaryRegex = regexp.MustCompile(`(.*?): (v?\d+\.\d+\.\d+)`)
31+
)
32+
2533
// cmdTagAndRelease is the command for the `release tag-and-release` subcommand.
2634
var cmdTagAndRelease = &cli.Command{
2735
Short: "release tag-and-release tags and creates a GitHub release for a merged pull request.",
@@ -61,3 +69,39 @@ func newTagAndReleaseRunner(cfg *config.Config) (*tagAndReleaseRunner, error) {
6169
func (r *tagAndReleaseRunner) run(ctx context.Context) error {
6270
return nil
6371
}
72+
73+
// libraryRelease holds the parsed information from a pull request body.
74+
type libraryRelease struct {
75+
// Body contains the release notes.
76+
Body string
77+
// Library is the library id of the library being released
78+
Library string
79+
// Version is the version that is being released
80+
Version string
81+
}
82+
83+
// parsePullRequestBody parses a string containing release notes and returns a slice of ParsedPullRequestBody.
84+
func parsePullRequestBody(body string) []libraryRelease {
85+
slog.Info("parsing pull request body")
86+
var parsedBodies []libraryRelease
87+
matches := detailsRegex.FindAllStringSubmatch(body, -1)
88+
for _, match := range matches {
89+
summary := match[1]
90+
content := strings.TrimSpace(match[2])
91+
92+
summaryMatches := summaryRegex.FindStringSubmatch(summary)
93+
if len(summaryMatches) == 3 {
94+
slog.Info("parsed pull request body", "library", summaryMatches[1], "version", summaryMatches[2])
95+
library := strings.TrimSpace(summaryMatches[1])
96+
version := strings.TrimSpace(summaryMatches[2])
97+
parsedBodies = append(parsedBodies, libraryRelease{
98+
Version: version,
99+
Library: library,
100+
Body: content,
101+
})
102+
}
103+
slog.Warn("failed to parse pull request body", "match", strings.Join(match, "\n"))
104+
}
105+
106+
return parsedBodies
107+
}

internal/librarian/tag_and_release_test.go

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ package librarian
1717
import (
1818
"testing"
1919

20+
"github.com/google/go-cmp/cmp"
2021
"github.com/googleapis/librarian/internal/config"
2122
)
2223

@@ -52,3 +53,128 @@ func TestNewTagAndReleaseRunner(t *testing.T) {
5253
})
5354
}
5455
}
56+
57+
func TestParsePullRequestBody(t *testing.T) {
58+
tests := []struct {
59+
name string
60+
body string
61+
want []libraryRelease
62+
}{
63+
{
64+
name: "single library",
65+
body: `
66+
Librarian Version: v0.2.0
67+
Language Image: image
68+
69+
<details><summary>google-cloud-storage: 1.2.3</summary>
70+
71+
[1.2.3](https://github.com/googleapis/google-cloud-go/compare/google-cloud-storage-v1.2.2...google-cloud-storage-v1.2.3) (2025-08-15)
72+
73+
### Features
74+
75+
* Add new feature ([abcdef1](https://github.com/googleapis/google-cloud-go/commit/abcdef1))
76+
77+
</details>`,
78+
want: []libraryRelease{
79+
{
80+
Version: "1.2.3",
81+
Library: "google-cloud-storage",
82+
Body: `[1.2.3](https://github.com/googleapis/google-cloud-go/compare/google-cloud-storage-v1.2.2...google-cloud-storage-v1.2.3) (2025-08-15)
83+
84+
### Features
85+
86+
* Add new feature ([abcdef1](https://github.com/googleapis/google-cloud-go/commit/abcdef1))`,
87+
},
88+
},
89+
},
90+
{
91+
name: "multiple libraries",
92+
body: `
93+
Librarian Version: 1.2.3
94+
Language Image: gcr.io/test/image:latest
95+
96+
<details><summary>library-one: 1.0.0</summary>
97+
98+
[1.0.0](https://github.com/googleapis/repo/compare/library-one-v0.9.0...library-one-v1.0.0) (2025-08-15)
99+
100+
### Features
101+
102+
* some feature ([1234567](https://github.com/googleapis/repo/commit/1234567))
103+
104+
</details>
105+
106+
<details><summary>another-library-name: 2.3.4</summary>
107+
108+
[2.3.4](https://github.com/googleapis/repo/compare/another-library-name-v2.3.3...another-library-name-v2.3.4) (2025-08-15)
109+
110+
### Bug Fixes
111+
112+
* some bug fix ([abcdefg](https://github.com/googleapis/repo/commit/abcdefg))
113+
114+
</details>`,
115+
want: []libraryRelease{
116+
{
117+
Version: "1.0.0",
118+
Library: "library-one",
119+
Body: `[1.0.0](https://github.com/googleapis/repo/compare/library-one-v0.9.0...library-one-v1.0.0) (2025-08-15)
120+
121+
### Features
122+
123+
* some feature ([1234567](https://github.com/googleapis/repo/commit/1234567))`,
124+
},
125+
{
126+
Version: "2.3.4",
127+
Library: "another-library-name",
128+
Body: `[2.3.4](https://github.com/googleapis/repo/compare/another-library-name-v2.3.3...another-library-name-v2.3.4) (2025-08-15)
129+
130+
### Bug Fixes
131+
132+
* some bug fix ([abcdefg](https://github.com/googleapis/repo/commit/abcdefg))`,
133+
},
134+
},
135+
},
136+
{
137+
name: "empty body",
138+
body: "",
139+
want: nil,
140+
},
141+
{
142+
name: "malformed summary",
143+
body: `
144+
Librarian Version: 1.2.3
145+
Language Image: gcr.io/test/image:latest
146+
147+
<details><summary>no-version-here</summary>
148+
149+
some content
150+
151+
</details>`,
152+
want: nil,
153+
},
154+
{
155+
name: "v prefix in version",
156+
body: `
157+
<details><summary>google-cloud-storage: v1.2.3</summary>
158+
159+
[v1.2.3](https://github.com/googleapis/google-cloud-go/compare/google-cloud-storage-v1.2.2...google-cloud-storage-v1.2.3) (2025-08-15)
160+
161+
</details>`,
162+
want: []libraryRelease{
163+
{
164+
Version: "v1.2.3",
165+
Library: "google-cloud-storage",
166+
Body: "[v1.2.3](https://github.com/googleapis/google-cloud-go/compare/google-cloud-storage-v1.2.2...google-cloud-storage-v1.2.3) (2025-08-15)",
167+
},
168+
},
169+
},
170+
}
171+
172+
for _, tt := range tests {
173+
t.Run(tt.name, func(t *testing.T) {
174+
got := parsePullRequestBody(tt.body)
175+
if diff := cmp.Diff(tt.want, got); diff != "" {
176+
t.Errorf("ParsePullRequestBody() mismatch (-want +got):\n%s", diff)
177+
}
178+
})
179+
}
180+
}

0 commit comments

Comments
 (0)