lyng CLI: support for shebang, started shell KMP code

This commit is contained in:
Sergey Chernov 2025-06-14 01:20:00 +04:00
parent b961296425
commit e0bb183929
5 changed files with 96 additions and 3 deletions

3
bin/lyng_test Executable file
View File

@ -0,0 +1,3 @@
#!/usr/bin/env lyng
println("Hello from lyng!")

View File

@ -29,11 +29,11 @@ kotlin {
sourceSets {
val commonMain by getting {
dependencies {
implementation(kotlin("stdlib-common"))
implementation(project(":lynglib"))
implementation(libs.okio)
implementation(libs.clikt)
implementation(kotlin("stdlib-common"))
// optional support for rendering markdown in help messages
// implementation(libs.clikt.markdown)
}
@ -42,9 +42,15 @@ kotlin {
dependencies {
implementation(kotlin("test-common"))
implementation(kotlin("test-annotations-common"))
implementation(libs.kotlinx.coroutines.core)
implementation(libs.okio.fakefilesystem)
}
}
// val nativeMain by getting {
// dependencies {
// implementation(kotlin("stdlib-common"))
// }
// }
val linuxX64Main by getting {
}

View File

@ -17,11 +17,28 @@ import okio.use
expect fun exit(code: Int)
expect class ShellCommandExecutor {
fun executeCommand(command: String): CommandResult
companion object {
fun create(): ShellCommandExecutor
}
}
data class CommandResult(
val exitCode: Int,
val output: String,
val error: String
)
val baseContext = Context().apply {
addFn("exit") {
exit(requireOnlyArg<ObjInt>().toInt())
ObjVoid
}
// ObjString.type.addFn("shell") {
//
// }
}
class Lyng(val launcher: (suspend () -> Unit) -> Unit) : CliktCommand() {
@ -90,11 +107,16 @@ class Lyng(val launcher: (suspend () -> Unit) -> Unit) : CliktCommand() {
}
suspend fun executeFile(fileName: String) {
val text = FileSystem.SYSTEM.source(fileName.toPath()).use { fileSource ->
var text = FileSystem.SYSTEM.source(fileName.toPath()).use { fileSource ->
fileSource.buffer().use { bs ->
bs.readUtf8()
}
}
if( text.startsWith("#!") ) {
// skip shebang
val pos = text.indexOf('\n')
text = text.substring(pos + 1)
}
processErrors {
Compiler().compile(Source(fileName, text)).execute(baseContext)
}

View File

@ -0,0 +1,21 @@
package net.sergeych
// Alternative implementation for native targets
actual class ShellCommandExecutor() {
actual fun executeCommand(command: String): CommandResult {
val process = ProcessBuilder("/bin/sh", "-c", command).start()
val exitCode = process.waitFor()
val output = process.inputStream.bufferedReader().readText()
val error = process.errorStream.bufferedReader().readText()
return CommandResult(
exitCode = exitCode,
output = output.trim(),
error = error.trim()
)
}
actual companion object {
actual fun create(): ShellCommandExecutor = ShellCommandExecutor()
}
}

View File

@ -1,7 +1,48 @@
@file:OptIn(ExperimentalForeignApi::class, ExperimentalForeignApi::class)
package net.sergeych
import kotlinx.cinterop.*
import platform.posix.fgets
import platform.posix.pclose
import platform.posix.popen
import kotlin.system.exitProcess
actual class ShellCommandExecutor() {
actual fun executeCommand(command: String): CommandResult {
val outputBuilder = StringBuilder()
val errorBuilder = StringBuilder()
val fp = popen(command, "r") ?: return CommandResult(
exitCode = -1,
output = "",
error = "Failed to execute command"
)
val buffer = ByteArray(4096)
while (true) {
val bytesRead = buffer.usePinned { pinned ->
fgets(pinned.addressOf(0), buffer.size.convert(), fp)
}
if (bytesRead == null) break
outputBuilder.append(bytesRead.toKString())
}
val status = pclose(fp)
val exitCode = if (status == 0) 0 else 1
return CommandResult(
exitCode = exitCode,
output = outputBuilder.toString().trim(),
error = errorBuilder.toString().trim()
)
}
actual companion object {
actual fun create(): ShellCommandExecutor = ShellCommandExecutor()
}
}
actual fun exit(code: Int) {
exitProcess(code)
}