Add lyng.io.process module for process execution utilities and shell scripting sugar (e.g., sh, exec, CommandRun).

This commit is contained in:
Sergey Chernov 2026-05-10 15:04:32 +03:00
parent 3e74019d9d
commit 234d1ef02b
8 changed files with 611 additions and 7 deletions

View File

@ -88,6 +88,10 @@ Sources: `lynglib/src/commonMain/kotlin/net/sergeych/lyng/Script.kt`, `lynglib/s
Requires installing `lyngio` into the import manager from host code.
- `import lyng.io.fs` (filesystem `Path` API)
- `import lyng.io.process` (process execution API)
- Shell-script sugar: `sh(command): CommandRun` and `exec(executable, args=[]): CommandRun`.
- Prefer `sh("git status --short").out` for small shell output, `sh("...").lines` for large stdout streams, and `.check()` for commands that must exit with code 0.
- Prefer `exec("git", ["add", file])` when arguments come from data, filenames, or user input; it bypasses shell parsing.
- `CommandRun` is active and owns process pipes; choose one consumption path per stream (`out` or `lines`, `err` or `errorLines`).
- `import lyng.io.console` (console capabilities, geometry, ANSI/output, events)
- `import lyng.io.http` (HTTP/HTTPS client API)
- `import lyng.io.http.server` (minimal HTTP/1.1 and WebSocket server API)

View File

@ -3,6 +3,7 @@
This module provides a way to run external processes and shell commands from Lyng scripts. It is designed to be multiplatform and uses coroutines for non-blocking execution.
> **Note:** `lyngio` is a separate library module. It must be explicitly added as a dependency to your host application and initialized in your Lyng scopes.
> The reference `lyng`/`jlyng` CLI installs this module in its import manager, so CLI scripts can use `import lyng.io.process` directly.
---
@ -44,24 +45,241 @@ suspend fun bootstrapProcess() {
---
#### Using from Lyng scripts
#### Shell-script shorthand
Most CLI scripts should start with the shorthand API:
```lyng
import lyng.io.process
val branch = sh("git branch --show-current").out.trim()
println("Branch: " + branch)
```
`sh(command)` starts `command` through the platform shell and returns an active `CommandRun`.
The process is already running when the handle is returned. The handle gives you both convenient capture properties and streaming line flows:
```lyng
val run = sh("git status --short")
println(run.out) // capture all stdout as String
println(run.err) // capture all stderr as String
println(run.code) // known after out/err/wait/check, otherwise null
println(run.ok) // true, false, or null
```
Use `.check()` when a non-zero exit code should fail the script:
```lyng
sh("lyng fmt --check examples/*.lyng").check()
sh("git diff --check").check()
```
Use `.wait()` when the exit code is data and should not raise:
```lyng
val code = sh("git diff --quiet").wait()
if (code == 0) {
println("clean")
} else {
println("changed")
}
```
Use `.lines` for output that may be large. It streams stdout instead of buffering the entire output:
```lyng
for (file in sh("git ls-files").lines) {
if (file.endsWith(".lyng")) {
println(file)
}
}
```
You can combine it with normal iterable helpers:
```lyng
val count = sh("git ls-files").lines.count {
it.endsWith(".lyng")
}
println("Lyng files: " + count)
```
Use `exec(executable, args)` when arguments come from data, filenames, or user input:
```lyng
val file = ARGV[0] as String
exec("git", ["add", file]).check()
```
`exec(...)` bypasses the shell, so arguments are passed as argv entries rather than parsed by shell quoting rules.
```lyng
val status = exec("git", ["status", "--short"]).out
println(status)
```
---
#### Capture vs streaming
`CommandRun` is active: it owns a running process. Choose the consumption style intentionally.
- `.out` captures stdout into memory, waits for the process to finish, and also drains stderr concurrently.
- `.err` captures stderr into memory, waits for the process to finish, and also drains stdout concurrently.
- `.check()` captures both streams, waits, and fails if the exit code is non-zero.
- `.lines` streams stdout line by line and does not buffer the whole output.
- `.errorLines` streams stderr line by line and does not buffer the whole error output.
- `.wait()` only waits and returns the exit code.
Treat a `CommandRun` as a one-shot stream owner. Pick one stdout consumption path (`out` or `lines`) and one stderr consumption path (`err` or `errorLines`). Do not collect the same stream twice; it is backed by the process pipe.
For small output, capture is simplest:
```lyng
val version = sh("git describe --tags --always").out.trim()
```
For large output, stream:
```lyng
for (line in sh("find . -type f").lines) {
if (line.endsWith(".lyng")) {
println(line)
}
}
```
For commands that may write a lot to both stdout and stderr, consume both streams. One practical pattern is to collect stderr in a background task while processing stdout:
```lyng
val r = sh("some-command-that-writes-a-lot")
val errors: List<String> = []
val stderrTask = launch {
for (line in r.errorLines) {
errors.add(line)
}
}
for (line in r.lines) {
println("OUT: " + line)
}
val code = r.wait()
stderrTask.await()
if (code != 0) {
println(errors.joinToString("\n"))
exit(code)
}
```
As with ordinary shell programming, avoid using `.out` for unbounded output. It is meant for command results that comfortably fit in memory.
---
#### Common script patterns
Capture one small value:
```lyng
val currentCommit = exec("git", ["rev-parse", "--short", "HEAD"]).out.trim()
```
Run a command for its side effects and fail on error:
```lyng
exec("mkdir", ["-p", "build/reports"]).check()
sh("lyng fmt --check src/**/*.lyng").check()
```
Filter a long output stream:
```lyng
for (path in sh("git ls-files").lines) {
if (path.endsWith(".lyng")) {
println(path)
}
}
```
Branch on a command status:
```lyng
if (exec("git", ["diff", "--quiet"]).wait() != 0) {
println("working tree has changes")
}
```
Keep user-controlled text out of the shell:
```lyng
val file = ARGV[0] as String
exec("git", ["log", "--oneline", "--", file]).check()
```
---
#### `sh` vs `exec`
Use `sh(...)` when you intentionally want shell syntax:
```lyng
sh("git status --short | wc -l").out.trim()
sh("find . -name '*.lyng' | sort").check()
```
Use `exec(...)` when you already have structured arguments:
```lyng
exec("git", ["commit", "-m", message]).check()
exec("cp", [sourcePath, targetPath]).check()
```
This distinction matters for safety. Shell commands are parsed by `/bin/sh -c` on Unix-like systems or the platform shell on supported platforms. If you interpolate untrusted text into a shell string, it can change the command being run. Prefer `exec(...)` for user input and filenames.
---
#### Low-level process API
The shorthand API is built on top of `Process.execute(...)` and `Process.shell(...)`. Use the low-level API when you need direct process control, including explicit signals or forceful termination:
```lyng
import lyng.io.process
// Execute a process with arguments
val p = Process.execute("ls", ["-l", "/tmp"])
for (line in p.stdout) {
for (line in p.stdout()) {
println("OUT: " + line)
}
val exitCode = p.waitFor()
println("Process exited with: " + exitCode)
// Run a shell command
val sh = Process.shell("echo 'Hello from shell' | wc -w")
for (line in sh.stdout) {
val shellProcess = Process.shell("echo 'Hello from shell' | wc -w")
for (line in shellProcess.stdout()) {
println("Word count: " + line.trim())
}
```
Signals and termination:
```lyng
val server = Process.shell("python3 -m http.server 8080")
delay(1000)
server.signal("SIGTERM")
val code = server.waitFor()
println("server exited: " + code)
```
---
#### Platform information
```lyng
import lyng.io.process
// Platform information
val details = Platform.details()
@ -77,15 +295,57 @@ if (Platform.isSupported()) {
---
#### Executable script example
```lyng
#!/usr/bin/env lyng
import lyng.io.process
if (ARGV.size == 0) {
println("usage: changed-files.lyng <extension>")
exit(2)
}
val ext = ARGV[0] as String
var matches = 0
for (file in sh("git status --short").lines) {
if (file.endsWith(ext)) {
println(file)
matches++
}
}
println("matched: " + matches)
```
---
#### API Reference
##### `Process` (static methods)
- `execute(executable: String, args: List<String>): RunningProcess` — Start an external process.
- `shell(command: String): RunningProcess` — Run a command through the system shell (e.g., `/bin/sh` or `cmd.exe`).
##### Shorthand functions
- `sh(command: String): CommandRun` — Run a command through the system shell and return an active handle.
- `exec(executable: String, args: List<String> = []): CommandRun` — Run an executable with argv-style arguments and return an active handle.
##### `CommandRun`
- `command: String` — Original command display text.
- `lines: Flow<String>` — Streaming stdout lines. Use for large output.
- `errorLines: Flow<String>` — Streaming stderr lines. Use for large error output.
- `out: String` — Captured stdout. Captures both stdout and stderr concurrently and waits for completion.
- `err: String` — Captured stderr. Captures both stdout and stderr concurrently and waits for completion.
- `wait(): Int` — Wait for the command to exit and return the exit code.
- `check(): CommandRun` — Capture output, wait, and fail if the exit code is non-zero.
- `code: Int?` — Known exit code after `wait`, `check`, `out`, or `err`; otherwise `null`.
- `ok: Bool?``true` for exit code 0, `false` for non-zero, or `null` before the exit code is known.
##### `RunningProcess` (instance methods)
- `stdout: Flow` — Standard output stream as a Lyng Flow of lines.
- `stderr: Flow` — Standard error stream as a Lyng Flow of lines.
- `stdout(): Flow` — Standard output stream as a Lyng Flow of lines.
- `stderr(): Flow` — Standard error stream as a Lyng Flow of lines.
- `waitFor(): Int` — Wait for the process to exit and return the exit code.
- `signal(name: String)` — Send a signal to the process (e.g., `"SIGINT"`, `"SIGTERM"`, `"SIGKILL"`).
- `destroy()` — Forcefully terminate the process.
@ -110,6 +370,7 @@ Example of a restricted policy in Kotlin:
```kotlin
import net.sergeych.lyngio.fs.security.AccessDecision
import net.sergeych.lyngio.fs.security.AccessContext
import net.sergeych.lyngio.fs.security.Decision
import net.sergeych.lyngio.process.security.ProcessAccessOp
import net.sergeych.lyngio.process.security.ProcessAccessPolicy

View File

@ -4,6 +4,7 @@ The Lyng CLI is the reference command-line tool for the Lyng language. It lets y
- Run Lyng scripts from files or inline strings (shebangs accepted)
- Use standard argument passing (`ARGV`) to your scripts.
- Import `lyng.io.process` for shell-script style process execution (`sh`, `exec`, and `CommandRun`).
- Resolve local file imports from the executed script's directory tree.
- Format Lyng source files via the built-in `fmt` subcommand.
- Register synchronous process-exit handlers with `atExit(...)`.
@ -81,6 +82,15 @@ lyng -- -my-script.lyng arg1 arg2
lyng -x "println(\"Hello\")" more args
```
The CLI installs `lyng.io.process`, so scripts can use the shell/process shorthand after importing it:
```lyng
import lyng.io.process
val branch = sh("git branch --show-current").out.trim()
exec("git", ["status", "--short"]).check()
```
- Print version/help:
```

View File

@ -14,7 +14,7 @@
- **[lyng.io.db](lyng.io.db.md):** Portable SQL database access. Provides `Database`, `SqlTransaction`, `ResultSet`, SQLite support through `lyng.io.db.sqlite`, and JVM JDBC support through `lyng.io.db.jdbc`.
- **[lyng.io.fs](lyng.io.fs.md):** Async filesystem access. Provides the `Path` class for file/directory operations, streaming, and globbing.
- **[lyng.io.process](lyng.io.process.md):** External process execution and shell commands. Provides `Process`, `RunningProcess`, and `Platform` information.
- **[lyng.io.process](lyng.io.process.md):** External process execution and shell commands. Provides shell-script sugar (`sh`, `exec`, `CommandRun`), low-level `Process`/`RunningProcess`, and `Platform` information.
- **[lyng.io.console](lyng.io.console.md):** Rich console/TTY access. Provides `Console` capability detection, geometry, output, and iterable events.
- **[lyng.io.http](lyng.io.http.md):** HTTP/HTTPS client access. Provides `Http`, `HttpRequest`, `HttpResponse`, and `HttpHeaders`.
- **[lyng.io.http.server](lyng.io.http.server.md):** Minimal HTTP/1.1 and WebSocket server. Provides `HttpServer`, `Router`, `ServerRequest`, `RequestContext`, and `ServerWebSocket`.

View File

@ -49,6 +49,7 @@ import net.sergeych.lyng.io.html.createHtmlModule
import net.sergeych.lyng.io.http.createHttpModule
import net.sergeych.lyng.io.http.server.createHttpServerModule
import net.sergeych.lyng.io.net.createNetModule
import net.sergeych.lyng.io.process.createProcessModule
import net.sergeych.lyng.io.ws.createWsModule
import net.sergeych.lyng.obj.*
import net.sergeych.lyng.pacman.ImportManager
@ -57,6 +58,7 @@ import net.sergeych.lyngio.fs.security.PermitAllAccessPolicy
import net.sergeych.lyngio.http.security.PermitAllHttpAccessPolicy
import net.sergeych.lyngio.net.security.PermitAllNetAccessPolicy
import net.sergeych.lyngio.net.shutdownSystemNetEngine
import net.sergeych.lyngio.process.security.PermitAllProcessAccessPolicy
import net.sergeych.lyngio.ws.security.PermitAllWsAccessPolicy
import net.sergeych.mp_tools.globalDefer
import okio.*
@ -150,6 +152,7 @@ private fun ImportManager.invalidateCliModuleCaches() {
invalidatePackageCache("lyng.io.html")
invalidatePackageCache("lyng.io.http")
invalidatePackageCache("lyng.io.http.server")
invalidatePackageCache("lyng.io.process")
invalidatePackageCache("lyng.io.ws")
invalidatePackageCache("lyng.io.net")
}
@ -242,6 +245,7 @@ private fun installCliModules(manager: ImportManager) {
createHtmlModule(manager)
createHttpModule(PermitAllHttpAccessPolicy, manager)
createHttpServerModule(PermitAllNetAccessPolicy, manager)
createProcessModule(PermitAllProcessAccessPolicy, manager)
createWsModule(PermitAllWsAccessPolicy, manager)
createNetModule(PermitAllNetAccessPolicy, manager)
}

View File

@ -17,10 +17,16 @@
package net.sergeych.lyng.io.process
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.toList
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import net.sergeych.lyng.ModuleScope
import net.sergeych.lyng.Scope
import net.sergeych.lyng.ScopeFacade
import net.sergeych.lyng.Source
import net.sergeych.lyng.miniast.*
import net.sergeych.lyng.obj.*
import net.sergeych.lyng.pacman.ImportManager
@ -28,6 +34,24 @@ import net.sergeych.lyng.requireScope
import net.sergeych.lyngio.process.*
import net.sergeych.lyngio.process.security.ProcessAccessDeniedException
import net.sergeych.lyngio.process.security.ProcessAccessPolicy
import net.sergeych.lyngio.stdlib_included.processLyng
import kotlin.Boolean
import kotlin.Exception
import kotlin.Int
import kotlin.String
import kotlin.also
import kotlin.apply
import kotlin.collections.joinToString
import kotlin.collections.listOf
import kotlin.collections.map
import kotlin.collections.mutableListOf
import kotlin.collections.mutableMapOf
import kotlin.collections.toTypedArray
import kotlin.let
import kotlin.takeIf
import kotlin.text.isNotBlank
import kotlin.text.uppercase
import kotlin.to
/**
* Install Lyng module `lyng.io.process` into the given scope's ImportManager.
@ -47,6 +71,8 @@ fun createProcessModule(policy: ProcessAccessPolicy, manager: ImportManager): Bo
}
private suspend fun buildProcessModule(module: ModuleScope, policy: ProcessAccessPolicy) {
module.eval(Source("lyng.io.process", processLyng))
val runner = try {
SecuredLyngProcessRunner(getSystemProcessRunner(), policy)
} catch (e: Exception) {
@ -54,6 +80,7 @@ private suspend fun buildProcessModule(module: ModuleScope, policy: ProcessAcces
}
val runningProcessType = object : ObjClass("RunningProcess") {}
val commandRunType = object : ObjClass("CommandRun") {}
runningProcessType.apply {
addFnDoc(
@ -149,6 +176,87 @@ private suspend fun buildProcessModule(module: ModuleScope, policy: ProcessAcces
}
}
commandRunType.apply {
addPropertyDoc(
name = "command",
doc = "Original shell command or argv-style command display text.",
type = type("lyng.String"),
moduleName = module.packageName,
getter = { ObjString(thisAs<ObjCommandRun>().command) }
)
addPropertyDoc(
name = "out",
doc = "Captured standard output as a string. Captures both stdout and stderr concurrently.",
type = type("lyng.String"),
moduleName = module.packageName,
getter = { ObjString(thisAs<ObjCommandRun>().captureAll().stdoutText) }
)
addPropertyDoc(
name = "err",
doc = "Captured standard error as a string. Captures both stdout and stderr concurrently.",
type = type("lyng.String"),
moduleName = module.packageName,
getter = { ObjString(thisAs<ObjCommandRun>().captureAll().stderrText) }
)
addPropertyDoc(
name = "lines",
doc = "Streaming standard output lines. Use this for large output instead of `out`.",
type = type("lyng.Flow"),
moduleName = module.packageName,
getter = { thisAs<ObjCommandRun>().process.stdout.toLyngFlow(this) }
)
addPropertyDoc(
name = "errorLines",
doc = "Streaming standard error lines. Use this for large stderr output instead of `err`.",
type = type("lyng.Flow"),
moduleName = module.packageName,
getter = { thisAs<ObjCommandRun>().process.stderr.toLyngFlow(this) }
)
addPropertyDoc(
name = "code",
doc = "Exit code after `wait`, `check`, `out`, or `err`; otherwise null.",
type = type("lyng.Int?"),
moduleName = module.packageName,
getter = {
val code = thisAs<ObjCommandRun>().knownExitCode()
code?.toObj() ?: ObjNull
}
)
addPropertyDoc(
name = "ok",
doc = "True if the known exit code is zero, false if non-zero, or null before the process is known to have exited.",
type = type("lyng.Bool?"),
moduleName = module.packageName,
getter = {
val code = thisAs<ObjCommandRun>().knownExitCode()
code?.let { (it == 0).toObj() } ?: ObjNull
}
)
addFnDoc(
name = "wait",
doc = "Wait for the process to exit and return its exit code.",
returns = type("lyng.Int"),
moduleName = module.packageName
) {
thisAs<ObjCommandRun>().waitFor().toObj()
}
addFnDoc(
name = "check",
doc = "Capture output, wait for completion, and fail if the exit code is non-zero.",
returns = type("CommandRun"),
moduleName = module.packageName
) {
val command = thisAs<ObjCommandRun>()
val captured = command.captureAll()
if (captured.exitCode != 0) {
val detail = captured.stderrText.takeIf { it.isNotBlank() } ?: captured.stdoutText
val suffix = detail.takeIf { it.isNotBlank() }?.let { ": $it" } ?: ""
raiseError("command failed with exit code ${captured.exitCode}: ${command.command}$suffix")
}
command
}
}
val platformType = object : ObjClass("Platform") {}
platformType.apply {
@ -197,6 +305,45 @@ private suspend fun buildProcessModule(module: ModuleScope, policy: ProcessAcces
type = type("RunningProcess"),
moduleName = module.packageName
)
module.addConstDoc(
name = "CommandRun",
value = commandRunType,
doc = "Shell-script friendly handle for a running command.",
type = type("CommandRun"),
moduleName = module.packageName
)
module.addFnDoc(
"sh",
doc = "Run a command via the system shell and return an active command handle.",
params = listOf(ParamDoc("command", type("lyng.String"))),
returns = type("CommandRun"),
moduleName = module.packageName
) {
if (runner == null) raiseError("Processes are not supported on this platform")
processGuard {
val command = requireOnlyArg<ObjString>().value
ObjCommandRun(commandRunType, command, runner.shell(command))
}
}
module.addFnDoc(
"exec",
doc = "Run an executable with argv-style arguments and return an active command handle.",
params = listOf(ParamDoc("executable", type("lyng.String")), ParamDoc("args", type("lyng.List"))),
returns = type("CommandRun"),
moduleName = module.packageName
) {
if (runner == null) raiseError("Processes are not supported on this platform")
processGuard {
if (args.list.size > 2) {
raiseError("Expected at most 2 arguments, got ${args.list.size}")
}
val executable = requiredArg<ObjString>(0).value
val rawArgs = if (args.list.size >= 2) requiredArg<ObjList>(1) else ObjList(mutableListOf())
val argv = rawArgs.list.map { (it as? ObjString)?.value ?: it.toString() }
ObjCommandRun(commandRunType, listOf(executable, *argv.toTypedArray()).joinToString(" "), runner.execute(executable, argv))
}
}
}
class ObjRunningProcess(
@ -206,6 +353,49 @@ class ObjRunningProcess(
override fun toString(): String = "RunningProcess($process)"
}
private data class CapturedCommandOutput(
val exitCode: Int,
val stdoutText: String,
val stderrText: String
)
private class ObjCommandRun(
override val objClass: ObjClass,
val command: String,
val process: LyngProcess
) : Obj() {
private val captureMutex = Mutex()
private var captured: CapturedCommandOutput? = null
private var exitCode: Int? = null
fun knownExitCode(): Int? = captured?.exitCode ?: exitCode
suspend fun waitFor(): Int {
captured?.let { return it.exitCode }
exitCode?.let { return it }
return process.waitFor().also { exitCode = it }
}
suspend fun captureAll(): CapturedCommandOutput = captureMutex.withLock {
captured?.let { return@withLock it }
coroutineScope {
val stdout = async { process.stdout.toList().joinToString("\n") }
val stderr = async { process.stderr.toList().joinToString("\n") }
val code = async { process.waitFor() }
CapturedCommandOutput(
exitCode = code.await(),
stdoutText = stdout.await(),
stderrText = stderr.await()
).also {
captured = it
exitCode = it.exitCode
}
}
}
override fun toString(): String = "CommandRun($command)"
}
private suspend inline fun ScopeFacade.processGuard(crossinline block: suspend () -> Obj): Obj {
return try {
block()

View File

@ -19,9 +19,11 @@ package net.sergeych.lyng.io.process
import kotlinx.coroutines.runBlocking
import net.sergeych.lyng.Compiler
import net.sergeych.lyng.ExecutionError
import net.sergeych.lyng.Script
import net.sergeych.lyngio.process.security.PermitAllProcessAccessPolicy
import kotlin.test.Test
import kotlin.test.assertFailsWith
import kotlin.test.assertTrue
class LyngProcessModuleTest {
@ -84,4 +86,78 @@ class LyngProcessModuleTest {
val result = script.execute(scope)
assertTrue(result.inspect(scope).contains("name"), "Result should contain 'name', but was: ${result.inspect(scope)}")
}
@Test
fun testShellSugarCapturesOutput() = runBlocking {
val scope = Script.newScope()
createProcessModule(PermitAllProcessAccessPolicy, scope)
val code = """
import lyng.io.process
val r = sh("echo shell-sugar")
assert(r.out.trim() == "shell-sugar")
assert(r.code == 0)
assert(r.ok == true)
true
""".trimIndent()
val script = Compiler.compile(code)
val result = script.execute(scope)
assertTrue(result.inspect(scope).contains("true"))
}
@Test
fun testShellSugarStreamsLines() = runBlocking {
val scope = Script.newScope()
createProcessModule(PermitAllProcessAccessPolicy, scope)
val code = """
import lyng.io.process
val lines: List<String> = []
for (line in sh("echo one && echo two").lines) {
lines.add(line)
}
lines.joinToString(",")
""".trimIndent()
val script = Compiler.compile(code)
val result = script.execute(scope)
assertTrue(result.inspect(scope).contains("one,two"))
}
@Test
fun testExecSugarCapturesOutput() = runBlocking {
val scope = Script.newScope()
createProcessModule(PermitAllProcessAccessPolicy, scope)
val code = """
import lyng.io.process
exec("echo", ["exec-sugar"]).out.trim()
""".trimIndent()
val script = Compiler.compile(code)
val result = script.execute(scope)
assertTrue(result.inspect(scope).contains("exec-sugar"))
}
@Test
fun testShellSugarCheckFailsOnNonZeroExit() = runBlocking {
val scope = Script.newScope()
createProcessModule(PermitAllProcessAccessPolicy, scope)
val code = """
import lyng.io.process
sh("echo check-failed && exit 7").check()
""".trimIndent()
val script = Compiler.compile(code)
assertFailsWith<ExecutionError> {
script.execute(scope)
}
Unit
}
}

View File

@ -0,0 +1,59 @@
package lyng.io.process
/* Handle to a running process. This is the low-level process API. */
extern class RunningProcess {
/* Standard output as a flow of process output lines. */
fun stdout(): Flow<String>
/* Standard error as a flow of process error lines. */
fun stderr(): Flow<String>
/* Send a signal such as "SIGINT", "SIGTERM", or "SIGKILL". */
fun signal(signal: String): void
/* Wait for process completion and return its exit code. */
fun waitFor(): Int
/* Forcefully terminate the process. */
fun destroy(): void
}
/* Shell-script friendly active handle for a running command. */
extern class CommandRun {
/* Original shell command or argv-style command display text. */
val command: String
/* Capture standard output as a string, wait for completion, and drain stderr concurrently. Use `lines` for large output. */
val out: String
/* Capture standard error as a string, wait for completion, and drain stdout concurrently. Use `errorLines` for large output. */
val err: String
/* Streaming standard output lines. Treat the process stdout stream as one-shot. */
val lines: Flow<String>
/* Streaming standard error lines. Treat the process stderr stream as one-shot. */
val errorLines: Flow<String>
/* Exit code after `wait`, `check`, `out`, or `err`; otherwise null. */
val code: Int?
/* True for exit code 0, false for non-zero, or null before the exit code is known. */
val ok: Bool?
/* Wait for the command to exit and return the exit code. */
fun wait(): Int
/* Capture output, wait for completion, and fail if the exit code is non-zero. */
fun check(): CommandRun
}
/* Process execution and control. */
extern object Process {
/* Execute a process with arguments without using a shell. */
fun execute(executable: String, args: List<String>): RunningProcess
/* Execute a command via the system shell. */
fun shell(command: String): RunningProcess
}
/* Platform information for process support. */
extern object Platform {
/* Get platform core details. */
fun details(): Map<String,Object?>
/* Check if processes are supported on this platform. */
fun isSupported(): Bool
}
/* Run a command via the system shell and return an active command handle. */
extern fun sh(command: String): CommandRun
/* Run an executable with argv-style arguments and return an active command handle. */
extern fun exec(executable: String, args: List<String> = []): CommandRun