From 5e05f3c06817591f5c3cc1db1f29ea2376d3e58c Mon Sep 17 00:00:00 2001 From: Frank Oh Date: Fri, 20 Mar 2026 18:22:36 +0900 Subject: [PATCH 1/3] =?UTF-8?q?=EB=AC=B8=EC=9E=90=EC=97=B4=20=EC=9C=A0?= =?UTF-8?q?=ED=8B=B8=EB=A6=AC=ED=8B=B0=20=ED=95=A8=EC=88=98=20=EB=B0=8F=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80=20(Claude=20Co?= =?UTF-8?q?de=20Review=20=EC=9B=8C=ED=81=AC=ED=94=8C=EB=A1=9C=EC=9A=B0=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=EC=9A=A9)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ReverseString: 유니코드 지원 문자열 뒤집기 - CamelToSnake/SnakeToCamel: 네이밍 컨벤션 변환 - TruncateWithEllipsis: 말줄임 처리 - CountWords: 단어 수 세기 - IsPalindrome: 회문 검사 - 모든 함수에 대해 테이블 드리븐 테스트 작성 Co-Authored-By: Claude Opus 4.6 (1M context) --- golang/strings/string_utils.go | 81 +++++++++++++++++ golang/strings/string_utils_test.go | 129 ++++++++++++++++++++++++++++ 2 files changed, 210 insertions(+) create mode 100644 golang/strings/string_utils.go create mode 100644 golang/strings/string_utils_test.go diff --git a/golang/strings/string_utils.go b/golang/strings/string_utils.go new file mode 100644 index 00000000..fa65cc63 --- /dev/null +++ b/golang/strings/string_utils.go @@ -0,0 +1,81 @@ +package go_strings + +import ( + "strings" + "unicode" +) + +// ReverseString reverses the given string, handling Unicode correctly. +func ReverseString(s string) string { + runes := []rune(s) + for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 { + runes[i], runes[j] = runes[j], runes[i] + } + return string(runes) +} + +// CamelToSnake converts a camelCase or PascalCase string to snake_case. +func CamelToSnake(s string) string { + var result strings.Builder + for i, r := range s { + if unicode.IsUpper(r) { + if i > 0 { + result.WriteRune('_') + } + result.WriteRune(unicode.ToLower(r)) + } else { + result.WriteRune(r) + } + } + return result.String() +} + +// SnakeToCamel converts a snake_case string to camelCase. +func SnakeToCamel(s string) string { + parts := strings.Split(s, "_") + var result strings.Builder + for i, part := range parts { + if part == "" { + continue + } + if i == 0 { + result.WriteString(strings.ToLower(part)) + } else { + result.WriteString(strings.ToUpper(part[:1]) + strings.ToLower(part[1:])) + } + } + return result.String() +} + +// TruncateWithEllipsis truncates a string to maxLen and appends "..." if truncated. +func TruncateWithEllipsis(s string, maxLen int) string { + runes := []rune(s) + if len(runes) <= maxLen { + return s + } + if maxLen <= 3 { + return string(runes[:maxLen]) + } + return string(runes[:maxLen-3]) + "..." +} + +// CountWords counts the number of words in a string. +func CountWords(s string) int { + return len(strings.Fields(s)) +} + +// IsPalindrome checks if a string is a palindrome, ignoring case and non-alphanumeric characters. +func IsPalindrome(s string) bool { + var cleaned []rune + for _, r := range strings.ToLower(s) { + if unicode.IsLetter(r) || unicode.IsDigit(r) { + cleaned = append(cleaned, r) + } + } + for i, j := 0, len(cleaned)-1; i < j; i, j = i+1, j-1 { + if cleaned[i] != cleaned[j] { + return false + } + } + return true +} diff --git a/golang/strings/string_utils_test.go b/golang/strings/string_utils_test.go new file mode 100644 index 00000000..62fbf695 --- /dev/null +++ b/golang/strings/string_utils_test.go @@ -0,0 +1,129 @@ +package go_strings + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestReverseString_다양한입력(t *testing.T) { + tests := []struct { + name string + input string + expected string + }{ + {"영문", "hello", "olleh"}, + {"한글", "안녕하세요", "요세하녕안"}, + {"빈문자열", "", ""}, + {"한글자", "a", "a"}, + {"회문", "racecar", "racecar"}, + {"이모지", "hello🌍", "🌍olleh"}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.expected, ReverseString(tt.input)) + }) + } +} + +func TestCamelToSnake_변환(t *testing.T) { + tests := []struct { + name string + input string + expected string + }{ + {"camelCase", "camelCase", "camel_case"}, + {"PascalCase", "PascalCase", "pascal_case"}, + {"단일소문자", "hello", "hello"}, + {"여러대문자", "HTTPServer", "h_t_t_p_server"}, + {"빈문자열", "", ""}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.expected, CamelToSnake(tt.input)) + }) + } +} + +func TestSnakeToCamel_변환(t *testing.T) { + tests := []struct { + name string + input string + expected string + }{ + {"기본", "snake_case", "snakeCase"}, + {"세단어", "hello_world_go", "helloWorldGo"}, + {"단일단어", "hello", "hello"}, + {"빈문자열", "", ""}, + {"앞뒤언더스코어", "_hello_world_", "helloWorld"}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.expected, SnakeToCamel(tt.input)) + }) + } +} + +func TestTruncateWithEllipsis_잘라내기(t *testing.T) { + tests := []struct { + name string + input string + maxLen int + expected string + }{ + {"짧은문자열", "hi", 10, "hi"}, + {"정확히같은길이", "hello", 5, "hello"}, + {"잘라내기", "hello world", 8, "hello..."}, + {"매우짧은최대길이", "hello", 3, "hel"}, + {"한글잘라내기", "안녕하세요 세계입니다", 7, "안녕하세..."}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.expected, TruncateWithEllipsis(tt.input, tt.maxLen)) + }) + } +} + +func TestCountWords_단어수세기(t *testing.T) { + tests := []struct { + name string + input string + expected int + }{ + {"일반문장", "hello world", 2}, + {"여러공백", " hello world ", 2}, + {"빈문자열", "", 0}, + {"탭포함", "hello\tworld\ngo", 3}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.expected, CountWords(tt.input)) + }) + } +} + +func TestIsPalindrome_회문검사(t *testing.T) { + tests := []struct { + name string + input string + expected bool + }{ + {"영문회문", "racecar", true}, + {"대소문자무시", "RaceCar", true}, + {"공백과특수문자무시", "A man, a plan, a canal: Panama", true}, + {"회문아님", "hello", false}, + {"빈문자열", "", true}, + {"숫자포함회문", "12321", true}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.expected, IsPalindrome(tt.input)) + }) + } +} From cfcd239919800aa2272ecd35b3cc5cf8ca9e8ab9 Mon Sep 17 00:00:00 2001 From: Frank Oh Date: Fri, 20 Mar 2026 18:33:38 +0900 Subject: [PATCH 2/3] =?UTF-8?q?fix:=20Claude=20Code=20Review=20=EC=9B=8C?= =?UTF-8?q?=ED=81=AC=ED=94=8C=EB=A1=9C=EC=9A=B0=20pull-requests=20?= =?UTF-8?q?=EA=B6=8C=ED=95=9C=EC=9D=84=20write=EB=A1=9C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit pull-requests: read → write로 변경하여 PR에 리뷰 코멘트를 작성할 수 있도록 수정. 이전 실행에서 permission_denials_count: 17로 권한 부족으로 코멘트 작성 실패. Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/claude-code-review.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/claude-code-review.yml b/.github/workflows/claude-code-review.yml index b5e8cfd4..b739b565 100644 --- a/.github/workflows/claude-code-review.yml +++ b/.github/workflows/claude-code-review.yml @@ -21,7 +21,7 @@ jobs: runs-on: ubuntu-latest permissions: contents: read - pull-requests: read + pull-requests: write issues: read id-token: write From 8d291d938deb68dda6b93ba632ac81afc03608c4 Mon Sep 17 00:00:00 2001 From: Frank Oh Date: Fri, 20 Mar 2026 18:36:12 +0900 Subject: [PATCH 3/3] =?UTF-8?q?Revert=20"fix:=20Claude=20Code=20Review=20?= =?UTF-8?q?=EC=9B=8C=ED=81=AC=ED=94=8C=EB=A1=9C=EC=9A=B0=20pull-requests?= =?UTF-8?q?=20=EA=B6=8C=ED=95=9C=EC=9D=84=20write=EB=A1=9C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit cfcd239919800aa2272ecd35b3cc5cf8ca9e8ab9. --- .github/workflows/claude-code-review.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/claude-code-review.yml b/.github/workflows/claude-code-review.yml index b739b565..b5e8cfd4 100644 --- a/.github/workflows/claude-code-review.yml +++ b/.github/workflows/claude-code-review.yml @@ -21,7 +21,7 @@ jobs: runs-on: ubuntu-latest permissions: contents: read - pull-requests: write + pull-requests: read issues: read id-token: write