From 4ec4583a437d4323adbdd00fb962dde958f2a479 Mon Sep 17 00:00:00 2001 From: Chris Lee Date: Thu, 4 Apr 2024 18:20:27 -0700 Subject: [PATCH] Add ShellSession --- README.md | 11 +++ api/kprocess.api | 36 +++++++++- gradle.properties | 2 +- .../io/cloudshiftdev/kprocess/KProcess.kt | 27 +++++-- .../kprocess/session/ShellSession.kt | 70 +++++++++++++++++++ .../io/cloudshiftdev/kprocess/session/Zip.kt | 9 +++ .../kprocess/session/ShellSessionImplTest.kt | 19 +++++ 7 files changed, 164 insertions(+), 10 deletions(-) create mode 100644 src/main/kotlin/io/cloudshiftdev/kprocess/session/ShellSession.kt create mode 100644 src/main/kotlin/io/cloudshiftdev/kprocess/session/Zip.kt create mode 100644 src/test/kotlin/io/cloudshiftdev/kprocess/session/ShellSessionImplTest.kt diff --git a/README.md b/README.md index 74bdd96..b3dbe00 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,17 @@ Co-routine, DSL-friendly process launching from Kotlin. } ``` +## Shell Session + +For executing multiple commands in a single shell session, use the `shellSession` function: +```kotlin +shellSession { + mkdir("foo") + cd("foo") + exec("git", "init") +} +``` + ## Key Features * Non-zero exit values are considered a failure by default (configure `failOnNonZeroExit`) and throw an exception; diff --git a/api/kprocess.api b/api/kprocess.api index aeb87e1..3a422cc 100644 --- a/api/kprocess.api +++ b/api/kprocess.api @@ -65,9 +65,9 @@ public abstract class io/cloudshiftdev/kprocess/KProcessException : java/lang/Ru } public final class io/cloudshiftdev/kprocess/KProcessKt { - public static final fun exec (Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public static final fun execToFile (Ljava/io/File;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public static final fun execToList (Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static final fun exec ([Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static final fun execToFile (Ljava/io/File;[Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static final fun execToList ([Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } public abstract class io/cloudshiftdev/kprocess/OutputConsumer { @@ -104,3 +104,33 @@ public final class io/cloudshiftdev/kprocess/ProcessStartException : io/cloudshi public fun (Ljava/lang/String;)V } +public abstract interface class io/cloudshiftdev/kprocess/session/ShellSession { + public abstract fun cd (Ljava/io/File;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public abstract fun cd (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public abstract fun changeDirectory (Ljava/io/File;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public abstract fun chmod ([Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public abstract fun currentDirectory (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public abstract fun cwd (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public abstract fun exec ([Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public abstract fun mkdir ([Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public abstract fun pwd (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class io/cloudshiftdev/kprocess/session/ShellSession$DefaultImpls { + public static fun cd (Lio/cloudshiftdev/kprocess/session/ShellSession;Ljava/io/File;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static fun cd (Lio/cloudshiftdev/kprocess/session/ShellSession;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static fun cwd (Lio/cloudshiftdev/kprocess/session/ShellSession;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun exec$default (Lio/cloudshiftdev/kprocess/session/ShellSession;[Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public static fun pwd (Lio/cloudshiftdev/kprocess/session/ShellSession;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class io/cloudshiftdev/kprocess/session/ShellSessionKt { + public static final fun shellSession (Ljava/io/File;Ljava/util/Map;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun shellSession$default (Ljava/io/File;Ljava/util/Map;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; +} + +public final class io/cloudshiftdev/kprocess/session/ZipKt { + public static final fun unzip (Lio/cloudshiftdev/kprocess/session/ShellSession;[Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static final fun zip (Lio/cloudshiftdev/kprocess/session/ShellSession;[Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + diff --git a/gradle.properties b/gradle.properties index df32178..0f0feeb 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ -version=0.17.1 +version=0.18.0 group=io.cloudshiftdev.kprocess org.gradle.vfs.watch=true diff --git a/src/main/kotlin/io/cloudshiftdev/kprocess/KProcess.kt b/src/main/kotlin/io/cloudshiftdev/kprocess/KProcess.kt index 65a5c95..eacf08a 100644 --- a/src/main/kotlin/io/cloudshiftdev/kprocess/KProcess.kt +++ b/src/main/kotlin/io/cloudshiftdev/kprocess/KProcess.kt @@ -10,17 +10,32 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.runInterruptible import kotlinx.coroutines.withContext -public suspend fun exec(spec: (ExecSpec.() -> Unit)): ExecResult = - execute(ExecSpecImpl().apply(spec)) +public suspend fun exec( + vararg commandAndArgs: String, + spec: (ExecSpec.() -> Unit) +): ExecResult = + ExecSpecImpl() + .apply { + commandLine(commandAndArgs.toList()) + spec() + } + .let { execute(it) } -public suspend fun execToList(spec: (ExecSpec>.() -> Unit)): ExecResult> = - exec { +public suspend fun execToList( + vararg commandAndArgs: String, + spec: (ExecSpec>.() -> Unit) +): ExecResult> = + exec(*commandAndArgs) { apply(spec) outputConsumer(OutputConsumer.lines { it.toList() }) } -public suspend fun execToFile(file: File, spec: (ExecSpec.() -> Unit)): ExecResult = - exec { +public suspend fun execToFile( + file: File, + vararg commandAndArgs: String, + spec: (ExecSpec.() -> Unit) +): ExecResult = + exec(*commandAndArgs) { apply(spec) outputConsumer(OutputConsumer.file(file)) } diff --git a/src/main/kotlin/io/cloudshiftdev/kprocess/session/ShellSession.kt b/src/main/kotlin/io/cloudshiftdev/kprocess/session/ShellSession.kt new file mode 100644 index 0000000..5bbc3cd --- /dev/null +++ b/src/main/kotlin/io/cloudshiftdev/kprocess/session/ShellSession.kt @@ -0,0 +1,70 @@ +package io.cloudshiftdev.kprocess.session + +import io.cloudshiftdev.kprocess.ExecResult +import io.cloudshiftdev.kprocess.ExecSpec +import java.io.File + +public interface ShellSession { + public suspend fun changeDirectory(dir: File) + + public suspend fun cd(dir: File): Unit = changeDirectory(dir) + + public suspend fun cd(dir: String): Unit = changeDirectory(File(dir)) + + public suspend fun currentDirectory(): File + + public suspend fun cwd(): File = currentDirectory() + + public suspend fun pwd(): File = currentDirectory() + + public suspend fun mkdir(vararg args: String) + + public suspend fun chmod(vararg args: String) + + public suspend fun exec( + vararg commandAndArgs: String, + spec: (ExecSpec.() -> Unit) = {} + ): ExecResult +} + +public suspend fun shellSession( + workingDir: File? = null, + environment: Map = emptyMap(), + session: suspend ShellSession.() -> Unit +) { + ShellSessionImpl(workingDir, environment).session() +} + +private class ShellSessionImpl(initialWorkingDir: File?, environment: Map) : + ShellSession { + private var workingDir: File = initialWorkingDir ?: File(System.getProperty("user.dir")) + private val environment: MutableMap = environment.toMutableMap() + + override suspend fun changeDirectory(dir: File) { + workingDir = resolveDir(dir) + } + + override suspend fun currentDirectory(): File = workingDir + + override suspend fun mkdir(vararg args: String) { + exec("mkdir", *args) { failOnNonZeroExit(false) } + } + + override suspend fun chmod(vararg args: String) { + exec("chmod", *args) + } + + override suspend fun exec( + vararg commandAndArgs: String, + spec: ExecSpec.() -> Unit + ): ExecResult { + return io.cloudshiftdev.kprocess.exec(*commandAndArgs) { + workingDir(workingDir) + environment(environment) + apply(spec) + } + } + + private fun resolveDir(dir: File): File = + if (dir.isAbsolute) dir else workingDir.resolve(dir).normalize() +} diff --git a/src/main/kotlin/io/cloudshiftdev/kprocess/session/Zip.kt b/src/main/kotlin/io/cloudshiftdev/kprocess/session/Zip.kt new file mode 100644 index 0000000..35248ee --- /dev/null +++ b/src/main/kotlin/io/cloudshiftdev/kprocess/session/Zip.kt @@ -0,0 +1,9 @@ +package io.cloudshiftdev.kprocess.session + +public suspend fun ShellSession.zip(vararg args: String) { + exec("zip", *args) +} + +public suspend fun ShellSession.unzip(vararg args: String) { + exec("unzip", *args) +} diff --git a/src/test/kotlin/io/cloudshiftdev/kprocess/session/ShellSessionImplTest.kt b/src/test/kotlin/io/cloudshiftdev/kprocess/session/ShellSessionImplTest.kt new file mode 100644 index 0000000..88a686e --- /dev/null +++ b/src/test/kotlin/io/cloudshiftdev/kprocess/session/ShellSessionImplTest.kt @@ -0,0 +1,19 @@ +package io.cloudshiftdev.kprocess.session + +import io.kotest.core.spec.style.FunSpec +import io.kotest.engine.spec.tempdir +import org.junit.jupiter.api.Assertions.* + +class ShellSessionImplTest : FunSpec() { + init { + test("shell session works") { + val tmpDir = tempdir() + shellSession(workingDir = tmpDir) { + mkdir("test") + cd("test") + val dir = pwd() + assertEquals(tmpDir.resolve("test"), dir) + } + } + } +}