Repository: goreleaser/goreleaser (Go)

Issue:
homebrew: Forumla Class Name doesn't always match what brew expects
### What happened?

If you have a app named `baton-1password`, goreleaser generates a class name for the homebrew Formula of `Baton1Password`, while homebrew expects a class name of `Baton1password` (lowercase p).


When in this situation, homebrew will error out like this:
```
In formula file: /opt/homebrew/Library/Taps/conductorone/homebrew-baton/Formula/baton-1password.rb
Expected to find class Baton1password, but only found: Baton1Password.
```

### How can we reproduce this?

https://github.com/ConductorOne/baton-1password/blob/main/.goreleaser.yaml#L68-L77

outputs:
https://github.com/ConductorOne/homebrew-baton/blob/main/Formula/baton-1password.rb


### goreleaser version

```bash
goreleaser/goreleaser-action@v6
```


### GoReleaser Check

- [X] goreleaser check shows no errors

### Search

- [X] I did search for other open and closed issues before opening this

### Supporter

- [ ] I am a [sponsor](https://github.com/sponsors/caarlos0/) or a [Pro](https://goreleaser.com/pro) customer

### Code of Conduct

- [X] I agree to follow this project's Code of Conduct

### Additional context

This is a related issue to https://github.com/goreleaser/goreleaser/pull/3117 and https://github.com/goreleaser/goreleaser/issues/3116

Basically the improved function doesn't match all of the behaviors of the ruby function.


Discussion:
thanks for the issue and for the PR 🙏🏻 



Patch:
diff --git a/internal/pipe/brew/brew.go b/internal/pipe/brew/brew.go
index 893595fd422..ffcdc3ee224 100644
--- a/internal/pipe/brew/brew.go
+++ b/internal/pipe/brew/brew.go
@@ -22,8 +22,6 @@ import (
 	"github.com/goreleaser/goreleaser/v2/internal/tmpl"
 	"github.com/goreleaser/goreleaser/v2/pkg/config"
 	"github.com/goreleaser/goreleaser/v2/pkg/context"
-	"golang.org/x/text/cases"
-	"golang.org/x/text/language"
 )
 
 const brewConfigExtra = "BrewConfig"
@@ -489,12 +487,52 @@ func split(s string) []string {
 // formulaNameFor transforms the formula name into a form
 // that more resembles a valid Ruby class name
 // e.g. foo_bar@v6.0.0-rc is turned into FooBarATv6_0_0RC
-// The order of these replacements is important
+//
+// This function must match the behavior of Homewbrews's Formulary.class_s function:
+//
+//	<https://github.com/Homebrew/brew/blob/587949bd8417c486795be04194f9e9baeaa9f5a7/Library/Homebrew/formulary.rb#L522-L528>
 func formulaNameFor(name string) string {
-	name = strings.ReplaceAll(name, "-", " ")
-	name = strings.ReplaceAll(name, "_", " ")
-	name = strings.ReplaceAll(name, ".", "")
-	name = cases.Title(language.English).String(name)
-	name = strings.ReplaceAll(name, " ", "")
-	return strings.ReplaceAll(name, "@", "AT")
+	if len(name) == 0 {
+		return name
+	}
+
+	var output strings.Builder
+	name = strings.ToLower(name)
+
+	// Capitalize the first character
+	output.WriteByte(strings.ToUpper(name[:1])[0])
+
+	// Traverse the rest of the string
+	for i := 1; i < len(name); i++ {
+		c := name[i]
+
+		switch c {
+		case '-', '_', '.', ' ':
+			// Capitalize the next character after a symbol
+			if i+1 < len(name) {
+				output.WriteByte(strings.ToUpper(name[i+1 : i+2])[0])
+				i++ // Skip the next character as it's already processed
+			}
+		case '+':
+			// Replace '+' with 'x'
+			output.WriteByte('x')
+		case '@':
+			// Replace occurrences of (.)@(\d) with \1AT\2
+			if i+1 < len(name) && isDigit(name[i+1]) {
+				output.WriteString("AT")
+				output.WriteByte(name[i+1])
+				i++ // Skip the next character as it's already processed
+			} else {
+				output.WriteByte(c)
+			}
+		default:
+			output.WriteByte(c)
+		}
+	}
+
+	return output.String()
+}
+
+func isDigit(b byte) bool {
+	return b >= '0' && b <= '9'
 }


Test patch:
diff --git a/internal/pipe/brew/brew_test.go b/internal/pipe/brew/brew_test.go
index 2b4e7f69280..bdbc2bee445 100644
--- a/internal/pipe/brew/brew_test.go
+++ b/internal/pipe/brew/brew_test.go
@@ -30,6 +30,10 @@ func TestNameWithDash(t *testing.T) {
 	require.Equal(t, "SomeBinary", formulaNameFor("some-binary"))
 }
 
+func TestNameNumberThenWord(t *testing.T) {
+	require.Equal(t, "Baton1password", formulaNameFor("baton-1password"))
+}
+
 func TestNameWithUnderline(t *testing.T) {
 	require.Equal(t, "SomeBinary", formulaNameFor("some_binary"))
 }