Skip to content

Commit 6155a78

Browse files
committed
feat(scc): add SCC wrapper for code analysis #319
Introduce `SCCWrapper` to integrate SCC (Sloc, Cloc, and Code) for analyzing directories. Includes both synchronous and asynchronous execution methods. Added a test file for the wrapper and documentation for SCC setup.
1 parent 40528eb commit 6155a78

File tree

3 files changed

+167
-0
lines changed

3 files changed

+167
-0
lines changed
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
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)
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package cc.unitmesh.devti.bridge.command
2+
3+
import org.assertj.core.api.Assertions.assertThat
4+
import java.io.File
5+
import kotlin.test.Ignore
6+
import kotlin.test.Test
7+
8+
class SCCWrapperTest {
9+
@Test
10+
@Ignore
11+
fun should_return_correct_output_when_scc_is_executed_successfully() {
12+
// Given
13+
val sccWrapper = SccWrapper()
14+
val directory = "."
15+
16+
// Create a valid directory for testing
17+
File(directory).mkdirs()
18+
19+
// When
20+
val result = sccWrapper.analyzeDirectory(directory, listOf())
21+
22+
// Then
23+
assertThat(result.size).isGreaterThan(0)
24+
}
25+
}

docs/bridge/composer.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
---
2+
layout: default
3+
title: AutoDev Bridge - Legacy Code Migration
4+
nav_order: 5
5+
has_children: true
6+
---
7+
8+
# AutoDev Bridge - Legacy Code Migration
9+
10+
Tools prepare
11+
12+
### [SCC](https://github.com/boyter/scc)
13+
14+
> A tool similar to cloc, sloccount and tokei. For counting the lines of code, blank lines, comment lines, and physical
15+
> lines of source code in many programming languages.
16+
17+
- macOS: `brew install scc`
18+
- Windows: `choco install scc`
19+
- Linux: `snap install scc`
20+
21+
see in [https://github.com/boyter/scc](https://github.com/boyter/scc)

0 commit comments

Comments
 (0)