Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 18 additions & 2 deletions .github/workflows/verify.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@ on:

jobs:
verify:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4

Expand All @@ -24,12 +28,24 @@ jobs:
- name: Build Plugin
run: ./gradlew buildPlugin

# 인코딩 회귀 테스트(이슈 #2/#4 포함) 실행.
# Windows 매트릭스에서 비-UTF-8 OS 동작까지 검증한다.
- name: Run Tests
run: ./gradlew test

- name: Verify Plugin (deprecated API + compatibility)
run: ./gradlew verifyPlugin

- name: Upload Test Report
if: always()
uses: actions/upload-artifact@v4
with:
name: test-report-${{ matrix.os }}
path: build/reports/tests/

- name: Upload Verification Report
if: always()
uses: actions/upload-artifact@v4
with:
name: plugin-verification-report
name: plugin-verification-report-${{ matrix.os }}
path: build/reports/pluginVerifier/
40 changes: 31 additions & 9 deletions src/main/kotlin/com/codingtestkit/service/CodeRunner.kt
Original file line number Diff line number Diff line change
Expand Up @@ -494,8 +494,13 @@ else console.log(_result);
}

private fun detectPython(): String {
return findExecutable("python3", "/usr/bin/python3", "/usr/local/bin/python3",
"/opt/homebrew/bin/python3", "python")
// Windows는 python3가 아니라 python 명령이 일반적
return if (isWindows) {
findExecutable("python", "python3")
} else {
findExecutable("python3", "/usr/bin/python3", "/usr/local/bin/python3",
"/opt/homebrew/bin/python3", "python")
}
}

private fun detectKotlinc(): String {
Expand Down Expand Up @@ -560,19 +565,36 @@ else console.log(_result);
return findExecutable("node", "/usr/local/bin/node", "/opt/homebrew/bin/node")
}

private val isWindows: Boolean = System.getProperty("os.name").lowercase().contains("win")

private fun findExecutable(vararg candidates: String): String {
for (candidate in candidates) {
// 절대 경로면 파일 존재 확인
if (candidate.startsWith("/") && File(candidate).exists()) return candidate
// 절대 경로 후보 (Unix: /usr/..., Windows: C:\...)
if (candidate.startsWith("/") || candidate.matches(Regex("^[A-Za-z]:[\\\\/].*"))) {
if (File(candidate).exists()) return candidate
// Windows 실행파일은 .exe 확장자 보정
if (isWindows && !candidate.endsWith(".exe")) {
val withExe = File("$candidate.exe")
if (withExe.exists()) return withExe.absolutePath
}
continue
}

// PATH에서 찾기
// PATH에서 찾기: Windows는 where, 그 외는 which.
// (Git Bash의 which는 ProcessBuilder가 실행 못 하는 MSYS 경로 /c/... 를 반환하므로 where 사용)
try {
val proc = ProcessBuilder("which", candidate).start()
val result = proc.inputStream.bufferedReader().readText().trim()
if (proc.waitFor() == 0 && result.isNotBlank()) return result
val locator = if (isWindows) "where" else "which"
val proc = ProcessBuilder(locator, candidate).start()
val output = proc.inputStream.bufferedReader().readText()
val ok = proc.waitFor() == 0
// where는 여러 줄을 반환할 수 있으므로 실제로 존재하는 첫 경로를 선택
val resolved = output.lineSequence()
.map { it.trim() }
.firstOrNull { it.isNotBlank() && File(it).exists() }
if (ok && resolved != null) return resolved
} catch (_: Exception) {}
}
return candidates.first() // fallback: 원래 이름 그대로
return candidates.first() // fallback: 원래 이름 그대로 (PATH 탐색에 맡김)
}

fun getDetectedPaths(): Map<String, String> = mapOf(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,8 @@ object ProblemFileManager {
}

private fun sanitizeFolderName(name: String): String {
return name.replace(Regex("[^a-zA-Z0-9가-힣_\\-() ]"), "_")
// 파일/폴더명에 안전한 . 와 + 도 보존한다 (예: 백준 1000 "A+B").
return name.replace(Regex("[^a-zA-Z0-9가-힣_.+\\-() ]"), "_")
.replace(Regex("_+"), "_")
.trim()
.take(60)
Expand Down
6 changes: 4 additions & 2 deletions src/main/kotlin/com/codingtestkit/service/SolvedAcApi.kt
Original file line number Diff line number Diff line change
Expand Up @@ -261,11 +261,13 @@ object SolvedAcApi {
private fun toSubscript(s: String): String = s.map { subscriptMap[it] ?: it }.joinToString("")

fun levelToString(level: Int): String {
if (level == 0) return "Unrated"
// 유효 레벨은 1~30 (Bronze V ~ Ruby I). 그 밖(0, 음수, 31+)은 모두 Unrated.
// 범위 밖 입력에서 rankIdx가 음수가 되어 ArrayIndexOutOfBounds가 나던 문제 방지.
if (level < 1 || level > 30) return "Unrated"
val tiers = arrayOf("Bronze", "Silver", "Gold", "Platinum", "Diamond", "Ruby")
val ranks = arrayOf("V", "IV", "III", "II", "I")
val tierIdx = (level - 1) / 5
val rankIdx = (level - 1) % 5
return if (tierIdx in tiers.indices) "${tiers[tierIdx]} ${ranks[rankIdx]}" else "Unrated"
return "${tiers[tierIdx]} ${ranks[rankIdx]}"
}
}
Loading