From ed77ea69518da2266fd9ea15c7265f2de2954a57 Mon Sep 17 00:00:00 2001 From: Sophia Yang <171981480+yangyzs@users.noreply.github.com> Date: Tue, 30 Jun 2026 15:30:19 -0400 Subject: [PATCH 1/3] feat(internal/librarian/java): add README metadata parsing helpers Add toCamelCase, parseGroupIDArtifactID, and parseRepoShortName helpers to format Maven coordinates, repository names, and identifiers for README generation. Add unit tests in readme_test.go. For #6515 --- internal/librarian/java/readme.go | 32 +++++++ internal/librarian/java/readme_test.go | 110 +++++++++++++++++++++++++ 2 files changed, 142 insertions(+) diff --git a/internal/librarian/java/readme.go b/internal/librarian/java/readme.go index 8ca97817b8..ae1ac7cec6 100644 --- a/internal/librarian/java/readme.go +++ b/internal/librarian/java/readme.go @@ -23,6 +23,7 @@ import ( "path/filepath" "regexp" "strings" + "unicode" ) var ( @@ -157,3 +158,34 @@ func extractTitle(filePath string) (string, error) { } return title, nil } + +// toCamelCase converts snake_case, kebab-case, or space-separated strings into CamelCase identifiers. +func toCamelCase(s string) string { + parts := strings.FieldsFunc(s, func(r rune) bool { + return r == '_' || r == '-' || r == ' ' + }) + var sb strings.Builder + for _, p := range parts { + r := []rune(p) + r[0] = unicode.ToUpper(r[0]) + sb.WriteString(string(r)) + } + return sb.String() +} + +// parseGroupIDArtifactID extracts GroupID and ArtifactID from a Maven distribution name. +func parseGroupIDArtifactID(distributionName string) (string, string) { + groupID, artifactID, _ := strings.Cut(distributionName, ":") + return groupID, artifactID +} + +// parseRepoShortName extracts the short repository name from the full repo path. +func parseRepoShortName(repo string) string { + if repo == "" { + return "" + } + if i := strings.LastIndexByte(repo, '/'); i >= 0 { + return repo[i+1:] + } + return repo +} diff --git a/internal/librarian/java/readme_test.go b/internal/librarian/java/readme_test.go index ea83f04e1d..3eea65eaf6 100644 --- a/internal/librarian/java/readme_test.go +++ b/internal/librarian/java/readme_test.go @@ -476,3 +476,113 @@ func TestExtractTitle_Error(t *testing.T) { }) } } + +func TestToCamelCase(t *testing.T) { + for _, test := range []struct { + name string + input string + want string + }{ + { + name: "snake case", + input: "custom_content", + want: "CustomContent", + }, + { + name: "kebab case", + input: "readme-partials", + want: "ReadmePartials", + }, + { + name: "space separated", + input: "about us", + want: "AboutUs", + }, + { + name: "already camel", + input: "About", + want: "About", + }, + { + name: "empty string", + input: "", + want: "", + }, + } { + t.Run(test.name, func(t *testing.T) { + got := toCamelCase(test.input) + if diff := cmp.Diff(test.want, got); diff != "" { + t.Errorf("mismatch (-want +got):\n%s", diff) + } + }) + } +} + +func TestParseGroupIDArtifactID(t *testing.T) { + for _, test := range []struct { + name string + input string + wantGroupID string + wantArtifactID string + }{ + { + name: "standard coordinates", + input: "com.google.cloud:google-cloud-storage", + wantGroupID: "com.google.cloud", + wantArtifactID: "google-cloud-storage", + }, + { + name: "missing artifact id", + input: "com.google.cloud", + wantGroupID: "com.google.cloud", + wantArtifactID: "", + }, + { + name: "empty distribution name", + input: "", + wantGroupID: "", + wantArtifactID: "", + }, + } { + t.Run(test.name, func(t *testing.T) { + gotGroup, gotArtifact := parseGroupIDArtifactID(test.input) + if diff := cmp.Diff(test.wantGroupID, gotGroup); diff != "" { + t.Errorf("group ID mismatch (-want +got):\n%s", diff) + } + if diff := cmp.Diff(test.wantArtifactID, gotArtifact); diff != "" { + t.Errorf("artifact ID mismatch (-want +got):\n%s", diff) + } + }) + } +} + +func TestParseRepoShortName(t *testing.T) { + for _, test := range []struct { + name string + input string + want string + }{ + { + name: "full repo path", + input: "googleapis/google-cloud-java", + want: "google-cloud-java", + }, + { + name: "short repo name only", + input: "google-cloud-java", + want: "google-cloud-java", + }, + { + name: "empty repo string", + input: "", + want: "", + }, + } { + t.Run(test.name, func(t *testing.T) { + got := parseRepoShortName(test.input) + if diff := cmp.Diff(test.want, got); diff != "" { + t.Errorf("mismatch (-want +got):\n%s", diff) + } + }) + } +} From bebd8b48785f0f05f87c38a66370d39538f4a5c4 Mon Sep 17 00:00:00 2001 From: Sophia Yang <171981480+yangyzs@users.noreply.github.com> Date: Tue, 30 Jun 2026 20:54:48 -0400 Subject: [PATCH 2/3] refactor(internal/librarian/java): optimize toCamelCase memory allocation per review --- internal/librarian/java/readme.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/internal/librarian/java/readme.go b/internal/librarian/java/readme.go index ae1ac7cec6..260b141f9e 100644 --- a/internal/librarian/java/readme.go +++ b/internal/librarian/java/readme.go @@ -24,6 +24,7 @@ import ( "regexp" "strings" "unicode" + "unicode/utf8" ) var ( @@ -166,9 +167,9 @@ func toCamelCase(s string) string { }) var sb strings.Builder for _, p := range parts { - r := []rune(p) - r[0] = unicode.ToUpper(r[0]) - sb.WriteString(string(r)) + r, size := utf8.DecodeRuneInString(p) + sb.WriteRune(unicode.ToUpper(r)) + sb.WriteString(p[size:]) } return sb.String() } From d0f22bc0b82086ddb4f1d846e8769288d49b92c9 Mon Sep 17 00:00:00 2001 From: Sophia Yang <171981480+yangyzs@users.noreply.github.com> Date: Wed, 1 Jul 2026 10:36:06 -0400 Subject: [PATCH 3/3] fix(java): restore missing closing brace after merge --- internal/librarian/java/readme.go | 2 ++ internal/librarian/java/readme_test.go | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/internal/librarian/java/readme.go b/internal/librarian/java/readme.go index a3eca56b7b..68768940b6 100644 --- a/internal/librarian/java/readme.go +++ b/internal/librarian/java/readme.go @@ -198,6 +198,8 @@ func parseRepoShortName(repo string) string { return repo[i+1:] } return repo +} + // collectSnippetFiles recursively scans dir/samples for Java and XML files containing snippets. func collectSnippetFiles(dir string) ([]string, error) { samplesDir := filepath.Join(dir, "samples") diff --git a/internal/librarian/java/readme_test.go b/internal/librarian/java/readme_test.go index e59d8bd687..dba2a6f58e 100644 --- a/internal/librarian/java/readme_test.go +++ b/internal/librarian/java/readme_test.go @@ -657,7 +657,7 @@ func TestCollectSnippetFiles(t *testing.T) { }) } } - + func TestExtractSnippetsFromFile(t *testing.T) { t.Parallel() for _, test := range []struct {