|
| 1 | +package cc.unitmesh.devti.bridge.command |
| 2 | + |
| 3 | +import com.intellij.util.io.awaitExit |
| 4 | +import kotlinx.coroutines.* |
| 5 | +import kotlinx.serialization.Serializable |
| 6 | +import kotlinx.serialization.SerialName |
| 7 | +import kotlinx.serialization.json.Json |
| 8 | + |
| 9 | +@Serializable |
| 10 | +data class SccResult( |
| 11 | + @SerialName("Name") val name: String, |
| 12 | + @SerialName("Bytes") val bytes: Int, |
| 13 | + @SerialName("CodeBytes") val codeBytes: Int, |
| 14 | + @SerialName("Lines") val lines: Int, |
| 15 | + @SerialName("Code") val code: Int, |
| 16 | + @SerialName("Comment") val comment: Int, |
| 17 | + @SerialName("Blank") val blank: Int, |
| 18 | + @SerialName("Complexity") val complexity: Int, |
| 19 | + @SerialName("Count") val count: Int, |
| 20 | + @SerialName("WeightedComplexity") val weightedComplexity: Int, |
| 21 | + @SerialName("Files") val files: List<FileInfo>, |
| 22 | + @SerialName("LineLength") val lineLength: Int? |
| 23 | +) |
| 24 | + |
| 25 | +@Serializable |
| 26 | +data class FileInfo( |
| 27 | + @SerialName("Location") val location: String, |
| 28 | + @SerialName("Filename") val filename: String, |
| 29 | + @SerialName("Lines") val lines: Long, |
| 30 | + @SerialName("Code") val code: Long, |
| 31 | + @SerialName("Comment") val comment: Long, |
| 32 | + @SerialName("Blank") val blank: Long, |
| 33 | + @SerialName("Complexity") val complexity: Long |
| 34 | +) |
| 35 | + |
| 36 | +class SccWrapper( |
| 37 | + private val sccPath: String = "scc", private val timeoutSeconds: Long = 60 |
| 38 | +) { |
| 39 | + private val jsonParser = Json { ignoreUnknownKeys = true } |
| 40 | + |
| 41 | + /** |
| 42 | + * 同步执行 scc 命令 |
| 43 | + * @param arguments scc 命令行参数 |
| 44 | + */ |
| 45 | + fun runSccSync(vararg arguments: String): List<SccResult> { |
| 46 | + val command = buildCommand(arguments) |
| 47 | + val process = ProcessBuilder(command) |
| 48 | + .redirectErrorStream(true) |
| 49 | + .start() |
| 50 | + |
| 51 | + val output = process.inputStream.reader().use { reader -> |
| 52 | + reader.readText() |
| 53 | + } |
| 54 | + |
| 55 | + val exitCode = process.waitFor() |
| 56 | + if (exitCode != 0) { |
| 57 | + throw SccException("scc exited with code $exitCode. Output: $output") |
| 58 | + } |
| 59 | + |
| 60 | + return parseResult(output) |
| 61 | + } |
| 62 | + |
| 63 | + /** |
| 64 | + * 异步执行 scc 命令(使用协程) |
| 65 | + * @param arguments scc 命令行参数 |
| 66 | + */ |
| 67 | + suspend fun runSccAsync(vararg arguments: String): List<SccResult> = |
| 68 | + withContext(Dispatchers.IO) { |
| 69 | + val command = buildCommand(arguments) |
| 70 | + val process = ProcessBuilder(command) |
| 71 | + .redirectErrorStream(true) |
| 72 | + .start() |
| 73 | + |
| 74 | + val output = process.inputStream.reader().use { reader -> |
| 75 | + reader.readText() |
| 76 | + } |
| 77 | + |
| 78 | + val exitCode = withTimeoutOrNull(timeoutSeconds * 1000) { |
| 79 | + process.awaitExit() |
| 80 | + } ?: throw SccException("scc execution timed out after $timeoutSeconds seconds") |
| 81 | + |
| 82 | + if (exitCode != 0) { |
| 83 | + throw SccException("scc exited with code $exitCode. Output: $output") |
| 84 | + } |
| 85 | + |
| 86 | + parseResult(output) |
| 87 | + } |
| 88 | + |
| 89 | + private fun buildCommand(arguments: Array<out String>): List<String> { |
| 90 | + val baseCommand = if (sccPath.contains(" ")) { |
| 91 | + listOf("cmd", "/c", sccPath) |
| 92 | + } else { |
| 93 | + listOf(sccPath) |
| 94 | + } |
| 95 | + |
| 96 | + return baseCommand + arguments.toList() + "--format" + "json" |
| 97 | + } |
| 98 | + |
| 99 | + private fun parseResult(json: String): List<SccResult> { |
| 100 | + return try { |
| 101 | + jsonParser.decodeFromString<List<SccResult>>(json) |
| 102 | + } catch (e: Exception) { |
| 103 | + throw SccException("Failed to parse scc output", e) |
| 104 | + } |
| 105 | + } |
| 106 | + |
| 107 | + // 快捷方法示例 |
| 108 | + fun analyzeDirectory(directory: String, excludeDirs: List<String> = emptyList()): List<SccResult> { |
| 109 | + val args = mutableListOf<String>().apply { |
| 110 | + add(directory) |
| 111 | + if (excludeDirs.isNotEmpty()) { |
| 112 | + add("--exclude-dir") |
| 113 | + add(excludeDirs.joinToString(",")) |
| 114 | + } |
| 115 | + } |
| 116 | + |
| 117 | + return runSccSync(*args.toTypedArray()) |
| 118 | + } |
| 119 | +} |
| 120 | + |
| 121 | +class SccException(message: String, cause: Throwable? = null) : RuntimeException(message, cause) |
0 commit comments