Misc Changes

This commit is contained in:
Joe Tretter
2026-05-06 20:44:34 -05:00
parent 5c7c7ad29d
commit 5727022baf
7 changed files with 451 additions and 110 deletions

View File

@@ -92,6 +92,11 @@ val RepoStateSaver = listSaver<RepoState, Any>(
)
object GitSandboxEngine {
data class ShellToken(
val value: String,
val quoted: Boolean = false,
)
fun commandReferenceLines(): List<String> = listOf(
"Available sandbox commands:",
" git ",
@@ -109,7 +114,8 @@ object GitSandboxEngine {
)
fun execute(repo: RepoState, command: String): Pair<RepoState, List<String>> {
val parts = tokenizeCommand(command)
val shellParts = tokenizeShellCommand(command)
val parts = shellParts.map { it.value }
if (parts.isEmpty()) return repo to emptyList()
return when {
parts[0] == "help" || parts[0] == "?" -> repo to commandReferenceLines()
@@ -129,7 +135,7 @@ object GitSandboxEngine {
}
}) to emptyList()
}
parts[0] == "echo" -> writeEcho(repo, parts)
parts[0] == "echo" -> writeEcho(repo, shellParts)
parts[0] == "ls" || parts[0] == "dir" -> repo to repo.files.filterNot { it.deleted }.map { it.name }.ifEmpty { listOf() }
parts[0] == "cd.." -> repo.copy(currentDir = parentDirectory(repo.currentDir)) to emptyList()
parts[0] != "git" -> repo to listOf("Command not supported in sandbox. Try a git command or 'touch'.")
@@ -157,7 +163,7 @@ object GitSandboxEngine {
}
}
parts.size >= 3 && parts[1] == "rm" -> removeGitPath(repo, parts.drop(2))
parts.size >= 4 && parts[1] == "mv" -> moveGitPath(repo, expandPathspecs(repo, parts.drop(2)))
parts.size >= 4 && parts[1] == "mv" -> moveGitPath(repo, expandPathspecTokens(repo, shellParts.drop(2)))
parts.size >= 2 && parts[1] == "commit" -> commit(repo, parts.drop(2))
parts.size >= 2 && parts[1] == "log" -> {
repo to if (repo.commits.isEmpty()) listOf("fatal: your current branch '${repo.headBranch}' does not have any commits yet")
@@ -194,12 +200,30 @@ object GitSandboxEngine {
}
fun tokenizeCommand(command: String): List<String> {
val result = mutableListOf<String>()
return tokenizeShellCommand(command).map { it.value }
}
fun tokenizeShellCommand(command: String): List<ShellToken> {
val result = mutableListOf<ShellToken>()
val current = StringBuilder()
var quoteChar: Char? = null
var escaping = false
var currentQuoted = false
var skipNext = false
command.forEach { char ->
fun emitCurrent(force: Boolean = false) {
if (current.isNotEmpty() || force && currentQuoted) {
result += ShellToken(value = current.toString(), quoted = currentQuoted)
current.clear()
currentQuoted = false
}
}
command.forEachIndexed { index, char ->
if (skipNext) {
skipNext = false
return@forEachIndexed
}
when {
escaping -> {
current.append(char)
@@ -217,11 +241,18 @@ object GitSandboxEngine {
}
char == '"' || char == '\'' -> {
quoteChar = char
currentQuoted = true
}
char.isWhitespace() -> {
if (current.isNotEmpty()) {
result += current.toString()
current.clear()
emitCurrent()
}
char == '>' -> {
emitCurrent()
if (command.getOrNull(index + 1) == '>') {
result += ShellToken(">>")
skipNext = true
} else if (command.getOrNull(index - 1) != '>') {
result += ShellToken(">")
}
}
else -> current.append(char)
@@ -231,14 +262,13 @@ object GitSandboxEngine {
if (escaping) {
current.append('\\')
}
if (current.isNotEmpty()) {
result += current.toString()
}
emitCurrent()
return result
}
private fun writeEcho(repo: RepoState, parts: List<String>): Pair<RepoState, List<String>> {
private fun writeEcho(repo: RepoState, shellParts: List<ShellToken>): Pair<RepoState, List<String>> {
val parts = shellParts.map { it.value }
val redirectIndex = parts.indexOfFirst { it == ">" || it == ">>" }
if (redirectIndex == -1 || redirectIndex == parts.lastIndex) {
return repo to listOf(parts.drop(1).joinToString(" "))
@@ -266,8 +296,13 @@ object GitSandboxEngine {
}
fun expandPathspecs(repo: RepoState, arguments: List<String>): List<String> {
return arguments.flatMap { argument ->
if (!argument.hasGlob()) {
return expandPathspecTokens(repo, arguments.map { ShellToken(it) })
}
fun expandPathspecTokens(repo: RepoState, arguments: List<ShellToken>): List<String> {
return arguments.flatMap { token ->
val argument = token.value
if (token.quoted || !argument.hasGlob()) {
listOf(argument)
} else {
val regex = argument.globToRegex()