Skip to content

Commit fee2b75

Browse files
authored
add sx.CamelCase (#2)
1 parent 0bf67b5 commit fee2b75

File tree

2 files changed

+139
-0
lines changed

2 files changed

+139
-0
lines changed

sx.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,3 +232,47 @@ func PascalCase[T StringOrStringSlice](input T, opts ...CaseOption) string {
232232
return ""
233233
}
234234
}
235+
236+
// lowercaseWord converts the first letter to lowercase
237+
func lowercaseWord(word string) string {
238+
if word == "" {
239+
return word
240+
}
241+
242+
r, size := utf8.DecodeRuneInString(word)
243+
if size == 0 {
244+
return word
245+
}
246+
247+
return string(unicode.ToLower(r)) + word[size:]
248+
}
249+
250+
// CamelCase converts input to camelCase
251+
func CamelCase[T StringOrStringSlice](input T, opts ...CaseOption) string {
252+
switch v := any(input).(type) {
253+
case string:
254+
pascalCase := PascalCase(v, opts...)
255+
return lowercaseWord(pascalCase)
256+
case []string:
257+
if len(v) == 0 {
258+
return ""
259+
}
260+
261+
options := CaseConfig{}
262+
for _, opt := range opts {
263+
opt(&options)
264+
}
265+
266+
result := joinWords(v, "", func(word string, i int) string {
267+
normalized := normalizeWord(word, options.Normalize)
268+
if i == 0 {
269+
return lowercaseWord(normalized)
270+
}
271+
272+
return capitalizeWord(normalized)
273+
})
274+
return result
275+
default:
276+
return ""
277+
}
278+
}

sx_test.go

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,3 +274,98 @@ func TestPascalCaseWithSlice(t *testing.T) {
274274
})
275275
}
276276
}
277+
278+
func TestCamelCase(t *testing.T) {
279+
tests := []struct {
280+
name string
281+
input string
282+
expected string
283+
options []sx.CaseOption
284+
}{
285+
{
286+
name: "PascalCase to camelCase",
287+
input: "PascalCase",
288+
expected: "pascalCase",
289+
},
290+
{
291+
name: "kebab-case to camelCase",
292+
input: "kebab-case",
293+
expected: "kebabCase",
294+
},
295+
{
296+
name: "snake_case to camelCase",
297+
input: "snake_case",
298+
expected: "snakeCase",
299+
},
300+
{
301+
name: "XMLHttpRequest",
302+
input: "XMLHttpRequest",
303+
expected: "xMLHttpRequest",
304+
},
305+
{
306+
name: "XMLHttpRequest normalized",
307+
input: "XMLHttpRequest",
308+
expected: "xmlHttpRequest",
309+
options: []sx.CaseOption{sx.WithNormalize(true)},
310+
},
311+
{
312+
name: "empty string",
313+
input: "",
314+
expected: "",
315+
},
316+
{
317+
name: "single word",
318+
input: "Word",
319+
expected: "word",
320+
},
321+
}
322+
323+
for _, tt := range tests {
324+
t.Run(tt.name, func(t *testing.T) {
325+
result := sx.CamelCase(tt.input, tt.options...)
326+
if result != tt.expected {
327+
t.Errorf("CamelCase(%q) = %q, want %q", tt.input, result, tt.expected)
328+
}
329+
})
330+
}
331+
}
332+
333+
func TestCamelCaseWithSlice(t *testing.T) {
334+
tests := []struct {
335+
name string
336+
input []string
337+
expected string
338+
options []sx.CaseOption
339+
}{
340+
{
341+
name: "string slice",
342+
input: []string{"hello", "world", "test"},
343+
expected: "helloWorldTest",
344+
},
345+
{
346+
name: "string slice normalized",
347+
input: []string{"HELLO", "WORLD", "TEST"},
348+
expected: "helloWorldTest",
349+
options: []sx.CaseOption{sx.WithNormalize(true)},
350+
},
351+
{
352+
name: "empty slice",
353+
input: []string{},
354+
expected: "",
355+
},
356+
{
357+
name: "single item slice",
358+
input: []string{"Word"},
359+
expected: "word",
360+
},
361+
}
362+
363+
for _, tt := range tests {
364+
t.Run(tt.name, func(t *testing.T) {
365+
result := sx.CamelCase(tt.input, tt.options...)
366+
if result != tt.expected {
367+
t.Errorf("CamelCase(%v) = %q, want %q", tt.input, result, tt.expected)
368+
}
369+
})
370+
}
371+
}

0 commit comments

Comments
 (0)