Files
Githug-Android/app/src/main/java/solutions/tretter/githugandroid/GitHelperCommands.kt

135 lines
5.7 KiB
Kotlin

package solutions.tretter.githugandroid
import java.io.File
internal class GitHelperCommands(
private val processRunner: GitProcessRunner,
) {
fun executeExecutableShortcut(
sandboxRoot: File,
workingDir: File,
currentRepo: RepoState,
tokens: List<String>,
): Pair<RepoState, List<String>>? {
val executable = tokens.firstOrNull() ?: return null
if (!executable.startsWith("./") || executable.length <= 2) return null
return executeShellScript(sandboxRoot, workingDir, currentRepo, listOf("sh", executable) + tokens.drop(1))
}
fun execute(
sandboxRoot: File,
workingDir: File,
currentRepo: RepoState,
tokens: List<String>,
): Pair<RepoState, List<String>> {
return when (tokens.first()) {
"ls", "dir" -> currentRepo to workingDir.listFiles()
?.sortedBy { it.name }
?.map { it.name }
.orEmpty()
"pwd" -> currentRepo to listOf(
"/sandbox" + if (workingDir == sandboxRoot) "" else "/${workingDir.relativeTo(sandboxRoot).path}"
)
"cat" -> {
val target = tokens.getOrNull(1) ?: return currentRepo to listOf("usage: cat <file>")
val file = File(workingDir, target)
if (!file.exists() || file.isDirectory) currentRepo to listOf("cat: $target: No such file")
else currentRepo to file.readLines().ifEmpty { listOf("") }
}
"sh" -> executeShellScript(sandboxRoot, workingDir, currentRepo, tokens)
"touch" -> {
val target = tokens.getOrNull(1) ?: return currentRepo to listOf("usage: touch <file>")
val file = File(workingDir, target)
if (file.exists()) {
currentRepo to listOf("$target already exists")
} else {
file.parentFile?.mkdirs()
file.writeText("")
currentRepo to emptyList()
}
}
"mkdir", "md" -> {
val target = tokens.getOrNull(1) ?: return currentRepo to listOf("usage: mkdir <dir>")
val dir = File(workingDir, target)
if (dir.exists()) currentRepo to listOf("mkdir: $target: File exists") else {
dir.mkdirs()
currentRepo to emptyList()
}
}
"cd", "cd.." -> {
val target = if (tokens.first() == "cd..") ".." else tokens.getOrNull(1)
?: return currentRepo to listOf("usage: cd <dir>")
val dir = File(workingDir, target).canonicalFile
when {
!dir.exists() -> currentRepo to listOf("cd: $target: does not exist")
!dir.isDirectory -> currentRepo to listOf("cd: $target: Not a directory")
!dir.path.startsWith(sandboxRoot.path) -> currentRepo to listOf("cd: $target: Permission denied")
else -> {
val relativeDir = sandboxRoot.toPath().relativize(dir.toPath()).toString().ifEmpty { "." }
currentRepo.copy(currentDir = relativeDir) to emptyList()
}
}
}
"rm", "del" -> {
val targets = tokens.drop(1)
if (targets.isEmpty()) return currentRepo to listOf("usage: rm <path>")
val errors = targets.mapNotNull { target ->
val file = File(workingDir, target)
if (!file.exists()) {
"${tokens.first()}: $target: No such file or directory"
} else {
file.deleteRecursively()
null
}
}
currentRepo to errors
}
"echo" -> executeEcho(workingDir, currentRepo, tokens)
else -> currentRepo to listOf("Command not supported in prototype runtime. Try a git command or helper command.")
}
}
private fun executeShellScript(
sandboxRoot: File,
workingDir: File,
currentRepo: RepoState,
tokens: List<String>,
): Pair<RepoState, List<String>> {
val script = tokens.getOrNull(1) ?: return currentRepo to listOf("usage: sh <script>")
val scriptFile = File(workingDir, script).canonicalFile
if (!scriptFile.path.startsWith(sandboxRoot.path)) {
return currentRepo to listOf("sh: $script: Permission denied")
}
if (!scriptFile.isFile) {
return currentRepo to listOf("sh: $script: No such file")
}
val result = processRunner.runShellProcess(
File(processRunner.shellExecutable()),
workingDir,
tokens.drop(1),
)
return currentRepo to result.outputLines
}
private fun executeEcho(workingDir: File, currentRepo: RepoState, tokens: List<String>): Pair<RepoState, List<String>> {
val redirectIndex = tokens.indexOfFirst { it == ">" || it == ">>" }
if (redirectIndex == -1 || redirectIndex == tokens.lastIndex) {
return currentRepo to listOf(tokens.drop(1).joinToString(" "))
}
val file = File(workingDir, tokens[redirectIndex + 1])
file.parentFile?.mkdirs()
val content = tokens.subList(1, redirectIndex).joinToString(" ")
if (tokens[redirectIndex] == ">>") {
if (file.exists() && file.length() > 0L) {
file.appendText("\n$content")
} else {
file.writeText(content)
}
} else {
file.writeText(content)
}
return currentRepo to emptyList()
}
}