Skip to content

Commit 86a03c7

Browse files
committed
feat: add GoPascalCase and GoCamelCase for Go-conventional acronym casing
PascalCase("execute_sql_query") returns "ExecuteSqlQuery", but Go convention (enforced by revive/golint) expects "ExecuteSQLQuery". GoPascalCase and GoCamelCase post-process the result to uppercase registered acronyms (SQL, API, URL, ID, HTTP, JSON, etc.) from the engine's acronym registry. Available as both package-level functions and Engine methods.
1 parent 9386dd0 commit 86a03c7

3 files changed

Lines changed: 170 additions & 0 deletions

File tree

inflect_gen.go

Lines changed: 41 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

internal/inflect/case.go

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,70 @@ func TitleCase(s string) string {
8181
return PascalCase(s)
8282
}
8383

84+
// GoPascalCase converts a string to PascalCase with Go-conventional acronym
85+
// casing. Registered acronyms (SQL, API, URL, ID, etc.) are fully uppercased
86+
// per Go naming conventions enforced by linters like revive and golint.
87+
//
88+
// This differs from [PascalCase] which treats acronyms as regular words:
89+
// PascalCase("execute_sql_query") returns "ExecuteSqlQuery", while
90+
// GoPascalCase("execute_sql_query") returns "ExecuteSQLQuery".
91+
//
92+
// Acronyms are sourced from the engine's acronym registry (see [AddAcronym],
93+
// [GetAcronyms]). The default set includes common acronyms like API, SQL, URL,
94+
// HTTP, ID, JSON, XML, etc.
95+
//
96+
// Examples:
97+
// - GoPascalCase("execute_sql_query") returns "ExecuteSQLQuery"
98+
// - GoPascalCase("get_api_key") returns "GetAPIKey"
99+
// - GoPascalCase("list_urls") returns "ListURLs"
100+
// - GoPascalCase("hello_world") returns "HelloWorld"
101+
// - GoPascalCase("user_id") returns "UserID"
102+
func GoPascalCase(s string) string {
103+
return defaultEngine.GoPascalCase(s)
104+
}
105+
106+
// GoPascalCase converts a string to PascalCase with Go-conventional acronym
107+
// casing. See the package-level [GoPascalCase] for details and examples.
108+
func (e *Engine) GoPascalCase(s string) string {
109+
return applyAcronymCasing(toCamelOrPascal(s, true), e)
110+
}
111+
112+
// GoCamelCase converts a string to camelCase with Go-conventional acronym
113+
// casing. Registered acronyms in non-leading positions are fully uppercased.
114+
//
115+
// This differs from [CamelCase] which treats acronyms as regular words:
116+
// CamelCase("get_sql_query") returns "getSqlQuery", while
117+
// GoCamelCase("get_sql_query") returns "getSQLQuery".
118+
//
119+
// When an acronym appears at the start, it remains lowercase per camelCase rules:
120+
// GoCamelCase("sql_query") returns "sqlQuery" (not "sQLQuery").
121+
//
122+
// Examples:
123+
// - GoCamelCase("get_sql_query") returns "getSQLQuery"
124+
// - GoCamelCase("get_api_key") returns "getAPIKey"
125+
// - GoCamelCase("sql_query") returns "sqlQuery"
126+
// - GoCamelCase("hello_world") returns "helloWorld"
127+
func GoCamelCase(s string) string {
128+
return defaultEngine.GoCamelCase(s)
129+
}
130+
131+
// GoCamelCase converts a string to camelCase with Go-conventional acronym
132+
// casing. See the package-level [GoCamelCase] for details and examples.
133+
func (e *Engine) GoCamelCase(s string) string {
134+
return applyAcronymCasing(toCamelOrPascal(s, false), e)
135+
}
136+
137+
// applyAcronymCasing post-processes a PascalCase or camelCase string to
138+
// uppercase registered acronyms. For example, "ExecuteSqlQuery" becomes
139+
// "ExecuteSQLQuery" when "SQL" is a registered acronym.
140+
func applyAcronymCasing(s string, e *Engine) string {
141+
for _, acr := range e.GetAcronyms() {
142+
titled := strings.ToUpper(acr[:1]) + strings.ToLower(acr[1:])
143+
s = strings.ReplaceAll(s, titled, acr)
144+
}
145+
return s
146+
}
147+
84148
// CamelCase converts a string to camelCase.
85149
//
86150
// It handles snake_case, kebab-case, and mixed inputs.

internal/inflect/case_test.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,71 @@ func TestPascalCase(t *testing.T) {
199199
}
200200
}
201201

202+
func TestGoPascalCase(t *testing.T) {
203+
tests := []struct {
204+
name string
205+
input string
206+
want string
207+
}{
208+
// Acronym casing (the key difference from PascalCase)
209+
{name: "SQL acronym", input: "execute_sql_query", want: "ExecuteSQLQuery"},
210+
{name: "API acronym", input: "get_api_key", want: "GetAPIKey"},
211+
{name: "URL acronym", input: "list_urls", want: "ListURLs"},
212+
{name: "ID acronym", input: "user_id", want: "UserID"},
213+
{name: "HTTP acronym", input: "http_server", want: "HTTPServer"},
214+
{name: "JSON acronym", input: "parse_json_data", want: "ParseJSONData"},
215+
{name: "multiple acronyms", input: "get_api_url", want: "GetAPIURL"},
216+
{name: "UUID acronym", input: "generate_uuid", want: "GenerateUUID"},
217+
218+
// Non-acronym words unchanged
219+
{name: "no acronyms", input: "hello_world", want: "HelloWorld"},
220+
{name: "three words", input: "one_two_three", want: "OneTwoThree"},
221+
222+
// Edge cases
223+
{name: "empty string", input: "", want: ""},
224+
{name: "single word", input: "hello", want: "Hello"},
225+
{name: "kebab-case", input: "get-api-key", want: "GetAPIKey"},
226+
}
227+
228+
for _, tt := range tests {
229+
t.Run(tt.name, func(t *testing.T) {
230+
got := inflect.GoPascalCase(tt.input)
231+
assert.Equal(t, tt.want, got)
232+
})
233+
}
234+
}
235+
236+
func TestGoCamelCase(t *testing.T) {
237+
tests := []struct {
238+
name string
239+
input string
240+
want string
241+
}{
242+
// Acronym casing in non-leading position
243+
{name: "SQL acronym", input: "get_sql_query", want: "getSQLQuery"},
244+
{name: "API acronym", input: "get_api_key", want: "getAPIKey"},
245+
{name: "URL acronym", input: "get_url", want: "getURL"},
246+
{name: "ID acronym", input: "get_user_id", want: "getUserID"},
247+
248+
// Leading acronym stays lowercase (camelCase rule)
249+
{name: "leading acronym", input: "sql_query", want: "sqlQuery"},
250+
{name: "leading http", input: "http_server", want: "httpServer"},
251+
252+
// Non-acronym words unchanged
253+
{name: "no acronyms", input: "hello_world", want: "helloWorld"},
254+
255+
// Edge cases
256+
{name: "empty string", input: "", want: ""},
257+
}
258+
259+
for _, tt := range tests {
260+
t.Run(tt.name, func(t *testing.T) {
261+
got := inflect.GoCamelCase(tt.input)
262+
assert.Equal(t, tt.want, got)
263+
})
264+
}
265+
}
266+
202267
func TestTitleCase(t *testing.T) {
203268
// TitleCase should be an alias for PascalCase
204269
tests := []struct {

0 commit comments

Comments
 (0)