From 176e37844c17eaabb5c9da0f9c23237df9c0b5a8 Mon Sep 17 00:00:00 2001 From: nea Date: Fri, 26 Aug 2022 14:56:23 +0200 Subject: idk make it maveny --- build.gradle.kts | 64 ++++---- example/build.gradle.kts | 28 +++- src/jsMain/kotlin/moe/nea89/website/Colored.kt | 23 +++ src/jsMain/kotlin/moe/nea89/website/Command.kt | 11 ++ src/jsMain/kotlin/moe/nea89/website/KConsole.kt | 173 +++++++++++++++++++++ src/jsMain/kotlin/moe/nea89/website/KFiles.kt | 161 +++++++++++++++++++ .../moe/nea89/website/ScrollIntoViewOptions.kt | 7 + .../moe/nea89/website/ShellExecutionContext.kt | 51 ++++++ src/jsMain/kotlin/moe/nea89/website/Styles.kt | 55 +++++++ src/jsMain/kotlin/moe/nea89/website/util.kt | 6 + src/main/kotlin/moe/nea89/website/Colored.kt | 23 --- src/main/kotlin/moe/nea89/website/Command.kt | 11 -- src/main/kotlin/moe/nea89/website/KConsole.kt | 173 --------------------- src/main/kotlin/moe/nea89/website/KFiles.kt | 161 ------------------- .../moe/nea89/website/ScrollIntoViewOptions.kt | 7 - .../moe/nea89/website/ShellExecutionContext.kt | 51 ------ src/main/kotlin/moe/nea89/website/Styles.kt | 55 ------- src/main/kotlin/moe/nea89/website/util.kt | 6 - 18 files changed, 547 insertions(+), 519 deletions(-) create mode 100644 src/jsMain/kotlin/moe/nea89/website/Colored.kt create mode 100644 src/jsMain/kotlin/moe/nea89/website/Command.kt create mode 100644 src/jsMain/kotlin/moe/nea89/website/KConsole.kt create mode 100644 src/jsMain/kotlin/moe/nea89/website/KFiles.kt create mode 100644 src/jsMain/kotlin/moe/nea89/website/ScrollIntoViewOptions.kt create mode 100644 src/jsMain/kotlin/moe/nea89/website/ShellExecutionContext.kt create mode 100644 src/jsMain/kotlin/moe/nea89/website/Styles.kt create mode 100644 src/jsMain/kotlin/moe/nea89/website/util.kt delete mode 100644 src/main/kotlin/moe/nea89/website/Colored.kt delete mode 100644 src/main/kotlin/moe/nea89/website/Command.kt delete mode 100644 src/main/kotlin/moe/nea89/website/KConsole.kt delete mode 100644 src/main/kotlin/moe/nea89/website/KFiles.kt delete mode 100644 src/main/kotlin/moe/nea89/website/ScrollIntoViewOptions.kt delete mode 100644 src/main/kotlin/moe/nea89/website/ShellExecutionContext.kt delete mode 100644 src/main/kotlin/moe/nea89/website/Styles.kt delete mode 100644 src/main/kotlin/moe/nea89/website/util.kt diff --git a/build.gradle.kts b/build.gradle.kts index 0844de8..b83f6d8 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,41 +1,47 @@ plugins { - kotlin("js") version "1.7.10" + kotlin("multiplatform") version "1.7.10" + `maven-publish` } -allprojects { - apply(plugin = "org.jetbrains.kotlin.js") - repositories { - mavenCentral() - } +repositories { + mavenCentral() +} +group = "moe.nea" +version = "0.0.1" - kotlin { - sourceSets.all { - languageSettings.optIn("kotlin.ExperimentalStdlibApi") - languageSettings.optIn("kotlin.RequiresOptIn") - } - js(IR) { - browser { - commonWebpackConfig { - sourceMaps = true - cssSupport.enabled = true - } +kotlin { + sourceSets.all { + languageSettings.optIn("kotlin.ExperimentalStdlibApi") + languageSettings.optIn("kotlin.RequiresOptIn") + } + js(IR) { + browser { + commonWebpackConfig { + sourceMaps = true + cssSupport.enabled = true } - binaries.executable() } + binaries.executable() } - - afterEvaluate { - rootProject.extensions.configure { - versions.webpackCli.version = "4.10.0" + sourceSets { + named("jsMain") { + dependencies { + api("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4") + api("org.jetbrains.kotlin-wrappers:kotlin-styled:5.3.5-pre.376") + } } } +} + - dependencies { - implementation(enforcedPlatform("org.jetbrains.kotlin-wrappers:kotlin-wrappers-bom:1.0.0-pre.376")) - api("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4") - api("org.jetbrains.kotlin-wrappers:kotlin-styled") - // implementation(npm("prop-types", "^15.6.2")) +publishing { + repositories { + mavenLocal() + } + publications { + create("library") { + from(components["kotlin"]) + } } } -group = "moe.nea" -version = "0.0.1" \ No newline at end of file + diff --git a/example/build.gradle.kts b/example/build.gradle.kts index 46f4a6d..2efa08e 100644 --- a/example/build.gradle.kts +++ b/example/build.gradle.kts @@ -1,7 +1,29 @@ +plugins { + kotlin("js") +} -dependencies { - implementation(npm("@fontsource/comic-mono", "^4.5.0")) - implementation(rootProject) +repositories { + mavenCentral() } +kotlin { + sourceSets.all { + languageSettings.optIn("kotlin.ExperimentalStdlibApi") + languageSettings.optIn("kotlin.RequiresOptIn") + } + js(IR) { + browser { + commonWebpackConfig { + sourceMaps = true + cssSupport.enabled = true + } + } + binaries.executable() + } +} + +dependencies { + implementation(npm("@fontsource/comic-mono", "^4.5.0")) + implementation(rootProject) +} \ No newline at end of file diff --git a/src/jsMain/kotlin/moe/nea89/website/Colored.kt b/src/jsMain/kotlin/moe/nea89/website/Colored.kt new file mode 100644 index 0000000..9918c85 --- /dev/null +++ b/src/jsMain/kotlin/moe/nea89/website/Colored.kt @@ -0,0 +1,23 @@ +package moe.nea89.website + +import kotlinx.css.* + +enum class CustomColor(val color: Color) { + BLACK(Color("#282a39")), + RED(Color("#ff4473")), + BLUE(Color("#00fefc")), + PURPLE(Color("#6064fe")), + GREEN(Color("#4ce080")), + WHITE(Color("#efefef")), +} + +data class ColoredElement( + val color: CustomColor, + val text: String +) + +fun red(text: String) = ColoredElement(CustomColor.RED, text) +fun blue(text: String) = ColoredElement(CustomColor.BLUE, text) +fun purple(text: String) = ColoredElement(CustomColor.PURPLE, text) +fun green(text: String) = ColoredElement(CustomColor.GREEN, text) + diff --git a/src/jsMain/kotlin/moe/nea89/website/Command.kt b/src/jsMain/kotlin/moe/nea89/website/Command.kt new file mode 100644 index 0000000..b8e4675 --- /dev/null +++ b/src/jsMain/kotlin/moe/nea89/website/Command.kt @@ -0,0 +1,11 @@ +package moe.nea89.website + +data class Command( + val name: String, + val aliases: Set, + val runner: suspend ShellExecutionContext.() -> Unit, +) + + +fun command(name: String, vararg aliases: String, block: suspend ShellExecutionContext. () -> Unit) = + Command(name, aliases.toSet(), block) \ No newline at end of file diff --git a/src/jsMain/kotlin/moe/nea89/website/KConsole.kt b/src/jsMain/kotlin/moe/nea89/website/KConsole.kt new file mode 100644 index 0000000..4a51219 --- /dev/null +++ b/src/jsMain/kotlin/moe/nea89/website/KConsole.kt @@ -0,0 +1,173 @@ +package moe.nea89.website + +import kotlinx.browser.document +import kotlinx.dom.addClass +import kotlinx.html.dom.append +import kotlinx.html.dom.create +import kotlinx.html.input +import kotlinx.html.js.p +import kotlinx.html.js.pre +import kotlinx.html.js.span +import org.w3c.dom.HTMLElement +import org.w3c.dom.HTMLParagraphElement +import org.w3c.dom.HTMLPreElement +import org.w3c.dom.events.KeyboardEvent +import styled.injectGlobal +import kotlin.collections.set + + +class KConsole( + val root: HTMLElement, + val text: HTMLPreElement, + val prompt: HTMLElement, + fileSystem: KFileSystem?, +) { + + + val fileAccessor = fileSystem?.let { FileAccessor(it) } + var PS1: KConsole.() -> String = { "$" } + + companion object { + + init { + injectGlobal(Styles.global) + } + + val shlexRegex = + """"([^"\\]+|\\.)+"|([^ "'\\]+|\\.)+|'([^'\\]+|\\.)+'""".toRegex() + + fun createFor(element: HTMLElement, fileSystem: KFileSystem? = null): KConsole { + val text = element.append.pre() + val prompt = text.append.p() + prompt.addClass(Styles.promptClass) + element.classList.add(Styles.consoleClass) + val console = KConsole(element, text, prompt, fileSystem) + val inp = element.append.input() + inp.hidden = true + inp.focus() + document.body!!.onkeydown = console::keydown + console.rerender() + return console + } + } + + enum class ConsoleState { + SHELLPROMPT, + IN_PROGRAM + } + + var state = ConsoleState.SHELLPROMPT + + var input: String = "" + + fun addLines(newLines: List) { + newLines.forEach { addLine(it) } + } + + fun addMultilineText(text: String) { + addLines(text.split("\n")) + } + + fun addLine(vararg elements: Any) { + addLine(document.create.p().apply { + elements.forEach { + when (it) { + is HTMLElement -> append(it) + is ColoredElement -> append(document.create.span().also { el -> + el.style.color = it.color.color.toString() + el.append(it.text) + }) + + is String -> append(it) + else -> throw RuntimeException("Unknown element") + } + } + }) + } + + private fun addLine(element: HTMLParagraphElement) { + text.insertBefore(element, prompt) + } + + fun rerender() { + if (state == KConsole.ConsoleState.SHELLPROMPT) { + prompt.innerText = "${PS1.invoke(this)} $input" + } else { + prompt.innerText = "" + } + } + + fun scrollDown() { + text.lastElementChild?.scrollIntoView() + } + + fun registerCommand(command: Command) { + command.aliases.forEach { + commands[it] = command + } + commands[command.name] = command + } + + val commands = mutableMapOf() + + fun executeCommand(commandLine: String) { + val parts = shlex(commandLine) + if (parts == null) { + addLine("Syntax Error") + return + } + if (parts.isEmpty()) { + return + } + val command = parts[0] + println("Running command: $command") + val arguments = parts.drop(1) + val commandThing = commands[command] + if (commandThing == null) { + addLine("Unknown command") + return + } + ShellExecutionContext.run(this, commandThing, command, arguments) + scrollDown() + } + + fun shlex(command: String): List? { + var i = 0 + val parts = mutableListOf() + while (i < command.length) { + val match = shlexRegex.matchAt(command, i) + if (match == null) { + println("Could not shlex: $command") + return null + } + // TODO: Proper string unescaping + parts.add(match.groupValues.drop(1).firstOrNull { it != "" } ?: "") + i += match.value.length + while (command[i] == ' ' && i < command.length) + i++ + } + return parts + } + + fun keydown(event: KeyboardEvent) { + if (event.altKey || event.ctrlKey || event.metaKey) return + if (event.isComposing || event.keyCode == 229) return + if (state != ConsoleState.SHELLPROMPT) return + when (event.key) { + "Enter" -> { + val toExecute = input + addLine("${PS1.invoke(this)} $toExecute") + input = "" + executeCommand(toExecute) + } + + "Backspace" -> input = input.substring(0, input.length - 1) + else -> + if (event.key.length == 1 || event.key.any { it !in 'a'..'z' && it !in 'A'..'Z' }) + input += event.key + } + event.preventDefault() + rerender() + scrollDown() + } +} diff --git a/src/jsMain/kotlin/moe/nea89/website/KFiles.kt b/src/jsMain/kotlin/moe/nea89/website/KFiles.kt new file mode 100644 index 0000000..aad3036 --- /dev/null +++ b/src/jsMain/kotlin/moe/nea89/website/KFiles.kt @@ -0,0 +1,161 @@ +package moe.nea89.website + +sealed class KFile { + /** + * Only be empty for the root fs + * */ + var parent: Directory? = null + private set + + val name: List + get() = + parent?.let { it.name + it.files.filter { it.value == this }.keys.first() } ?: emptyList() + + fun linkTo(parent: Directory) { + if (this.parent == null) + this.parent = parent + } + + val fileType: String + get() = when (this) { + is Directory -> "directory" + is Download -> "download" + is Image -> "image" + is Text -> "text file" + } + + data class Text(val text: String) : KFile() + data class Image(val url: String) : KFile() + data class Download(val url: String) : KFile() + data class Directory(val files: Map) : KFile() +} + +data class KFileSystem(val root: KFile.Directory) { + init { + if (!verifyHierarchy(root)) { + throw RuntimeException("File system had missing links. Use linkTo with the primary parent directory") + } + } + + private fun verifyHierarchy(el: KFile.Directory): Boolean = + el.files.values.all { + it.parent == el && (it !is KFile.Directory || verifyHierarchy(it)) + } + + + /** + * Uses normalized paths + * */ + fun resolve(parts: List): KFile? = + parts.fold(root) { current, part -> + if (part == "." || part == "") + current + else if (part == "..") + current?.parent + else if (current is KFile.Directory) { + current.files[part] + } else + null + } +} + + +enum class FSError { + ENOENT, EISNOTDIR +} + +class FileAccessor(val fileSystem: KFileSystem, var implicitPushD: Boolean = false) { // TODO implicit pushd support + val dirStack = mutableListOf>() + var currentDir = listOf() + + fun cd(path: String): FSError? { + val file = resolve(path) ?: return FSError.ENOENT + return when (file) { + !is KFile.Directory -> FSError.EISNOTDIR + else -> { + currentDir = file.name + null + } + } + } + + fun resolve(path: String): KFile? { + val parts = path.split("/").filter { it.isNotEmpty() && it != "." } + return if (path.startsWith("/")) { + fileSystem.resolve(parts) + } else { + fileSystem.resolve(currentDir + parts) + } + } + + fun pushD() { + dirStack.add(currentDir) + } + + fun useD(block: () -> Unit) { + val d = currentDir + try { + block() + } finally { + currentDir = d + } + } + + fun popD(): Boolean { + currentDir = dirStack.removeLastOrNull() ?: return false + return true + } +} + +@DslMarker +annotation class KFileDsl + +fun fileSystem(block: FileSystemBuilder.() -> Unit): KFileSystem = + KFileSystem(FileSystemBuilder().also(block).build()) + + +@KFileDsl +class FileSystemBuilder { + private val files = mutableMapOf() + + fun addNode(name: String, file: KFile): FileSystemBuilder { + val parts = name.split("/", limit = 2) + if (parts.size != 1) { + return addNode(parts[0], FileSystemBuilder().addNode(parts[1], file).build()) + } + if (files.containsKey(name)) { + throw RuntimeException("Tried to double set file: $name") + } + files[name] = file + return this + } + + infix fun String.text(rawText: String) { + addNode(this, KFile.Text(rawText)) + } + + infix fun String.image(dataUrl: String) { + addNode(this, KFile.Image(dataUrl)) + } + + infix fun String.download(url: String) { + addNode(this, KFile.Download(url)) + } + + operator fun String.invoke(block: FileSystemBuilder.() -> Unit) { + addNode(this, FileSystemBuilder().also(block).build()) + } + + fun build() = KFile.Directory(files).also { dir -> + files.values.forEach { file -> file.linkTo(dir) } + } +} + +suspend fun ShellExecutionContext.requireFileAccessor(): FileAccessor { + val fa = console.fileAccessor + if (fa == null) { + console.addLine("There is no file accessor present :(") + exit() + } + return fa +} diff --git a/src/jsMain/kotlin/moe/nea89/website/ScrollIntoViewOptions.kt b/src/jsMain/kotlin/moe/nea89/website/ScrollIntoViewOptions.kt new file mode 100644 index 0000000..aab14e2 --- /dev/null +++ b/src/jsMain/kotlin/moe/nea89/website/ScrollIntoViewOptions.kt @@ -0,0 +1,7 @@ +package moe.nea89.website + +interface ScrollIntoViewOptions { + var behavior: String + var block: String + var inline: String +} \ No newline at end of file diff --git a/src/jsMain/kotlin/moe/nea89/website/ShellExecutionContext.kt b/src/jsMain/kotlin/moe/nea89/website/ShellExecutionContext.kt new file mode 100644 index 0000000..bd72421 --- /dev/null +++ b/src/jsMain/kotlin/moe/nea89/website/ShellExecutionContext.kt @@ -0,0 +1,51 @@ +package moe.nea89.website + +import kotlinx.browser.window +import kotlinx.coroutines.suspendCancellableCoroutine +import kotlin.coroutines.* +import kotlin.time.Duration +import kotlin.time.DurationUnit + +class ShellExecutionContext( + val console: KConsole, + val name: String, + val args: List, +) { + + suspend fun wait(duration: Duration) { + suspendCancellableCoroutine { + window.setTimeout({ + it.resume(Unit) + }, timeout = duration.toInt(DurationUnit.MILLISECONDS)) + } + } + + suspend fun exit(): Nothing { + suspendCancellableCoroutine { + it.cancel() + console.state = KConsole.ConsoleState.SHELLPROMPT + console.rerender() + } + throw RuntimeException("THIs shOULDNT EXIST") + } + + companion object { + fun run( + console: KConsole, command: Command, name: String, args: List + ) { + console.state = KConsole.ConsoleState.IN_PROGRAM + val se = ShellExecutionContext(console, name, args) + window.requestAnimationFrame { + command.runner.createCoroutine(se, object : Continuation { + override val context: CoroutineContext + get() = EmptyCoroutineContext + + override fun resumeWith(result: Result) { + console.state = KConsole.ConsoleState.SHELLPROMPT + console.rerender() + } + }).resume(Unit) + } + } + } +} diff --git a/src/jsMain/kotlin/moe/nea89/website/Styles.kt b/src/jsMain/kotlin/moe/nea89/website/Styles.kt new file mode 100644 index 0000000..e1470d7 --- /dev/null +++ b/src/jsMain/kotlin/moe/nea89/website/Styles.kt @@ -0,0 +1,55 @@ +package moe.nea89.website + +import kotlinx.css.* +import kotlinx.css.properties.IterationCount +import kotlinx.css.properties.Timing +import kotlinx.css.properties.s +import styled.StyleSheet +import styled.animation + + +object Styles : StyleSheet("DefaultConsoleStyles") { + val consoleClass = "Console" + val promptClass = "prompt" + + val bgColor = CustomColor.BLACK.color + val fgColor = CustomColor.WHITE.color + val monospacedFont = "monospace" + + val global by css { + "*" { + padding(0.px) + margin(0.px) + boxSizing = BoxSizing.borderBox + } + + ".$promptClass" { + width = LinearDimension.fitContent + borderRightColor = fgColor + borderRightWidth = 2.px + paddingRight = 2.px + borderRightStyle = BorderStyle.solid + animation(1.s, Timing.stepStart, iterationCount = IterationCount.infinite) { + 0 { + borderRightStyle = BorderStyle.solid + } + 50 { + borderRightStyle = BorderStyle.none + } + } + } + + ".$consoleClass" { + width = 100.pct + height = 100.pct + backgroundColor = bgColor + color = fgColor + fontFamily = monospacedFont + width = 100.pct + height = 100.pct + pre { + fontFamily = monospacedFont + } + } + } +} \ No newline at end of file diff --git a/src/jsMain/kotlin/moe/nea89/website/util.kt b/src/jsMain/kotlin/moe/nea89/website/util.kt new file mode 100644 index 0000000..47c7843 --- /dev/null +++ b/src/jsMain/kotlin/moe/nea89/website/util.kt @@ -0,0 +1,6 @@ +package moe.nea89.website + +fun dyn(init: T.() -> Unit): dynamic = js("{}").also(init) + + + diff --git a/src/main/kotlin/moe/nea89/website/Colored.kt b/src/main/kotlin/moe/nea89/website/Colored.kt deleted file mode 100644 index 9918c85..0000000 --- a/src/main/kotlin/moe/nea89/website/Colored.kt +++ /dev/null @@ -1,23 +0,0 @@ -package moe.nea89.website - -import kotlinx.css.* - -enum class CustomColor(val color: Color) { - BLACK(Color("#282a39")), - RED(Color("#ff4473")), - BLUE(Color("#00fefc")), - PURPLE(Color("#6064fe")), - GREEN(Color("#4ce080")), - WHITE(Color("#efefef")), -} - -data class ColoredElement( - val color: CustomColor, - val text: String -) - -fun red(text: String) = ColoredElement(CustomColor.RED, text) -fun blue(text: String) = ColoredElement(CustomColor.BLUE, text) -fun purple(text: String) = ColoredElement(CustomColor.PURPLE, text) -fun green(text: String) = ColoredElement(CustomColor.GREEN, text) - diff --git a/src/main/kotlin/moe/nea89/website/Command.kt b/src/main/kotlin/moe/nea89/website/Command.kt deleted file mode 100644 index b8e4675..0000000 --- a/src/main/kotlin/moe/nea89/website/Command.kt +++ /dev/null @@ -1,11 +0,0 @@ -package moe.nea89.website - -data class Command( - val name: String, - val aliases: Set, - val runner: suspend ShellExecutionContext.() -> Unit, -) - - -fun command(name: String, vararg aliases: String, block: suspend ShellExecutionContext. () -> Unit) = - Command(name, aliases.toSet(), block) \ No newline at end of file diff --git a/src/main/kotlin/moe/nea89/website/KConsole.kt b/src/main/kotlin/moe/nea89/website/KConsole.kt deleted file mode 100644 index 4a51219..0000000 --- a/src/main/kotlin/moe/nea89/website/KConsole.kt +++ /dev/null @@ -1,173 +0,0 @@ -package moe.nea89.website - -import kotlinx.browser.document -import kotlinx.dom.addClass -import kotlinx.html.dom.append -import kotlinx.html.dom.create -import kotlinx.html.input -import kotlinx.html.js.p -import kotlinx.html.js.pre -import kotlinx.html.js.span -import org.w3c.dom.HTMLElement -import org.w3c.dom.HTMLParagraphElement -import org.w3c.dom.HTMLPreElement -import org.w3c.dom.events.KeyboardEvent -import styled.injectGlobal -import kotlin.collections.set - - -class KConsole( - val root: HTMLElement, - val text: HTMLPreElement, - val prompt: HTMLElement, - fileSystem: KFileSystem?, -) { - - - val fileAccessor = fileSystem?.let { FileAccessor(it) } - var PS1: KConsole.() -> String = { "$" } - - companion object { - - init { - injectGlobal(Styles.global) - } - - val shlexRegex = - """"([^"\\]+|\\.)+"|([^ "'\\]+|\\.)+|'([^'\\]+|\\.)+'""".toRegex() - - fun createFor(element: HTMLElement, fileSystem: KFileSystem? = null): KConsole { - val text = element.append.pre() - val prompt = text.append.p() - prompt.addClass(Styles.promptClass) - element.classList.add(Styles.consoleClass) - val console = KConsole(element, text, prompt, fileSystem) - val inp = element.append.input() - inp.hidden = true - inp.focus() - document.body!!.onkeydown = console::keydown - console.rerender() - return console - } - } - - enum class ConsoleState { - SHELLPROMPT, - IN_PROGRAM - } - - var state = ConsoleState.SHELLPROMPT - - var input: String = "" - - fun addLines(newLines: List) { - newLines.forEach { addLine(it) } - } - - fun addMultilineText(text: String) { - addLines(text.split("\n")) - } - - fun addLine(vararg elements: Any) { - addLine(document.create.p().apply { - elements.forEach { - when (it) { - is HTMLElement -> append(it) - is ColoredElement -> append(document.create.span().also { el -> - el.style.color = it.color.color.toString() - el.append(it.text) - }) - - is String -> append(it) - else -> throw RuntimeException("Unknown element") - } - } - }) - } - - private fun addLine(element: HTMLParagraphElement) { - text.insertBefore(element, prompt) - } - - fun rerender() { - if (state == KConsole.ConsoleState.SHELLPROMPT) { - prompt.innerText = "${PS1.invoke(this)} $input" - } else { - prompt.innerText = "" - } - } - - fun scrollDown() { - text.lastElementChild?.scrollIntoView() - } - - fun registerCommand(command: Command) { - command.aliases.forEach { - commands[it] = command - } - commands[command.name] = command - } - - val commands = mutableMapOf() - - fun executeCommand(commandLine: String) { - val parts = shlex(commandLine) - if (parts == null) { - addLine("Syntax Error") - return - } - if (parts.isEmpty()) { - return - } - val command = parts[0] - println("Running command: $command") - val arguments = parts.drop(1) - val commandThing = commands[command] - if (commandThing == null) { - addLine("Unknown command") - return - } - ShellExecutionContext.run(this, commandThing, command, arguments) - scrollDown() - } - - fun shlex(command: String): List? { - var i = 0 - val parts = mutableListOf() - while (i < command.length) { - val match = shlexRegex.matchAt(command, i) - if (match == null) { - println("Could not shlex: $command") - return null - } - // TODO: Proper string unescaping - parts.add(match.groupValues.drop(1).firstOrNull { it != "" } ?: "") - i += match.value.length - while (command[i] == ' ' && i < command.length) - i++ - } - return parts - } - - fun keydown(event: KeyboardEvent) { - if (event.altKey || event.ctrlKey || event.metaKey) return - if (event.isComposing || event.keyCode == 229) return - if (state != ConsoleState.SHELLPROMPT) return - when (event.key) { - "Enter" -> { - val toExecute = input - addLine("${PS1.invoke(this)} $toExecute") - input = "" - executeCommand(toExecute) - } - - "Backspace" -> input = input.substring(0, input.length - 1) - else -> - if (event.key.length == 1 || event.key.any { it !in 'a'..'z' && it !in 'A'..'Z' }) - input += event.key - } - event.preventDefault() - rerender() - scrollDown() - } -} diff --git a/src/main/kotlin/moe/nea89/website/KFiles.kt b/src/main/kotlin/moe/nea89/website/KFiles.kt deleted file mode 100644 index aad3036..0000000 --- a/src/main/kotlin/moe/nea89/website/KFiles.kt +++ /dev/null @@ -1,161 +0,0 @@ -package moe.nea89.website - -sealed class KFile { - /** - * Only be empty for the root fs - * */ - var parent: Directory? = null - private set - - val name: List - get() = - parent?.let { it.name + it.files.filter { it.value == this }.keys.first() } ?: emptyList() - - fun linkTo(parent: Directory) { - if (this.parent == null) - this.parent = parent - } - - val fileType: String - get() = when (this) { - is Directory -> "directory" - is Download -> "download" - is Image -> "image" - is Text -> "text file" - } - - data class Text(val text: String) : KFile() - data class Image(val url: String) : KFile() - data class Download(val url: String) : KFile() - data class Directory(val files: Map) : KFile() -} - -data class KFileSystem(val root: KFile.Directory) { - init { - if (!verifyHierarchy(root)) { - throw RuntimeException("File system had missing links. Use linkTo with the primary parent directory") - } - } - - private fun verifyHierarchy(el: KFile.Directory): Boolean = - el.files.values.all { - it.parent == el && (it !is KFile.Directory || verifyHierarchy(it)) - } - - - /** - * Uses normalized paths - * */ - fun resolve(parts: List): KFile? = - parts.fold(root) { current, part -> - if (part == "." || part == "") - current - else if (part == "..") - current?.parent - else if (current is KFile.Directory) { - current.files[part] - } else - null - } -} - - -enum class FSError { - ENOENT, EISNOTDIR -} - -class FileAccessor(val fileSystem: KFileSystem, var implicitPushD: Boolean = false) { // TODO implicit pushd support - val dirStack = mutableListOf>() - var currentDir = listOf() - - fun cd(path: String): FSError? { - val file = resolve(path) ?: return FSError.ENOENT - return when (file) { - !is KFile.Directory -> FSError.EISNOTDIR - else -> { - currentDir = file.name - null - } - } - } - - fun resolve(path: String): KFile? { - val parts = path.split("/").filter { it.isNotEmpty() && it != "." } - return if (path.startsWith("/")) { - fileSystem.resolve(parts) - } else { - fileSystem.resolve(currentDir + parts) - } - } - - fun pushD() { - dirStack.add(currentDir) - } - - fun useD(block: () -> Unit) { - val d = currentDir - try { - block() - } finally { - currentDir = d - } - } - - fun popD(): Boolean { - currentDir = dirStack.removeLastOrNull() ?: return false - return true - } -} - -@DslMarker -annotation class KFileDsl - -fun fileSystem(block: FileSystemBuilder.() -> Unit): KFileSystem = - KFileSystem(FileSystemBuilder().also(block).build()) - - -@KFileDsl -class FileSystemBuilder { - private val files = mutableMapOf() - - fun addNode(name: String, file: KFile): FileSystemBuilder { - val parts = name.split("/", limit = 2) - if (parts.size != 1) { - return addNode(parts[0], FileSystemBuilder().addNode(parts[1], file).build()) - } - if (files.containsKey(name)) { - throw RuntimeException("Tried to double set file: $name") - } - files[name] = file - return this - } - - infix fun String.text(rawText: String) { - addNode(this, KFile.Text(rawText)) - } - - infix fun String.image(dataUrl: String) { - addNode(this, KFile.Image(dataUrl)) - } - - infix fun String.download(url: String) { - addNode(this, KFile.Download(url)) - } - - operator fun String.invoke(block: FileSystemBuilder.() -> Unit) { - addNode(this, FileSystemBuilder().also(block).build()) - } - - fun build() = KFile.Directory(files).also { dir -> - files.values.forEach { file -> file.linkTo(dir) } - } -} - -suspend fun ShellExecutionContext.requireFileAccessor(): FileAccessor { - val fa = console.fileAccessor - if (fa == null) { - console.addLine("There is no file accessor present :(") - exit() - } - return fa -} diff --git a/src/main/kotlin/moe/nea89/website/ScrollIntoViewOptions.kt b/src/main/kotlin/moe/nea89/website/ScrollIntoViewOptions.kt deleted file mode 100644 index aab14e2..0000000 --- a/src/main/kotlin/moe/nea89/website/ScrollIntoViewOptions.kt +++ /dev/null @@ -1,7 +0,0 @@ -package moe.nea89.website - -interface ScrollIntoViewOptions { - var behavior: String - var block: String - var inline: String -} \ No newline at end of file diff --git a/src/main/kotlin/moe/nea89/website/ShellExecutionContext.kt b/src/main/kotlin/moe/nea89/website/ShellExecutionContext.kt deleted file mode 100644 index bd72421..0000000 --- a/src/main/kotlin/moe/nea89/website/ShellExecutionContext.kt +++ /dev/null @@ -1,51 +0,0 @@ -package moe.nea89.website - -import kotlinx.browser.window -import kotlinx.coroutines.suspendCancellableCoroutine -import kotlin.coroutines.* -import kotlin.time.Duration -import kotlin.time.DurationUnit - -class ShellExecutionContext( - val console: KConsole, - val name: String, - val args: List, -) { - - suspend fun wait(duration: Duration) { - suspendCancellableCoroutine { - window.setTimeout({ - it.resume(Unit) - }, timeout = duration.toInt(DurationUnit.MILLISECONDS)) - } - } - - suspend fun exit(): Nothing { - suspendCancellableCoroutine { - it.cancel() - console.state = KConsole.ConsoleState.SHELLPROMPT - console.rerender() - } - throw RuntimeException("THIs shOULDNT EXIST") - } - - companion object { - fun run( - console: KConsole, command: Command, name: String, args: List - ) { - console.state = KConsole.ConsoleState.IN_PROGRAM - val se = ShellExecutionContext(console, name, args) - window.requestAnimationFrame { - command.runner.createCoroutine(se, object : Continuation { - override val context: CoroutineContext - get() = EmptyCoroutineContext - - override fun resumeWith(result: Result) { - console.state = KConsole.ConsoleState.SHELLPROMPT - console.rerender() - } - }).resume(Unit) - } - } - } -} diff --git a/src/main/kotlin/moe/nea89/website/Styles.kt b/src/main/kotlin/moe/nea89/website/Styles.kt deleted file mode 100644 index e1470d7..0000000 --- a/src/main/kotlin/moe/nea89/website/Styles.kt +++ /dev/null @@ -1,55 +0,0 @@ -package moe.nea89.website - -import kotlinx.css.* -import kotlinx.css.properties.IterationCount -import kotlinx.css.properties.Timing -import kotlinx.css.properties.s -import styled.StyleSheet -import styled.animation - - -object Styles : StyleSheet("DefaultConsoleStyles") { - val consoleClass = "Console" - val promptClass = "prompt" - - val bgColor = CustomColor.BLACK.color - val fgColor = CustomColor.WHITE.color - val monospacedFont = "monospace" - - val global by css { - "*" { - padding(0.px) - margin(0.px) - boxSizing = BoxSizing.borderBox - } - - ".$promptClass" { - width = LinearDimension.fitContent - borderRightColor = fgColor - borderRightWidth = 2.px - paddingRight = 2.px - borderRightStyle = BorderStyle.solid - animation(1.s, Timing.stepStart, iterationCount = IterationCount.infinite) { - 0 { - borderRightStyle = BorderStyle.solid - } - 50 { - borderRightStyle = BorderStyle.none - } - } - } - - ".$consoleClass" { - width = 100.pct - height = 100.pct - backgroundColor = bgColor - color = fgColor - fontFamily = monospacedFont - width = 100.pct - height = 100.pct - pre { - fontFamily = monospacedFont - } - } - } -} \ No newline at end of file diff --git a/src/main/kotlin/moe/nea89/website/util.kt b/src/main/kotlin/moe/nea89/website/util.kt deleted file mode 100644 index 47c7843..0000000 --- a/src/main/kotlin/moe/nea89/website/util.kt +++ /dev/null @@ -1,6 +0,0 @@ -package moe.nea89.website - -fun dyn(init: T.() -> Unit): dynamic = js("{}").also(init) - - - -- cgit