From 5568539d118010826dba19e3979ed3292acfef90 Mon Sep 17 00:00:00 2001 From: Marcin Kuszczak <1508798+aartiPl@users.noreply.github.com> Date: Wed, 5 Apr 2023 21:19:34 +0200 Subject: [PATCH 01/50] Initial commit for 0.6.0 --- TODO.adoc | 15 +++ TODO.md | 2 - build.gradle.kts | 59 +++++----- .../github/kscripting/shell/ShellExecutor.kt | 11 +- .../github/kscripting/shell/ShellExecutor2.kt | 14 +++ .../kscripting/shell/ShellExecutor2Builder.kt | 29 +++++ .../io/github/kscripting/shell/apiTest.kt | 7 ++ .../shell/model/CommandTimeoutException.kt | 3 + .../shell/model/GobbledProcessResult.kt | 13 -- .../kscripting/shell/model/ProcessResult.kt | 10 +- .../kscripting/shell/process/ProcessRunner.kt | 9 +- .../io/github/kscripting/shell/verifier.kt | 111 ++++++++++++++++++ 12 files changed, 219 insertions(+), 64 deletions(-) create mode 100644 TODO.adoc delete mode 100644 TODO.md create mode 100644 src/main/kotlin/io/github/kscripting/shell/ShellExecutor2.kt create mode 100644 src/main/kotlin/io/github/kscripting/shell/ShellExecutor2Builder.kt create mode 100644 src/main/kotlin/io/github/kscripting/shell/apiTest.kt create mode 100644 src/main/kotlin/io/github/kscripting/shell/model/CommandTimeoutException.kt delete mode 100644 src/main/kotlin/io/github/kscripting/shell/model/GobbledProcessResult.kt create mode 100644 src/main/kotlin/io/github/kscripting/shell/verifier.kt diff --git a/TODO.adoc b/TODO.adoc new file mode 100644 index 0000000..6b0732f --- /dev/null +++ b/TODO.adoc @@ -0,0 +1,15 @@ += shell 0.6.0-SNAPSHOT features: + +* Move shell test classes from kscript to the library +* dry-run, with test output provider +* printing of commands +* Command for encapsulating commands? +* Throw on exitCode != 0 and/or skipErrors +* verifier for different types like in assertk/assertj +* log masking (shallow - in printout and deep in stderr/stdout); masking list of the words; is the design with Streams valid for that? +* Remove command from result +* Encoding of special characters in output (\n, \, etc.) +* Interface dla ShellExecutor (ten sam dla DryRunResultProvider) +* Printing Output of commands +* Possibility of parametrization of commands (properties) + masking (for tests and interoperability) +* Silent mode () \ No newline at end of file diff --git a/TODO.md b/TODO.md deleted file mode 100644 index c6fdd83..0000000 --- a/TODO.md +++ /dev/null @@ -1,2 +0,0 @@ -# shell 0.5 features: - diff --git a/build.gradle.kts b/build.gradle.kts index d2414f5..7407a64 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -12,18 +12,16 @@ repositories { } group = "io.github.kscripting" -version = "0.5.1" +version = "0.6.0-SNAPSHOT" sourceSets { create("integration") { -// test { //With that idea can understand that 'integration' is test source set and do not complain about test -// names starting with upper case, but it doesn't compile correctly with it java.srcDir("$projectDir/src/integration/kotlin") resources.srcDir("$projectDir/src/integration/resources") + compileClasspath += main.get().output + test.get().output runtimeClasspath += main.get().output + test.get().output } -// } } configurations { @@ -43,9 +41,10 @@ tasks.withType().all { kotlinOptions { jvmTarget = "1.8" } + } -tasks.create("integration") { +tasks.create("integrationTest") { val itags = System.getProperty("includeTags") ?: "" val etags = System.getProperty("excludeTags") ?: "" @@ -92,30 +91,6 @@ tasks.test { useJUnitPlatform() } -dependencies { - implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlinVersion") - implementation("org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion") - implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4") - - implementation("org.jetbrains.kotlin:kotlin-scripting-common:$kotlinVersion") - implementation("org.jetbrains.kotlin:kotlin-scripting-jvm:$kotlinVersion") - implementation("org.jetbrains.kotlin:kotlin-scripting-dependencies-maven-all:$kotlinVersion") - implementation("io.arrow-kt:arrow-core:1.1.2") - implementation("org.apache.commons:commons-lang3:3.12.0") - - implementation("org.slf4j:slf4j-nop:2.0.4") - - testImplementation("org.junit.platform:junit-platform-suite-engine:1.9.0") - testImplementation("org.junit.platform:junit-platform-suite-api:1.9.0") - testImplementation("org.junit.platform:junit-platform-suite-commons:1.9.0") - testImplementation("org.junit.jupiter:junit-jupiter-engine:5.9.0") - testImplementation("org.junit.jupiter:junit-jupiter-params:5.9.0") - testImplementation("com.willowtreeapps.assertk:assertk-jvm:0.25") - testImplementation("io.mockk:mockk:1.13.2") - - testImplementation(kotlin("script-runtime")) -} - publishing { publications { create("mavenJava") { @@ -123,7 +98,7 @@ publishing { from(components["java"]) pom { - name.set("kscript") + name.set("shell") description.set("Shell - library for interoperability with different system shells") url.set("https://github.com/kscripting/shell") @@ -166,3 +141,27 @@ publishing { signing { sign(publishing.publications["mavenJava"]) } + +dependencies { + implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlinVersion") + implementation("org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion") + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4") + + implementation("org.jetbrains.kotlin:kotlin-scripting-common:$kotlinVersion") + implementation("org.jetbrains.kotlin:kotlin-scripting-jvm:$kotlinVersion") + implementation("org.jetbrains.kotlin:kotlin-scripting-dependencies-maven-all:$kotlinVersion") + implementation("io.arrow-kt:arrow-core:1.1.2") + implementation("org.apache.commons:commons-lang3:3.12.0") + + implementation("org.slf4j:slf4j-nop:2.0.5") + + testImplementation("org.junit.platform:junit-platform-suite-engine:1.9.0") + testImplementation("org.junit.platform:junit-platform-suite-api:1.9.0") + testImplementation("org.junit.platform:junit-platform-suite-commons:1.9.0") + testImplementation("org.junit.jupiter:junit-jupiter-engine:5.9.0") + testImplementation("org.junit.jupiter:junit-jupiter-params:5.9.0") + testImplementation("com.willowtreeapps.assertk:assertk-jvm:0.25") + testImplementation("io.mockk:mockk:1.13.2") + + testImplementation(kotlin("script-runtime")) +} diff --git a/src/main/kotlin/io/github/kscripting/shell/ShellExecutor.kt b/src/main/kotlin/io/github/kscripting/shell/ShellExecutor.kt index 1423eca..c944c45 100644 --- a/src/main/kotlin/io/github/kscripting/shell/ShellExecutor.kt +++ b/src/main/kotlin/io/github/kscripting/shell/ShellExecutor.kt @@ -1,9 +1,8 @@ package io.github.kscripting.shell -import io.github.kscripting.shell.model.GobbledProcessResult +import io.github.kscripting.shell.model.ProcessResult import io.github.kscripting.shell.model.OsPath import io.github.kscripting.shell.model.OsType -import io.github.kscripting.shell.model.ProcessResult import io.github.kscripting.shell.process.EnvAdjuster import io.github.kscripting.shell.process.ProcessRunner import io.github.kscripting.shell.process.ProcessRunner.DEFAULT_ERR_PRINTERS @@ -23,13 +22,13 @@ object ShellExecutor { inheritInput: Boolean = false, outPrinter: List = emptyList(), errPrinter: List = emptyList() - ): GobbledProcessResult { + ): ProcessResult { val outStream = ByteArrayOutputStream(1024) val errStream = ByteArrayOutputStream(1024) val utf8 = StandardCharsets.UTF_8.name() - var result: ProcessResult + var result: Int PrintStream(outStream, true, utf8).use { additionalOutPrinter -> PrintStream(errStream, true, utf8).use { additionalErrPrinter -> @@ -46,7 +45,7 @@ object ShellExecutor { } } - return GobbledProcessResult(result.command, result.exitCode, outStream.toString(utf8), errStream.toString(utf8)) + return ProcessResult(result, outStream.toString(utf8), errStream.toString(utf8)) } fun eval( @@ -58,7 +57,7 @@ object ShellExecutor { inheritInput: Boolean = false, outPrinter: List = DEFAULT_OUT_PRINTERS, errPrinter: List = DEFAULT_ERR_PRINTERS - ): ProcessResult { + ): Int { //NOTE: cmd is an argument to shell (bash/cmd), so it should stay not split by whitespace as a single string if (osType == OsType.WINDOWS) { // if the first character in args in `cmd /c ` is a quote, cmd will remove it as well as the diff --git a/src/main/kotlin/io/github/kscripting/shell/ShellExecutor2.kt b/src/main/kotlin/io/github/kscripting/shell/ShellExecutor2.kt new file mode 100644 index 0000000..0c1d909 --- /dev/null +++ b/src/main/kotlin/io/github/kscripting/shell/ShellExecutor2.kt @@ -0,0 +1,14 @@ +package io.github.kscripting.shell + +import io.github.kscripting.shell.model.ProcessResult + +class ShellExecutor2 { + + fun execute(): ProcessResult { + return ProcessResult(0, "", "") + } + + companion object { + fun builder() = ShellExecutor2Builder() + } +} diff --git a/src/main/kotlin/io/github/kscripting/shell/ShellExecutor2Builder.kt b/src/main/kotlin/io/github/kscripting/shell/ShellExecutor2Builder.kt new file mode 100644 index 0000000..57d0dd1 --- /dev/null +++ b/src/main/kotlin/io/github/kscripting/shell/ShellExecutor2Builder.kt @@ -0,0 +1,29 @@ +package io.github.kscripting.shell + +class ShellExecutor2Builder { + private var printCommandsPattern: String? = null + + fun withPrinter(printer: (String) -> String) { + //e.g. withPrinter { ">> $it" } + + } + +// fun throwOnExitCode(thrower: (ProcessResult) -> ProcessResult) { +// +// } +// +// fun throwOnExitCode(thrower: (GobbledProcessResult) -> GobbledProcessResult) { +// +// } + +// fun withResultProvider(provider: (String) -> ProcessResult) { +// +// } +// +// fun withResultProvider(provider: (String) -> GobbledProcessResult) { +// +// } + + + fun build(): ShellExecutor2 = ShellExecutor2() +} diff --git a/src/main/kotlin/io/github/kscripting/shell/apiTest.kt b/src/main/kotlin/io/github/kscripting/shell/apiTest.kt new file mode 100644 index 0000000..205384d --- /dev/null +++ b/src/main/kotlin/io/github/kscripting/shell/apiTest.kt @@ -0,0 +1,7 @@ +package io.github.kscripting.shell + +fun main() { + val executor = ShellExecutor2.builder().build() + + executor.execute() +} \ No newline at end of file diff --git a/src/main/kotlin/io/github/kscripting/shell/model/CommandTimeoutException.kt b/src/main/kotlin/io/github/kscripting/shell/model/CommandTimeoutException.kt new file mode 100644 index 0000000..8a80ee2 --- /dev/null +++ b/src/main/kotlin/io/github/kscripting/shell/model/CommandTimeoutException.kt @@ -0,0 +1,3 @@ +package io.github.kscripting.shell.model + +class CommandTimeoutException(override val message: String) : RuntimeException(message) diff --git a/src/main/kotlin/io/github/kscripting/shell/model/GobbledProcessResult.kt b/src/main/kotlin/io/github/kscripting/shell/model/GobbledProcessResult.kt deleted file mode 100644 index e28f545..0000000 --- a/src/main/kotlin/io/github/kscripting/shell/model/GobbledProcessResult.kt +++ /dev/null @@ -1,13 +0,0 @@ -package io.github.kscripting.shell.model - -import io.github.kscripting.shell.util.ShellEscapeUtils.whitespaceCharsToSymbols - -data class GobbledProcessResult(val command: String, val exitCode: Int, val stdout: String, val stderr: String) { - override fun toString(): String { - return """|Command : '${whitespaceCharsToSymbols(command)}' - |Exit Code : $exitCode - |Stdout : '${whitespaceCharsToSymbols(stdout)}' - |Stderr : '${whitespaceCharsToSymbols(stderr)}' - |""".trimMargin() - } -} diff --git a/src/main/kotlin/io/github/kscripting/shell/model/ProcessResult.kt b/src/main/kotlin/io/github/kscripting/shell/model/ProcessResult.kt index ad709da..8f938c7 100644 --- a/src/main/kotlin/io/github/kscripting/shell/model/ProcessResult.kt +++ b/src/main/kotlin/io/github/kscripting/shell/model/ProcessResult.kt @@ -1,11 +1,3 @@ package io.github.kscripting.shell.model -import io.github.kscripting.shell.util.ShellEscapeUtils.whitespaceCharsToSymbols - -data class ProcessResult(val command: String, val exitCode: Int) { - override fun toString(): String { - return """|Command : '${whitespaceCharsToSymbols(command)}' - |Exit Code : $exitCode - |""".trimMargin() - } -} +data class ProcessResult(val exitCode: Int, val stdout: String, val stderr: String) diff --git a/src/main/kotlin/io/github/kscripting/shell/process/ProcessRunner.kt b/src/main/kotlin/io/github/kscripting/shell/process/ProcessRunner.kt index 4fc3f2f..46d8c7f 100644 --- a/src/main/kotlin/io/github/kscripting/shell/process/ProcessRunner.kt +++ b/src/main/kotlin/io/github/kscripting/shell/process/ProcessRunner.kt @@ -1,5 +1,6 @@ package io.github.kscripting.shell.process +import io.github.kscripting.shell.model.CommandTimeoutException import io.github.kscripting.shell.model.OsPath import io.github.kscripting.shell.model.ProcessResult import io.github.kscripting.shell.model.toNativeFile @@ -20,7 +21,7 @@ object ProcessRunner { inheritInput: Boolean = false, outPrinter: List = DEFAULT_OUT_PRINTERS, errPrinter: List = DEFAULT_ERR_PRINTERS, - ): ProcessResult { + ): Int { return runProcess( command.asList(), workingDirectory, envAdjuster, waitTimeMinutes, inheritInput, outPrinter, errPrinter ) @@ -34,7 +35,7 @@ object ProcessRunner { inheritInput: Boolean = false, outPrinter: List = DEFAULT_OUT_PRINTERS, errPrinter: List = DEFAULT_ERR_PRINTERS, - ): ProcessResult { + ): Int { try { // simplify with https://stackoverflow.com/questions/35421699/how-to-invoke-external-command-from-within-kotlin-code val process = ProcessBuilder(command) @@ -56,10 +57,10 @@ object ProcessRunner { errorStreamReader.finish() if (!exitedNormally) { - throw IllegalStateException("Command has timed out after $waitTimeMinutes minutes.") + throw CommandTimeoutException("Command has timed out after $waitTimeMinutes minutes.") } - return ProcessResult(command.joinToString(" "), process.exitValue()) + return process.exitValue() } catch (e: Exception) { throw IllegalStateException("Error executing command: '$command'", e) } diff --git a/src/main/kotlin/io/github/kscripting/shell/verifier.kt b/src/main/kotlin/io/github/kscripting/shell/verifier.kt new file mode 100644 index 0000000..3e48633 --- /dev/null +++ b/src/main/kotlin/io/github/kscripting/shell/verifier.kt @@ -0,0 +1,111 @@ +package io.github.kscripting.shell + +import java.util.* + +abstract class Verifier(value: T) + +/** + * Asserts on the given value with an optional name. + * + * ``` + * assertThat(true, name = "true").isTrue() + * ``` + */ +fun verifyThat( + actual: T, + name: String? = null, + displayActual: (T) -> String = { "display(it)" } +): Assert = ValueAssert( + value = actual, + name = name, + context = AssertingContext { displayActual(actual) } +) + +sealed class Assert(val name: String?, internal val context: AssertingContext) { + + + /** + * Allows checking the actual value of an assert. This can be used to build your own custom assertions. + * ``` + * fun Assert.isTen() = given { actual -> + * if (actual == 10) return + * expected("to be 10 but was:${show(actual)}") + * } + * ``` + */ + @Suppress("TooGenericExceptionCaught") + inline fun given(assertion: (T) -> Unit) { + if (this is ValueAssert) { + try { + assertion(value) + } catch (e: Throwable) { + notifyFailure(e) + } + } + } + + @PublishedApi + internal fun failing(error: Throwable, name: String? = this.name): Assert { + return FailingAssert(error, name, context) + } + + /** + * Asserts on the given value with an optional name. + * + * ``` + * assertThat(true, name = "true").isTrue() + * ``` + */ + abstract fun assertThat(actual: R, name: String? = this.name): Assert +} + +@PublishedApi +internal class ValueAssert(val value: T, name: String?, context: AssertingContext) : + Assert(name, context) { + + override fun assertThat(actual: R, name: String?): Assert { + val newContext = if (context.originatingSubject != null || this.value == actual) { + context + } else { + context.copy(originatingSubject = this.value) + } + return ValueAssert(actual, name, newContext) + } +} + + + +internal class FailingAssert(val error: Throwable, name: String?, context: AssertingContext) : + Assert(name, context) { + override fun assertThat(actual: R, name: String?): Assert = FailingAssert(error, name, context) +} + + +internal data class AssertingContext( + val originatingSubject: Any? = null, + val displayOriginatingSubject: () -> String +) + +fun notifyFailure(e: Throwable) { + FailureContext.fail(e) +} + +/** + * Assertions are run in a failure context which captures failures to report them. + */ +internal object FailureContext { + + fun fail(error: Throwable) { + + } +} + +fun Assert.isEqualTo(other: String?, ignoreCase: Boolean = false) = given { actual -> + if (actual.equals(other, ignoreCase)) return + //fail(other, actual) +} + +fun main() { + verifyThat("").isEqualTo("") + verifyThat(Date())//.isEqualTo() +} From 9b4824b73734afab62975132a3fbf2f92bf3e2b3 Mon Sep 17 00:00:00 2001 From: Marcin Kuszczak <1508798+aartiPl@users.noreply.github.com> Date: Sat, 6 May 2023 13:16:11 +0200 Subject: [PATCH 02/50] Added shorthand functions for composing OsPaths --- .../io/github/kscripting/shell/model/OsPath.kt | 7 +++++++ .../github/kscripting/shell/model/OsPathExt.kt | 4 +++- .../github/kscripting/shell/model/OsPathTest.kt | 17 +++++++++++++++++ 3 files changed, 27 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/io/github/kscripting/shell/model/OsPath.kt b/src/main/kotlin/io/github/kscripting/shell/model/OsPath.kt index 30c2555..55e46d7 100644 --- a/src/main/kotlin/io/github/kscripting/shell/model/OsPath.kt +++ b/src/main/kotlin/io/github/kscripting/shell/model/OsPath.kt @@ -6,6 +6,13 @@ import arrow.core.right //Path representation for different OSes data class OsPath(val osType: OsType, val pathType: PathType, val pathParts: List, val pathSeparator: Char) { + operator fun div(osPath: OsPath): OsPath { + return this.resolve(osPath) + } + + operator fun div(path: String): OsPath { + return this.resolve(path) + } fun resolve(vararg pathParts: String): OsPath { return resolve(createOrThrow(osType, pathParts.joinToString(pathSeparator.toString()))) diff --git a/src/main/kotlin/io/github/kscripting/shell/model/OsPathExt.kt b/src/main/kotlin/io/github/kscripting/shell/model/OsPathExt.kt index 89f9225..b8d0c99 100644 --- a/src/main/kotlin/io/github/kscripting/shell/model/OsPathExt.kt +++ b/src/main/kotlin/io/github/kscripting/shell/model/OsPathExt.kt @@ -30,9 +30,11 @@ fun OsPath.toNativeFile(): File = toNativePath().toFile() // OsPath operations -fun OsPath.exists() = toNativePath().exists() +fun OsPath.exists(): Boolean = toNativePath().exists() fun OsPath.createDirectories(): OsPath = OsPath.createOrThrow(nativeType, toNativePath().createDirectories().pathString) +fun OsPath.deleteRecursively(): Boolean = this.toNativeFile().deleteRecursively() +fun OsPath.readBytes(): ByteArray = this.toNativeFile().readBytes() fun OsPath.copyTo(target: OsPath, overwrite: Boolean = false): OsPath = OsPath.createOrThrow(nativeType, toNativePath().copyTo(target.toNativePath(), overwrite).pathString) diff --git a/src/test/kotlin/io/github/kscripting/shell/model/OsPathTest.kt b/src/test/kotlin/io/github/kscripting/shell/model/OsPathTest.kt index d09e842..4190968 100644 --- a/src/test/kotlin/io/github/kscripting/shell/model/OsPathTest.kt +++ b/src/test/kotlin/io/github/kscripting/shell/model/OsPathTest.kt @@ -322,4 +322,21 @@ class OsPathTest { OsPath.createOrThrow(OsType.MSYS, "../home/admin/.kscript").convert(OsType.WINDOWS).stringPath() ).isEqualTo("..\\home\\admin\\.kscript") } + + // ************************************* Shorthand for creating composite paths ************************************ + + @Test + fun `Add paths`() { + val p = OsPath.createOrThrow(OsType.MSYS, "/c/home") + val p1 = OsPath.createOrThrow(OsType.MSYS, "admin/.kscript") + + assertThat(p / p1).isEqualTo(OsPath.createOrThrow(OsType.MSYS, "/c/home/admin/.kscript")) + + val test = OsPath.createOrThrow(OsType.LINUX, "/c/home/admin/.kscript") + println(test) + println(test.osType) + println(test.pathType) + println(test.pathParts) + println(test.pathSeparator) + } } From 5c376613c0ed9f6fef64f30563de80fba433813f Mon Sep 17 00:00:00 2001 From: Marcin Kuszczak <1508798+aartiPl@users.noreply.github.com> Date: Sat, 6 May 2023 13:19:10 +0200 Subject: [PATCH 03/50] Added shorthand functions for composing OsPaths --- .../io/github/kscripting/shell/model/OsPath.kt | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/main/kotlin/io/github/kscripting/shell/model/OsPath.kt b/src/main/kotlin/io/github/kscripting/shell/model/OsPath.kt index 55e46d7..f037372 100644 --- a/src/main/kotlin/io/github/kscripting/shell/model/OsPath.kt +++ b/src/main/kotlin/io/github/kscripting/shell/model/OsPath.kt @@ -143,6 +143,13 @@ data class OsPath(val osType: OsType, val pathType: PathType, val pathParts: Lis } } + fun createOrThrow(root: String, vararg pathParts: String): OsPath { + return when (val result = internalCreate(OsType.native, root, *pathParts)) { + is Either.Right -> result.value + is Either.Left -> throw IllegalArgumentException(result.value) + } + } + fun create(osType: OsType, root: String, vararg pathParts: String): OsPath? { return when (val result = internalCreate(osType, root, *pathParts)) { is Either.Right -> result.value @@ -150,6 +157,13 @@ data class OsPath(val osType: OsType, val pathType: PathType, val pathParts: Lis } } + fun create(root: String, vararg pathParts: String): OsPath? { + return when (val result = internalCreate(OsType.native, root, *pathParts)) { + is Either.Right -> result.value + is Either.Left -> null + } + } + //Relaxed validation: //1. It doesn't matter if there is '/' or '\' used as path separator - both are treated he same //2. Duplicated or trailing slashes '/' and backslashes '\' are just ignored From 1a32e9c8dd800727d830ed6336c96c48b3339ea7 Mon Sep 17 00:00:00 2001 From: Marcin Kuszczak <1508798+aartiPl@users.noreply.github.com> Date: Sat, 17 Jun 2023 10:43:11 +0200 Subject: [PATCH 04/50] Added tests for commands --- TODO.adoc | 3 +- build.gradle.kts | 35 +++++----- src/echo_program.bat | 3 + src/echo_program.sh | 3 + .../shell/integration/ShellExecutorTest.kt | 22 +++++++ .../kscripting/shell/integration/TestBase.kt | 12 ++++ .../shell/integration/tools/Sanitizer.kt | 37 +++++++++++ .../shell/integration/tools/TestAssertion.kt | 65 +++++++++++++++++++ .../shell/integration/tools/TestContext.kt | 59 +++++++++++++++++ .../shell/integration/tools/TestMatcher.kt | 51 +++++++++++++++ .../github/kscripting/shell/ShellExecutor.kt | 38 ++++------- .../github/kscripting/shell/model/OsPath.kt | 34 +++++----- .../kscripting/shell/model/OsPathExt.kt | 17 +++-- .../kscripting/shell/util/ShellEscapeUtils.kt | 6 -- .../kscripting/shell/model/OsPathTest.kt | 40 +++++++----- 15 files changed, 332 insertions(+), 93 deletions(-) create mode 100644 src/echo_program.bat create mode 100644 src/echo_program.sh create mode 100644 src/itest/kotlin/io/github/kscripting/shell/integration/ShellExecutorTest.kt create mode 100644 src/itest/kotlin/io/github/kscripting/shell/integration/TestBase.kt create mode 100644 src/itest/kotlin/io/github/kscripting/shell/integration/tools/Sanitizer.kt create mode 100644 src/itest/kotlin/io/github/kscripting/shell/integration/tools/TestAssertion.kt create mode 100644 src/itest/kotlin/io/github/kscripting/shell/integration/tools/TestContext.kt create mode 100644 src/itest/kotlin/io/github/kscripting/shell/integration/tools/TestMatcher.kt delete mode 100644 src/main/kotlin/io/github/kscripting/shell/util/ShellEscapeUtils.kt diff --git a/TODO.adoc b/TODO.adoc index fc552b2..9967430 100644 --- a/TODO.adoc +++ b/TODO.adoc @@ -7,9 +7,8 @@ * Throw on exitCode != 0 and/or skipErrors * verifier for different types like in assertk/assertj * log masking (shallow - in printout and deep in stderr/stdout); masking list of the words; is the design with Streams valid for that? -* Remove command from result * Encoding of special characters in output (\n, \, etc.) -* Interface dla ShellExecutor (ten sam dla DryRunResultProvider) +* Interface for ShellExecutor (to allow mocking) * Printing Output of commands * Possibility of parametrization of commands (properties) + masking (for tests and interoperability) * Silent mode () diff --git a/build.gradle.kts b/build.gradle.kts index 7407a64..b2364e0 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,10 +1,11 @@ -val kotlinVersion: String = "1.7.21" +val kotlinVersion: String = "1.8.21" plugins { - kotlin("jvm") version "1.7.21" + kotlin("jvm") version "1.8.21" id("com.adarshr.test-logger") version "3.2.0" `maven-publish` signing + idea } repositories { @@ -15,9 +16,9 @@ group = "io.github.kscripting" version = "0.6.0-SNAPSHOT" sourceSets { - create("integration") { - java.srcDir("$projectDir/src/integration/kotlin") - resources.srcDir("$projectDir/src/integration/resources") + create("itest") { + kotlin.srcDir("$projectDir/src/itest/kotlin") + resources.srcDir("$projectDir/src/itest/resources") compileClasspath += main.get().output + test.get().output runtimeClasspath += main.get().output + test.get().output @@ -25,12 +26,12 @@ sourceSets { } configurations { - get("integrationImplementation").apply { extendsFrom(get("testImplementation")) } + get("itestImplementation").apply { extendsFrom(get("testImplementation")) } } java { toolchain { - languageVersion.set(JavaLanguageVersion.of(8)) + languageVersion.set(JavaLanguageVersion.of(11)) } withJavadocJar() @@ -39,12 +40,11 @@ java { tasks.withType().all { kotlinOptions { - jvmTarget = "1.8" + jvmTarget = "11" } - } -tasks.create("integrationTest") { +tasks.create("itest") { val itags = System.getProperty("includeTags") ?: "" val etags = System.getProperty("excludeTags") ?: "" @@ -64,8 +64,8 @@ tasks.create("integrationTest") { description = "Runs the integration tests." group = "verification" - testClassesDirs = sourceSets["integration"].output.classesDirs - classpath = sourceSets["integration"].runtimeClasspath + testClassesDirs = sourceSets["itest"].output.classesDirs + classpath = sourceSets["itest"].runtimeClasspath outputs.upToDateWhen { false } mustRunAfter(tasks["test"]) //dependsOn(tasks["assemble"], tasks["test"]) @@ -78,7 +78,13 @@ tasks.create("integrationTest") { tasks.create("printIntegrationClasspath") { doLast { - println(sourceSets["integration"].runtimeClasspath.asPath) + println(sourceSets["itest"].runtimeClasspath.asPath) + } +} + +idea { + module { + testSources.from(sourceSets["itest"].kotlin.srcDirs) } } @@ -147,9 +153,6 @@ dependencies { implementation("org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4") - implementation("org.jetbrains.kotlin:kotlin-scripting-common:$kotlinVersion") - implementation("org.jetbrains.kotlin:kotlin-scripting-jvm:$kotlinVersion") - implementation("org.jetbrains.kotlin:kotlin-scripting-dependencies-maven-all:$kotlinVersion") implementation("io.arrow-kt:arrow-core:1.1.2") implementation("org.apache.commons:commons-lang3:3.12.0") diff --git a/src/echo_program.bat b/src/echo_program.bat new file mode 100644 index 0000000..2d05949 --- /dev/null +++ b/src/echo_program.bat @@ -0,0 +1,3 @@ +@echo off + +echo %1 diff --git a/src/echo_program.sh b/src/echo_program.sh new file mode 100644 index 0000000..dd5455d --- /dev/null +++ b/src/echo_program.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +echo "$1" diff --git a/src/itest/kotlin/io/github/kscripting/shell/integration/ShellExecutorTest.kt b/src/itest/kotlin/io/github/kscripting/shell/integration/ShellExecutorTest.kt new file mode 100644 index 0000000..99b5006 --- /dev/null +++ b/src/itest/kotlin/io/github/kscripting/shell/integration/ShellExecutorTest.kt @@ -0,0 +1,22 @@ +package io.github.kscripting.shell.integration + +import io.github.kscripting.shell.integration.tools.TestAssertion.verify +import io.github.kscripting.shell.integration.tools.TestContext +import org.junit.jupiter.api.Tag +import org.junit.jupiter.api.Test + +class ShellExecutorTest : TestBase { + @Test + @Tag("posix") + @Tag("windows") + fun `Simple echo of parameters works`() { + verify("echo_program test", 0, "test[nl]", "") + } + + companion object { + init { + TestContext.copyToExecutablePath("src/echo_program.sh") + TestContext.copyToExecutablePath("src/echo_program.bat") + } + } +} diff --git a/src/itest/kotlin/io/github/kscripting/shell/integration/TestBase.kt b/src/itest/kotlin/io/github/kscripting/shell/integration/TestBase.kt new file mode 100644 index 0000000..ee5e3d2 --- /dev/null +++ b/src/itest/kotlin/io/github/kscripting/shell/integration/TestBase.kt @@ -0,0 +1,12 @@ +package io.github.kscripting.shell.integration + +import org.junit.jupiter.api.BeforeAll + +interface TestBase { + companion object { + @BeforeAll + @JvmStatic + fun setUp() { + } + } +} diff --git a/src/itest/kotlin/io/github/kscripting/shell/integration/tools/Sanitizer.kt b/src/itest/kotlin/io/github/kscripting/shell/integration/tools/Sanitizer.kt new file mode 100644 index 0000000..898460d --- /dev/null +++ b/src/itest/kotlin/io/github/kscripting/shell/integration/tools/Sanitizer.kt @@ -0,0 +1,37 @@ +package io.github.kscripting.shell.integration.tools + +import io.github.kscripting.shell.model.ProcessResult + +//Mapping: +//[bs] --> \\ +//[nl] --> \n +class Sanitizer(private val mapping: Map = mapOf()) { + fun sanitizeInput(string: String): String { + return sanitize(string) { key, value -> + this.replace(key, value) + } + } + + fun sanitizeOutput(string: String): String { + return sanitize(string) { key, value -> + this.replace(value, key) + } + } + + fun sanitize(processResult: ProcessResult): ProcessResult { + return processResult.copy( + stdout = sanitizeOutput(processResult.stdout), + stderr = sanitizeOutput(processResult.stderr) + ) + } + + fun sanitize(string: String, fn: String.(String, String) -> String): String { + var result = string + + for (entry in mapping.entries) { + result = result.fn(entry.key, entry.value) + } + + return result + } +} diff --git a/src/itest/kotlin/io/github/kscripting/shell/integration/tools/TestAssertion.kt b/src/itest/kotlin/io/github/kscripting/shell/integration/tools/TestAssertion.kt new file mode 100644 index 0000000..89661e3 --- /dev/null +++ b/src/itest/kotlin/io/github/kscripting/shell/integration/tools/TestAssertion.kt @@ -0,0 +1,65 @@ +package io.github.kscripting.shell.integration.tools + +import io.github.kscripting.shell.integration.tools.TestContext.runProcess +import io.github.kscripting.shell.model.ProcessResult +import io.github.kscripting.shell.process.EnvAdjuster + +object TestAssertion { + private val defaultSanitizer = + Sanitizer(mapOf("[bs]" to "\\", "[nl]" to System.getProperty("line.separator"), "[tb]" to "\t")) + + fun genericEquals(value: T) = GenericEquals(value) + + fun any() = AnyMatch() + fun eq(string: String, ignoreCase: Boolean = false) = Equals(string, ignoreCase) + fun startsWith(string: String, ignoreCase: Boolean = false) = StartsWith(string, ignoreCase) + fun contains(string: String, ignoreCase: Boolean = false) = Contains(string, ignoreCase) + + fun verify( + command: String, + exitCode: Int = 0, + stdOut: TestMatcher, + stdErr: String = "", + sanitizer: Sanitizer = defaultSanitizer, + envAdjuster: EnvAdjuster = {} + ): ProcessResult = verify(command, exitCode, stdOut, eq(stdErr), sanitizer, envAdjuster) + + fun verify( + command: String, + exitCode: Int = 0, + stdOut: String, + stdErr: TestMatcher, + sanitizer: Sanitizer = defaultSanitizer, + envAdjuster: EnvAdjuster = {} + ): ProcessResult = verify(command, exitCode, eq(stdOut), stdErr, sanitizer, envAdjuster) + + fun verify( + command: String, + exitCode: Int = 0, + stdOut: String = "", + stdErr: String = "", + sanitizer: Sanitizer = defaultSanitizer, + envAdjuster: EnvAdjuster = {} + ): ProcessResult = verify(command, exitCode, eq(stdOut), eq(stdErr), sanitizer, envAdjuster) + + fun verify( + command: String, + exitCode: Int = 0, + stdOut: TestMatcher, + stdErr: TestMatcher, + sanitizer: Sanitizer = defaultSanitizer, + envAdjuster: EnvAdjuster = {} + ): ProcessResult { + val processResult = runProcess(sanitizer.sanitizeInput(command), envAdjuster) + println(sanitizer.sanitize(processResult)) + + val extCde = genericEquals(exitCode) + + extCde.checkAssertion("ExitCode", processResult.exitCode, sanitizer) + stdOut.checkAssertion("StdOut", processResult.stdout, sanitizer) + stdErr.checkAssertion("StdErr", processResult.stderr, sanitizer) + println() + + return processResult + } +} diff --git a/src/itest/kotlin/io/github/kscripting/shell/integration/tools/TestContext.kt b/src/itest/kotlin/io/github/kscripting/shell/integration/tools/TestContext.kt new file mode 100644 index 0000000..67e8f4c --- /dev/null +++ b/src/itest/kotlin/io/github/kscripting/shell/integration/tools/TestContext.kt @@ -0,0 +1,59 @@ +package io.github.kscripting.shell.integration.tools + +import io.github.kscripting.shell.ShellExecutor +import io.github.kscripting.shell.model.* +import io.github.kscripting.shell.process.EnvAdjuster + +object TestContext { + private val osType: OsType = OsType.find(System.getProperty("osType")) ?: OsType.native + private val nativeType = if (osType.isPosixHostedOnWindows()) OsType.WINDOWS else osType + + private val projectPath: OsPath = OsPath.createOrThrow(nativeType, System.getProperty("projectPath")) + private val execPath: OsPath = projectPath.resolve("build/shell/bin") + private val pathEnvName = if (osType.isWindowsLike()) "Path" else "PATH" + private val systemPath: String = System.getenv()[pathEnvName]!! + + private val pathSeparator: String = if (osType.isWindowsLike() || osType.isPosixHostedOnWindows()) ";" else ":" + private val envPath: String = "${execPath.convert(osType)}$pathSeparator$systemPath" + + val nl: String = System.getProperty("line.separator") + val projectDir: String = projectPath.convert(osType).stringPath() + + init { + println("osType : $osType") + println("nativeType : $nativeType") + println("projectDir : $projectDir") + println("execDir : ${execPath.convert(osType)}") + println("Kotlin version : ${ShellExecutor.evalAndGobble(osType, "kotlin -version", null)}") + + execPath.createDirectories() + } + + fun resolvePath(path: String): OsPath { + return OsPath.createOrThrow(osType, path) + } + + fun runProcess(command: String, envAdjuster: EnvAdjuster): ProcessResult { + //In MSYS all quotes should be single quotes, otherwise content is interpreted e.g. backslashes. + //(MSYS bash interpreter is also replacing double quotes into the single quotes: see: bash -xc 'kscript "println(1+1)"') + val newCommand = when { + osType.isPosixHostedOnWindows() -> command.replace('"', '\'') + else -> command + } + + fun internalEnvAdjuster(map: MutableMap) { + map[pathEnvName] = envPath + envAdjuster(map) + } + + return ShellExecutor.evalAndGobble(osType, newCommand, null, ::internalEnvAdjuster) + } + + fun copyToExecutablePath(source: String) { + val sourceFile = projectPath.resolve(source).toNativeFile() + val targetFile = execPath.resolve(sourceFile.name).toNativeFile() + + sourceFile.copyTo(targetFile, overwrite = true) + targetFile.setExecutable(true) + } +} diff --git a/src/itest/kotlin/io/github/kscripting/shell/integration/tools/TestMatcher.kt b/src/itest/kotlin/io/github/kscripting/shell/integration/tools/TestMatcher.kt new file mode 100644 index 0000000..730f988 --- /dev/null +++ b/src/itest/kotlin/io/github/kscripting/shell/integration/tools/TestMatcher.kt @@ -0,0 +1,51 @@ +package io.github.kscripting.shell.integration.tools + +import io.github.kscripting.shell.integration.tools.TestContext.nl +import org.opentest4j.AssertionFailedError + +abstract class TestMatcher(protected val expectedValue: T, private val expressionName: String) { + abstract fun matches(value: T, sanitizer: Sanitizer = emptySanitizer): Boolean + + fun checkAssertion(assertionName: String, value: T, sanitizer: Sanitizer = emptySanitizer) { + if (matches(value, sanitizer)) { + return + } + + throw AssertionFailedError( + """| + |Expected that '$assertionName' value: + |'${sanitizer.sanitizeOutput(value.toString())}' + |$expressionName + |'${expectedValue.toString()}' + | + |""".trimMargin() + ) + } + + companion object { + val emptySanitizer = Sanitizer(emptyMap()) + } +} + +class GenericEquals(expectedValue: T) : TestMatcher(expectedValue, "is equal to") { + override fun matches(value: T, sanitizer: Sanitizer): Boolean = (value == expectedValue) +} + +class AnyMatch : TestMatcher("", "has any value") { + override fun matches(value: String, sanitizer: Sanitizer): Boolean = true +} + +class Equals(private val expectedString: String, private val ignoreCase: Boolean) : + TestMatcher(expectedString, "is equal to") { + override fun matches(value: String, sanitizer: Sanitizer): Boolean = value.equals(sanitizer.sanitizeInput(expectedString), ignoreCase) +} + +class StartsWith(private val expectedString: String, private val ignoreCase: Boolean) : + TestMatcher(expectedString, "starts with") { + override fun matches(value: String, sanitizer: Sanitizer): Boolean = value.startsWith(sanitizer.sanitizeInput(expectedString), ignoreCase) +} + +class Contains(private val expectedString: String, private val ignoreCase: Boolean) : + TestMatcher(expectedString, "contains") { + override fun matches(value: String, sanitizer: Sanitizer): Boolean = value.contains(sanitizer.sanitizeInput(expectedString), ignoreCase) +} diff --git a/src/main/kotlin/io/github/kscripting/shell/ShellExecutor.kt b/src/main/kotlin/io/github/kscripting/shell/ShellExecutor.kt index c944c45..695cfd3 100644 --- a/src/main/kotlin/io/github/kscripting/shell/ShellExecutor.kt +++ b/src/main/kotlin/io/github/kscripting/shell/ShellExecutor.kt @@ -1,8 +1,8 @@ package io.github.kscripting.shell -import io.github.kscripting.shell.model.ProcessResult import io.github.kscripting.shell.model.OsPath import io.github.kscripting.shell.model.OsType +import io.github.kscripting.shell.model.ProcessResult import io.github.kscripting.shell.process.EnvAdjuster import io.github.kscripting.shell.process.ProcessRunner import io.github.kscripting.shell.process.ProcessRunner.DEFAULT_ERR_PRINTERS @@ -12,6 +12,7 @@ import java.io.PrintStream import java.nio.charset.StandardCharsets object ShellExecutor { + private val UTF_8 = StandardCharsets.UTF_8.name() fun evalAndGobble( osType: OsType, @@ -26,12 +27,10 @@ object ShellExecutor { val outStream = ByteArrayOutputStream(1024) val errStream = ByteArrayOutputStream(1024) - val utf8 = StandardCharsets.UTF_8.name() - var result: Int - PrintStream(outStream, true, utf8).use { additionalOutPrinter -> - PrintStream(errStream, true, utf8).use { additionalErrPrinter -> + PrintStream(outStream, true, UTF_8).use { additionalOutPrinter -> + PrintStream(errStream, true, UTF_8).use { additionalErrPrinter -> result = eval( osType, command, @@ -45,7 +44,7 @@ object ShellExecutor { } } - return ProcessResult(result, outStream.toString(utf8), errStream.toString(utf8)) + return ProcessResult(result, outStream.toString(UTF_8), errStream.toString(UTF_8)) } fun eval( @@ -58,27 +57,18 @@ object ShellExecutor { outPrinter: List = DEFAULT_OUT_PRINTERS, errPrinter: List = DEFAULT_ERR_PRINTERS ): Int { - //NOTE: cmd is an argument to shell (bash/cmd), so it should stay not split by whitespace as a single string - if (osType == OsType.WINDOWS) { - // if the first character in args in `cmd /c ` is a quote, cmd will remove it as well as the - // last quote character within args before processing the term, which removes our quotes. - return ProcessRunner.runProcess( - "cmd", - "/c", - " $command", - workingDirectory = workingDirectory, - envAdjuster = envAdjuster, - waitTimeMinutes = waitTimeMinutes, - inheritInput = inheritInput, - outPrinter = outPrinter, - errPrinter = errPrinter - ) + //NOTE: command is an argument to shell (bash/cmd), so it should stay not split by whitespace as a single string + + val commandList = when (osType) { + // For Windows: if the first character in args in `cmd /c ` is a quote, cmd will remove it as well + // as the last quote character within args before processing the term, which removes our quotes. + // Empty character before command preserves quotes correctly. + OsType.WINDOWS -> listOf("cmd", "/c", " $command") + else -> listOf("bash", "-c", command) } return ProcessRunner.runProcess( - "bash", - "-c", - command, + commandList, workingDirectory = workingDirectory, envAdjuster = envAdjuster, waitTimeMinutes = waitTimeMinutes, diff --git a/src/main/kotlin/io/github/kscripting/shell/model/OsPath.kt b/src/main/kotlin/io/github/kscripting/shell/model/OsPath.kt index f037372..1311b87 100644 --- a/src/main/kotlin/io/github/kscripting/shell/model/OsPath.kt +++ b/src/main/kotlin/io/github/kscripting/shell/model/OsPath.kt @@ -136,29 +136,27 @@ data class OsPath(val osType: OsType, val pathType: PathType, val pathParts: Lis '\\' } - fun createOrThrow(osType: OsType, root: String, vararg pathParts: String): OsPath { - return when (val result = internalCreate(osType, root, *pathParts)) { - is Either.Right -> result.value - is Either.Left -> throw IllegalArgumentException(result.value) - } - } + fun createOrThrow(vararg pathParts: String): OsPath = + createOrThrow(OsType.native, pathParts.toList()) + + fun createOrThrow(osType: OsType, vararg pathParts: String): OsPath = + createOrThrow(osType, pathParts.toList()) - fun createOrThrow(root: String, vararg pathParts: String): OsPath { - return when (val result = internalCreate(OsType.native, root, *pathParts)) { + fun createOrThrow(osType: OsType, pathParts: List): OsPath { + return when (val result = internalCreate(osType, pathParts)) { is Either.Right -> result.value is Either.Left -> throw IllegalArgumentException(result.value) } } - fun create(osType: OsType, root: String, vararg pathParts: String): OsPath? { - return when (val result = internalCreate(osType, root, *pathParts)) { - is Either.Right -> result.value - is Either.Left -> null - } - } + fun create(vararg pathParts: String): OsPath? = + create(OsType.native, pathParts.toList()) + + fun create(osType: OsType, vararg pathParts: String): OsPath? = + create(osType, pathParts.toList()) - fun create(root: String, vararg pathParts: String): OsPath? { - return when (val result = internalCreate(OsType.native, root, *pathParts)) { + fun create(osType: OsType, pathParts: List): OsPath? { + return when (val result = internalCreate(osType, pathParts)) { is Either.Right -> result.value is Either.Left -> null } @@ -167,10 +165,10 @@ data class OsPath(val osType: OsType, val pathType: PathType, val pathParts: Lis //Relaxed validation: //1. It doesn't matter if there is '/' or '\' used as path separator - both are treated he same //2. Duplicated or trailing slashes '/' and backslashes '\' are just ignored - private fun internalCreate(osType: OsType, root: String, vararg pathParts: String): Either { + private fun internalCreate(osType: OsType, pathParts: List): Either { val pathSeparatorCharacter = resolvePathSeparator(osType) - val path = listOf(root, *pathParts).joinToString(pathSeparatorCharacter.toString()) + val path = pathParts.joinToString(pathSeparatorCharacter.toString()) val pathPartsResolved = path.split('/', '\\').toMutableList() //Validate root element of path and find out if it is absolute or relative diff --git a/src/main/kotlin/io/github/kscripting/shell/model/OsPathExt.kt b/src/main/kotlin/io/github/kscripting/shell/model/OsPathExt.kt index b8d0c99..c926262 100644 --- a/src/main/kotlin/io/github/kscripting/shell/model/OsPathExt.kt +++ b/src/main/kotlin/io/github/kscripting/shell/model/OsPathExt.kt @@ -23,10 +23,10 @@ fun URI.toOsPath(): OsPath = fun OsPath.toNativePath(): Path = Paths.get(toNativeOsPath().stringPath()) -fun OsPath.toNativeOsPath() = if (osType.isPosixHostedOnWindows()) convert(OsType.WINDOWS) else this - fun OsPath.toNativeFile(): File = toNativePath().toFile() +fun OsPath.toNativeOsPath(): OsPath = if (osType.isPosixHostedOnWindows()) convert(OsType.WINDOWS) else this + // OsPath operations @@ -46,21 +46,20 @@ fun OsPath.readText(charset: Charset = Charsets.UTF_8): String = toNativePath(). // OsPath accessors - val OsPath.leaf - get() = if (pathParts.isEmpty()) "" else pathParts.last() + get(): String? = if (pathParts.isEmpty()) null else pathParts.last() val OsPath.root - get() = if (pathParts.isEmpty()) "" else pathParts.first() + get(): String? = if (pathParts.isEmpty()) null else pathParts.first() val OsPath.rootOsPath - get() = OsPath.createOrThrow(osType, root) + get():OsPath = if (root == null) OsPath.createOrThrow(osType) else OsPath.createOrThrow(osType, root!!) val OsPath.parent - get() = toNativePath().parent + get(): OsPath = OsPath.createOrThrow(osType, pathParts.dropLast(1)) val OsPath.nativeType - get() = if (osType.isPosixHostedOnWindows()) OsType.WINDOWS else osType + get(): OsType = if (osType.isPosixHostedOnWindows()) OsType.WINDOWS else osType val OsPath.extension - get() = leaf.substringAfterLast('.', "") + get(): String? = leaf?.substringAfterLast('.', "") diff --git a/src/main/kotlin/io/github/kscripting/shell/util/ShellEscapeUtils.kt b/src/main/kotlin/io/github/kscripting/shell/util/ShellEscapeUtils.kt deleted file mode 100644 index f7185ad..0000000 --- a/src/main/kotlin/io/github/kscripting/shell/util/ShellEscapeUtils.kt +++ /dev/null @@ -1,6 +0,0 @@ -package io.github.kscripting.shell.util - -object ShellEscapeUtils { - fun whitespaceCharsToSymbols(string: String): String = - string.replace("\\", "[bs]").lines().joinToString("[nl]") -} diff --git a/src/test/kotlin/io/github/kscripting/shell/model/OsPathTest.kt b/src/test/kotlin/io/github/kscripting/shell/model/OsPathTest.kt index 4190968..4dd4938 100644 --- a/src/test/kotlin/io/github/kscripting/shell/model/OsPathTest.kt +++ b/src/test/kotlin/io/github/kscripting/shell/model/OsPathTest.kt @@ -69,6 +69,13 @@ class OsPathTest { it.prop(OsPath::pathType).isEqualTo(PathType.RELATIVE) it.prop(OsPath::osType).isEqualTo(OsType.LINUX) } + + //Home dir is correctly handled + assertThat(OsPath.createOrThrow(OsType.LINUX, "~/admin/.git")).let { + it.prop(OsPath::pathParts).isEqualTo(listOf("~", "admin", ".git")) + it.prop(OsPath::pathType).isEqualTo(PathType.ABSOLUTE) + it.prop(OsPath::osType).isEqualTo(OsType.LINUX) + } } @Test @@ -99,8 +106,7 @@ class OsPathTest { @Test fun `Test invalid Linux paths`() { assertThat { OsPath.createOrThrow(OsType.LINUX, "/ad*asdf") }.isFailure() - .isInstanceOf(IllegalArgumentException::class.java) - .hasMessage("Invalid character '*' in path '/ad*asdf'") + .isInstanceOf(IllegalArgumentException::class.java).hasMessage("Invalid character '*' in path '/ad*asdf'") } @Test @@ -127,14 +133,12 @@ class OsPathTest { assertThat( OsPath.createOrThrow(OsType.LINUX, "./home/admin/") - .resolve(OsPath.createOrThrow(OsType.LINUX, "./.kscript/")) - .stringPath() + .resolve(OsPath.createOrThrow(OsType.LINUX, "./.kscript/")).stringPath() ).isEqualTo("./home/admin/.kscript") assertThat( OsPath.createOrThrow(OsType.LINUX, "../home/admin/") - .resolve(OsPath.createOrThrow(OsType.LINUX, "./.kscript/")) - .stringPath() + .resolve(OsPath.createOrThrow(OsType.LINUX, "./.kscript/")).stringPath() ).isEqualTo("../home/admin/.kscript") assertThat( @@ -149,14 +153,12 @@ class OsPathTest { assertThat { OsPath.createOrThrow(OsType.LINUX, "./home/admin").resolve(OsPath.createOrThrow(OsType.WINDOWS, ".\\run")) - }.isFailure() - .isInstanceOf(IllegalArgumentException::class.java) + }.isFailure().isInstanceOf(IllegalArgumentException::class.java) .hasMessage("Paths from different OS's: 'LINUX' path can not be resolved with 'WINDOWS' path") assertThat { OsPath.createOrThrow(OsType.LINUX, "./home/admin").resolve(OsPath.createOrThrow(OsType.LINUX, "/run")) - }.isFailure() - .isInstanceOf(IllegalArgumentException::class.java) + }.isFailure().isInstanceOf(IllegalArgumentException::class.java) .hasMessage("Can not resolve absolute, relative or undefined path './home/admin' using absolute path '/run'") } @@ -323,20 +325,22 @@ class OsPathTest { ).isEqualTo("..\\home\\admin\\.kscript") } + // ************************************************* Special cases ************************************************* + + @Test + fun `Special cases`() { + assertThat(OsPath.createOrThrow(OsType.LINUX)).isEqualTo( + OsPath(OsType.LINUX, PathType.UNDEFINED, emptyList(), '/') + ) + } + // ************************************* Shorthand for creating composite paths ************************************ @Test - fun `Add paths`() { + fun `Concatenate paths`() { val p = OsPath.createOrThrow(OsType.MSYS, "/c/home") val p1 = OsPath.createOrThrow(OsType.MSYS, "admin/.kscript") assertThat(p / p1).isEqualTo(OsPath.createOrThrow(OsType.MSYS, "/c/home/admin/.kscript")) - - val test = OsPath.createOrThrow(OsType.LINUX, "/c/home/admin/.kscript") - println(test) - println(test.osType) - println(test.pathType) - println(test.pathParts) - println(test.pathSeparator) } } From 3de30a9b652f04c87110f15c84aa5b2e4f3cc0c0 Mon Sep 17 00:00:00 2001 From: Marcin Kuszczak <1508798+aartiPl@users.noreply.github.com> Date: Sat, 17 Jun 2023 18:19:34 +0200 Subject: [PATCH 05/50] * Better tests * Sanitizer * --- TODO.adoc | 1 + src/doEcho.bat | 8 +++ src/doEcho.sh | 8 +++ src/echo_program.bat | 3 - src/echo_program.sh | 3 - .../shell/integration/ShellExecutorTest.kt | 19 +++++- .../shell/integration/tools/Sanitizer.kt | 37 ----------- .../shell/integration/tools/TestAssertion.kt | 23 +++---- .../shell/integration/tools/TestContext.kt | 18 +++++- .../shell/integration/tools/TestMatcher.kt | 26 ++++---- .../github/kscripting/shell/ShellExecutor.kt | 16 ++++- .../kscripting/shell/process/ProcessRunner.kt | 21 +++++-- .../kscripting/shell/process/StreamGobbler.kt | 20 ++++-- .../github/kscripting/shell/util/Sanitizer.kt | 61 ++++++++++++++++++ .../kscripting/shell/util/SanitizerTest.kt | 45 +++++++++++++ src/unicodeOutput.txt | 63 +++++++++++++++++++ 16 files changed, 284 insertions(+), 88 deletions(-) create mode 100644 src/doEcho.bat create mode 100644 src/doEcho.sh delete mode 100644 src/echo_program.bat delete mode 100644 src/echo_program.sh delete mode 100644 src/itest/kotlin/io/github/kscripting/shell/integration/tools/Sanitizer.kt create mode 100644 src/main/kotlin/io/github/kscripting/shell/util/Sanitizer.kt create mode 100644 src/test/kotlin/io/github/kscripting/shell/util/SanitizerTest.kt create mode 100644 src/unicodeOutput.txt diff --git a/TODO.adoc b/TODO.adoc index 9967430..53c63b7 100644 --- a/TODO.adoc +++ b/TODO.adoc @@ -13,3 +13,4 @@ * Possibility of parametrization of commands (properties) + masking (for tests and interoperability) * Silent mode () * Use https://github.com/JetBrains/pty4j[PTY4j] instead of ProcessBuilder to read color codes from input and append them to output (https://stackoverflow.com/questions/45789329/java-capture-process-output-with-color) +* Providing different types of shell (e.g. 'sh' or 'zsh') and executing without shell diff --git a/src/doEcho.bat b/src/doEcho.bat new file mode 100644 index 0000000..816a0ef --- /dev/null +++ b/src/doEcho.bat @@ -0,0 +1,8 @@ +@echo off + +IF "-f"=="%1" ( + type %2 + exit +) + +echo %1 diff --git a/src/doEcho.sh b/src/doEcho.sh new file mode 100644 index 0000000..c9de237 --- /dev/null +++ b/src/doEcho.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +if [[ "-f" = "$1" ]]; then + cat $2 + exit +fi + +echo "$1" diff --git a/src/echo_program.bat b/src/echo_program.bat deleted file mode 100644 index 2d05949..0000000 --- a/src/echo_program.bat +++ /dev/null @@ -1,3 +0,0 @@ -@echo off - -echo %1 diff --git a/src/echo_program.sh b/src/echo_program.sh deleted file mode 100644 index dd5455d..0000000 --- a/src/echo_program.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env bash - -echo "$1" diff --git a/src/itest/kotlin/io/github/kscripting/shell/integration/ShellExecutorTest.kt b/src/itest/kotlin/io/github/kscripting/shell/integration/ShellExecutorTest.kt index 99b5006..51e11b0 100644 --- a/src/itest/kotlin/io/github/kscripting/shell/integration/ShellExecutorTest.kt +++ b/src/itest/kotlin/io/github/kscripting/shell/integration/ShellExecutorTest.kt @@ -2,6 +2,9 @@ package io.github.kscripting.shell.integration import io.github.kscripting.shell.integration.tools.TestAssertion.verify import io.github.kscripting.shell.integration.tools.TestContext +import io.github.kscripting.shell.integration.tools.TestContext.execPath +import io.github.kscripting.shell.integration.tools.TestContext.resolvePath +import io.github.kscripting.shell.model.readText import org.junit.jupiter.api.Tag import org.junit.jupiter.api.Test @@ -10,13 +13,23 @@ class ShellExecutorTest : TestBase { @Tag("posix") @Tag("windows") fun `Simple echo of parameters works`() { - verify("echo_program test", 0, "test[nl]", "") + verify("doEcho test", 0, "test[nl]", "") + } + + @Test + @Tag("posix") + @Tag("windows") + fun `Unicode characters output works`() { + val path = execPath().resolve("unicodeOutput.txt") + println(path) + verify("doEcho -f $path", 0, path.readText(), "") } companion object { init { - TestContext.copyToExecutablePath("src/echo_program.sh") - TestContext.copyToExecutablePath("src/echo_program.bat") + TestContext.copyToExecutablePath("src/doEcho.sh") + TestContext.copyToExecutablePath("src/doEcho.bat") + TestContext.copyToExecutablePath("src/unicodeOutput.txt") } } } diff --git a/src/itest/kotlin/io/github/kscripting/shell/integration/tools/Sanitizer.kt b/src/itest/kotlin/io/github/kscripting/shell/integration/tools/Sanitizer.kt deleted file mode 100644 index 898460d..0000000 --- a/src/itest/kotlin/io/github/kscripting/shell/integration/tools/Sanitizer.kt +++ /dev/null @@ -1,37 +0,0 @@ -package io.github.kscripting.shell.integration.tools - -import io.github.kscripting.shell.model.ProcessResult - -//Mapping: -//[bs] --> \\ -//[nl] --> \n -class Sanitizer(private val mapping: Map = mapOf()) { - fun sanitizeInput(string: String): String { - return sanitize(string) { key, value -> - this.replace(key, value) - } - } - - fun sanitizeOutput(string: String): String { - return sanitize(string) { key, value -> - this.replace(value, key) - } - } - - fun sanitize(processResult: ProcessResult): ProcessResult { - return processResult.copy( - stdout = sanitizeOutput(processResult.stdout), - stderr = sanitizeOutput(processResult.stderr) - ) - } - - fun sanitize(string: String, fn: String.(String, String) -> String): String { - var result = string - - for (entry in mapping.entries) { - result = result.fn(entry.key, entry.value) - } - - return result - } -} diff --git a/src/itest/kotlin/io/github/kscripting/shell/integration/tools/TestAssertion.kt b/src/itest/kotlin/io/github/kscripting/shell/integration/tools/TestAssertion.kt index 89661e3..27bec43 100644 --- a/src/itest/kotlin/io/github/kscripting/shell/integration/tools/TestAssertion.kt +++ b/src/itest/kotlin/io/github/kscripting/shell/integration/tools/TestAssertion.kt @@ -5,9 +5,6 @@ import io.github.kscripting.shell.model.ProcessResult import io.github.kscripting.shell.process.EnvAdjuster object TestAssertion { - private val defaultSanitizer = - Sanitizer(mapOf("[bs]" to "\\", "[nl]" to System.getProperty("line.separator"), "[tb]" to "\t")) - fun genericEquals(value: T) = GenericEquals(value) fun any() = AnyMatch() @@ -20,44 +17,40 @@ object TestAssertion { exitCode: Int = 0, stdOut: TestMatcher, stdErr: String = "", - sanitizer: Sanitizer = defaultSanitizer, envAdjuster: EnvAdjuster = {} - ): ProcessResult = verify(command, exitCode, stdOut, eq(stdErr), sanitizer, envAdjuster) + ): ProcessResult = verify(command, exitCode, stdOut, eq(stdErr), envAdjuster) fun verify( command: String, exitCode: Int = 0, stdOut: String, stdErr: TestMatcher, - sanitizer: Sanitizer = defaultSanitizer, envAdjuster: EnvAdjuster = {} - ): ProcessResult = verify(command, exitCode, eq(stdOut), stdErr, sanitizer, envAdjuster) + ): ProcessResult = verify(command, exitCode, eq(stdOut), stdErr, envAdjuster) fun verify( command: String, exitCode: Int = 0, stdOut: String = "", stdErr: String = "", - sanitizer: Sanitizer = defaultSanitizer, envAdjuster: EnvAdjuster = {} - ): ProcessResult = verify(command, exitCode, eq(stdOut), eq(stdErr), sanitizer, envAdjuster) + ): ProcessResult = verify(command, exitCode, eq(stdOut), eq(stdErr), envAdjuster) fun verify( command: String, exitCode: Int = 0, stdOut: TestMatcher, stdErr: TestMatcher, - sanitizer: Sanitizer = defaultSanitizer, envAdjuster: EnvAdjuster = {} ): ProcessResult { - val processResult = runProcess(sanitizer.sanitizeInput(command), envAdjuster) - println(sanitizer.sanitize(processResult)) + val processResult = runProcess(command, envAdjuster) + println(processResult) val extCde = genericEquals(exitCode) - extCde.checkAssertion("ExitCode", processResult.exitCode, sanitizer) - stdOut.checkAssertion("StdOut", processResult.stdout, sanitizer) - stdErr.checkAssertion("StdErr", processResult.stderr, sanitizer) + extCde.checkAssertion("ExitCode", processResult.exitCode) + stdOut.checkAssertion("StdOut", processResult.stdout) + stdErr.checkAssertion("StdErr", processResult.stderr) println() return processResult diff --git a/src/itest/kotlin/io/github/kscripting/shell/integration/tools/TestContext.kt b/src/itest/kotlin/io/github/kscripting/shell/integration/tools/TestContext.kt index 67e8f4c..f1d49db 100644 --- a/src/itest/kotlin/io/github/kscripting/shell/integration/tools/TestContext.kt +++ b/src/itest/kotlin/io/github/kscripting/shell/integration/tools/TestContext.kt @@ -3,6 +3,7 @@ package io.github.kscripting.shell.integration.tools import io.github.kscripting.shell.ShellExecutor import io.github.kscripting.shell.model.* import io.github.kscripting.shell.process.EnvAdjuster +import io.github.kscripting.shell.util.Sanitizer object TestContext { private val osType: OsType = OsType.find(System.getProperty("osType")) ?: OsType.native @@ -16,7 +17,10 @@ object TestContext { private val pathSeparator: String = if (osType.isWindowsLike() || osType.isPosixHostedOnWindows()) ";" else ":" private val envPath: String = "${execPath.convert(osType)}$pathSeparator$systemPath" - val nl: String = System.getProperty("line.separator") + private val inputSanitizer = Sanitizer( + listOf("[bs]" to "\\", "[nl]" to System.getProperty("line.separator"), "[tb]" to "\t") + ) + val projectDir: String = projectPath.convert(osType).stringPath() init { @@ -24,7 +28,7 @@ object TestContext { println("nativeType : $nativeType") println("projectDir : $projectDir") println("execDir : ${execPath.convert(osType)}") - println("Kotlin version : ${ShellExecutor.evalAndGobble(osType, "kotlin -version", null)}") + println("Kotlin version : ${ShellExecutor.evalAndGobble(osType, "kotlin -version", null).stdout}") execPath.createDirectories() } @@ -46,7 +50,13 @@ object TestContext { envAdjuster(map) } - return ShellExecutor.evalAndGobble(osType, newCommand, null, ::internalEnvAdjuster) + return ShellExecutor.evalAndGobble( + osType, + newCommand, + null, + ::internalEnvAdjuster, + inputSanitizer = inputSanitizer + ) } fun copyToExecutablePath(source: String) { @@ -56,4 +66,6 @@ object TestContext { sourceFile.copyTo(targetFile, overwrite = true) targetFile.setExecutable(true) } + + fun execPath(): OsPath = execPath } diff --git a/src/itest/kotlin/io/github/kscripting/shell/integration/tools/TestMatcher.kt b/src/itest/kotlin/io/github/kscripting/shell/integration/tools/TestMatcher.kt index 730f988..c60d131 100644 --- a/src/itest/kotlin/io/github/kscripting/shell/integration/tools/TestMatcher.kt +++ b/src/itest/kotlin/io/github/kscripting/shell/integration/tools/TestMatcher.kt @@ -1,51 +1,49 @@ package io.github.kscripting.shell.integration.tools -import io.github.kscripting.shell.integration.tools.TestContext.nl import org.opentest4j.AssertionFailedError abstract class TestMatcher(protected val expectedValue: T, private val expressionName: String) { - abstract fun matches(value: T, sanitizer: Sanitizer = emptySanitizer): Boolean + abstract fun matches(value: T): Boolean - fun checkAssertion(assertionName: String, value: T, sanitizer: Sanitizer = emptySanitizer) { - if (matches(value, sanitizer)) { + fun checkAssertion(assertionName: String, value: T) { + if (matches(value)) { return } throw AssertionFailedError( """| |Expected that '$assertionName' value: - |'${sanitizer.sanitizeOutput(value.toString())}' + |'${value.toString()}' |$expressionName |'${expectedValue.toString()}' | |""".trimMargin() ) } - - companion object { - val emptySanitizer = Sanitizer(emptyMap()) - } } class GenericEquals(expectedValue: T) : TestMatcher(expectedValue, "is equal to") { - override fun matches(value: T, sanitizer: Sanitizer): Boolean = (value == expectedValue) + override fun matches(value: T): Boolean = (value == expectedValue) } class AnyMatch : TestMatcher("", "has any value") { - override fun matches(value: String, sanitizer: Sanitizer): Boolean = true + override fun matches(value: String): Boolean = true } class Equals(private val expectedString: String, private val ignoreCase: Boolean) : TestMatcher(expectedString, "is equal to") { - override fun matches(value: String, sanitizer: Sanitizer): Boolean = value.equals(sanitizer.sanitizeInput(expectedString), ignoreCase) + override fun matches(value: String): Boolean = + value.equals(expectedString, ignoreCase) } class StartsWith(private val expectedString: String, private val ignoreCase: Boolean) : TestMatcher(expectedString, "starts with") { - override fun matches(value: String, sanitizer: Sanitizer): Boolean = value.startsWith(sanitizer.sanitizeInput(expectedString), ignoreCase) + override fun matches(value: String): Boolean = + value.startsWith(expectedString, ignoreCase) } class Contains(private val expectedString: String, private val ignoreCase: Boolean) : TestMatcher(expectedString, "contains") { - override fun matches(value: String, sanitizer: Sanitizer): Boolean = value.contains(sanitizer.sanitizeInput(expectedString), ignoreCase) + override fun matches(value: String): Boolean = + value.contains(expectedString, ignoreCase) } diff --git a/src/main/kotlin/io/github/kscripting/shell/ShellExecutor.kt b/src/main/kotlin/io/github/kscripting/shell/ShellExecutor.kt index 695cfd3..22798de 100644 --- a/src/main/kotlin/io/github/kscripting/shell/ShellExecutor.kt +++ b/src/main/kotlin/io/github/kscripting/shell/ShellExecutor.kt @@ -7,6 +7,8 @@ import io.github.kscripting.shell.process.EnvAdjuster import io.github.kscripting.shell.process.ProcessRunner import io.github.kscripting.shell.process.ProcessRunner.DEFAULT_ERR_PRINTERS import io.github.kscripting.shell.process.ProcessRunner.DEFAULT_OUT_PRINTERS +import io.github.kscripting.shell.util.Sanitizer +import io.github.kscripting.shell.util.Sanitizer.Companion.EMPTY_SANITIZER import java.io.ByteArrayOutputStream import java.io.PrintStream import java.nio.charset.StandardCharsets @@ -21,6 +23,8 @@ object ShellExecutor { envAdjuster: EnvAdjuster = {}, waitTimeMinutes: Int = 10, inheritInput: Boolean = false, + inputSanitizer: Sanitizer = EMPTY_SANITIZER, + outputSanitizer: Sanitizer = inputSanitizer.swapped(), outPrinter: List = emptyList(), errPrinter: List = emptyList() ): ProcessResult { @@ -38,6 +42,8 @@ object ShellExecutor { envAdjuster, waitTimeMinutes, inheritInput, + inputSanitizer, + outputSanitizer, outPrinter + additionalOutPrinter, errPrinter + additionalErrPrinter ) @@ -54,17 +60,21 @@ object ShellExecutor { envAdjuster: EnvAdjuster = {}, waitTimeMinutes: Int = 10, inheritInput: Boolean = false, + inputSanitizer: Sanitizer = EMPTY_SANITIZER, + outputSanitizer: Sanitizer = inputSanitizer.swapped(), outPrinter: List = DEFAULT_OUT_PRINTERS, errPrinter: List = DEFAULT_ERR_PRINTERS ): Int { //NOTE: command is an argument to shell (bash/cmd), so it should stay not split by whitespace as a single string + val sanitizedCommand = inputSanitizer.sanitize(command) + val commandList = when (osType) { // For Windows: if the first character in args in `cmd /c ` is a quote, cmd will remove it as well // as the last quote character within args before processing the term, which removes our quotes. // Empty character before command preserves quotes correctly. - OsType.WINDOWS -> listOf("cmd", "/c", " $command") - else -> listOf("bash", "-c", command) + OsType.WINDOWS -> listOf("cmd", "/c", " $sanitizedCommand") + else -> listOf("bash", "-c", sanitizedCommand) } return ProcessRunner.runProcess( @@ -73,6 +83,8 @@ object ShellExecutor { envAdjuster = envAdjuster, waitTimeMinutes = waitTimeMinutes, inheritInput = inheritInput, + inputSanitizer = inputSanitizer, + outputSanitizer = outputSanitizer, outPrinter = outPrinter, errPrinter = errPrinter ) diff --git a/src/main/kotlin/io/github/kscripting/shell/process/ProcessRunner.kt b/src/main/kotlin/io/github/kscripting/shell/process/ProcessRunner.kt index 46d8c7f..b4a3b6f 100644 --- a/src/main/kotlin/io/github/kscripting/shell/process/ProcessRunner.kt +++ b/src/main/kotlin/io/github/kscripting/shell/process/ProcessRunner.kt @@ -2,8 +2,9 @@ package io.github.kscripting.shell.process import io.github.kscripting.shell.model.CommandTimeoutException import io.github.kscripting.shell.model.OsPath -import io.github.kscripting.shell.model.ProcessResult import io.github.kscripting.shell.model.toNativeFile +import io.github.kscripting.shell.util.Sanitizer +import io.github.kscripting.shell.util.Sanitizer.Companion.EMPTY_SANITIZER import java.io.PrintStream import java.util.concurrent.TimeUnit @@ -19,11 +20,21 @@ object ProcessRunner { envAdjuster: EnvAdjuster = {}, waitTimeMinutes: Int = 10, inheritInput: Boolean = false, + inputSanitizer: Sanitizer = EMPTY_SANITIZER, + outputSanitizer: Sanitizer = inputSanitizer.swapped(), outPrinter: List = DEFAULT_OUT_PRINTERS, errPrinter: List = DEFAULT_ERR_PRINTERS, ): Int { return runProcess( - command.asList(), workingDirectory, envAdjuster, waitTimeMinutes, inheritInput, outPrinter, errPrinter + command.asList(), + workingDirectory, + envAdjuster, + waitTimeMinutes, + inheritInput, + inputSanitizer, + outputSanitizer, + outPrinter, + errPrinter ) } @@ -33,6 +44,8 @@ object ProcessRunner { envAdjuster: EnvAdjuster = {}, waitTimeMinutes: Int = 10, inheritInput: Boolean = false, + inputSanitizer: Sanitizer = EMPTY_SANITIZER, + outputSanitizer: Sanitizer = inputSanitizer.swapped(), outPrinter: List = DEFAULT_OUT_PRINTERS, errPrinter: List = DEFAULT_ERR_PRINTERS, ): Int { @@ -48,8 +61,8 @@ object ProcessRunner { // we need to gobble the streams to prevent that the internal pipes hit their respective buffer limits, which // would lock the sub-process execution (see see https://github.com/kscripting/kscript/issues/55 // https://stackoverflow.com/questions/14165517/processbuilder-forwarding-stdout-and-stderr-of-started-processes-without-blocki - val inputStreamReader = StreamGobbler(process.inputStream, outPrinter).start() - val errorStreamReader = StreamGobbler(process.errorStream, errPrinter).start() + val inputStreamReader = StreamGobbler(outputSanitizer, process.inputStream, outPrinter).start() + val errorStreamReader = StreamGobbler(outputSanitizer, process.errorStream, errPrinter).start() val exitedNormally = process.waitFor(waitTimeMinutes.toLong(), TimeUnit.MINUTES) diff --git a/src/main/kotlin/io/github/kscripting/shell/process/StreamGobbler.kt b/src/main/kotlin/io/github/kscripting/shell/process/StreamGobbler.kt index a3137f5..4e3d7cd 100644 --- a/src/main/kotlin/io/github/kscripting/shell/process/StreamGobbler.kt +++ b/src/main/kotlin/io/github/kscripting/shell/process/StreamGobbler.kt @@ -1,5 +1,6 @@ package io.github.kscripting.shell.process +import io.github.kscripting.shell.util.Sanitizer import java.io.BufferedReader import java.io.InputStream import java.io.InputStreamReader @@ -7,6 +8,7 @@ import java.io.PrintStream class StreamGobbler( + private val outputSanitizer: Sanitizer, inputStream: InputStream, private val printStream: List, ) { @@ -29,13 +31,23 @@ class StreamGobbler( private fun readInputStreamSequentially() { val charArray = CharArray(1024) var length: Int + var rest = "" while (reader.read(charArray).also { length = it } != -1) { - val content = String(charArray, 0, length) + var content = rest + String(charArray, 0, length) + rest = outputSanitizer.calculatePotentialMatch(content) + content = outputSanitizer.sanitize(content.dropLast(rest.length)) + outputToPrintStreams(content) + } + + if (rest.isNotEmpty()) { + outputToPrintStreams(outputSanitizer.sanitize(rest)) + } + } - printStream.forEach { - it.print(content) - } + private fun outputToPrintStreams(content: String) { + printStream.forEach { + it.print(content) } } } diff --git a/src/main/kotlin/io/github/kscripting/shell/util/Sanitizer.kt b/src/main/kotlin/io/github/kscripting/shell/util/Sanitizer.kt new file mode 100644 index 0000000..2b7f359 --- /dev/null +++ b/src/main/kotlin/io/github/kscripting/shell/util/Sanitizer.kt @@ -0,0 +1,61 @@ +package io.github.kscripting.shell.util + +//Mapping: +//[bs] --> \\ +//[nl] --> \n +class Sanitizer(substitutions: List> = emptyList()) { + private val substitutions: List> = substitutions.sortedByDescending { it.first.length } + + constructor(vararg substitutions: Pair) : this(substitutions.toList()) + + fun sanitize(string: String): String { + var result = string + + for (substitution in substitutions) { + result = result.replace(substitution.first, substitution.second) + } + + return result + } + + fun calculatePotentialMatch(content: String): String { + //It will be always max. key size - 1 (string was not matched because one character was missing); + //We can simplify and remove that rest always; drawback: if shorter substrings are part of the longer substring + //then longer substring will never be matched + + if (substitutions.isEmpty() || substitutions[0].first.length == 1) { + //if there are no substitutions or they are no longer than 1 character then just return + return "" + } + + var rest = "" + var checkFactor = 1 + + while (rest.isEmpty() && checkFactor < substitutions[0].first.length) { + for (substitution in substitutions) { + val substitutionKey = substitution.first + + if (checkFactor >= substitutionKey.length) { + break + } + + val potentialEnding = substitutionKey.substring(0, substitutionKey.length - checkFactor) + + if (content.endsWith(potentialEnding)) { + rest = potentialEnding + break + } + } + + checkFactor += 1 + } + + return rest + } + + fun swapped() = Sanitizer(substitutions.map { it.second to it.first }.sortedByDescending { it.first }) + + companion object { + val EMPTY_SANITIZER = Sanitizer() + } +} diff --git a/src/test/kotlin/io/github/kscripting/shell/util/SanitizerTest.kt b/src/test/kotlin/io/github/kscripting/shell/util/SanitizerTest.kt new file mode 100644 index 0000000..ea38b7c --- /dev/null +++ b/src/test/kotlin/io/github/kscripting/shell/util/SanitizerTest.kt @@ -0,0 +1,45 @@ +package io.github.kscripting.shell.util + +import assertk.assertThat +import assertk.assertions.isEqualTo +import org.junit.jupiter.api.Test + + +class SanitizerTest { + @Test + fun `Test sanitize`() { + val sanitizer = Sanitizer("" to "<***>") + assertThat(sanitizer.sanitize("This is !!!")).isEqualTo("This is <***>!!!") + } + + @Test + fun `Test swapped`() { + val sanitizer = Sanitizer("" to "<***>").swapped() + assertThat(sanitizer.sanitize("This is <***>!!!")).isEqualTo("This is !!!") + } + + @Test + fun `Test calculatePotentialMatch - corner cases`() { + assertThat(Sanitizer.EMPTY_SANITIZER.calculatePotentialMatch("abcd")).isEqualTo("") + + val sanitizer = Sanitizer("[" to "]") + assertThat(sanitizer.calculatePotentialMatch("abcd[bs[")).isEqualTo("") + } + + @Test + fun `Test calculatePotentialMatch - normal cases`() { + val sanitizer = Sanitizer("" to "\\", "" to "\t", "" to "\n") + + assertThat(sanitizer.calculatePotentialMatch("")).isEqualTo("") + assertThat(sanitizer.calculatePotentialMatch("abcd")).isEqualTo("") + assertThat(sanitizer.calculatePotentialMatch("abcd" to "", "" to "", "" to "") + assertThat(longSanitizer.calculatePotentialMatch("abcd")).isEqualTo("") + assertThat(longSanitizer.calculatePotentialMatch("abcdabcd Date: Sat, 17 Jun 2023 18:56:08 +0200 Subject: [PATCH 06/50] * Ability to not use shell --- .../shell/integration/tools/TestContext.kt | 4 +- .../github/kscripting/shell/ShellExecutor.kt | 41 ++++++++++++------- .../github/kscripting/shell/model/OsType.kt | 2 +- .../kscripting/shell/model/ShellType.kt | 8 ++++ .../kscripting/shell/process/ProcessRunner.kt | 7 +--- 5 files changed, 40 insertions(+), 22 deletions(-) create mode 100644 src/main/kotlin/io/github/kscripting/shell/model/ShellType.kt diff --git a/src/itest/kotlin/io/github/kscripting/shell/integration/tools/TestContext.kt b/src/itest/kotlin/io/github/kscripting/shell/integration/tools/TestContext.kt index f1d49db..0d70a8a 100644 --- a/src/itest/kotlin/io/github/kscripting/shell/integration/tools/TestContext.kt +++ b/src/itest/kotlin/io/github/kscripting/shell/integration/tools/TestContext.kt @@ -28,7 +28,7 @@ object TestContext { println("nativeType : $nativeType") println("projectDir : $projectDir") println("execDir : ${execPath.convert(osType)}") - println("Kotlin version : ${ShellExecutor.evalAndGobble(osType, "kotlin -version", null).stdout}") + println("Kotlin version : ${ShellExecutor.evalAndGobble("kotlin -version", osType, null).stdout}") execPath.createDirectories() } @@ -51,8 +51,8 @@ object TestContext { } return ShellExecutor.evalAndGobble( - osType, newCommand, + osType, null, ::internalEnvAdjuster, inputSanitizer = inputSanitizer diff --git a/src/main/kotlin/io/github/kscripting/shell/ShellExecutor.kt b/src/main/kotlin/io/github/kscripting/shell/ShellExecutor.kt index 22798de..a5e78b5 100644 --- a/src/main/kotlin/io/github/kscripting/shell/ShellExecutor.kt +++ b/src/main/kotlin/io/github/kscripting/shell/ShellExecutor.kt @@ -3,6 +3,7 @@ package io.github.kscripting.shell import io.github.kscripting.shell.model.OsPath import io.github.kscripting.shell.model.OsType import io.github.kscripting.shell.model.ProcessResult +import io.github.kscripting.shell.model.ShellType import io.github.kscripting.shell.process.EnvAdjuster import io.github.kscripting.shell.process.ProcessRunner import io.github.kscripting.shell.process.ProcessRunner.DEFAULT_ERR_PRINTERS @@ -15,10 +16,18 @@ import java.nio.charset.StandardCharsets object ShellExecutor { private val UTF_8 = StandardCharsets.UTF_8.name() + private val DEFAULT_SHELL_MAPPER: Map = mapOf( + OsType.WINDOWS to ShellType.CMD, + OsType.LINUX to ShellType.BASH, + OsType.FREEBSD to ShellType.BASH, + OsType.MACOS to ShellType.BASH, + OsType.CYGWIN to ShellType.BASH, + OsType.MSYS to ShellType.BASH, + ) fun evalAndGobble( - osType: OsType, command: String, + osType: OsType = OsType.native, workingDirectory: OsPath? = null, envAdjuster: EnvAdjuster = {}, waitTimeMinutes: Int = 10, @@ -26,7 +35,8 @@ object ShellExecutor { inputSanitizer: Sanitizer = EMPTY_SANITIZER, outputSanitizer: Sanitizer = inputSanitizer.swapped(), outPrinter: List = emptyList(), - errPrinter: List = emptyList() + errPrinter: List = emptyList(), + shellMapper: Map = DEFAULT_SHELL_MAPPER ): ProcessResult { val outStream = ByteArrayOutputStream(1024) val errStream = ByteArrayOutputStream(1024) @@ -36,8 +46,8 @@ object ShellExecutor { PrintStream(outStream, true, UTF_8).use { additionalOutPrinter -> PrintStream(errStream, true, UTF_8).use { additionalErrPrinter -> result = eval( - osType, command, + osType, workingDirectory, envAdjuster, waitTimeMinutes, @@ -54,8 +64,8 @@ object ShellExecutor { } fun eval( - osType: OsType, command: String, + osType: OsType = OsType.native, workingDirectory: OsPath? = null, envAdjuster: EnvAdjuster = {}, waitTimeMinutes: Int = 10, @@ -63,18 +73,22 @@ object ShellExecutor { inputSanitizer: Sanitizer = EMPTY_SANITIZER, outputSanitizer: Sanitizer = inputSanitizer.swapped(), outPrinter: List = DEFAULT_OUT_PRINTERS, - errPrinter: List = DEFAULT_ERR_PRINTERS + errPrinter: List = DEFAULT_ERR_PRINTERS, + shellMapper: Map = DEFAULT_SHELL_MAPPER ): Int { - //NOTE: command is an argument to shell (bash/cmd), so it should stay not split by whitespace as a single string - val sanitizedCommand = inputSanitizer.sanitize(command) - val commandList = when (osType) { - // For Windows: if the first character in args in `cmd /c ` is a quote, cmd will remove it as well - // as the last quote character within args before processing the term, which removes our quotes. - // Empty character before command preserves quotes correctly. - OsType.WINDOWS -> listOf("cmd", "/c", " $sanitizedCommand") - else -> listOf("bash", "-c", sanitizedCommand) + val commandList = when (val shellType = shellMapper.getValue(osType)) { + //NOTE: usually command is an argument to shell (bash/cmd), so it should stay not split by whitespace as + //a single string, but when there is no shell, we have to split the command + ShellType.NONE -> sanitizedCommand.split("\\s+") + ShellType.CMD -> + // For Windows: if the first character in args in `cmd /c ` is a quote, cmd will remove it as well + // as the last quote character within args before processing the term, which removes our quotes. + // Empty character before command preserves quotes correctly. + shellType.executorCommand.toList() + " $sanitizedCommand" + else -> + shellType.executorCommand.toList() + sanitizedCommand } return ProcessRunner.runProcess( @@ -83,7 +97,6 @@ object ShellExecutor { envAdjuster = envAdjuster, waitTimeMinutes = waitTimeMinutes, inheritInput = inheritInput, - inputSanitizer = inputSanitizer, outputSanitizer = outputSanitizer, outPrinter = outPrinter, errPrinter = errPrinter diff --git a/src/main/kotlin/io/github/kscripting/shell/model/OsType.kt b/src/main/kotlin/io/github/kscripting/shell/model/OsType.kt index cd50efb..016f26d 100644 --- a/src/main/kotlin/io/github/kscripting/shell/model/OsType.kt +++ b/src/main/kotlin/io/github/kscripting/shell/model/OsType.kt @@ -3,7 +3,7 @@ package io.github.kscripting.shell.model import org.apache.commons.lang3.SystemUtils enum class OsType(val osName: String) { - LINUX("linux"), MACOS("darwin"), WINDOWS("windows"), CYGWIN("cygwin"), MSYS("msys"), FREEBSD("freebsd"); + LINUX("linux"), FREEBSD("freebsd"), MACOS("darwin"), CYGWIN("cygwin"), MSYS("msys"), WINDOWS("windows"); fun isPosixLike() = (this == LINUX || this == MACOS || this == FREEBSD || this == CYGWIN || this == MSYS) fun isPosixHostedOnWindows() = (this == CYGWIN || this == MSYS) diff --git a/src/main/kotlin/io/github/kscripting/shell/model/ShellType.kt b/src/main/kotlin/io/github/kscripting/shell/model/ShellType.kt new file mode 100644 index 0000000..51408f5 --- /dev/null +++ b/src/main/kotlin/io/github/kscripting/shell/model/ShellType.kt @@ -0,0 +1,8 @@ +package io.github.kscripting.shell.model + +enum class ShellType(vararg val executorCommand: String) { + NONE(), + BASH("/usr/bin/env bash", "-c"), + SH("/usr/bin/env sh", "-c"), + CMD("cmd", "/c") +} diff --git a/src/main/kotlin/io/github/kscripting/shell/process/ProcessRunner.kt b/src/main/kotlin/io/github/kscripting/shell/process/ProcessRunner.kt index b4a3b6f..107ba3e 100644 --- a/src/main/kotlin/io/github/kscripting/shell/process/ProcessRunner.kt +++ b/src/main/kotlin/io/github/kscripting/shell/process/ProcessRunner.kt @@ -20,8 +20,7 @@ object ProcessRunner { envAdjuster: EnvAdjuster = {}, waitTimeMinutes: Int = 10, inheritInput: Boolean = false, - inputSanitizer: Sanitizer = EMPTY_SANITIZER, - outputSanitizer: Sanitizer = inputSanitizer.swapped(), + outputSanitizer: Sanitizer = EMPTY_SANITIZER, outPrinter: List = DEFAULT_OUT_PRINTERS, errPrinter: List = DEFAULT_ERR_PRINTERS, ): Int { @@ -31,7 +30,6 @@ object ProcessRunner { envAdjuster, waitTimeMinutes, inheritInput, - inputSanitizer, outputSanitizer, outPrinter, errPrinter @@ -44,8 +42,7 @@ object ProcessRunner { envAdjuster: EnvAdjuster = {}, waitTimeMinutes: Int = 10, inheritInput: Boolean = false, - inputSanitizer: Sanitizer = EMPTY_SANITIZER, - outputSanitizer: Sanitizer = inputSanitizer.swapped(), + outputSanitizer: Sanitizer = EMPTY_SANITIZER, outPrinter: List = DEFAULT_OUT_PRINTERS, errPrinter: List = DEFAULT_ERR_PRINTERS, ): Int { From a584e1dfeda5cd35ff18b43fe0b2a283ed270cad Mon Sep 17 00:00:00 2001 From: Marcin Kuszczak <1508798+aartiPl@users.noreply.github.com> Date: Mon, 19 Jun 2023 19:08:09 +0200 Subject: [PATCH 07/50] Test improvements --- build.gradle.kts | 24 +--- src/{doEcho.sh => doEcho} | 0 .../shell/integration/ShellExecutorTest.kt | 14 +- .../shell/integration/tools/TestAssertion.kt | 25 ++-- .../shell/integration/tools/TestContext.kt | 17 ++- .../github/kscripting/shell/ShellExecutor.kt | 17 ++- .../kscripting/shell/model/ShellType.kt | 4 +- src/unicodeOutput.txt | 126 +++++++++--------- 8 files changed, 128 insertions(+), 99 deletions(-) rename src/{doEcho.sh => doEcho} (100%) diff --git a/build.gradle.kts b/build.gradle.kts index b2364e0..d9cbb50 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,3 +1,5 @@ +import org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask + val kotlinVersion: String = "1.8.21" plugins { @@ -15,6 +17,10 @@ repositories { group = "io.github.kscripting" version = "0.6.0-SNAPSHOT" +kotlin { + jvmToolchain(11) +} + sourceSets { create("itest") { kotlin.srcDir("$projectDir/src/itest/kotlin") @@ -29,21 +35,6 @@ configurations { get("itestImplementation").apply { extendsFrom(get("testImplementation")) } } -java { - toolchain { - languageVersion.set(JavaLanguageVersion.of(11)) - } - - withJavadocJar() - withSourcesJar() -} - -tasks.withType().all { - kotlinOptions { - jvmTarget = "11" - } -} - tasks.create("itest") { val itags = System.getProperty("includeTags") ?: "" val etags = System.getProperty("excludeTags") ?: "" @@ -67,8 +58,7 @@ tasks.create("itest") { testClassesDirs = sourceSets["itest"].output.classesDirs classpath = sourceSets["itest"].runtimeClasspath outputs.upToDateWhen { false } - mustRunAfter(tasks["test"]) - //dependsOn(tasks["assemble"], tasks["test"]) + dependsOn(tasks["build"]) doLast { println("Include tags: $itags") diff --git a/src/doEcho.sh b/src/doEcho similarity index 100% rename from src/doEcho.sh rename to src/doEcho diff --git a/src/itest/kotlin/io/github/kscripting/shell/integration/ShellExecutorTest.kt b/src/itest/kotlin/io/github/kscripting/shell/integration/ShellExecutorTest.kt index 51e11b0..cb7d623 100644 --- a/src/itest/kotlin/io/github/kscripting/shell/integration/ShellExecutorTest.kt +++ b/src/itest/kotlin/io/github/kscripting/shell/integration/ShellExecutorTest.kt @@ -3,8 +3,8 @@ package io.github.kscripting.shell.integration import io.github.kscripting.shell.integration.tools.TestAssertion.verify import io.github.kscripting.shell.integration.tools.TestContext import io.github.kscripting.shell.integration.tools.TestContext.execPath -import io.github.kscripting.shell.integration.tools.TestContext.resolvePath import io.github.kscripting.shell.model.readText +import io.github.kscripting.shell.util.Sanitizer import org.junit.jupiter.api.Tag import org.junit.jupiter.api.Test @@ -22,14 +22,22 @@ class ShellExecutorTest : TestBase { fun `Unicode characters output works`() { val path = execPath().resolve("unicodeOutput.txt") println(path) - verify("doEcho -f $path", 0, path.readText(), "") + verify( + "doEcho -f $path", + 0, + path.readText(), + "", + inputSanitizer = Sanitizer.EMPTY_SANITIZER, + outputSanitizer = Sanitizer.EMPTY_SANITIZER + ) } companion object { init { - TestContext.copyToExecutablePath("src/doEcho.sh") + TestContext.copyToExecutablePath("src/doEcho") TestContext.copyToExecutablePath("src/doEcho.bat") TestContext.copyToExecutablePath("src/unicodeOutput.txt") + Thread.sleep(1000) //It takes time to copy and set executable bit [tests fail without this] } } } diff --git a/src/itest/kotlin/io/github/kscripting/shell/integration/tools/TestAssertion.kt b/src/itest/kotlin/io/github/kscripting/shell/integration/tools/TestAssertion.kt index 27bec43..bb581af 100644 --- a/src/itest/kotlin/io/github/kscripting/shell/integration/tools/TestAssertion.kt +++ b/src/itest/kotlin/io/github/kscripting/shell/integration/tools/TestAssertion.kt @@ -3,6 +3,7 @@ package io.github.kscripting.shell.integration.tools import io.github.kscripting.shell.integration.tools.TestContext.runProcess import io.github.kscripting.shell.model.ProcessResult import io.github.kscripting.shell.process.EnvAdjuster +import io.github.kscripting.shell.util.Sanitizer object TestAssertion { fun genericEquals(value: T) = GenericEquals(value) @@ -17,33 +18,41 @@ object TestAssertion { exitCode: Int = 0, stdOut: TestMatcher, stdErr: String = "", - envAdjuster: EnvAdjuster = {} - ): ProcessResult = verify(command, exitCode, stdOut, eq(stdErr), envAdjuster) + envAdjuster: EnvAdjuster = {}, + inputSanitizer: Sanitizer? = null, + outputSanitizer: Sanitizer? = null, + ): ProcessResult = verify(command, exitCode, stdOut, eq(stdErr), envAdjuster, inputSanitizer, outputSanitizer) fun verify( command: String, exitCode: Int = 0, stdOut: String, stdErr: TestMatcher, - envAdjuster: EnvAdjuster = {} - ): ProcessResult = verify(command, exitCode, eq(stdOut), stdErr, envAdjuster) + envAdjuster: EnvAdjuster = {}, + inputSanitizer: Sanitizer? = null, + outputSanitizer: Sanitizer? = null, + ): ProcessResult = verify(command, exitCode, eq(stdOut), stdErr, envAdjuster, inputSanitizer, outputSanitizer) fun verify( command: String, exitCode: Int = 0, stdOut: String = "", stdErr: String = "", - envAdjuster: EnvAdjuster = {} - ): ProcessResult = verify(command, exitCode, eq(stdOut), eq(stdErr), envAdjuster) + envAdjuster: EnvAdjuster = {}, + inputSanitizer: Sanitizer? = null, + outputSanitizer: Sanitizer? = null, + ): ProcessResult = verify(command, exitCode, eq(stdOut), eq(stdErr), envAdjuster, inputSanitizer, outputSanitizer) fun verify( command: String, exitCode: Int = 0, stdOut: TestMatcher, stdErr: TestMatcher, - envAdjuster: EnvAdjuster = {} + envAdjuster: EnvAdjuster = {}, + inputSanitizer: Sanitizer? = null, + outputSanitizer: Sanitizer? = null, ): ProcessResult { - val processResult = runProcess(command, envAdjuster) + val processResult = runProcess(command, envAdjuster, inputSanitizer, outputSanitizer) println(processResult) val extCde = genericEquals(exitCode) diff --git a/src/itest/kotlin/io/github/kscripting/shell/integration/tools/TestContext.kt b/src/itest/kotlin/io/github/kscripting/shell/integration/tools/TestContext.kt index 0d70a8a..8d32564 100644 --- a/src/itest/kotlin/io/github/kscripting/shell/integration/tools/TestContext.kt +++ b/src/itest/kotlin/io/github/kscripting/shell/integration/tools/TestContext.kt @@ -17,8 +17,10 @@ object TestContext { private val pathSeparator: String = if (osType.isWindowsLike() || osType.isPosixHostedOnWindows()) ";" else ":" private val envPath: String = "${execPath.convert(osType)}$pathSeparator$systemPath" + val nl: String = System.getProperty("line.separator") + private val inputSanitizer = Sanitizer( - listOf("[bs]" to "\\", "[nl]" to System.getProperty("line.separator"), "[tb]" to "\t") + listOf("[bs]" to "\\", "[nl]" to nl, "[tb]" to "\t") ) val projectDir: String = projectPath.convert(osType).stringPath() @@ -29,15 +31,21 @@ object TestContext { println("projectDir : $projectDir") println("execDir : ${execPath.convert(osType)}") println("Kotlin version : ${ShellExecutor.evalAndGobble("kotlin -version", osType, null).stdout}") + println("Env path : $envPath") execPath.createDirectories() } - fun resolvePath(path: String): OsPath { + fun path(path: String): OsPath { return OsPath.createOrThrow(osType, path) } - fun runProcess(command: String, envAdjuster: EnvAdjuster): ProcessResult { + fun runProcess( + command: String, + envAdjuster: EnvAdjuster, + inputSanitizer: Sanitizer? = null, + outputSanitizer: Sanitizer? = null + ): ProcessResult { //In MSYS all quotes should be single quotes, otherwise content is interpreted e.g. backslashes. //(MSYS bash interpreter is also replacing double quotes into the single quotes: see: bash -xc 'kscript "println(1+1)"') val newCommand = when { @@ -55,7 +63,8 @@ object TestContext { osType, null, ::internalEnvAdjuster, - inputSanitizer = inputSanitizer + inputSanitizer = inputSanitizer ?: this.inputSanitizer, + outputSanitizer = outputSanitizer ?: this.inputSanitizer.swapped() ) } diff --git a/src/main/kotlin/io/github/kscripting/shell/ShellExecutor.kt b/src/main/kotlin/io/github/kscripting/shell/ShellExecutor.kt index a5e78b5..798c419 100644 --- a/src/main/kotlin/io/github/kscripting/shell/ShellExecutor.kt +++ b/src/main/kotlin/io/github/kscripting/shell/ShellExecutor.kt @@ -13,8 +13,10 @@ import io.github.kscripting.shell.util.Sanitizer.Companion.EMPTY_SANITIZER import java.io.ByteArrayOutputStream import java.io.PrintStream import java.nio.charset.StandardCharsets +import java.util.regex.Pattern object ShellExecutor { + private val SPLIT_PATTERN = Pattern.compile("([^\"]\\S*|\".+?\")\\s*") private val UTF_8 = StandardCharsets.UTF_8.name() private val DEFAULT_SHELL_MAPPER: Map = mapOf( OsType.WINDOWS to ShellType.CMD, @@ -55,7 +57,8 @@ object ShellExecutor { inputSanitizer, outputSanitizer, outPrinter + additionalOutPrinter, - errPrinter + additionalErrPrinter + errPrinter + additionalErrPrinter, + shellMapper ) } } @@ -81,12 +84,22 @@ object ShellExecutor { val commandList = when (val shellType = shellMapper.getValue(osType)) { //NOTE: usually command is an argument to shell (bash/cmd), so it should stay not split by whitespace as //a single string, but when there is no shell, we have to split the command - ShellType.NONE -> sanitizedCommand.split("\\s+") + ShellType.NONE -> { + val result = mutableListOf() + //Split by whitespace preserving spaces inside quotes + val matcher = SPLIT_PATTERN.matcher(sanitizedCommand) + while (matcher.find()) { + result.add(matcher.group(1)) // Add .replace("\"", "") to remove surrounding quotes. + } + result + } + ShellType.CMD -> // For Windows: if the first character in args in `cmd /c ` is a quote, cmd will remove it as well // as the last quote character within args before processing the term, which removes our quotes. // Empty character before command preserves quotes correctly. shellType.executorCommand.toList() + " $sanitizedCommand" + else -> shellType.executorCommand.toList() + sanitizedCommand } diff --git a/src/main/kotlin/io/github/kscripting/shell/model/ShellType.kt b/src/main/kotlin/io/github/kscripting/shell/model/ShellType.kt index 51408f5..6617453 100644 --- a/src/main/kotlin/io/github/kscripting/shell/model/ShellType.kt +++ b/src/main/kotlin/io/github/kscripting/shell/model/ShellType.kt @@ -2,7 +2,7 @@ package io.github.kscripting.shell.model enum class ShellType(vararg val executorCommand: String) { NONE(), - BASH("/usr/bin/env bash", "-c"), - SH("/usr/bin/env sh", "-c"), + BASH("bash", "-c"), + SH("sh", "-c"), CMD("cmd", "/c") } diff --git a/src/unicodeOutput.txt b/src/unicodeOutput.txt index 57a1120..c2cac84 100644 --- a/src/unicodeOutput.txt +++ b/src/unicodeOutput.txt @@ -1,63 +1,63 @@ -┏━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━┓ -┃ Release name ┃ Author ┃ Download count ┃ Publish date ┃ -┡━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━┩ -│ v4.2.2 │ aartiPl │ 4331 │ 2023-04-29T18:59:09Z │ -├──────────────┼─────────────────────┼────────────────┼──────────────────────┤ -│ v4.2.1 │ aartiPl │ 16914 │ 2023-01-26T16:25:31Z │ -├──────────────┼─────────────────────┼────────────────┼──────────────────────┤ -│ v4.2.0 │ aartiPl │ 1798 │ 2023-01-14T09:32:57Z │ -├──────────────┼─────────────────────┼────────────────┼──────────────────────┤ -│ v4.2.0-RC.3 │ aartiPl │ 521 │ 2023-01-11T19:41:35Z │ -├──────────────┼─────────────────────┼────────────────┼──────────────────────┤ -│ v4.2.0-RC.2 │ aartiPl │ 145 │ 2023-01-06T11:12:32Z │ -├──────────────┼─────────────────────┼────────────────┼──────────────────────┤ -│ v4.1.1 │ github-actions[bot] │ 24184 │ 2022-08-27T16:13:30Z │ -├──────────────┼─────────────────────┼────────────────┼──────────────────────┤ -│ v4.1.0 │ holgerbrandl │ 392 │ 2022-08-26T12:26:01Z │ -├──────────────┼─────────────────────┼────────────────┼──────────────────────┤ -│ v4.0.3 │ holgerbrandl │ 5126 │ 2022-05-28T16:14:11Z │ -├──────────────┼─────────────────────┼────────────────┼──────────────────────┤ -│ v4.0.2 │ holgerbrandl │ 3252 │ 2022-05-18T18:11:15Z │ -├──────────────┼─────────────────────┼────────────────┼──────────────────────┤ -│ v4.0.0 │ github-actions[bot] │ 1739 │ 2022-04-27T20:19:35Z │ -├──────────────┼─────────────────────┼────────────────┼──────────────────────┤ -│ v3.1.0 │ github-actions[bot] │ 52479 │ 2021-04-27T21:28:47Z │ -├──────────────┼─────────────────────┼────────────────┼──────────────────────┤ -│ v3.0.2 │ holgerbrandl │ 21257 │ 2020-11-10T22:51:58Z │ -├──────────────┼─────────────────────┼────────────────┼──────────────────────┤ -│ v3.0.1 │ holgerbrandl │ 569 │ 2020-11-10T22:36:29Z │ -├──────────────┼─────────────────────┼────────────────┼──────────────────────┤ -│ v2.9.3 │ holgerbrandl │ 39811 │ 2020-01-17T06:50:59Z │ -├──────────────┼─────────────────────┼────────────────┼──────────────────────┤ -│ v2.9.0 │ holgerbrandl │ 26370 │ 2019-11-24T23:39:47Z │ -├──────────────┼─────────────────────┼────────────────┼──────────────────────┤ -│ v2.8.0 │ holgerbrandl │ 19797 │ 2019-06-17T07:01Z │ -├──────────────┼─────────────────────┼────────────────┼──────────────────────┤ -│ v2.7.1 │ holgerbrandl │ 30339 │ 2019-02-21T22:10:26Z │ -├──────────────┼─────────────────────┼────────────────┼──────────────────────┤ -│ v2.7.0 │ holgerbrandl │ 577 │ 2019-02-16T13:50:30Z │ -├──────────────┼─────────────────────┼────────────────┼──────────────────────┤ -│ v2.6.0 │ holgerbrandl │ 11424 │ 2018-10-29T09:58:15Z │ -├──────────────┼─────────────────────┼────────────────┼──────────────────────┤ -│ v2.5.0 │ holgerbrandl │ 6607 │ 2018-09-06T10:42:57Z │ -├──────────────┼─────────────────────┼────────────────┼──────────────────────┤ -│ v2.4.5 │ holgerbrandl │ 5164 │ 2018-07-02T06:27:28Z │ -├──────────────┼─────────────────────┼────────────────┼──────────────────────┤ -│ v2.4.4 │ holgerbrandl │ 72492 │ 2018-06-08T07:53:41Z │ -├──────────────┼─────────────────────┼────────────────┼──────────────────────┤ -│ v2.4.3 │ holgerbrandl │ 3098 │ 2018-05-15T09:03:20Z │ -├──────────────┼─────────────────────┼────────────────┼──────────────────────┤ -│ v2.4.2 │ holgerbrandl │ 1480 │ 2018-05-15T08:55:29Z │ -├──────────────┼─────────────────────┼────────────────┼──────────────────────┤ -│ v2.4.1 │ holgerbrandl │ 3914 │ 2018-02-27T11:53:47Z │ -├──────────────┼─────────────────────┼────────────────┼──────────────────────┤ -│ v2.4.0 │ holgerbrandl │ 1818 │ 2018-02-12T13:43:21Z │ -├──────────────┼─────────────────────┼────────────────┼──────────────────────┤ -│ v2.3.0 │ holgerbrandl │ 2257 │ 2017-12-12T01:05:54Z │ -├──────────────┼─────────────────────┼────────────────┼──────────────────────┤ -│ v2.2.1 │ holgerbrandl │ 1983 │ 2017-11-15T20:04:51Z │ -├──────────────┼─────────────────────┼────────────────┼──────────────────────┤ -│ v2.2.0 │ holgerbrandl │ 1560 │ 2017-11-14T13:29:17Z │ -├──────────────┼─────────────────────┼────────────────┼──────────────────────┤ -│ v2.1.1 │ holgerbrandl │ 1644 │ 2017-11-07T10:26:15Z │ -└──────────────┴─────────────────────┴────────────────┴──────────────────────┘ \ No newline at end of file +┏━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━┓ +┃ Release name ┃ Author ┃ Publish date ┃ +┡━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━┩ +│ v4.2.2 │ aartiPl │ 2023-04-29T18:59:09Z │ +├──────────────┼─────────────────────┼──────────────────────┤ +│ v4.2.1 │ aartiPl │ 2023-01-26T16:25:31Z │ +├──────────────┼─────────────────────┼──────────────────────┤ +│ v4.2.0 │ aartiPl │ 2023-01-14T09:32:57Z │ +├──────────────┼─────────────────────┼──────────────────────┤ +│ v4.2.0-RC.3 │ aartiPl │ 2023-01-11T19:41:35Z │ +├──────────────┼─────────────────────┼──────────────────────┤ +│ v4.2.0-RC.2 │ aartiPl │ 2023-01-06T11:12:32Z │ +├──────────────┼─────────────────────┼──────────────────────┤ +│ v4.1.1 │ github-actions[bot] │ 2022-08-27T16:13:30Z │ +├──────────────┼─────────────────────┼──────────────────────┤ +│ v4.1.0 │ holgerbrandl │ 2022-08-26T12:26:01Z │ +├──────────────┼─────────────────────┼──────────────────────┤ +│ v4.0.3 │ holgerbrandl │ 2022-05-28T16:14:11Z │ +├──────────────┼─────────────────────┼──────────────────────┤ +│ v4.0.2 │ holgerbrandl │ 2022-05-18T18:11:15Z │ +├──────────────┼─────────────────────┼──────────────────────┤ +│ v4.0.0 │ github-actions[bot] │ 2022-04-27T20:19:35Z │ +├──────────────┼─────────────────────┼──────────────────────┤ +│ v3.1.0 │ github-actions[bot] │ 2021-04-27T21:28:47Z │ +├──────────────┼─────────────────────┼──────────────────────┤ +│ v3.0.2 │ holgerbrandl │ 2020-11-10T22:51:58Z │ +├──────────────┼─────────────────────┼──────────────────────┤ +│ v3.0.1 │ holgerbrandl │ 2020-11-10T22:36:29Z │ +├──────────────┼─────────────────────┼──────────────────────┤ +│ v2.9.3 │ holgerbrandl │ 2020-01-17T06:50:59Z │ +├──────────────┼─────────────────────┼──────────────────────┤ +│ v2.9.0 │ holgerbrandl │ 2019-11-24T23:39:47Z │ +├──────────────┼─────────────────────┼──────────────────────┤ +│ v2.8.0 │ holgerbrandl │ 2019-06-17T07:01Z │ +├──────────────┼─────────────────────┼──────────────────────┤ +│ v2.7.1 │ holgerbrandl │ 2019-02-21T22:10:26Z │ +├──────────────┼─────────────────────┼──────────────────────┤ +│ v2.7.0 │ holgerbrandl │ 2019-02-16T13:50:30Z │ +├──────────────┼─────────────────────┼──────────────────────┤ +│ v2.6.0 │ holgerbrandl │ 2018-10-29T09:58:15Z │ +├──────────────┼─────────────────────┼──────────────────────┤ +│ v2.5.0 │ holgerbrandl │ 2018-09-06T10:42:57Z │ +├──────────────┼─────────────────────┼──────────────────────┤ +│ v2.4.5 │ holgerbrandl │ 2018-07-02T06:27:28Z │ +├──────────────┼─────────────────────┼──────────────────────┤ +│ v2.4.4 │ holgerbrandl │ 2018-06-08T07:53:41Z │ +├──────────────┼─────────────────────┼──────────────────────┤ +│ v2.4.3 │ holgerbrandl │ 2018-05-15T09:03:20Z │ +├──────────────┼─────────────────────┼──────────────────────┤ +│ v2.4.2 │ holgerbrandl │ 2018-05-15T08:55:29Z │ +├──────────────┼─────────────────────┼──────────────────────┤ +│ v2.4.1 │ holgerbrandl │ 2018-02-27T11:53:47Z │ +├──────────────┼─────────────────────┼──────────────────────┤ +│ v2.4.0 │ holgerbrandl │ 2018-02-12T13:43:21Z │ +├──────────────┼─────────────────────┼──────────────────────┤ +│ v2.3.0 │ holgerbrandl │ 2017-12-12T01:05:54Z │ +├──────────────┼─────────────────────┼──────────────────────┤ +│ v2.2.1 │ holgerbrandl │ 2017-11-15T20:04:51Z │ +├──────────────┼─────────────────────┼──────────────────────┤ +│ v2.2.0 │ holgerbrandl │ 2017-11-14T13:29:17Z │ +├──────────────┼─────────────────────┼──────────────────────┤ +│ v2.1.1 │ holgerbrandl │ 2017-11-07T10:26:15Z │ +└──────────────┴─────────────────────┴──────────────────────┘ \ No newline at end of file From f3b8e7640b69d9165c8fb1a41bbc40ef3265e2d8 Mon Sep 17 00:00:00 2001 From: Marcin Kuszczak <1508798+aartiPl@users.noreply.github.com> Date: Thu, 22 Jun 2023 21:08:07 +0200 Subject: [PATCH 08/50] Passing Input Stream to ProcessRunner. --- .../shell/integration/ShellExecutorTest.kt | 7 +++++++ .../shell/integration/tools/TestAssertion.kt | 13 +++++++++---- .../shell/integration/tools/TestContext.kt | 7 +++++-- .../io/github/kscripting/shell/ShellExecutor.kt | 8 +++++++- .../kscripting/shell/process/ProcessRunner.kt | 13 ++++++++++++- 5 files changed, 40 insertions(+), 8 deletions(-) diff --git a/src/itest/kotlin/io/github/kscripting/shell/integration/ShellExecutorTest.kt b/src/itest/kotlin/io/github/kscripting/shell/integration/ShellExecutorTest.kt index cb7d623..5818999 100644 --- a/src/itest/kotlin/io/github/kscripting/shell/integration/ShellExecutorTest.kt +++ b/src/itest/kotlin/io/github/kscripting/shell/integration/ShellExecutorTest.kt @@ -7,6 +7,7 @@ import io.github.kscripting.shell.model.readText import io.github.kscripting.shell.util.Sanitizer import org.junit.jupiter.api.Tag import org.junit.jupiter.api.Test +import java.io.ByteArrayInputStream class ShellExecutorTest : TestBase { @Test @@ -32,6 +33,12 @@ class ShellExecutorTest : TestBase { ) } + @Test + @Tag("posix") + fun `Call command utilizing input stream`() { + verify("read INPUT; echo \$INPUT", 0, "Input to READ[nl]", "", inputStream = "Input to READ".byteInputStream()) + } + companion object { init { TestContext.copyToExecutablePath("src/doEcho") diff --git a/src/itest/kotlin/io/github/kscripting/shell/integration/tools/TestAssertion.kt b/src/itest/kotlin/io/github/kscripting/shell/integration/tools/TestAssertion.kt index bb581af..b1aa00d 100644 --- a/src/itest/kotlin/io/github/kscripting/shell/integration/tools/TestAssertion.kt +++ b/src/itest/kotlin/io/github/kscripting/shell/integration/tools/TestAssertion.kt @@ -4,6 +4,7 @@ import io.github.kscripting.shell.integration.tools.TestContext.runProcess import io.github.kscripting.shell.model.ProcessResult import io.github.kscripting.shell.process.EnvAdjuster import io.github.kscripting.shell.util.Sanitizer +import java.io.InputStream object TestAssertion { fun genericEquals(value: T) = GenericEquals(value) @@ -21,7 +22,8 @@ object TestAssertion { envAdjuster: EnvAdjuster = {}, inputSanitizer: Sanitizer? = null, outputSanitizer: Sanitizer? = null, - ): ProcessResult = verify(command, exitCode, stdOut, eq(stdErr), envAdjuster, inputSanitizer, outputSanitizer) + inputStream: InputStream? = null + ): ProcessResult = verify(command, exitCode, stdOut, eq(stdErr), envAdjuster, inputSanitizer, outputSanitizer, inputStream) fun verify( command: String, @@ -31,7 +33,8 @@ object TestAssertion { envAdjuster: EnvAdjuster = {}, inputSanitizer: Sanitizer? = null, outputSanitizer: Sanitizer? = null, - ): ProcessResult = verify(command, exitCode, eq(stdOut), stdErr, envAdjuster, inputSanitizer, outputSanitizer) + inputStream: InputStream? = null + ): ProcessResult = verify(command, exitCode, eq(stdOut), stdErr, envAdjuster, inputSanitizer, outputSanitizer, inputStream) fun verify( command: String, @@ -41,7 +44,8 @@ object TestAssertion { envAdjuster: EnvAdjuster = {}, inputSanitizer: Sanitizer? = null, outputSanitizer: Sanitizer? = null, - ): ProcessResult = verify(command, exitCode, eq(stdOut), eq(stdErr), envAdjuster, inputSanitizer, outputSanitizer) + inputStream: InputStream? = null + ): ProcessResult = verify(command, exitCode, eq(stdOut), eq(stdErr), envAdjuster, inputSanitizer, outputSanitizer, inputStream) fun verify( command: String, @@ -51,8 +55,9 @@ object TestAssertion { envAdjuster: EnvAdjuster = {}, inputSanitizer: Sanitizer? = null, outputSanitizer: Sanitizer? = null, + inputStream: InputStream? = null ): ProcessResult { - val processResult = runProcess(command, envAdjuster, inputSanitizer, outputSanitizer) + val processResult = runProcess(command, envAdjuster, inputSanitizer, outputSanitizer, inputStream) println(processResult) val extCde = genericEquals(exitCode) diff --git a/src/itest/kotlin/io/github/kscripting/shell/integration/tools/TestContext.kt b/src/itest/kotlin/io/github/kscripting/shell/integration/tools/TestContext.kt index 8d32564..2009b4f 100644 --- a/src/itest/kotlin/io/github/kscripting/shell/integration/tools/TestContext.kt +++ b/src/itest/kotlin/io/github/kscripting/shell/integration/tools/TestContext.kt @@ -4,6 +4,7 @@ import io.github.kscripting.shell.ShellExecutor import io.github.kscripting.shell.model.* import io.github.kscripting.shell.process.EnvAdjuster import io.github.kscripting.shell.util.Sanitizer +import java.io.InputStream object TestContext { private val osType: OsType = OsType.find(System.getProperty("osType")) ?: OsType.native @@ -44,7 +45,8 @@ object TestContext { command: String, envAdjuster: EnvAdjuster, inputSanitizer: Sanitizer? = null, - outputSanitizer: Sanitizer? = null + outputSanitizer: Sanitizer? = null, + inputStream: InputStream? = null ): ProcessResult { //In MSYS all quotes should be single quotes, otherwise content is interpreted e.g. backslashes. //(MSYS bash interpreter is also replacing double quotes into the single quotes: see: bash -xc 'kscript "println(1+1)"') @@ -64,7 +66,8 @@ object TestContext { null, ::internalEnvAdjuster, inputSanitizer = inputSanitizer ?: this.inputSanitizer, - outputSanitizer = outputSanitizer ?: this.inputSanitizer.swapped() + outputSanitizer = outputSanitizer ?: this.inputSanitizer.swapped(), + inputStream = inputStream ) } diff --git a/src/main/kotlin/io/github/kscripting/shell/ShellExecutor.kt b/src/main/kotlin/io/github/kscripting/shell/ShellExecutor.kt index 798c419..7a2c75b 100644 --- a/src/main/kotlin/io/github/kscripting/shell/ShellExecutor.kt +++ b/src/main/kotlin/io/github/kscripting/shell/ShellExecutor.kt @@ -11,10 +11,12 @@ import io.github.kscripting.shell.process.ProcessRunner.DEFAULT_OUT_PRINTERS import io.github.kscripting.shell.util.Sanitizer import io.github.kscripting.shell.util.Sanitizer.Companion.EMPTY_SANITIZER import java.io.ByteArrayOutputStream +import java.io.InputStream import java.io.PrintStream import java.nio.charset.StandardCharsets import java.util.regex.Pattern +@Suppress("MemberVisibilityCanBePrivate") object ShellExecutor { private val SPLIT_PATTERN = Pattern.compile("([^\"]\\S*|\".+?\")\\s*") private val UTF_8 = StandardCharsets.UTF_8.name() @@ -38,6 +40,7 @@ object ShellExecutor { outputSanitizer: Sanitizer = inputSanitizer.swapped(), outPrinter: List = emptyList(), errPrinter: List = emptyList(), + inputStream: InputStream? = null, shellMapper: Map = DEFAULT_SHELL_MAPPER ): ProcessResult { val outStream = ByteArrayOutputStream(1024) @@ -58,6 +61,7 @@ object ShellExecutor { outputSanitizer, outPrinter + additionalOutPrinter, errPrinter + additionalErrPrinter, + inputStream, shellMapper ) } @@ -77,6 +81,7 @@ object ShellExecutor { outputSanitizer: Sanitizer = inputSanitizer.swapped(), outPrinter: List = DEFAULT_OUT_PRINTERS, errPrinter: List = DEFAULT_ERR_PRINTERS, + inputStream: InputStream? = null, shellMapper: Map = DEFAULT_SHELL_MAPPER ): Int { val sanitizedCommand = inputSanitizer.sanitize(command) @@ -112,7 +117,8 @@ object ShellExecutor { inheritInput = inheritInput, outputSanitizer = outputSanitizer, outPrinter = outPrinter, - errPrinter = errPrinter + errPrinter = errPrinter, + inputStream = inputStream ) } } diff --git a/src/main/kotlin/io/github/kscripting/shell/process/ProcessRunner.kt b/src/main/kotlin/io/github/kscripting/shell/process/ProcessRunner.kt index 107ba3e..ac7354d 100644 --- a/src/main/kotlin/io/github/kscripting/shell/process/ProcessRunner.kt +++ b/src/main/kotlin/io/github/kscripting/shell/process/ProcessRunner.kt @@ -5,6 +5,7 @@ import io.github.kscripting.shell.model.OsPath import io.github.kscripting.shell.model.toNativeFile import io.github.kscripting.shell.util.Sanitizer import io.github.kscripting.shell.util.Sanitizer.Companion.EMPTY_SANITIZER +import java.io.InputStream import java.io.PrintStream import java.util.concurrent.TimeUnit @@ -23,6 +24,7 @@ object ProcessRunner { outputSanitizer: Sanitizer = EMPTY_SANITIZER, outPrinter: List = DEFAULT_OUT_PRINTERS, errPrinter: List = DEFAULT_ERR_PRINTERS, + inputStream: InputStream? = null ): Int { return runProcess( command.asList(), @@ -32,7 +34,8 @@ object ProcessRunner { inheritInput, outputSanitizer, outPrinter, - errPrinter + errPrinter, + inputStream ) } @@ -45,16 +48,24 @@ object ProcessRunner { outputSanitizer: Sanitizer = EMPTY_SANITIZER, outPrinter: List = DEFAULT_OUT_PRINTERS, errPrinter: List = DEFAULT_ERR_PRINTERS, + inputStream: InputStream? = null ): Int { try { // simplify with https://stackoverflow.com/questions/35421699/how-to-invoke-external-command-from-within-kotlin-code val process = ProcessBuilder(command) .directory(workingDirectory?.toNativeFile()) .redirectInput(if (inheritInput) ProcessBuilder.Redirect.INHERIT else ProcessBuilder.Redirect.PIPE) + .redirectOutput(ProcessBuilder.Redirect.PIPE) .apply { envAdjuster(environment()) }.start() + // My own explanation is here: https://github.com/lxc/lxd/issues/6856 + if (!inheritInput) { + inputStream?.transferTo(process.outputStream) + process.outputStream.close() + } + // we need to gobble the streams to prevent that the internal pipes hit their respective buffer limits, which // would lock the sub-process execution (see see https://github.com/kscripting/kscript/issues/55 // https://stackoverflow.com/questions/14165517/processbuilder-forwarding-stdout-and-stderr-of-started-processes-without-blocki From 8ce8ace3da347b2e34e49e602a6f773ab0b2fc87 Mon Sep 17 00:00:00 2001 From: Marcin Kuszczak <1508798+aartiPl@users.noreply.github.com> Date: Thu, 22 Jun 2023 21:15:35 +0200 Subject: [PATCH 09/50] Removed files, which should be in kscript --- .../github/kscripting/shell/model/ScriptContext.kt | 8 -------- .../github/kscripting/shell/model/ScriptLocation.kt | 12 ------------ .../io/github/kscripting/shell/model/ScriptSource.kt | 3 --- .../io/github/kscripting/shell/model/ScriptType.kt | 9 --------- 4 files changed, 32 deletions(-) delete mode 100644 src/main/kotlin/io/github/kscripting/shell/model/ScriptContext.kt delete mode 100644 src/main/kotlin/io/github/kscripting/shell/model/ScriptLocation.kt delete mode 100644 src/main/kotlin/io/github/kscripting/shell/model/ScriptSource.kt delete mode 100644 src/main/kotlin/io/github/kscripting/shell/model/ScriptType.kt diff --git a/src/main/kotlin/io/github/kscripting/shell/model/ScriptContext.kt b/src/main/kotlin/io/github/kscripting/shell/model/ScriptContext.kt deleted file mode 100644 index 425b1ef..0000000 --- a/src/main/kotlin/io/github/kscripting/shell/model/ScriptContext.kt +++ /dev/null @@ -1,8 +0,0 @@ -package io.github.kscripting.shell.model - -data class ScriptContext( - val osType: OsType, - val workingDir: OsPath, - val executorDir: OsPath, - val scriptLocation: ScriptLocation -) diff --git a/src/main/kotlin/io/github/kscripting/shell/model/ScriptLocation.kt b/src/main/kotlin/io/github/kscripting/shell/model/ScriptLocation.kt deleted file mode 100644 index 6a6daef..0000000 --- a/src/main/kotlin/io/github/kscripting/shell/model/ScriptLocation.kt +++ /dev/null @@ -1,12 +0,0 @@ -package io.github.kscripting.shell.model - -import java.net.URI - -data class ScriptLocation( - val level: Int, - val scriptSource: ScriptSource, - val scriptType: ScriptType, - val sourceUri: URI?, - val sourceContextUri: URI, - val scriptName: String //without Kotlin extension (but possibly with other extensions) -) diff --git a/src/main/kotlin/io/github/kscripting/shell/model/ScriptSource.kt b/src/main/kotlin/io/github/kscripting/shell/model/ScriptSource.kt deleted file mode 100644 index 2642083..0000000 --- a/src/main/kotlin/io/github/kscripting/shell/model/ScriptSource.kt +++ /dev/null @@ -1,3 +0,0 @@ -package io.github.kscripting.shell.model - -enum class ScriptSource { FILE, HTTP, STD_INPUT, OTHER_FILE, PARAMETER } diff --git a/src/main/kotlin/io/github/kscripting/shell/model/ScriptType.kt b/src/main/kotlin/io/github/kscripting/shell/model/ScriptType.kt deleted file mode 100644 index 4da5076..0000000 --- a/src/main/kotlin/io/github/kscripting/shell/model/ScriptType.kt +++ /dev/null @@ -1,9 +0,0 @@ -package io.github.kscripting.shell.model - -enum class ScriptType(val extension: String) { - KT(".kt"), KTS(".kts"); - - companion object { - fun findByExtension(name: String): ScriptType? = values().find { type -> name.endsWith(type.extension, true) } - } -} From 024a9ae98fd6bb32eab30c3df09519eafb0308dc Mon Sep 17 00:00:00 2001 From: Marcin Kuszczak <1508798+aartiPl@users.noreply.github.com> Date: Sat, 24 Jun 2023 17:12:03 +0200 Subject: [PATCH 10/50] Removed experimental files --- TODO.adoc | 2 + build.gradle.kts | 74 ++++++++---- .../github/kscripting/shell/ShellExecutor2.kt | 14 --- .../kscripting/shell/ShellExecutor2Builder.kt | 29 ----- .../io/github/kscripting/shell/apiTest.kt | 7 -- .../kscripting/shell/process/ProcessRunner.kt | 7 +- .../io/github/kscripting/shell/verifier.kt | 111 ------------------ 7 files changed, 56 insertions(+), 188 deletions(-) delete mode 100644 src/main/kotlin/io/github/kscripting/shell/ShellExecutor2.kt delete mode 100644 src/main/kotlin/io/github/kscripting/shell/ShellExecutor2Builder.kt delete mode 100644 src/main/kotlin/io/github/kscripting/shell/apiTest.kt delete mode 100644 src/main/kotlin/io/github/kscripting/shell/verifier.kt diff --git a/TODO.adoc b/TODO.adoc index 53c63b7..31ceed6 100644 --- a/TODO.adoc +++ b/TODO.adoc @@ -14,3 +14,5 @@ * Silent mode () * Use https://github.com/JetBrains/pty4j[PTY4j] instead of ProcessBuilder to read color codes from input and append them to output (https://stackoverflow.com/questions/45789329/java-capture-process-output-with-color) * Providing different types of shell (e.g. 'sh' or 'zsh') and executing without shell +* Library for testing +* Ideas: Generic verifier? ShellExecutorBuilder? diff --git a/build.gradle.kts b/build.gradle.kts index d9cbb50..b2f57ab 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,9 +1,7 @@ -import org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask - -val kotlinVersion: String = "1.8.21" +val kotlinVersion: String = "1.8.22" plugins { - kotlin("jvm") version "1.8.21" + kotlin("jvm") version "1.8.22" id("com.adarshr.test-logger") version "3.2.0" `maven-publish` signing @@ -87,9 +85,36 @@ tasks.test { useJUnitPlatform() } +val testToolsJar by tasks.creating(org.gradle.jvm.tasks.Jar::class) { + //archiveFileName.set("eulenspiegel-testHelpers-$version.jar") + include("io/github/kscripting/shell/integration/tools/*") + from(sourceSets["itest"].output) +} + +val licencesSpec = Action { + license { + name.set("MIT License") + url.set("https://opensource.org/licenses/MIT") + } +} + +val developersSpec = Action { + developer { + id.set("aartiPl") + name.set("Marcin Kuszczak") + email.set("aarti@interia.pl") + } +} + +val scmSpec = Action { + connection.set("scm:git:git://https://github.com/kscripting/shell.git") + developerConnection.set("scm:git:ssh:https://github.com/kscripting/shell.git") + url.set("https://github.com/kscripting/shell") +} + publishing { publications { - create("mavenJava") { + create("shell") { artifactId = "shell" from(components["java"]) @@ -98,26 +123,26 @@ publishing { description.set("Shell - library for interoperability with different system shells") url.set("https://github.com/kscripting/shell") - licenses { - license { - name.set("MIT License") - url.set("https://opensource.org/licenses/MIT") - } - } - developers { - developer { - id.set("aartiPl") - name.set("Marcin Kuszczak") - email.set("aarti@interia.pl") - } - } - scm { - connection.set("scm:git:git://https://github.com/kscripting/shell.git") - developerConnection.set("scm:git:ssh:https://github.com/kscripting/shell.git") - url.set("https://github.com/kscripting/shell") - } + licenses(licencesSpec) + developers(developersSpec) + scm(scmSpec) } } + +// create("shellTest") { +// artifactId = "shell-test" +// artifact(testToolsJar) +// +// pom { +// name.set("shell-test") +// description.set("Shell Tests - library for testing shell apps") +// url.set("https://github.com/kscripting/shell") +// +// licenses(licencesSpec) +// developers(developersSpec) +// scm(scmSpec) +// } +// } } repositories { @@ -135,7 +160,8 @@ publishing { } signing { - sign(publishing.publications["mavenJava"]) + sign(publishing.publications["shell"]) +// sign(publishing.publications["shellTest"]) } dependencies { diff --git a/src/main/kotlin/io/github/kscripting/shell/ShellExecutor2.kt b/src/main/kotlin/io/github/kscripting/shell/ShellExecutor2.kt deleted file mode 100644 index 0c1d909..0000000 --- a/src/main/kotlin/io/github/kscripting/shell/ShellExecutor2.kt +++ /dev/null @@ -1,14 +0,0 @@ -package io.github.kscripting.shell - -import io.github.kscripting.shell.model.ProcessResult - -class ShellExecutor2 { - - fun execute(): ProcessResult { - return ProcessResult(0, "", "") - } - - companion object { - fun builder() = ShellExecutor2Builder() - } -} diff --git a/src/main/kotlin/io/github/kscripting/shell/ShellExecutor2Builder.kt b/src/main/kotlin/io/github/kscripting/shell/ShellExecutor2Builder.kt deleted file mode 100644 index 57d0dd1..0000000 --- a/src/main/kotlin/io/github/kscripting/shell/ShellExecutor2Builder.kt +++ /dev/null @@ -1,29 +0,0 @@ -package io.github.kscripting.shell - -class ShellExecutor2Builder { - private var printCommandsPattern: String? = null - - fun withPrinter(printer: (String) -> String) { - //e.g. withPrinter { ">> $it" } - - } - -// fun throwOnExitCode(thrower: (ProcessResult) -> ProcessResult) { -// -// } -// -// fun throwOnExitCode(thrower: (GobbledProcessResult) -> GobbledProcessResult) { -// -// } - -// fun withResultProvider(provider: (String) -> ProcessResult) { -// -// } -// -// fun withResultProvider(provider: (String) -> GobbledProcessResult) { -// -// } - - - fun build(): ShellExecutor2 = ShellExecutor2() -} diff --git a/src/main/kotlin/io/github/kscripting/shell/apiTest.kt b/src/main/kotlin/io/github/kscripting/shell/apiTest.kt deleted file mode 100644 index 205384d..0000000 --- a/src/main/kotlin/io/github/kscripting/shell/apiTest.kt +++ /dev/null @@ -1,7 +0,0 @@ -package io.github.kscripting.shell - -fun main() { - val executor = ShellExecutor2.builder().build() - - executor.execute() -} \ No newline at end of file diff --git a/src/main/kotlin/io/github/kscripting/shell/process/ProcessRunner.kt b/src/main/kotlin/io/github/kscripting/shell/process/ProcessRunner.kt index ac7354d..8ab88bc 100644 --- a/src/main/kotlin/io/github/kscripting/shell/process/ProcessRunner.kt +++ b/src/main/kotlin/io/github/kscripting/shell/process/ProcessRunner.kt @@ -61,9 +61,10 @@ object ProcessRunner { }.start() // My own explanation is here: https://github.com/lxc/lxd/issues/6856 - if (!inheritInput) { - inputStream?.transferTo(process.outputStream) - process.outputStream.close() + val outputStream = process.outputStream + if (!inheritInput && outputStream != null) { + inputStream?.transferTo(outputStream) + outputStream.close() } // we need to gobble the streams to prevent that the internal pipes hit their respective buffer limits, which diff --git a/src/main/kotlin/io/github/kscripting/shell/verifier.kt b/src/main/kotlin/io/github/kscripting/shell/verifier.kt deleted file mode 100644 index 3e48633..0000000 --- a/src/main/kotlin/io/github/kscripting/shell/verifier.kt +++ /dev/null @@ -1,111 +0,0 @@ -package io.github.kscripting.shell - -import java.util.* - -abstract class Verifier(value: T) - -/** - * Asserts on the given value with an optional name. - * - * ``` - * assertThat(true, name = "true").isTrue() - * ``` - */ -fun verifyThat( - actual: T, - name: String? = null, - displayActual: (T) -> String = { "display(it)" } -): Assert = ValueAssert( - value = actual, - name = name, - context = AssertingContext { displayActual(actual) } -) - -sealed class Assert(val name: String?, internal val context: AssertingContext) { - - - /** - * Allows checking the actual value of an assert. This can be used to build your own custom assertions. - * ``` - * fun Assert.isTen() = given { actual -> - * if (actual == 10) return - * expected("to be 10 but was:${show(actual)}") - * } - * ``` - */ - @Suppress("TooGenericExceptionCaught") - inline fun given(assertion: (T) -> Unit) { - if (this is ValueAssert) { - try { - assertion(value) - } catch (e: Throwable) { - notifyFailure(e) - } - } - } - - @PublishedApi - internal fun failing(error: Throwable, name: String? = this.name): Assert { - return FailingAssert(error, name, context) - } - - /** - * Asserts on the given value with an optional name. - * - * ``` - * assertThat(true, name = "true").isTrue() - * ``` - */ - abstract fun assertThat(actual: R, name: String? = this.name): Assert -} - -@PublishedApi -internal class ValueAssert(val value: T, name: String?, context: AssertingContext) : - Assert(name, context) { - - override fun assertThat(actual: R, name: String?): Assert { - val newContext = if (context.originatingSubject != null || this.value == actual) { - context - } else { - context.copy(originatingSubject = this.value) - } - return ValueAssert(actual, name, newContext) - } -} - - - -internal class FailingAssert(val error: Throwable, name: String?, context: AssertingContext) : - Assert(name, context) { - override fun assertThat(actual: R, name: String?): Assert = FailingAssert(error, name, context) -} - - -internal data class AssertingContext( - val originatingSubject: Any? = null, - val displayOriginatingSubject: () -> String -) - -fun notifyFailure(e: Throwable) { - FailureContext.fail(e) -} - -/** - * Assertions are run in a failure context which captures failures to report them. - */ -internal object FailureContext { - - fun fail(error: Throwable) { - - } -} - -fun Assert.isEqualTo(other: String?, ignoreCase: Boolean = false) = given { actual -> - if (actual.equals(other, ignoreCase)) return - //fail(other, actual) -} - -fun main() { - verifyThat("").isEqualTo("") - verifyThat(Date())//.isEqualTo() -} From 226cbf889e0506e7667165c3a2992b9e2607e9a3 Mon Sep 17 00:00:00 2001 From: Marcin Kuszczak <1508798+aartiPl@users.noreply.github.com> Date: Sat, 24 Jun 2023 20:27:20 +0200 Subject: [PATCH 11/50] * Publishing test artifact * Improvements --- build.gradle.kts | 25 +++------- .../shell/integration/ShellExecutorTest.kt | 16 +++++-- .../shell/integration/tools/TestAssertion.kt | 24 +++++----- .../shell/integration/tools/TestContext.kt | 46 +++++++++---------- .../github/kscripting/shell/ShellExecutor.kt | 12 ++--- .../github/kscripting/shell/model/OsPath.kt | 2 +- 6 files changed, 58 insertions(+), 67 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index b2f57ab..2415fa0 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,3 +1,4 @@ + val kotlinVersion: String = "1.8.22" plugins { @@ -56,7 +57,7 @@ tasks.create("itest") { testClassesDirs = sourceSets["itest"].output.classesDirs classpath = sourceSets["itest"].runtimeClasspath outputs.upToDateWhen { false } - dependsOn(tasks["build"]) + dependsOn(tasks["assemble"]) doLast { println("Include tags: $itags") @@ -85,8 +86,9 @@ tasks.test { useJUnitPlatform() } -val testToolsJar by tasks.creating(org.gradle.jvm.tasks.Jar::class) { +val testToolsJar by tasks.registering(Jar::class) { //archiveFileName.set("eulenspiegel-testHelpers-$version.jar") + archiveClassifier.set("test") include("io/github/kscripting/shell/integration/tools/*") from(sourceSets["itest"].output) } @@ -112,11 +114,14 @@ val scmSpec = Action { url.set("https://github.com/kscripting/shell") } +val publishArtifact = artifacts.add("archives", testToolsJar) + publishing { publications { create("shell") { artifactId = "shell" from(components["java"]) + artifact(publishArtifact) pom { name.set("shell") @@ -128,21 +133,6 @@ publishing { scm(scmSpec) } } - -// create("shellTest") { -// artifactId = "shell-test" -// artifact(testToolsJar) -// -// pom { -// name.set("shell-test") -// description.set("Shell Tests - library for testing shell apps") -// url.set("https://github.com/kscripting/shell") -// -// licenses(licencesSpec) -// developers(developersSpec) -// scm(scmSpec) -// } -// } } repositories { @@ -161,7 +151,6 @@ publishing { signing { sign(publishing.publications["shell"]) -// sign(publishing.publications["shellTest"]) } dependencies { diff --git a/src/itest/kotlin/io/github/kscripting/shell/integration/ShellExecutorTest.kt b/src/itest/kotlin/io/github/kscripting/shell/integration/ShellExecutorTest.kt index 5818999..52d6514 100644 --- a/src/itest/kotlin/io/github/kscripting/shell/integration/ShellExecutorTest.kt +++ b/src/itest/kotlin/io/github/kscripting/shell/integration/ShellExecutorTest.kt @@ -3,11 +3,11 @@ package io.github.kscripting.shell.integration import io.github.kscripting.shell.integration.tools.TestAssertion.verify import io.github.kscripting.shell.integration.tools.TestContext import io.github.kscripting.shell.integration.tools.TestContext.execPath +import io.github.kscripting.shell.integration.tools.TestContext.testPath import io.github.kscripting.shell.model.readText import io.github.kscripting.shell.util.Sanitizer import org.junit.jupiter.api.Tag import org.junit.jupiter.api.Test -import java.io.ByteArrayInputStream class ShellExecutorTest : TestBase { @Test @@ -21,7 +21,7 @@ class ShellExecutorTest : TestBase { @Tag("posix") @Tag("windows") fun `Unicode characters output works`() { - val path = execPath().resolve("unicodeOutput.txt") + val path = testPath.resolve("unicodeOutput.txt") println(path) verify( "doEcho -f $path", @@ -39,11 +39,17 @@ class ShellExecutorTest : TestBase { verify("read INPUT; echo \$INPUT", 0, "Input to READ[nl]", "", inputStream = "Input to READ".byteInputStream()) } +// @Test +// @Tag("windows") +// fun `Call command utilizing input stream (windows)`() { +// verify("set /p INPUT=\"\" && echo %INPUT%", 0, "Input to READ[nl]", "", inputStream = "Input to READ".byteInputStream()) +// } + companion object { init { - TestContext.copyToExecutablePath("src/doEcho") - TestContext.copyToExecutablePath("src/doEcho.bat") - TestContext.copyToExecutablePath("src/unicodeOutput.txt") + TestContext.copyFile("src/doEcho", execPath) + TestContext.copyFile("src/doEcho.bat", execPath) + TestContext.copyFile("src/unicodeOutput.txt", testPath) Thread.sleep(1000) //It takes time to copy and set executable bit [tests fail without this] } } diff --git a/src/itest/kotlin/io/github/kscripting/shell/integration/tools/TestAssertion.kt b/src/itest/kotlin/io/github/kscripting/shell/integration/tools/TestAssertion.kt index b1aa00d..904c167 100644 --- a/src/itest/kotlin/io/github/kscripting/shell/integration/tools/TestAssertion.kt +++ b/src/itest/kotlin/io/github/kscripting/shell/integration/tools/TestAssertion.kt @@ -19,45 +19,45 @@ object TestAssertion { exitCode: Int = 0, stdOut: TestMatcher, stdErr: String = "", - envAdjuster: EnvAdjuster = {}, inputSanitizer: Sanitizer? = null, outputSanitizer: Sanitizer? = null, - inputStream: InputStream? = null - ): ProcessResult = verify(command, exitCode, stdOut, eq(stdErr), envAdjuster, inputSanitizer, outputSanitizer, inputStream) + inputStream: InputStream? = null, + envAdjuster: EnvAdjuster = {} + ): ProcessResult = verify(command, exitCode, stdOut, eq(stdErr), inputSanitizer, outputSanitizer, inputStream, envAdjuster) fun verify( command: String, exitCode: Int = 0, stdOut: String, stdErr: TestMatcher, - envAdjuster: EnvAdjuster = {}, inputSanitizer: Sanitizer? = null, outputSanitizer: Sanitizer? = null, - inputStream: InputStream? = null - ): ProcessResult = verify(command, exitCode, eq(stdOut), stdErr, envAdjuster, inputSanitizer, outputSanitizer, inputStream) + inputStream: InputStream? = null, + envAdjuster: EnvAdjuster = {} + ): ProcessResult = verify(command, exitCode, eq(stdOut), stdErr, inputSanitizer, outputSanitizer, inputStream, envAdjuster) fun verify( command: String, exitCode: Int = 0, stdOut: String = "", stdErr: String = "", - envAdjuster: EnvAdjuster = {}, inputSanitizer: Sanitizer? = null, outputSanitizer: Sanitizer? = null, - inputStream: InputStream? = null - ): ProcessResult = verify(command, exitCode, eq(stdOut), eq(stdErr), envAdjuster, inputSanitizer, outputSanitizer, inputStream) + inputStream: InputStream? = null, + envAdjuster: EnvAdjuster = {} + ): ProcessResult = verify(command, exitCode, eq(stdOut), eq(stdErr), inputSanitizer, outputSanitizer, inputStream, envAdjuster) fun verify( command: String, exitCode: Int = 0, stdOut: TestMatcher, stdErr: TestMatcher, - envAdjuster: EnvAdjuster = {}, inputSanitizer: Sanitizer? = null, outputSanitizer: Sanitizer? = null, - inputStream: InputStream? = null + inputStream: InputStream? = null, + envAdjuster: EnvAdjuster = {} ): ProcessResult { - val processResult = runProcess(command, envAdjuster, inputSanitizer, outputSanitizer, inputStream) + val processResult = runProcess(command, inputSanitizer, outputSanitizer, inputStream, envAdjuster) println(processResult) val extCde = genericEquals(exitCode) diff --git a/src/itest/kotlin/io/github/kscripting/shell/integration/tools/TestContext.kt b/src/itest/kotlin/io/github/kscripting/shell/integration/tools/TestContext.kt index 2009b4f..84534fb 100644 --- a/src/itest/kotlin/io/github/kscripting/shell/integration/tools/TestContext.kt +++ b/src/itest/kotlin/io/github/kscripting/shell/integration/tools/TestContext.kt @@ -6,12 +6,15 @@ import io.github.kscripting.shell.process.EnvAdjuster import io.github.kscripting.shell.util.Sanitizer import java.io.InputStream +@Suppress("MemberVisibilityCanBePrivate") object TestContext { - private val osType: OsType = OsType.find(System.getProperty("osType")) ?: OsType.native - private val nativeType = if (osType.isPosixHostedOnWindows()) OsType.WINDOWS else osType + val osType: OsType = OsType.find(System.getProperty("osType")) ?: OsType.native + val nativeType = if (osType.isPosixHostedOnWindows()) OsType.WINDOWS else osType + + val projectPath: OsPath = OsPath.createOrThrow(nativeType, System.getProperty("projectPath")).convert(osType) + val execPath: OsPath = projectPath.resolve("build/shell_test/bin") + val testPath: OsPath = projectPath.resolve("build/shell_test/tmp") - private val projectPath: OsPath = OsPath.createOrThrow(nativeType, System.getProperty("projectPath")) - private val execPath: OsPath = projectPath.resolve("build/shell/bin") private val pathEnvName = if (osType.isWindowsLike()) "Path" else "PATH" private val systemPath: String = System.getenv()[pathEnvName]!! @@ -20,16 +23,13 @@ object TestContext { val nl: String = System.getProperty("line.separator") - private val inputSanitizer = Sanitizer( - listOf("[bs]" to "\\", "[nl]" to nl, "[tb]" to "\t") - ) - - val projectDir: String = projectPath.convert(osType).stringPath() + val defaultInputSanitizer = Sanitizer(listOf("[bs]" to "\\", "[nl]" to nl, "[tb]" to "\t")) + val defaultOutputSanitizer = defaultInputSanitizer.swapped() init { println("osType : $osType") println("nativeType : $nativeType") - println("projectDir : $projectDir") + println("projectDir : $projectPath") println("execDir : ${execPath.convert(osType)}") println("Kotlin version : ${ShellExecutor.evalAndGobble("kotlin -version", osType, null).stdout}") println("Env path : $envPath") @@ -37,16 +37,12 @@ object TestContext { execPath.createDirectories() } - fun path(path: String): OsPath { - return OsPath.createOrThrow(osType, path) - } - fun runProcess( command: String, - envAdjuster: EnvAdjuster, inputSanitizer: Sanitizer? = null, outputSanitizer: Sanitizer? = null, - inputStream: InputStream? = null + inputStream: InputStream? = null, + envAdjuster: EnvAdjuster ): ProcessResult { //In MSYS all quotes should be single quotes, otherwise content is interpreted e.g. backslashes. //(MSYS bash interpreter is also replacing double quotes into the single quotes: see: bash -xc 'kscript "println(1+1)"') @@ -64,20 +60,20 @@ object TestContext { newCommand, osType, null, - ::internalEnvAdjuster, - inputSanitizer = inputSanitizer ?: this.inputSanitizer, - outputSanitizer = outputSanitizer ?: this.inputSanitizer.swapped(), - inputStream = inputStream + inputSanitizer = inputSanitizer ?: this.defaultInputSanitizer, + outputSanitizer = outputSanitizer ?: this.defaultOutputSanitizer, + inputStream = inputStream, + envAdjuster = ::internalEnvAdjuster ) } - fun copyToExecutablePath(source: String) { + fun copyFile(source: String, target: OsPath) { val sourceFile = projectPath.resolve(source).toNativeFile() - val targetFile = execPath.resolve(sourceFile.name).toNativeFile() + val targetFile = target.resolve(sourceFile.name).toNativeFile() sourceFile.copyTo(targetFile, overwrite = true) - targetFile.setExecutable(true) + if (target == execPath) { + targetFile.setExecutable(true) + } } - - fun execPath(): OsPath = execPath } diff --git a/src/main/kotlin/io/github/kscripting/shell/ShellExecutor.kt b/src/main/kotlin/io/github/kscripting/shell/ShellExecutor.kt index 7a2c75b..391a4dc 100644 --- a/src/main/kotlin/io/github/kscripting/shell/ShellExecutor.kt +++ b/src/main/kotlin/io/github/kscripting/shell/ShellExecutor.kt @@ -33,7 +33,6 @@ object ShellExecutor { command: String, osType: OsType = OsType.native, workingDirectory: OsPath? = null, - envAdjuster: EnvAdjuster = {}, waitTimeMinutes: Int = 10, inheritInput: Boolean = false, inputSanitizer: Sanitizer = EMPTY_SANITIZER, @@ -41,7 +40,8 @@ object ShellExecutor { outPrinter: List = emptyList(), errPrinter: List = emptyList(), inputStream: InputStream? = null, - shellMapper: Map = DEFAULT_SHELL_MAPPER + shellMapper: Map = DEFAULT_SHELL_MAPPER, + envAdjuster: EnvAdjuster = {} ): ProcessResult { val outStream = ByteArrayOutputStream(1024) val errStream = ByteArrayOutputStream(1024) @@ -54,7 +54,6 @@ object ShellExecutor { command, osType, workingDirectory, - envAdjuster, waitTimeMinutes, inheritInput, inputSanitizer, @@ -62,7 +61,8 @@ object ShellExecutor { outPrinter + additionalOutPrinter, errPrinter + additionalErrPrinter, inputStream, - shellMapper + shellMapper, + envAdjuster ) } } @@ -74,7 +74,6 @@ object ShellExecutor { command: String, osType: OsType = OsType.native, workingDirectory: OsPath? = null, - envAdjuster: EnvAdjuster = {}, waitTimeMinutes: Int = 10, inheritInput: Boolean = false, inputSanitizer: Sanitizer = EMPTY_SANITIZER, @@ -82,7 +81,8 @@ object ShellExecutor { outPrinter: List = DEFAULT_OUT_PRINTERS, errPrinter: List = DEFAULT_ERR_PRINTERS, inputStream: InputStream? = null, - shellMapper: Map = DEFAULT_SHELL_MAPPER + shellMapper: Map = DEFAULT_SHELL_MAPPER, + envAdjuster: EnvAdjuster = {} ): Int { val sanitizedCommand = inputSanitizer.sanitize(command) diff --git a/src/main/kotlin/io/github/kscripting/shell/model/OsPath.kt b/src/main/kotlin/io/github/kscripting/shell/model/OsPath.kt index 1311b87..f8958ed 100644 --- a/src/main/kotlin/io/github/kscripting/shell/model/OsPath.kt +++ b/src/main/kotlin/io/github/kscripting/shell/model/OsPath.kt @@ -15,7 +15,7 @@ data class OsPath(val osType: OsType, val pathType: PathType, val pathParts: Lis } fun resolve(vararg pathParts: String): OsPath { - return resolve(createOrThrow(osType, pathParts.joinToString(pathSeparator.toString()))) + return resolve(createOrThrow(osType, *pathParts)) } fun resolve(path: OsPath): OsPath { From 14fc674139a1419b8055ee058b030f74a5e815aa Mon Sep 17 00:00:00 2001 From: Marcin Kuszczak <1508798+aartiPl@users.noreply.github.com> Date: Sat, 24 Jun 2023 22:25:53 +0200 Subject: [PATCH 12/50] Improvements --- .../shell/integration/ShellExecutorTest.kt | 4 +- .../kscripting/shell/integration/TestBase.kt | 12 ------ .../{TestAssertion.kt => ShellTestBase.kt} | 23 ++++++++--- .../tools/ShellTestCompanionBase.kt | 41 +++++++++++++++++++ .../shell/integration/tools/TestContext.kt | 34 +-------------- .../kscripting/shell/model/OsPathExt.kt | 2 +- 6 files changed, 64 insertions(+), 52 deletions(-) delete mode 100644 src/itest/kotlin/io/github/kscripting/shell/integration/TestBase.kt rename src/itest/kotlin/io/github/kscripting/shell/integration/tools/{TestAssertion.kt => ShellTestBase.kt} (75%) create mode 100644 src/itest/kotlin/io/github/kscripting/shell/integration/tools/ShellTestCompanionBase.kt diff --git a/src/itest/kotlin/io/github/kscripting/shell/integration/ShellExecutorTest.kt b/src/itest/kotlin/io/github/kscripting/shell/integration/ShellExecutorTest.kt index 52d6514..9d554d0 100644 --- a/src/itest/kotlin/io/github/kscripting/shell/integration/ShellExecutorTest.kt +++ b/src/itest/kotlin/io/github/kscripting/shell/integration/ShellExecutorTest.kt @@ -1,6 +1,6 @@ package io.github.kscripting.shell.integration -import io.github.kscripting.shell.integration.tools.TestAssertion.verify +import io.github.kscripting.shell.integration.tools.ShellTestBase import io.github.kscripting.shell.integration.tools.TestContext import io.github.kscripting.shell.integration.tools.TestContext.execPath import io.github.kscripting.shell.integration.tools.TestContext.testPath @@ -9,7 +9,7 @@ import io.github.kscripting.shell.util.Sanitizer import org.junit.jupiter.api.Tag import org.junit.jupiter.api.Test -class ShellExecutorTest : TestBase { +class ShellExecutorTest : ShellTestBase { @Test @Tag("posix") @Tag("windows") diff --git a/src/itest/kotlin/io/github/kscripting/shell/integration/TestBase.kt b/src/itest/kotlin/io/github/kscripting/shell/integration/TestBase.kt deleted file mode 100644 index ee5e3d2..0000000 --- a/src/itest/kotlin/io/github/kscripting/shell/integration/TestBase.kt +++ /dev/null @@ -1,12 +0,0 @@ -package io.github.kscripting.shell.integration - -import org.junit.jupiter.api.BeforeAll - -interface TestBase { - companion object { - @BeforeAll - @JvmStatic - fun setUp() { - } - } -} diff --git a/src/itest/kotlin/io/github/kscripting/shell/integration/tools/TestAssertion.kt b/src/itest/kotlin/io/github/kscripting/shell/integration/tools/ShellTestBase.kt similarity index 75% rename from src/itest/kotlin/io/github/kscripting/shell/integration/tools/TestAssertion.kt rename to src/itest/kotlin/io/github/kscripting/shell/integration/tools/ShellTestBase.kt index 904c167..b6bf57b 100644 --- a/src/itest/kotlin/io/github/kscripting/shell/integration/tools/TestAssertion.kt +++ b/src/itest/kotlin/io/github/kscripting/shell/integration/tools/ShellTestBase.kt @@ -1,12 +1,15 @@ package io.github.kscripting.shell.integration.tools -import io.github.kscripting.shell.integration.tools.TestContext.runProcess +import io.github.kscripting.shell.ShellExecutor +import io.github.kscripting.shell.integration.tools.TestContext.defaultInputSanitizer +import io.github.kscripting.shell.integration.tools.TestContext.defaultOutputSanitizer import io.github.kscripting.shell.model.ProcessResult import io.github.kscripting.shell.process.EnvAdjuster import io.github.kscripting.shell.util.Sanitizer +import org.junit.jupiter.api.BeforeAll import java.io.InputStream -object TestAssertion { +interface ShellTestBase { fun genericEquals(value: T) = GenericEquals(value) fun any() = AnyMatch() @@ -23,7 +26,8 @@ object TestAssertion { outputSanitizer: Sanitizer? = null, inputStream: InputStream? = null, envAdjuster: EnvAdjuster = {} - ): ProcessResult = verify(command, exitCode, stdOut, eq(stdErr), inputSanitizer, outputSanitizer, inputStream, envAdjuster) + ): ProcessResult = + verify(command, exitCode, stdOut, eq(stdErr), inputSanitizer, outputSanitizer, inputStream, envAdjuster) fun verify( command: String, @@ -34,7 +38,8 @@ object TestAssertion { outputSanitizer: Sanitizer? = null, inputStream: InputStream? = null, envAdjuster: EnvAdjuster = {} - ): ProcessResult = verify(command, exitCode, eq(stdOut), stdErr, inputSanitizer, outputSanitizer, inputStream, envAdjuster) + ): ProcessResult = + verify(command, exitCode, eq(stdOut), stdErr, inputSanitizer, outputSanitizer, inputStream, envAdjuster) fun verify( command: String, @@ -45,7 +50,8 @@ object TestAssertion { outputSanitizer: Sanitizer? = null, inputStream: InputStream? = null, envAdjuster: EnvAdjuster = {} - ): ProcessResult = verify(command, exitCode, eq(stdOut), eq(stdErr), inputSanitizer, outputSanitizer, inputStream, envAdjuster) + ): ProcessResult = + verify(command, exitCode, eq(stdOut), eq(stdErr), inputSanitizer, outputSanitizer, inputStream, envAdjuster) fun verify( command: String, @@ -69,4 +75,11 @@ object TestAssertion { return processResult } + + companion object : ShellTestCompanionBase() { + @BeforeAll + @JvmStatic + fun setUp() { + } + } } diff --git a/src/itest/kotlin/io/github/kscripting/shell/integration/tools/ShellTestCompanionBase.kt b/src/itest/kotlin/io/github/kscripting/shell/integration/tools/ShellTestCompanionBase.kt new file mode 100644 index 0000000..b23a77f --- /dev/null +++ b/src/itest/kotlin/io/github/kscripting/shell/integration/tools/ShellTestCompanionBase.kt @@ -0,0 +1,41 @@ +package io.github.kscripting.shell.integration.tools + +import io.github.kscripting.shell.ShellExecutor +import io.github.kscripting.shell.model.ProcessResult +import io.github.kscripting.shell.process.EnvAdjuster +import io.github.kscripting.shell.util.Sanitizer +import java.io.InputStream + +abstract class ShellTestCompanionBase { + open fun commonEnvAdjuster(specificEnvAdjuster: EnvAdjuster = {}): EnvAdjuster { + return { map -> + map[TestContext.pathEnvName] = TestContext.envPath + specificEnvAdjuster(map) + } + } + + open fun runProcess( + command: String, + inputSanitizer: Sanitizer?, + outputSanitizer: Sanitizer?, + inputStream: InputStream?, + envAdjuster: EnvAdjuster + ): ProcessResult { + //In MSYS all quotes should be single quotes, otherwise content is interpreted e.g. backslashes. + //(MSYS bash interpreter is also replacing double quotes into the single quotes: see: bash -xc 'kscript "println(1+1)"') + val newCommand = when { + TestContext.osType.isPosixHostedOnWindows() -> command.replace('"', '\'') + else -> command + } + + return ShellExecutor.evalAndGobble( + newCommand, + TestContext.osType, + null, + inputSanitizer = inputSanitizer ?: TestContext.defaultInputSanitizer, + outputSanitizer = outputSanitizer ?: TestContext.defaultOutputSanitizer, + inputStream = inputStream, + envAdjuster = commonEnvAdjuster(envAdjuster) + ) + } +} diff --git a/src/itest/kotlin/io/github/kscripting/shell/integration/tools/TestContext.kt b/src/itest/kotlin/io/github/kscripting/shell/integration/tools/TestContext.kt index 84534fb..f15b93b 100644 --- a/src/itest/kotlin/io/github/kscripting/shell/integration/tools/TestContext.kt +++ b/src/itest/kotlin/io/github/kscripting/shell/integration/tools/TestContext.kt @@ -15,11 +15,11 @@ object TestContext { val execPath: OsPath = projectPath.resolve("build/shell_test/bin") val testPath: OsPath = projectPath.resolve("build/shell_test/tmp") - private val pathEnvName = if (osType.isWindowsLike()) "Path" else "PATH" + val pathEnvName = if (osType.isWindowsLike()) "Path" else "PATH" private val systemPath: String = System.getenv()[pathEnvName]!! private val pathSeparator: String = if (osType.isWindowsLike() || osType.isPosixHostedOnWindows()) ";" else ":" - private val envPath: String = "${execPath.convert(osType)}$pathSeparator$systemPath" + val envPath: String = "${execPath.convert(osType)}$pathSeparator$systemPath" val nl: String = System.getProperty("line.separator") @@ -37,36 +37,6 @@ object TestContext { execPath.createDirectories() } - fun runProcess( - command: String, - inputSanitizer: Sanitizer? = null, - outputSanitizer: Sanitizer? = null, - inputStream: InputStream? = null, - envAdjuster: EnvAdjuster - ): ProcessResult { - //In MSYS all quotes should be single quotes, otherwise content is interpreted e.g. backslashes. - //(MSYS bash interpreter is also replacing double quotes into the single quotes: see: bash -xc 'kscript "println(1+1)"') - val newCommand = when { - osType.isPosixHostedOnWindows() -> command.replace('"', '\'') - else -> command - } - - fun internalEnvAdjuster(map: MutableMap) { - map[pathEnvName] = envPath - envAdjuster(map) - } - - return ShellExecutor.evalAndGobble( - newCommand, - osType, - null, - inputSanitizer = inputSanitizer ?: this.defaultInputSanitizer, - outputSanitizer = outputSanitizer ?: this.defaultOutputSanitizer, - inputStream = inputStream, - envAdjuster = ::internalEnvAdjuster - ) - } - fun copyFile(source: String, target: OsPath) { val sourceFile = projectPath.resolve(source).toNativeFile() val targetFile = target.resolve(sourceFile.name).toNativeFile() diff --git a/src/main/kotlin/io/github/kscripting/shell/model/OsPathExt.kt b/src/main/kotlin/io/github/kscripting/shell/model/OsPathExt.kt index c926262..f05237f 100644 --- a/src/main/kotlin/io/github/kscripting/shell/model/OsPathExt.kt +++ b/src/main/kotlin/io/github/kscripting/shell/model/OsPathExt.kt @@ -47,7 +47,7 @@ fun OsPath.readText(charset: Charset = Charsets.UTF_8): String = toNativePath(). // OsPath accessors val OsPath.leaf - get(): String? = if (pathParts.isEmpty()) null else pathParts.last() + get(): String = if (pathParts.isEmpty()) "" else pathParts.last() val OsPath.root get(): String? = if (pathParts.isEmpty()) null else pathParts.first() From f11b39db597a2727be78718d6996945fdffd0998 Mon Sep 17 00:00:00 2001 From: Marcin Kuszczak <1508798+aartiPl@users.noreply.github.com> Date: Sat, 24 Jun 2023 22:29:40 +0200 Subject: [PATCH 13/50] Added github actions --- .github/workflows/build.yml | 104 ++++++++++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 .github/workflows/build.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..53816a2 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,104 @@ +name: build + +on: + workflow_dispatch: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + build: + strategy: + fail-fast: false + matrix: + os: + - ubuntu-20.04 + - macos-12 + - windows-2022 + variant: + - posix + include: + - os: windows-2022 + variant: cygwin + - os: windows-2022 + variant: cmd + runs-on: ${{ matrix.os }} + timeout-minutes: 30 + + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Setup Java + uses: actions/setup-java@v3 + with: + distribution: 'zulu' + java-version: '11' + + - name: Setup Kotlin + uses: fwilhe2/setup-kotlin@main + with: + version: 1.7.21 + + - name: Install dependencies for ${{ runner.os }} + shell: bash + run: | + if [ "$RUNNER_OS" == "Windows" ]; then + choco install zip + # Overwrite the WSL bash.exe + # cp /c/msys64/usr/bin/bash.exe /c/Windows/System32/bash.exe + mv /c/Windows/System32/bash.exe /c/Windows/System32/wsl-bash.exe + fi + + - name: Run tests for Posix (and MSYS on Windows) + if: matrix.variant == 'posix' + shell: bash + run: | + # For Windows this action is running MSYS Os type + + echo "OsType: $OSTYPE" + + ./gradlew clean assemble test || { echo 'Compilation or Unit tests failed' ; exit 1; } + + if [[ "$OSTYPE" == "linux"* ]]; then + echo "Linux test..." + ./gradlew -DosType=$OSTYPE -DincludeTags='posix | linux' integrationTest + elif [[ "$OSTYPE" == "darwin"* ]]; then + echo "MacOs test..." + ./gradlew -DosType=$OSTYPE -DincludeTags='posix | macos' integrationTest + elif [[ "$OSTYPE" == "cygwin" ]]; then + echo "Cygwin test..." + ./gradlew -DosType=$OSTYPE -DincludeTags='posix | cygwin' integrationTest + elif [[ "$OSTYPE" == "msys" ]]; then + echo "MSys test..." + ./gradlew -DosType=$OSTYPE -DincludeTags='posix | msys' integrationTest + elif [[ "$OSTYPE" == "freebsd"* ]]; then + echo "FreeBsd test..." + ./gradlew -DosType=$OSTYPE -DincludeTags='posix' integrationTest + else + echo "Unknown OS" + exit 1 + fi + + - name: Run tests specific for Windows (cmd shell) + if: matrix.variant == 'cmd' + shell: cmd + run: | + echo "Windows test..." + .\gradlew.bat -DosType=windows -DincludeTags="windows" clean assemble test integrationTest + + - name: Install Cygwin (only Windows) + if: matrix.variant == 'cygwin' + uses: egor-tensin/setup-cygwin@v3 + + - name: Run tests specific for Cygwin + if: matrix.variant == 'cygwin' + shell: C:\tools\cygwin\bin\bash.exe --login --norc -eo pipefail -o igncr '{0}' + run: | + echo $OSTYPE + echo "Cygwin test..." + echo "Changing directory to $GITHUB_WORKSPACE ..." + cd $GITHUB_WORKSPACE + ./gradlew clean assemble test || { echo 'Compilation or Unit tests failed' ; exit 1; } + ./gradlew -DosType=$OSTYPE -DincludeTags='posix | cygwin' integrationTest From fdd3ec425c639242268b1a9e418829ef72eb46b0 Mon Sep 17 00:00:00 2001 From: Marcin Kuszczak <1508798+aartiPl@users.noreply.github.com> Date: Sat, 24 Jun 2023 22:33:25 +0200 Subject: [PATCH 14/50] Make ./gradlew executable --- .github/workflows/build.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 53816a2..2e94054 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -59,6 +59,8 @@ jobs: echo "OsType: $OSTYPE" + chmod +x ./gradlew + ./gradlew clean assemble test || { echo 'Compilation or Unit tests failed' ; exit 1; } if [[ "$OSTYPE" == "linux"* ]]; then From 86e7c2be38f143cca693b4c68a56083920b43c92 Mon Sep 17 00:00:00 2001 From: Marcin Kuszczak <1508798+aartiPl@users.noreply.github.com> Date: Sat, 24 Jun 2023 22:34:51 +0200 Subject: [PATCH 15/50] integrationTest -> itest --- .github/workflows/build.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2e94054..d0bae46 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -65,19 +65,19 @@ jobs: if [[ "$OSTYPE" == "linux"* ]]; then echo "Linux test..." - ./gradlew -DosType=$OSTYPE -DincludeTags='posix | linux' integrationTest + ./gradlew -DosType=$OSTYPE -DincludeTags='posix | linux' itest elif [[ "$OSTYPE" == "darwin"* ]]; then echo "MacOs test..." - ./gradlew -DosType=$OSTYPE -DincludeTags='posix | macos' integrationTest + ./gradlew -DosType=$OSTYPE -DincludeTags='posix | macos' itest elif [[ "$OSTYPE" == "cygwin" ]]; then echo "Cygwin test..." - ./gradlew -DosType=$OSTYPE -DincludeTags='posix | cygwin' integrationTest + ./gradlew -DosType=$OSTYPE -DincludeTags='posix | cygwin' itest elif [[ "$OSTYPE" == "msys" ]]; then echo "MSys test..." - ./gradlew -DosType=$OSTYPE -DincludeTags='posix | msys' integrationTest + ./gradlew -DosType=$OSTYPE -DincludeTags='posix | msys' itest elif [[ "$OSTYPE" == "freebsd"* ]]; then echo "FreeBsd test..." - ./gradlew -DosType=$OSTYPE -DincludeTags='posix' integrationTest + ./gradlew -DosType=$OSTYPE -DincludeTags='posix' itest else echo "Unknown OS" exit 1 @@ -88,7 +88,7 @@ jobs: shell: cmd run: | echo "Windows test..." - .\gradlew.bat -DosType=windows -DincludeTags="windows" clean assemble test integrationTest + .\gradlew.bat -DosType=windows -DincludeTags="windows" clean assemble test itest - name: Install Cygwin (only Windows) if: matrix.variant == 'cygwin' @@ -103,4 +103,4 @@ jobs: echo "Changing directory to $GITHUB_WORKSPACE ..." cd $GITHUB_WORKSPACE ./gradlew clean assemble test || { echo 'Compilation or Unit tests failed' ; exit 1; } - ./gradlew -DosType=$OSTYPE -DincludeTags='posix | cygwin' integrationTest + ./gradlew -DosType=$OSTYPE -DincludeTags='posix | cygwin' itest From 56fd140e8269784f633697b5111bac64e7eb13ef Mon Sep 17 00:00:00 2001 From: Marcin Kuszczak <1508798+aartiPl@users.noreply.github.com> Date: Sat, 24 Jun 2023 23:16:18 +0200 Subject: [PATCH 16/50] adjustment for nl --- .../github/kscripting/shell/integration/tools/TestContext.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/itest/kotlin/io/github/kscripting/shell/integration/tools/TestContext.kt b/src/itest/kotlin/io/github/kscripting/shell/integration/tools/TestContext.kt index f15b93b..99e75ba 100644 --- a/src/itest/kotlin/io/github/kscripting/shell/integration/tools/TestContext.kt +++ b/src/itest/kotlin/io/github/kscripting/shell/integration/tools/TestContext.kt @@ -21,7 +21,10 @@ object TestContext { private val pathSeparator: String = if (osType.isWindowsLike() || osType.isPosixHostedOnWindows()) ";" else ":" val envPath: String = "${execPath.convert(osType)}$pathSeparator$systemPath" - val nl: String = System.getProperty("line.separator") + val nl: String = when { + osType.isPosixHostedOnWindows() -> "\n" + else -> System.getProperty("line.separator") + } val defaultInputSanitizer = Sanitizer(listOf("[bs]" to "\\", "[nl]" to nl, "[tb]" to "\t")) val defaultOutputSanitizer = defaultInputSanitizer.swapped() From 439ab1c5def37473862a430fe36ed6bef724ebec Mon Sep 17 00:00:00 2001 From: Marcin Kuszczak <1508798+aartiPl@users.noreply.github.com> Date: Sat, 24 Jun 2023 23:23:08 +0200 Subject: [PATCH 17/50] adjustment for nl --- .../tools/ShellTestCompanionBase.kt | 2 +- .../shell/integration/tools/TestContext.kt | 18 +++++++----------- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/src/itest/kotlin/io/github/kscripting/shell/integration/tools/ShellTestCompanionBase.kt b/src/itest/kotlin/io/github/kscripting/shell/integration/tools/ShellTestCompanionBase.kt index b23a77f..836aaff 100644 --- a/src/itest/kotlin/io/github/kscripting/shell/integration/tools/ShellTestCompanionBase.kt +++ b/src/itest/kotlin/io/github/kscripting/shell/integration/tools/ShellTestCompanionBase.kt @@ -9,7 +9,7 @@ import java.io.InputStream abstract class ShellTestCompanionBase { open fun commonEnvAdjuster(specificEnvAdjuster: EnvAdjuster = {}): EnvAdjuster { return { map -> - map[TestContext.pathEnvName] = TestContext.envPath + map[TestContext.pathEnvVariableName] = TestContext.pathEnvVariableCalculatedPath specificEnvAdjuster(map) } } diff --git a/src/itest/kotlin/io/github/kscripting/shell/integration/tools/TestContext.kt b/src/itest/kotlin/io/github/kscripting/shell/integration/tools/TestContext.kt index 99e75ba..9f201a5 100644 --- a/src/itest/kotlin/io/github/kscripting/shell/integration/tools/TestContext.kt +++ b/src/itest/kotlin/io/github/kscripting/shell/integration/tools/TestContext.kt @@ -2,24 +2,20 @@ package io.github.kscripting.shell.integration.tools import io.github.kscripting.shell.ShellExecutor import io.github.kscripting.shell.model.* -import io.github.kscripting.shell.process.EnvAdjuster import io.github.kscripting.shell.util.Sanitizer -import java.io.InputStream @Suppress("MemberVisibilityCanBePrivate") object TestContext { val osType: OsType = OsType.find(System.getProperty("osType")) ?: OsType.native - val nativeType = if (osType.isPosixHostedOnWindows()) OsType.WINDOWS else osType - val projectPath: OsPath = OsPath.createOrThrow(nativeType, System.getProperty("projectPath")).convert(osType) + val projectPath: OsPath = OsPath.createOrThrow(OsType.native, System.getProperty("projectPath")).convert(osType) val execPath: OsPath = projectPath.resolve("build/shell_test/bin") val testPath: OsPath = projectPath.resolve("build/shell_test/tmp") - val pathEnvName = if (osType.isWindowsLike()) "Path" else "PATH" - private val systemPath: String = System.getenv()[pathEnvName]!! - - private val pathSeparator: String = if (osType.isWindowsLike() || osType.isPosixHostedOnWindows()) ";" else ":" - val envPath: String = "${execPath.convert(osType)}$pathSeparator$systemPath" + val pathEnvVariableName = if (osType.isWindowsLike()) "Path" else "PATH" + val pathEnvVariableValue: String = System.getenv()[pathEnvVariableName]!! + val pathEnvVariableSeparator: String = if (osType.isWindowsLike() || osType.isPosixHostedOnWindows()) ";" else ":" + val pathEnvVariableCalculatedPath: String = "${execPath.convert(osType)}$pathEnvVariableSeparator$pathEnvVariableValue" val nl: String = when { osType.isPosixHostedOnWindows() -> "\n" @@ -31,11 +27,11 @@ object TestContext { init { println("osType : $osType") - println("nativeType : $nativeType") + println("nativeType : ${OsType.native}") println("projectDir : $projectPath") println("execDir : ${execPath.convert(osType)}") println("Kotlin version : ${ShellExecutor.evalAndGobble("kotlin -version", osType, null).stdout}") - println("Env path : $envPath") + println("Env path : $pathEnvVariableCalculatedPath") execPath.createDirectories() } From 7359239cb05885f1fec6a93ef5c0d000bc5f7287 Mon Sep 17 00:00:00 2001 From: Marcin Kuszczak <1508798+aartiPl@users.noreply.github.com> Date: Sat, 24 Jun 2023 23:39:27 +0200 Subject: [PATCH 18/50] update --- .../io/github/kscripting/shell/integration/tools/TestContext.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/itest/kotlin/io/github/kscripting/shell/integration/tools/TestContext.kt b/src/itest/kotlin/io/github/kscripting/shell/integration/tools/TestContext.kt index 9f201a5..35d04ef 100644 --- a/src/itest/kotlin/io/github/kscripting/shell/integration/tools/TestContext.kt +++ b/src/itest/kotlin/io/github/kscripting/shell/integration/tools/TestContext.kt @@ -30,7 +30,7 @@ object TestContext { println("nativeType : ${OsType.native}") println("projectDir : $projectPath") println("execDir : ${execPath.convert(osType)}") - println("Kotlin version : ${ShellExecutor.evalAndGobble("kotlin -version", osType, null).stdout}") + println("Kotlin version : ${ShellExecutor.evalAndGobble("kotlin -version", osType).stdout}") println("Env path : $pathEnvVariableCalculatedPath") execPath.createDirectories() From 3f3dc2a76e963ba65d0ec9e131ec83c5892904bc Mon Sep 17 00:00:00 2001 From: Marcin Kuszczak <1508798+aartiPl@users.noreply.github.com> Date: Mon, 26 Jun 2023 23:01:06 +0200 Subject: [PATCH 19/50] Removed UNDEFINED PathType --- .../github/kscripting/shell/model/OsPath.kt | 35 ++++++++++--------- .../kscripting/shell/model/OsPathExt.kt | 3 ++ .../github/kscripting/shell/model/PathType.kt | 2 +- .../kscripting/shell/model/OsPathTest.kt | 22 ++++++------ 4 files changed, 34 insertions(+), 28 deletions(-) diff --git a/src/main/kotlin/io/github/kscripting/shell/model/OsPath.kt b/src/main/kotlin/io/github/kscripting/shell/model/OsPath.kt index f8958ed..dc19069 100644 --- a/src/main/kotlin/io/github/kscripting/shell/model/OsPath.kt +++ b/src/main/kotlin/io/github/kscripting/shell/model/OsPath.kt @@ -42,6 +42,8 @@ data class OsPath(val osType: OsType, val pathType: PathType, val pathParts: Lis } //Not all conversions make sense: only Windows to CygWin and Msys and vice versa + //TODO: conversion of paths like /usr /opt etc. is wrong; it needs also windows root of installation cygwin/msys + // base path: cygpath -w / fun convert(targetOsType: OsType): OsPath { if (this.osType == targetOsType) { return this @@ -136,11 +138,9 @@ data class OsPath(val osType: OsType, val pathType: PathType, val pathParts: Lis '\\' } - fun createOrThrow(vararg pathParts: String): OsPath = - createOrThrow(OsType.native, pathParts.toList()) + fun createOrThrow(vararg pathParts: String): OsPath = createOrThrow(OsType.native, pathParts.toList()) - fun createOrThrow(osType: OsType, vararg pathParts: String): OsPath = - createOrThrow(osType, pathParts.toList()) + fun createOrThrow(osType: OsType, vararg pathParts: String): OsPath = createOrThrow(osType, pathParts.toList()) fun createOrThrow(osType: OsType, pathParts: List): OsPath { return when (val result = internalCreate(osType, pathParts)) { @@ -149,11 +149,9 @@ data class OsPath(val osType: OsType, val pathType: PathType, val pathParts: Lis } } - fun create(vararg pathParts: String): OsPath? = - create(OsType.native, pathParts.toList()) + fun create(vararg pathParts: String): OsPath? = create(OsType.native, pathParts.toList()) - fun create(osType: OsType, vararg pathParts: String): OsPath? = - create(osType, pathParts.toList()) + fun create(osType: OsType, vararg pathParts: String): OsPath? = create(osType, pathParts.toList()) fun create(osType: OsType, pathParts: List): OsPath? { return when (val result = internalCreate(osType, pathParts)) { @@ -163,7 +161,7 @@ data class OsPath(val osType: OsType, val pathType: PathType, val pathParts: Lis } //Relaxed validation: - //1. It doesn't matter if there is '/' or '\' used as path separator - both are treated he same + //1. It doesn't matter if there is '/' or '\' used as path separator - both are treated the same //2. Duplicated or trailing slashes '/' and backslashes '\' are just ignored private fun internalCreate(osType: OsType, pathParts: List): Either { val pathSeparatorCharacter = resolvePathSeparator(osType) @@ -175,9 +173,12 @@ data class OsPath(val osType: OsType, val pathType: PathType, val pathParts: Lis val rootElementSizeInInputPath: Int val pathType: PathType + var isUndefined = false + when { pathPartsResolved.isEmpty() -> { - pathType = PathType.UNDEFINED + pathType = PathType.RELATIVE + isUndefined = true rootElementSizeInInputPath = 0 } @@ -207,7 +208,8 @@ data class OsPath(val osType: OsType, val pathType: PathType, val pathParts: Lis else -> { //This is undefined path - pathType = PathType.UNDEFINED + pathType = PathType.RELATIVE + isUndefined = true rootElementSizeInInputPath = 0 } } @@ -219,6 +221,10 @@ data class OsPath(val osType: OsType, val pathType: PathType, val pathParts: Lis return "Invalid character '$forbiddenCharacter' in path '$path'".left() } + if (isUndefined) { + pathPartsResolved.add(0, ".") + } + //Remove empty path parts - there were duplicated or trailing slashes / backslashes in initial path pathPartsResolved.removeAll { it.isEmpty() } @@ -243,12 +249,9 @@ data class OsPath(val osType: OsType, val pathType: PathType, val pathParts: Lis // /a/../ --> / val newParts = mutableListOf() - var index = 0 + var index = 1 - if (pathType != PathType.UNDEFINED) { - newParts.add(pathParts[0]) - index = 1 - } + newParts.add(pathParts[0]) while (index < pathParts.size) { if (pathParts[index] == ".") { diff --git a/src/main/kotlin/io/github/kscripting/shell/model/OsPathExt.kt b/src/main/kotlin/io/github/kscripting/shell/model/OsPathExt.kt index f05237f..0460a6d 100644 --- a/src/main/kotlin/io/github/kscripting/shell/model/OsPathExt.kt +++ b/src/main/kotlin/io/github/kscripting/shell/model/OsPathExt.kt @@ -18,6 +18,9 @@ fun Path.toOsPath(): OsPath = OsPath.createOrThrow(OsType.native, absolutePathSt fun URI.toOsPath(): OsPath = if (this.scheme == "file") File(this).toOsPath() else throw IllegalArgumentException("Invalid conversion from URL to OsPath") +fun String.toOsPath(osType: OsType = OsType.native): OsPath = + OsPath.createOrThrow(osType, this) + // Conversion from OsPath diff --git a/src/main/kotlin/io/github/kscripting/shell/model/PathType.kt b/src/main/kotlin/io/github/kscripting/shell/model/PathType.kt index ebae207..cd3322f 100644 --- a/src/main/kotlin/io/github/kscripting/shell/model/PathType.kt +++ b/src/main/kotlin/io/github/kscripting/shell/model/PathType.kt @@ -2,5 +2,5 @@ package io.github.kscripting.shell.model enum class PathType { - ABSOLUTE, RELATIVE, UNDEFINED + ABSOLUTE, RELATIVE } diff --git a/src/test/kotlin/io/github/kscripting/shell/model/OsPathTest.kt b/src/test/kotlin/io/github/kscripting/shell/model/OsPathTest.kt index 4dd4938..7a07614 100644 --- a/src/test/kotlin/io/github/kscripting/shell/model/OsPathTest.kt +++ b/src/test/kotlin/io/github/kscripting/shell/model/OsPathTest.kt @@ -27,14 +27,14 @@ class OsPathTest { } assertThat(OsPath.createOrThrow(OsType.LINUX, "")).let { - it.prop(OsPath::pathParts).isEqualTo(emptyList()) - it.prop(OsPath::pathType).isEqualTo(PathType.UNDEFINED) + it.prop(OsPath::pathParts).isEqualTo(listOf(".")) + it.prop(OsPath::pathType).isEqualTo(PathType.RELATIVE) it.prop(OsPath::osType).isEqualTo(OsType.LINUX) } assertThat(OsPath.createOrThrow(OsType.LINUX, "file.txt")).let { - it.prop(OsPath::pathParts).isEqualTo(listOf("file.txt")) - it.prop(OsPath::pathType).isEqualTo(PathType.UNDEFINED) + it.prop(OsPath::pathParts).isEqualTo(listOf(".", "file.txt")) + it.prop(OsPath::pathType).isEqualTo(PathType.RELATIVE) it.prop(OsPath::osType).isEqualTo(OsType.LINUX) } @@ -116,7 +116,7 @@ class OsPathTest { ).isEqualTo("/home/admin/.kscript") assertThat(OsPath.createOrThrow(OsType.LINUX, "/a/b/c/../d/script").stringPath()).isEqualTo("/a/b/d/script") assertThat(OsPath.createOrThrow(OsType.LINUX, "./././../../script").stringPath()).isEqualTo("../../script") - assertThat(OsPath.createOrThrow(OsType.LINUX, "script/file.txt").stringPath()).isEqualTo("script/file.txt") + assertThat(OsPath.createOrThrow(OsType.LINUX, "script/file.txt").stringPath()).isEqualTo("./script/file.txt") } @Test @@ -179,14 +179,14 @@ class OsPathTest { } assertThat(OsPath.createOrThrow(OsType.WINDOWS, "")).let { - it.prop(OsPath::pathParts).isEqualTo(emptyList()) - it.prop(OsPath::pathType).isEqualTo(PathType.UNDEFINED) + it.prop(OsPath::pathParts).isEqualTo(listOf(".")) + it.prop(OsPath::pathType).isEqualTo(PathType.RELATIVE) it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) } assertThat(OsPath.createOrThrow(OsType.WINDOWS, "file.txt")).let { - it.prop(OsPath::pathParts).isEqualTo(listOf("file.txt")) - it.prop(OsPath::pathType).isEqualTo(PathType.UNDEFINED) + it.prop(OsPath::pathParts).isEqualTo(listOf(".", "file.txt")) + it.prop(OsPath::pathType).isEqualTo(PathType.RELATIVE) it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) } @@ -276,7 +276,7 @@ class OsPathTest { assertThat( OsPath.createOrThrow(OsType.WINDOWS, ".\\.\\.\\..\\..\\script").stringPath() ).isEqualTo("..\\..\\script") - assertThat(OsPath.createOrThrow(OsType.WINDOWS, "script\\file.txt").stringPath()).isEqualTo("script\\file.txt") + assertThat(OsPath.createOrThrow(OsType.WINDOWS, "script\\file.txt").stringPath()).isEqualTo(".\\script\\file.txt") } // ****************************************** WINDOWS <-> CYGWIN <-> MSYS ****************************************** @@ -330,7 +330,7 @@ class OsPathTest { @Test fun `Special cases`() { assertThat(OsPath.createOrThrow(OsType.LINUX)).isEqualTo( - OsPath(OsType.LINUX, PathType.UNDEFINED, emptyList(), '/') + OsPath(OsType.LINUX, PathType.RELATIVE, listOf("."), '/') ) } From f1faf5bbc2d057e1bb01c74e77c2c1c661ea417d Mon Sep 17 00:00:00 2001 From: Marcin Kuszczak <1508798+aartiPl@users.noreply.github.com> Date: Thu, 6 Jul 2023 14:40:53 +0200 Subject: [PATCH 20/50] Refactored to contain 'root' value --- .../github/kscripting/shell/model/OsPath.kt | 41 +++++++++++++------ .../kscripting/shell/model/OsPathExt.kt | 2 +- .../kscripting/shell/model/OsPathTest.kt | 8 +++- 3 files changed, 36 insertions(+), 15 deletions(-) diff --git a/src/main/kotlin/io/github/kscripting/shell/model/OsPath.kt b/src/main/kotlin/io/github/kscripting/shell/model/OsPath.kt index dc19069..47e779f 100644 --- a/src/main/kotlin/io/github/kscripting/shell/model/OsPath.kt +++ b/src/main/kotlin/io/github/kscripting/shell/model/OsPath.kt @@ -5,7 +5,12 @@ import arrow.core.left import arrow.core.right //Path representation for different OSes -data class OsPath(val osType: OsType, val pathType: PathType, val pathParts: List, val pathSeparator: Char) { +@Suppress("MemberVisibilityCanBePrivate") +data class OsPath(val osType: OsType, val root: String, val pathType: PathType, val pathParts: List) { + val isRelative: Boolean get() = root.isEmpty() + val isAbsolute: Boolean get() = !isRelative + val pathSeparator: Char get() = if (osType.isWindowsLike()) '\\' else '/' + operator fun div(osPath: OsPath): OsPath { return this.resolve(osPath) } @@ -38,7 +43,7 @@ data class OsPath(val osType: OsType, val pathType: PathType, val pathParts: Lis is Either.Left -> throw IllegalArgumentException(result.value) } - return OsPath(osType, pathType, normalizedPath, pathSeparator) + return OsPath(osType, "", pathType, normalizedPath) } //Not all conversions make sense: only Windows to CygWin and Msys and vice versa @@ -50,7 +55,7 @@ data class OsPath(val osType: OsType, val pathType: PathType, val pathParts: Lis } if ((this.osType.isPosixLike() && targetOsType.isPosixLike()) || (this.osType.isWindowsLike() && targetOsType.isWindowsLike())) { - return OsPath(targetOsType, pathType, pathParts, pathSeparator) + return OsPath(targetOsType, "", pathType, pathParts) } val toPosix = osType.isWindowsLike() && targetOsType.isPosixHostedOnWindows() @@ -101,7 +106,7 @@ data class OsPath(val osType: OsType, val pathType: PathType, val pathParts: Lis else -> throw IllegalArgumentException("Invalid conversion: ${pathType.name} to ${targetOsType.name}") } - return OsPath(targetOsType, pathType, newParts, resolvePathSeparator(targetOsType)) + return OsPath(targetOsType, "", pathType, newParts) } fun stringPath(): String { @@ -132,11 +137,8 @@ data class OsPath(val osType: OsType, val pathType: PathType, val pathParts: Lis private const val alphaChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" - fun resolvePathSeparator(osType: OsType) = if (osType.isPosixLike()) { - '/' - } else { - '\\' - } + private val windowsDriveRegex = "^([a-zA-Z]:(?=\\\\)|\\\\\\\\(?:[^\\*:<>?\\\\\\/|]+\\\\[^\\*:<>?\\\\\\/|]+|\\?\\\\(?:[a-zA-Z]:(?=\\\\)|(?:UNC\\\\)?[^\\*:<>?\\\\\\/|]+\\\\[^\\*:<>?\\\\\\/|]+)))".toRegex() + fun createOrThrow(vararg pathParts: String): OsPath = createOrThrow(OsType.native, pathParts.toList()) @@ -164,9 +166,24 @@ data class OsPath(val osType: OsType, val pathType: PathType, val pathParts: Lis //1. It doesn't matter if there is '/' or '\' used as path separator - both are treated the same //2. Duplicated or trailing slashes '/' and backslashes '\' are just ignored private fun internalCreate(osType: OsType, pathParts: List): Either { - val pathSeparatorCharacter = resolvePathSeparator(osType) + val path = pathParts.joinToString("/") + + //Detect root + val root: String = when { + path.startsWith("~") -> "~" + osType.isPosixLike() && path.startsWith("/") -> "/" + osType.isWindowsLike() -> { + val match = windowsDriveRegex.find(path) + match?.groupValues?.get(1) ?: "" + } + else -> "" + } + + //TODO: https://learn.microsoft.com/pl-pl/dotnet/standard/io/file-path-formats + // https://regex101.com/r/aU4yZ7/1 + println("root: '$root'") - val path = pathParts.joinToString(pathSeparatorCharacter.toString()) + //.drop(root.length) val pathPartsResolved = path.split('/', '\\').toMutableList() //Validate root element of path and find out if it is absolute or relative @@ -233,7 +250,7 @@ data class OsPath(val osType: OsType, val pathType: PathType, val pathParts: Lis is Either.Left -> return result.value.left() } - return OsPath(osType, pathType, normalizedPath, pathSeparatorCharacter).right() + return OsPath(osType, root, pathType, normalizedPath).right() } fun normalize(path: String, pathParts: List, pathType: PathType): Either> { diff --git a/src/main/kotlin/io/github/kscripting/shell/model/OsPathExt.kt b/src/main/kotlin/io/github/kscripting/shell/model/OsPathExt.kt index 0460a6d..920a59b 100644 --- a/src/main/kotlin/io/github/kscripting/shell/model/OsPathExt.kt +++ b/src/main/kotlin/io/github/kscripting/shell/model/OsPathExt.kt @@ -56,7 +56,7 @@ val OsPath.root get(): String? = if (pathParts.isEmpty()) null else pathParts.first() val OsPath.rootOsPath - get():OsPath = if (root == null) OsPath.createOrThrow(osType) else OsPath.createOrThrow(osType, root!!) + get():OsPath = if (isRelative) OsPath.createOrThrow(osType) else OsPath.createOrThrow(osType, root) val OsPath.parent get(): OsPath = OsPath.createOrThrow(osType, pathParts.dropLast(1)) diff --git a/src/test/kotlin/io/github/kscripting/shell/model/OsPathTest.kt b/src/test/kotlin/io/github/kscripting/shell/model/OsPathTest.kt index 7a07614..4cd9bc2 100644 --- a/src/test/kotlin/io/github/kscripting/shell/model/OsPathTest.kt +++ b/src/test/kotlin/io/github/kscripting/shell/model/OsPathTest.kt @@ -330,7 +330,7 @@ class OsPathTest { @Test fun `Special cases`() { assertThat(OsPath.createOrThrow(OsType.LINUX)).isEqualTo( - OsPath(OsType.LINUX, PathType.RELATIVE, listOf("."), '/') + OsPath(OsType.LINUX, "", PathType.RELATIVE, listOf(".")) ) } @@ -341,6 +341,10 @@ class OsPathTest { val p = OsPath.createOrThrow(OsType.MSYS, "/c/home") val p1 = OsPath.createOrThrow(OsType.MSYS, "admin/.kscript") - assertThat(p / p1).isEqualTo(OsPath.createOrThrow(OsType.MSYS, "/c/home/admin/.kscript")) + assertThat(p / p1).let { + it.prop(OsPath::pathParts).isEqualTo(listOf("/", "c", "home", "admin", ".kscript")) + it.prop(OsPath::pathType).isEqualTo(PathType.ABSOLUTE) + it.prop(OsPath::osType).isEqualTo(OsType.MSYS) + } } } From 33a28ec90a953c882098e62099ce1338d718faf5 Mon Sep 17 00:00:00 2001 From: Marcin Kuszczak <1508798+aartiPl@users.noreply.github.com> Date: Thu, 6 Jul 2023 17:16:38 +0200 Subject: [PATCH 21/50] Refactored parsing --- .../github/kscripting/shell/model/OsPath.kt | 139 +++++----------- .../kscripting/shell/model/OsPathTest.kt | 156 +++++++++++------- 2 files changed, 137 insertions(+), 158 deletions(-) diff --git a/src/main/kotlin/io/github/kscripting/shell/model/OsPath.kt b/src/main/kotlin/io/github/kscripting/shell/model/OsPath.kt index 47e779f..5f9c22e 100644 --- a/src/main/kotlin/io/github/kscripting/shell/model/OsPath.kt +++ b/src/main/kotlin/io/github/kscripting/shell/model/OsPath.kt @@ -6,9 +6,10 @@ import arrow.core.right //Path representation for different OSes @Suppress("MemberVisibilityCanBePrivate") -data class OsPath(val osType: OsType, val root: String, val pathType: PathType, val pathParts: List) { +data class OsPath(val osType: OsType, val root: String, val pathParts: List) { val isRelative: Boolean get() = root.isEmpty() val isAbsolute: Boolean get() = !isRelative + val isEmpty: Boolean get() = root.isEmpty() && pathParts.isEmpty() val pathSeparator: Char get() = if (osType.isWindowsLike()) '\\' else '/' operator fun div(osPath: OsPath): OsPath { @@ -28,53 +29,53 @@ data class OsPath(val osType: OsType, val root: String, val pathType: PathType, "Paths from different OS's: '${this.osType.name}' path can not be resolved with '${path.osType.name}' path" } - require(path.pathType != PathType.ABSOLUTE) { - "Can not resolve absolute, relative or undefined path '${stringPath()}' using absolute path '${path.stringPath()}'" + require(path.isRelative) { + "Can not resolve absolute or relative path '${stringPath()}' using absolute path '${path.stringPath()}'" } - val newPath = stringPath() + pathSeparator + path.stringPath() val newPathParts = buildList { addAll(pathParts) addAll(path.pathParts) } - val normalizedPath = when (val result = normalize(newPath, newPathParts, pathType)) { + val normalizedPath = when (val result = normalize(this.root, newPathParts)) { is Either.Right -> result.value is Either.Left -> throw IllegalArgumentException(result.value) } - return OsPath(osType, "", pathType, normalizedPath) + return OsPath(osType, this.root, normalizedPath) } //Not all conversions make sense: only Windows to CygWin and Msys and vice versa //TODO: conversion of paths like /usr /opt etc. is wrong; it needs also windows root of installation cygwin/msys // base path: cygpath -w / - fun convert(targetOsType: OsType): OsPath { + fun convert(targetOsType: OsType /*nativeRootPath: OsPath = emptyOsPath*/): OsPath { if (this.osType == targetOsType) { return this } if ((this.osType.isPosixLike() && targetOsType.isPosixLike()) || (this.osType.isWindowsLike() && targetOsType.isWindowsLike())) { - return OsPath(targetOsType, "", pathType, pathParts) + return OsPath(targetOsType, this.root, pathParts) } - val toPosix = osType.isWindowsLike() && targetOsType.isPosixHostedOnWindows() - val fromPosix = osType.isPosixHostedOnWindows() && targetOsType.isWindowsLike() + val toHosted = osType.isWindowsLike() && targetOsType.isPosixHostedOnWindows() + val toNative = osType.isPosixHostedOnWindows() && targetOsType.isWindowsLike() - require(toPosix || fromPosix) { + require(toHosted || toNative) { "Only conversion between Windows and Posix hosted on Windows paths are supported" } val newParts = mutableListOf() + var newRoot = "" when { - toPosix -> { + toHosted -> { val drive: String - if (pathType == PathType.ABSOLUTE) { - drive = pathParts[0][0].lowercase() + if (isAbsolute) { + drive = root.dropLast(2).lowercase() - newParts.add("/") + newRoot = "/" if (targetOsType == OsType.CYGWIN) { newParts.add("cygdrive") @@ -82,40 +83,33 @@ data class OsPath(val osType: OsType, val root: String, val pathType: PathType, } else { newParts.add(drive) } - - newParts.addAll(pathParts.subList(1, pathParts.size)) - } else { - newParts.addAll(pathParts) } + + newParts.addAll(pathParts) } - fromPosix -> { - if (pathType == PathType.ABSOLUTE) { + toNative -> { + if (isAbsolute) { if (osType == OsType.CYGWIN) { - newParts.add(pathParts[2] + ":") - newParts.addAll(pathParts.subList(3, pathParts.size)) - } else { - newParts.add(pathParts[1] + ":") + newRoot = pathParts[1] + ":\\" newParts.addAll(pathParts.subList(2, pathParts.size)) + } else { + newRoot = pathParts[0] + ":\\" + newParts.addAll(pathParts.subList(1, pathParts.size)) } } else { newParts.addAll(pathParts) } } - else -> throw IllegalArgumentException("Invalid conversion: ${pathType.name} to ${targetOsType.name}") + else -> throw IllegalArgumentException("Invalid conversion: $this to ${targetOsType.name}") } - return OsPath(targetOsType, "", pathType, newParts) + return OsPath(targetOsType, newRoot, newParts) } - fun stringPath(): String { - if (osType.isPosixLike() && pathParts.isNotEmpty() && pathParts[0] == "/") { - return "/" + pathParts.subList(1, pathParts.size).joinToString(pathSeparator.toString()) { it } - } + fun stringPath(): String = root + pathParts.joinToString(pathSeparator.toString()) { it } - return pathParts.joinToString(pathSeparator.toString()) { it } - } override fun toString(): String = stringPath() @@ -137,7 +131,8 @@ data class OsPath(val osType: OsType, val root: String, val pathType: PathType, private const val alphaChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" - private val windowsDriveRegex = "^([a-zA-Z]:(?=\\\\)|\\\\\\\\(?:[^\\*:<>?\\\\\\/|]+\\\\[^\\*:<>?\\\\\\/|]+|\\?\\\\(?:[a-zA-Z]:(?=\\\\)|(?:UNC\\\\)?[^\\*:<>?\\\\\\/|]+\\\\[^\\*:<>?\\\\\\/|]+)))".toRegex() + private val windowsDriveRegex = + "^([a-zA-Z]:(?=[\\\\/])|\\\\\\\\(?:[^*:<>?\\\\/|]+\\\\[^*:<>?\\\\/|]+|\\?\\\\(?:[a-zA-Z]:(?=\\\\)|(?:UNC\\\\)?[^*:<>?\\\\/|]+\\\\[^*:<>?\\\\/|]+)))".toRegex() fun createOrThrow(vararg pathParts: String): OsPath = createOrThrow(OsType.native, pathParts.toList()) @@ -174,8 +169,10 @@ data class OsPath(val osType: OsType, val root: String, val pathType: PathType, osType.isPosixLike() && path.startsWith("/") -> "/" osType.isWindowsLike() -> { val match = windowsDriveRegex.find(path) - match?.groupValues?.get(1) ?: "" + val matchedRoot = match?.groupValues?.get(1) + if (matchedRoot != null) matchedRoot + "\\" else "" } + else -> "" } @@ -183,77 +180,25 @@ data class OsPath(val osType: OsType, val root: String, val pathType: PathType, // https://regex101.com/r/aU4yZ7/1 println("root: '$root'") - //.drop(root.length) - val pathPartsResolved = path.split('/', '\\').toMutableList() + //Remove empty path parts - there were duplicated or trailing slashes / backslashes in initial path + val pathPartsResolved = path.drop(root.length).split('/', '\\').filter { it.isNotBlank() } //Validate root element of path and find out if it is absolute or relative - val rootElementSizeInInputPath: Int - val pathType: PathType - - var isUndefined = false - - when { - pathPartsResolved.isEmpty() -> { - pathType = PathType.RELATIVE - isUndefined = true - rootElementSizeInInputPath = 0 - } - - pathPartsResolved[0] == "~" -> { - pathType = PathType.ABSOLUTE - rootElementSizeInInputPath = 1 - } - - pathPartsResolved[0] == ".." || pathPartsResolved[0] == "." -> { - pathType = PathType.RELATIVE - rootElementSizeInInputPath = pathPartsResolved[0].length - } - - osType.isPosixLike() && path.startsWith("/") -> { - //After split first element is empty for absolute paths on Linux; assigning correct value below - pathPartsResolved.add(0, "/") - pathType = PathType.ABSOLUTE - rootElementSizeInInputPath = 1 - } - - osType.isWindowsLike() && pathPartsResolved[0].length == 2 && pathPartsResolved[0][1] == ':' && alphaChars.contains( - pathPartsResolved[0][0] - ) -> { - pathType = PathType.ABSOLUTE - rootElementSizeInInputPath = 2 - } - - else -> { - //This is undefined path - pathType = PathType.RELATIVE - isUndefined = true - rootElementSizeInInputPath = 0 - } - } - - val forbiddenCharacter = - path.substring(rootElementSizeInInputPath).find { forbiddenCharacters.contains(it) } + val forbiddenCharacter = path.substring(root.length).find { forbiddenCharacters.contains(it) } if (forbiddenCharacter != null) { return "Invalid character '$forbiddenCharacter' in path '$path'".left() } - if (isUndefined) { - pathPartsResolved.add(0, ".") - } - - //Remove empty path parts - there were duplicated or trailing slashes / backslashes in initial path - pathPartsResolved.removeAll { it.isEmpty() } - - val normalizedPath = when (val result = normalize(path, pathPartsResolved, pathType)) { + val normalizedPath = when (val result = normalize(root, pathPartsResolved)) { is Either.Right -> result.value is Either.Left -> return result.value.left() } - return OsPath(osType, root, pathType, normalizedPath).right() + return OsPath(osType, root, normalizedPath).right() } - fun normalize(path: String, pathParts: List, pathType: PathType): Either> { + fun normalize(root: String, pathParts: List): Either> { //Relative: // ./../ --> ../ // ./a/../ --> ./ @@ -265,10 +210,10 @@ data class OsPath(val osType: OsType, val root: String, val pathType: PathType, // /../ --> invalid (above root) // /a/../ --> / - val newParts = mutableListOf() - var index = 1 + val pathType: PathType = if (root.isNotEmpty()) PathType.ABSOLUTE else PathType.RELATIVE - newParts.add(pathParts[0]) + val newParts = mutableListOf() + var index = 0 while (index < pathParts.size) { if (pathParts[index] == ".") { @@ -276,7 +221,7 @@ data class OsPath(val osType: OsType, val root: String, val pathType: PathType, } else if (pathParts[index] == "..") { if (pathType == PathType.ABSOLUTE && newParts.size == 1) { - return "Path after normalization goes beyond root element: '$path'".left() + return "Path after normalization goes beyond root element: '$root'".left() } if (newParts.size > 0) { diff --git a/src/test/kotlin/io/github/kscripting/shell/model/OsPathTest.kt b/src/test/kotlin/io/github/kscripting/shell/model/OsPathTest.kt index 4cd9bc2..46672c4 100644 --- a/src/test/kotlin/io/github/kscripting/shell/model/OsPathTest.kt +++ b/src/test/kotlin/io/github/kscripting/shell/model/OsPathTest.kt @@ -9,71 +9,83 @@ class OsPathTest { @Test fun `Test Linux paths`() { assertThat(OsPath.createOrThrow(OsType.LINUX, "/")).let { - it.prop(OsPath::pathParts).isEqualTo(listOf("/")) - it.prop(OsPath::pathType).isEqualTo(PathType.ABSOLUTE) + it.prop(OsPath::root).isEqualTo("/") + it.prop(OsPath::pathParts).isEqualTo(emptyList()) + it.prop(OsPath::isAbsolute).isTrue() + it.prop(OsPath::isEmpty).isFalse() it.prop(OsPath::osType).isEqualTo(OsType.LINUX) } assertThat(OsPath.createOrThrow(OsType.LINUX, "/home/admin/.kscript")).let { - it.prop(OsPath::pathParts).isEqualTo(listOf("/", "home", "admin", ".kscript")) - it.prop(OsPath::pathType).isEqualTo(PathType.ABSOLUTE) + it.prop(OsPath::root).isEqualTo("/") + it.prop(OsPath::pathParts).isEqualTo(listOf("home", "admin", ".kscript")) + it.prop(OsPath::isAbsolute).isTrue() it.prop(OsPath::osType).isEqualTo(OsType.LINUX) } assertThat(OsPath.createOrThrow(OsType.LINUX, "./home/admin/.kscript")).let { - it.prop(OsPath::pathParts).isEqualTo(listOf(".", "home", "admin", ".kscript")) - it.prop(OsPath::pathType).isEqualTo(PathType.RELATIVE) + it.prop(OsPath::root).isEqualTo("") + it.prop(OsPath::pathParts).isEqualTo(listOf("home", "admin", ".kscript")) + it.prop(OsPath::isRelative).isTrue() it.prop(OsPath::osType).isEqualTo(OsType.LINUX) } assertThat(OsPath.createOrThrow(OsType.LINUX, "")).let { - it.prop(OsPath::pathParts).isEqualTo(listOf(".")) - it.prop(OsPath::pathType).isEqualTo(PathType.RELATIVE) + it.prop(OsPath::root).isEqualTo("") + it.prop(OsPath::pathParts).isEqualTo(emptyList()) + it.prop(OsPath::isRelative).isTrue() it.prop(OsPath::osType).isEqualTo(OsType.LINUX) } assertThat(OsPath.createOrThrow(OsType.LINUX, "file.txt")).let { - it.prop(OsPath::pathParts).isEqualTo(listOf(".", "file.txt")) - it.prop(OsPath::pathType).isEqualTo(PathType.RELATIVE) + it.prop(OsPath::root).isEqualTo("") + it.prop(OsPath::pathParts).isEqualTo(listOf("file.txt")) + it.prop(OsPath::isRelative).isTrue() it.prop(OsPath::osType).isEqualTo(OsType.LINUX) } assertThat(OsPath.createOrThrow(OsType.LINUX, ".")).let { - it.prop(OsPath::pathParts).isEqualTo(listOf(".")) - it.prop(OsPath::pathType).isEqualTo(PathType.RELATIVE) + it.prop(OsPath::root).isEqualTo("") + it.prop(OsPath::pathParts).isEqualTo(emptyList()) + it.prop(OsPath::isRelative).isTrue() it.prop(OsPath::osType).isEqualTo(OsType.LINUX) } assertThat(OsPath.createOrThrow(OsType.LINUX, "../home/admin/.kscript")).let { + it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(listOf("..", "home", "admin", ".kscript")) - it.prop(OsPath::pathType).isEqualTo(PathType.RELATIVE) + it.prop(OsPath::isRelative).isTrue() it.prop(OsPath::osType).isEqualTo(OsType.LINUX) } assertThat(OsPath.createOrThrow(OsType.LINUX, "..")).let { + it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(listOf("..")) - it.prop(OsPath::pathType).isEqualTo(PathType.RELATIVE) + it.prop(OsPath::isRelative).isTrue() it.prop(OsPath::osType).isEqualTo(OsType.LINUX) } //Duplicated separators are accepted assertThat(OsPath.createOrThrow(OsType.LINUX, "..//home////admin/.kscript/")).let { + it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(listOf("..", "home", "admin", ".kscript")) - it.prop(OsPath::pathType).isEqualTo(PathType.RELATIVE) + it.prop(OsPath::isRelative).isTrue() it.prop(OsPath::osType).isEqualTo(OsType.LINUX) } //Both types of separator are accepted assertThat(OsPath.createOrThrow(OsType.LINUX, "..//home\\admin\\.kscript/")).let { + it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(listOf("..", "home", "admin", ".kscript")) - it.prop(OsPath::pathType).isEqualTo(PathType.RELATIVE) + it.prop(OsPath::isRelative).isTrue() it.prop(OsPath::osType).isEqualTo(OsType.LINUX) } //Home dir is correctly handled assertThat(OsPath.createOrThrow(OsType.LINUX, "~/admin/.git")).let { - it.prop(OsPath::pathParts).isEqualTo(listOf("~", "admin", ".git")) - it.prop(OsPath::pathType).isEqualTo(PathType.ABSOLUTE) + it.prop(OsPath::root).isEqualTo("~") + it.prop(OsPath::pathParts).isEqualTo(listOf("admin", ".git")) + it.prop(OsPath::isAbsolute).isTrue() it.prop(OsPath::osType).isEqualTo(OsType.LINUX) } } @@ -81,26 +93,28 @@ class OsPathTest { @Test fun `Normalization of Linux paths`() { assertThat(OsPath.createOrThrow(OsType.LINUX, "/home/admin/.kscript/../../")).let { - it.prop(OsPath::pathParts).isEqualTo(listOf("/", "home")) - it.prop(OsPath::pathType).isEqualTo(PathType.ABSOLUTE) + it.prop(OsPath::root).isEqualTo("/") + it.prop(OsPath::pathParts).isEqualTo(listOf("home")) + it.prop(OsPath::isAbsolute).isTrue() it.prop(OsPath::osType).isEqualTo(OsType.LINUX) } assertThat(OsPath.createOrThrow(OsType.LINUX, "./././../../script")).let { it.prop(OsPath::pathParts).isEqualTo(listOf("..", "..", "script")) - it.prop(OsPath::pathType).isEqualTo(PathType.RELATIVE) + it.prop(OsPath::isRelative).isTrue() it.prop(OsPath::osType).isEqualTo(OsType.LINUX) } assertThat(OsPath.createOrThrow(OsType.LINUX, "/a/b/c/../d/script")).let { - it.prop(OsPath::pathParts).isEqualTo(listOf("/", "a", "b", "d", "script")) - it.prop(OsPath::pathType).isEqualTo(PathType.ABSOLUTE) + it.prop(OsPath::root).isEqualTo("/") + it.prop(OsPath::pathParts).isEqualTo(listOf("a", "b", "d", "script")) + it.prop(OsPath::isAbsolute).isTrue() it.prop(OsPath::osType).isEqualTo(OsType.LINUX) } assertThat { OsPath.createOrThrow(OsType.LINUX, "/.kscript/../../") }.isFailure() .isInstanceOf(IllegalArgumentException::class.java) - .hasMessage("Path after normalization goes beyond root element: '/.kscript/../../'") + .hasMessage("Path after normalization goes beyond root element: '/'") } @Test @@ -111,12 +125,11 @@ class OsPathTest { @Test fun `Test Linux stringPath`() { - assertThat( - OsPath.createOrThrow(OsType.LINUX, "/home/admin/.kscript").stringPath() - ).isEqualTo("/home/admin/.kscript") + assertThat(OsPath.createOrThrow(OsType.LINUX, "/home/admin/.kscript").stringPath()) + .isEqualTo("/home/admin/.kscript") assertThat(OsPath.createOrThrow(OsType.LINUX, "/a/b/c/../d/script").stringPath()).isEqualTo("/a/b/d/script") assertThat(OsPath.createOrThrow(OsType.LINUX, "./././../../script").stringPath()).isEqualTo("../../script") - assertThat(OsPath.createOrThrow(OsType.LINUX, "script/file.txt").stringPath()).isEqualTo("./script/file.txt") + assertThat(OsPath.createOrThrow(OsType.LINUX, "script/file.txt").stringPath()).isEqualTo("script/file.txt") } @Test @@ -134,7 +147,7 @@ class OsPathTest { assertThat( OsPath.createOrThrow(OsType.LINUX, "./home/admin/") .resolve(OsPath.createOrThrow(OsType.LINUX, "./.kscript/")).stringPath() - ).isEqualTo("./home/admin/.kscript") + ).isEqualTo("home/admin/.kscript") assertThat( OsPath.createOrThrow(OsType.LINUX, "../home/admin/") @@ -149,7 +162,7 @@ class OsPathTest { assertThat( OsPath.createOrThrow(OsType.LINUX, ".").resolve(OsPath.createOrThrow(OsType.LINUX, "./.kscript/")) .stringPath() - ).isEqualTo("./.kscript") + ).isEqualTo(".kscript") assertThat { OsPath.createOrThrow(OsType.LINUX, "./home/admin").resolve(OsPath.createOrThrow(OsType.WINDOWS, ".\\run")) @@ -159,7 +172,7 @@ class OsPathTest { assertThat { OsPath.createOrThrow(OsType.LINUX, "./home/admin").resolve(OsPath.createOrThrow(OsType.LINUX, "/run")) }.isFailure().isInstanceOf(IllegalArgumentException::class.java) - .hasMessage("Can not resolve absolute, relative or undefined path './home/admin' using absolute path '/run'") + .hasMessage("Can not resolve absolute or relative path 'home/admin' using absolute path '/run'") } // ************************************************* WINDOWS PATHS ************************************************* @@ -167,64 +180,74 @@ class OsPathTest { @Test fun `Test Windows paths`() { assertThat(OsPath.createOrThrow(OsType.WINDOWS, "C:\\")).let { - it.prop(OsPath::pathParts).isEqualTo(listOf("C:")) - it.prop(OsPath::pathType).isEqualTo(PathType.ABSOLUTE) + it.prop(OsPath::root).isEqualTo("C:\\") + it.prop(OsPath::pathParts).isEqualTo(emptyList()) + it.prop(OsPath::isAbsolute).isTrue() it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) } assertThat(OsPath.createOrThrow(OsType.WINDOWS, "C:\\home\\admin\\.kscript")).let { - it.prop(OsPath::pathParts).isEqualTo(listOf("C:", "home", "admin", ".kscript")) - it.prop(OsPath::pathType).isEqualTo(PathType.ABSOLUTE) + it.prop(OsPath::root).isEqualTo("C:\\") + it.prop(OsPath::pathParts).isEqualTo(listOf("home", "admin", ".kscript")) + it.prop(OsPath::isAbsolute).isTrue() it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) } assertThat(OsPath.createOrThrow(OsType.WINDOWS, "")).let { - it.prop(OsPath::pathParts).isEqualTo(listOf(".")) - it.prop(OsPath::pathType).isEqualTo(PathType.RELATIVE) + it.prop(OsPath::root).isEqualTo("") + it.prop(OsPath::pathParts).isEqualTo(emptyList()) + it.prop(OsPath::isRelative).isTrue() it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) } assertThat(OsPath.createOrThrow(OsType.WINDOWS, "file.txt")).let { - it.prop(OsPath::pathParts).isEqualTo(listOf(".", "file.txt")) - it.prop(OsPath::pathType).isEqualTo(PathType.RELATIVE) + it.prop(OsPath::root).isEqualTo("") + it.prop(OsPath::pathParts).isEqualTo(listOf("file.txt")) + it.prop(OsPath::isRelative).isTrue() it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) } assertThat(OsPath.createOrThrow(OsType.WINDOWS, ".")).let { - it.prop(OsPath::pathParts).isEqualTo(listOf(".")) - it.prop(OsPath::pathType).isEqualTo(PathType.RELATIVE) + it.prop(OsPath::root).isEqualTo("") + it.prop(OsPath::pathParts).isEqualTo(emptyList()) + it.prop(OsPath::isRelative).isTrue() it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) } assertThat(OsPath.createOrThrow(OsType.WINDOWS, ".\\home\\admin\\.kscript")).let { - it.prop(OsPath::pathParts).isEqualTo(listOf(".", "home", "admin", ".kscript")) - it.prop(OsPath::pathType).isEqualTo(PathType.RELATIVE) + it.prop(OsPath::root).isEqualTo("") + it.prop(OsPath::pathParts).isEqualTo(listOf("home", "admin", ".kscript")) + it.prop(OsPath::isRelative).isTrue() it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) } assertThat(OsPath.createOrThrow(OsType.WINDOWS, "..\\home\\admin\\.kscript")).let { + it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(listOf("..", "home", "admin", ".kscript")) - it.prop(OsPath::pathType).isEqualTo(PathType.RELATIVE) + it.prop(OsPath::isRelative).isTrue() it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) } assertThat(OsPath.createOrThrow(OsType.WINDOWS, "..")).let { + it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(listOf("..")) - it.prop(OsPath::pathType).isEqualTo(PathType.RELATIVE) + it.prop(OsPath::isRelative).isTrue() it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) } //Duplicated separators are accepted assertThat(OsPath.createOrThrow(OsType.WINDOWS, "C:\\home\\\\\\\\admin\\.kscript\\")).let { - it.prop(OsPath::pathParts).isEqualTo(listOf("C:", "home", "admin", ".kscript")) - it.prop(OsPath::pathType).isEqualTo(PathType.ABSOLUTE) + it.prop(OsPath::root).isEqualTo("C:\\") + it.prop(OsPath::pathParts).isEqualTo(listOf("home", "admin", ".kscript")) + it.prop(OsPath::isAbsolute).isTrue() it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) } //Both types of separator are accepted assertThat(OsPath.createOrThrow(OsType.WINDOWS, "C:/home\\admin/.kscript////")).let { - it.prop(OsPath::pathParts).isEqualTo(listOf("C:", "home", "admin", ".kscript")) - it.prop(OsPath::pathType).isEqualTo(PathType.ABSOLUTE) + it.prop(OsPath::root).isEqualTo("C:\\") + it.prop(OsPath::pathParts).isEqualTo(listOf("home", "admin", ".kscript")) + it.prop(OsPath::isAbsolute).isTrue() it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) } } @@ -232,26 +255,29 @@ class OsPathTest { @Test fun `Normalization of Windows paths`() { assertThat(OsPath.createOrThrow(OsType.WINDOWS, "C:\\home\\admin\\.kscript\\..\\..\\")).let { - it.prop(OsPath::pathParts).isEqualTo(listOf("C:", "home")) - it.prop(OsPath::pathType).isEqualTo(PathType.ABSOLUTE) + it.prop(OsPath::root).isEqualTo("C:\\") + it.prop(OsPath::pathParts).isEqualTo(listOf("home")) + it.prop(OsPath::isAbsolute).isTrue() it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) } assertThat(OsPath.createOrThrow(OsType.WINDOWS, ".\\.\\.\\..\\..\\script")).let { + it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(listOf("..", "..", "script")) - it.prop(OsPath::pathType).isEqualTo(PathType.RELATIVE) + it.prop(OsPath::isRelative).isTrue() it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) } assertThat(OsPath.createOrThrow(OsType.WINDOWS, "C:\\a\\b\\c\\..\\d\\script")).let { - it.prop(OsPath::pathParts).isEqualTo(listOf("C:", "a", "b", "d", "script")) - it.prop(OsPath::pathType).isEqualTo(PathType.ABSOLUTE) + it.prop(OsPath::root).isEqualTo("C:\\") + it.prop(OsPath::pathParts).isEqualTo(listOf("a", "b", "d", "script")) + it.prop(OsPath::isAbsolute).isTrue() it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) } assertThat { OsPath.createOrThrow(OsType.WINDOWS, "C:\\.kscript\\..\\..\\") }.isFailure() .isInstanceOf(IllegalArgumentException::class.java) - .hasMessage("Path after normalization goes beyond root element: 'C:\\.kscript\\..\\..\\'") + .hasMessage("Path after normalization goes beyond root element: 'C:\\'") } @Test @@ -276,7 +302,9 @@ class OsPathTest { assertThat( OsPath.createOrThrow(OsType.WINDOWS, ".\\.\\.\\..\\..\\script").stringPath() ).isEqualTo("..\\..\\script") - assertThat(OsPath.createOrThrow(OsType.WINDOWS, "script\\file.txt").stringPath()).isEqualTo(".\\script\\file.txt") + assertThat( + OsPath.createOrThrow(OsType.WINDOWS, "script\\file.txt").stringPath() + ).isEqualTo("script\\file.txt") } // ****************************************** WINDOWS <-> CYGWIN <-> MSYS ****************************************** @@ -329,9 +357,14 @@ class OsPathTest { @Test fun `Special cases`() { - assertThat(OsPath.createOrThrow(OsType.LINUX)).isEqualTo( - OsPath(OsType.LINUX, "", PathType.RELATIVE, listOf(".")) - ) + //Empty path + assertThat(OsPath.createOrThrow(OsType.LINUX)).let { + it.prop(OsPath::root).isEqualTo("") + it.prop(OsPath::pathParts).isEqualTo(emptyList()) + it.prop(OsPath::isAbsolute).isFalse() + it.prop(OsPath::isEmpty).isTrue() + it.prop(OsPath::osType).isEqualTo(OsType.LINUX) + } } // ************************************* Shorthand for creating composite paths ************************************ @@ -342,8 +375,9 @@ class OsPathTest { val p1 = OsPath.createOrThrow(OsType.MSYS, "admin/.kscript") assertThat(p / p1).let { - it.prop(OsPath::pathParts).isEqualTo(listOf("/", "c", "home", "admin", ".kscript")) - it.prop(OsPath::pathType).isEqualTo(PathType.ABSOLUTE) + it.prop(OsPath::root).isEqualTo("/") + it.prop(OsPath::pathParts).isEqualTo(listOf("c", "home", "admin", ".kscript")) + it.prop(OsPath::isAbsolute).isTrue() it.prop(OsPath::osType).isEqualTo(OsType.MSYS) } } From b53aff9cdc6fb2f7297e81ef88e5d66cd0d60fbc Mon Sep 17 00:00:00 2001 From: Marcin Kuszczak <1508798+aartiPl@users.noreply.github.com> Date: Thu, 6 Jul 2023 17:37:05 +0200 Subject: [PATCH 22/50] Removed PathType --- .../io/github/kscripting/shell/model/OsPath.kt | 18 +++++++++++------- .../io/github/kscripting/shell/model/OsType.kt | 2 +- .../github/kscripting/shell/model/PathType.kt | 6 ------ 3 files changed, 12 insertions(+), 14 deletions(-) delete mode 100644 src/main/kotlin/io/github/kscripting/shell/model/PathType.kt diff --git a/src/main/kotlin/io/github/kscripting/shell/model/OsPath.kt b/src/main/kotlin/io/github/kscripting/shell/model/OsPath.kt index 5f9c22e..3e30503 100644 --- a/src/main/kotlin/io/github/kscripting/shell/model/OsPath.kt +++ b/src/main/kotlin/io/github/kscripting/shell/model/OsPath.kt @@ -25,6 +25,10 @@ data class OsPath(val osType: OsType, val root: String, val pathParts: List() @@ -114,8 +118,11 @@ data class OsPath(val osType: OsType, val root: String, val pathParts: List') @@ -129,8 +136,6 @@ data class OsPath(val osType: OsType, val root: String, val pathParts: List "~" + path.startsWith("~/") || path.startsWith("~\\") -> "~" osType.isPosixLike() && path.startsWith("/") -> "/" osType.isWindowsLike() -> { val match = windowsDriveRegex.find(path) @@ -178,7 +183,6 @@ data class OsPath(val osType: OsType, val root: String, val pathParts: List invalid (above root) // /a/../ --> / - val pathType: PathType = if (root.isNotEmpty()) PathType.ABSOLUTE else PathType.RELATIVE + val isAbsolute: Boolean = root.isNotEmpty() val newParts = mutableListOf() var index = 0 @@ -220,7 +224,7 @@ data class OsPath(val osType: OsType, val root: String, val pathParts: List Date: Thu, 6 Jul 2023 20:38:54 +0200 Subject: [PATCH 23/50] Update --- .../github/kscripting/shell/model/OsPath.kt | 41 +++++++------ .../kscripting/shell/model/OsPathExt.kt | 11 ++-- .../kscripting/shell/model/OsPathTest.kt | 60 +++++++++---------- 3 files changed, 56 insertions(+), 56 deletions(-) diff --git a/src/main/kotlin/io/github/kscripting/shell/model/OsPath.kt b/src/main/kotlin/io/github/kscripting/shell/model/OsPath.kt index 3e30503..3586760 100644 --- a/src/main/kotlin/io/github/kscripting/shell/model/OsPath.kt +++ b/src/main/kotlin/io/github/kscripting/shell/model/OsPath.kt @@ -7,10 +7,11 @@ import arrow.core.right //Path representation for different OSes @Suppress("MemberVisibilityCanBePrivate") data class OsPath(val osType: OsType, val root: String, val pathParts: List) { + val isEmpty: Boolean get() = this === emptyPath val isRelative: Boolean get() = root.isEmpty() val isAbsolute: Boolean get() = !isRelative - val isEmpty: Boolean get() = root.isEmpty() && pathParts.isEmpty() val pathSeparator: Char get() = if (osType.isWindowsLike()) '\\' else '/' + val path get(): String = root + pathParts.joinToString(pathSeparator.toString()) { it } operator fun div(osPath: OsPath): OsPath { return this.resolve(osPath) @@ -24,22 +25,22 @@ data class OsPath(val osType: OsType, val root: String, val pathParts: List') @@ -184,9 +183,18 @@ data class OsPath(val osType: OsType, val root: String, val pathParts: List result.value + is Either.Left -> return result.value.left() + } + + if (root.isEmpty() && normalizedPath.isEmpty()) { + return emptyPath.right() + } + //Validate root element of path and find out if it is absolute or relative val forbiddenCharacter = path.substring(root.length).find { forbiddenCharacters.contains(it) } @@ -194,11 +202,6 @@ data class OsPath(val osType: OsType, val root: String, val pathParts: List result.value - is Either.Left -> return result.value.left() - } - return OsPath(osType, root, normalizedPath).right() } diff --git a/src/main/kotlin/io/github/kscripting/shell/model/OsPathExt.kt b/src/main/kotlin/io/github/kscripting/shell/model/OsPathExt.kt index 920a59b..a8d55d7 100644 --- a/src/main/kotlin/io/github/kscripting/shell/model/OsPathExt.kt +++ b/src/main/kotlin/io/github/kscripting/shell/model/OsPathExt.kt @@ -24,7 +24,7 @@ fun String.toOsPath(osType: OsType = OsType.native): OsPath = // Conversion from OsPath -fun OsPath.toNativePath(): Path = Paths.get(toNativeOsPath().stringPath()) +fun OsPath.toNativePath(): Path = Paths.get(toNativeOsPath().path) fun OsPath.toNativeFile(): File = toNativePath().toFile() @@ -50,13 +50,10 @@ fun OsPath.readText(charset: Charset = Charsets.UTF_8): String = toNativePath(). // OsPath accessors val OsPath.leaf - get(): String = if (pathParts.isEmpty()) "" else pathParts.last() - -val OsPath.root - get(): String? = if (pathParts.isEmpty()) null else pathParts.first() + get(): String = if (pathParts.isEmpty()) root else pathParts.last() val OsPath.rootOsPath - get():OsPath = if (isRelative) OsPath.createOrThrow(osType) else OsPath.createOrThrow(osType, root) + get():OsPath = if (isRelative) OsPath.emptyPath else OsPath.createOrThrow(osType, root) val OsPath.parent get(): OsPath = OsPath.createOrThrow(osType, pathParts.dropLast(1)) @@ -65,4 +62,4 @@ val OsPath.nativeType get(): OsType = if (osType.isPosixHostedOnWindows()) OsType.WINDOWS else osType val OsPath.extension - get(): String? = leaf?.substringAfterLast('.', "") + get(): String? = leaf.substringAfterLast('.', "") diff --git a/src/test/kotlin/io/github/kscripting/shell/model/OsPathTest.kt b/src/test/kotlin/io/github/kscripting/shell/model/OsPathTest.kt index 46672c4..bb6fb1c 100644 --- a/src/test/kotlin/io/github/kscripting/shell/model/OsPathTest.kt +++ b/src/test/kotlin/io/github/kscripting/shell/model/OsPathTest.kt @@ -12,7 +12,7 @@ class OsPathTest { it.prop(OsPath::root).isEqualTo("/") it.prop(OsPath::pathParts).isEqualTo(emptyList()) it.prop(OsPath::isAbsolute).isTrue() - it.prop(OsPath::isEmpty).isFalse() + it.prop(OsPath::isRelative).isFalse() it.prop(OsPath::osType).isEqualTo(OsType.LINUX) } @@ -34,7 +34,8 @@ class OsPathTest { it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(emptyList()) it.prop(OsPath::isRelative).isTrue() - it.prop(OsPath::osType).isEqualTo(OsType.LINUX) + it.prop(OsPath::isAbsolute).isFalse() + it.prop(OsPath::osType).isEqualTo(OsType.ANY) } assertThat(OsPath.createOrThrow(OsType.LINUX, "file.txt")).let { @@ -48,7 +49,7 @@ class OsPathTest { it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(emptyList()) it.prop(OsPath::isRelative).isTrue() - it.prop(OsPath::osType).isEqualTo(OsType.LINUX) + it.prop(OsPath::osType).isEqualTo(OsType.ANY) } assertThat(OsPath.createOrThrow(OsType.LINUX, "../home/admin/.kscript")).let { @@ -125,43 +126,42 @@ class OsPathTest { @Test fun `Test Linux stringPath`() { - assertThat(OsPath.createOrThrow(OsType.LINUX, "/home/admin/.kscript").stringPath()) + assertThat(OsPath.createOrThrow(OsType.LINUX, "/home/admin/.kscript").path) .isEqualTo("/home/admin/.kscript") - assertThat(OsPath.createOrThrow(OsType.LINUX, "/a/b/c/../d/script").stringPath()).isEqualTo("/a/b/d/script") - assertThat(OsPath.createOrThrow(OsType.LINUX, "./././../../script").stringPath()).isEqualTo("../../script") - assertThat(OsPath.createOrThrow(OsType.LINUX, "script/file.txt").stringPath()).isEqualTo("script/file.txt") + assertThat(OsPath.createOrThrow(OsType.LINUX, "/a/b/c/../d/script").path).isEqualTo("/a/b/d/script") + assertThat(OsPath.createOrThrow(OsType.LINUX, "./././../../script").path).isEqualTo("../../script") + assertThat(OsPath.createOrThrow(OsType.LINUX, "script/file.txt").path).isEqualTo("script/file.txt") } @Test fun `Test Linux resolve`() { assertThat( OsPath.createOrThrow(OsType.LINUX, "/").resolve(OsPath.createOrThrow(OsType.LINUX, "./.kscript/")) - .stringPath() + .path ).isEqualTo("/.kscript") assertThat( OsPath.createOrThrow(OsType.LINUX, "/home/admin/") - .resolve(OsPath.createOrThrow(OsType.LINUX, "./.kscript/")).stringPath() + .resolve(OsPath.createOrThrow(OsType.LINUX, "./.kscript/")).path ).isEqualTo("/home/admin/.kscript") assertThat( OsPath.createOrThrow(OsType.LINUX, "./home/admin/") - .resolve(OsPath.createOrThrow(OsType.LINUX, "./.kscript/")).stringPath() + .resolve(OsPath.createOrThrow(OsType.LINUX, "./.kscript/")).path ).isEqualTo("home/admin/.kscript") assertThat( OsPath.createOrThrow(OsType.LINUX, "../home/admin/") - .resolve(OsPath.createOrThrow(OsType.LINUX, "./.kscript/")).stringPath() + .resolve(OsPath.createOrThrow(OsType.LINUX, "./.kscript/")).path ).isEqualTo("../home/admin/.kscript") assertThat( OsPath.createOrThrow(OsType.LINUX, "..").resolve(OsPath.createOrThrow(OsType.LINUX, "./.kscript/")) - .stringPath() + .path ).isEqualTo("../.kscript") assertThat( - OsPath.createOrThrow(OsType.LINUX, ".").resolve(OsPath.createOrThrow(OsType.LINUX, "./.kscript/")) - .stringPath() + OsPath.createOrThrow(OsType.LINUX, ".").resolve(OsPath.createOrThrow(OsType.LINUX, "./.kscript/")).path ).isEqualTo(".kscript") assertThat { @@ -197,7 +197,7 @@ class OsPathTest { it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(emptyList()) it.prop(OsPath::isRelative).isTrue() - it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) + it.prop(OsPath::osType).isEqualTo(OsType.ANY) } assertThat(OsPath.createOrThrow(OsType.WINDOWS, "file.txt")).let { @@ -211,7 +211,7 @@ class OsPathTest { it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(emptyList()) it.prop(OsPath::isRelative).isTrue() - it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) + it.prop(OsPath::osType).isEqualTo(OsType.ANY) } assertThat(OsPath.createOrThrow(OsType.WINDOWS, ".\\home\\admin\\.kscript")).let { @@ -294,16 +294,16 @@ class OsPathTest { @Test fun `Test Windows stringPath`() { assertThat( - OsPath.createOrThrow(OsType.WINDOWS, "C:\\home\\admin\\.kscript").stringPath() + OsPath.createOrThrow(OsType.WINDOWS, "C:\\home\\admin\\.kscript").path ).isEqualTo("C:\\home\\admin\\.kscript") assertThat( - OsPath.createOrThrow(OsType.WINDOWS, "c:\\a\\b\\c\\..\\d\\script").stringPath() + OsPath.createOrThrow(OsType.WINDOWS, "c:\\a\\b\\c\\..\\d\\script").path ).isEqualTo("c:\\a\\b\\d\\script") assertThat( - OsPath.createOrThrow(OsType.WINDOWS, ".\\.\\.\\..\\..\\script").stringPath() + OsPath.createOrThrow(OsType.WINDOWS, ".\\.\\.\\..\\..\\script").path ).isEqualTo("..\\..\\script") assertThat( - OsPath.createOrThrow(OsType.WINDOWS, "script\\file.txt").stringPath() + OsPath.createOrThrow(OsType.WINDOWS, "script\\file.txt").path ).isEqualTo("script\\file.txt") } @@ -312,44 +312,44 @@ class OsPathTest { @Test fun `Test Windows to Cygwin`() { assertThat( - OsPath.createOrThrow(OsType.WINDOWS, "C:\\home\\admin\\.kscript").convert(OsType.CYGWIN).stringPath() + OsPath.createOrThrow(OsType.WINDOWS, "C:\\home\\admin\\.kscript").convert(OsType.CYGWIN).path ).isEqualTo("/cygdrive/c/home/admin/.kscript") assertThat( - OsPath.createOrThrow(OsType.WINDOWS, "..\\home\\admin\\.kscript").convert(OsType.CYGWIN).stringPath() + OsPath.createOrThrow(OsType.WINDOWS, "..\\home\\admin\\.kscript").convert(OsType.CYGWIN).path ).isEqualTo("../home/admin/.kscript") } @Test fun `Test Cygwin to Windows`() { assertThat( - OsPath.createOrThrow(OsType.CYGWIN, "/cygdrive/c/home/admin/.kscript").convert(OsType.WINDOWS).stringPath() + OsPath.createOrThrow(OsType.CYGWIN, "/cygdrive/c/home/admin/.kscript").convert(OsType.WINDOWS).path ).isEqualTo("c:\\home\\admin\\.kscript") assertThat( - OsPath.createOrThrow(OsType.CYGWIN, "../home/admin/.kscript").convert(OsType.WINDOWS).stringPath() + OsPath.createOrThrow(OsType.CYGWIN, "../home/admin/.kscript").convert(OsType.WINDOWS).path ).isEqualTo("..\\home\\admin\\.kscript") } @Test fun `Test Windows to MSys`() { assertThat( - OsPath.createOrThrow(OsType.WINDOWS, "C:\\home\\admin\\.kscript").convert(OsType.MSYS).stringPath() + OsPath.createOrThrow(OsType.WINDOWS, "C:\\home\\admin\\.kscript").convert(OsType.MSYS).path ).isEqualTo("/c/home/admin/.kscript") assertThat( - OsPath.createOrThrow(OsType.WINDOWS, "..\\home\\admin\\.kscript").convert(OsType.MSYS).stringPath() + OsPath.createOrThrow(OsType.WINDOWS, "..\\home\\admin\\.kscript").convert(OsType.MSYS).path ).isEqualTo("../home/admin/.kscript") } @Test fun `Test MSys to Windows`() { assertThat( - OsPath.createOrThrow(OsType.MSYS, "/c/home/admin/.kscript").convert(OsType.WINDOWS).stringPath() + OsPath.createOrThrow(OsType.MSYS, "/c/home/admin/.kscript").convert(OsType.WINDOWS).path ).isEqualTo("c:\\home\\admin\\.kscript") assertThat( - OsPath.createOrThrow(OsType.MSYS, "../home/admin/.kscript").convert(OsType.WINDOWS).stringPath() + OsPath.createOrThrow(OsType.MSYS, "../home/admin/.kscript").convert(OsType.WINDOWS).path ).isEqualTo("..\\home\\admin\\.kscript") } @@ -361,9 +361,9 @@ class OsPathTest { assertThat(OsPath.createOrThrow(OsType.LINUX)).let { it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(emptyList()) + it.prop(OsPath::isRelative).isTrue() it.prop(OsPath::isAbsolute).isFalse() - it.prop(OsPath::isEmpty).isTrue() - it.prop(OsPath::osType).isEqualTo(OsType.LINUX) + it.prop(OsPath::osType).isEqualTo(OsType.ANY) } } From 86930ffdba7e380b3a989b7eb9406383a3a64a63 Mon Sep 17 00:00:00 2001 From: Marcin Kuszczak <1508798+aartiPl@users.noreply.github.com> Date: Thu, 6 Jul 2023 20:57:33 +0200 Subject: [PATCH 24/50] Fixed test --- .../shell/integration/tools/TestContext.kt | 2 +- .../github/kscripting/shell/model/OsPath.kt | 21 +++++++++---------- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/src/itest/kotlin/io/github/kscripting/shell/integration/tools/TestContext.kt b/src/itest/kotlin/io/github/kscripting/shell/integration/tools/TestContext.kt index 35d04ef..102636e 100644 --- a/src/itest/kotlin/io/github/kscripting/shell/integration/tools/TestContext.kt +++ b/src/itest/kotlin/io/github/kscripting/shell/integration/tools/TestContext.kt @@ -15,7 +15,7 @@ object TestContext { val pathEnvVariableName = if (osType.isWindowsLike()) "Path" else "PATH" val pathEnvVariableValue: String = System.getenv()[pathEnvVariableName]!! val pathEnvVariableSeparator: String = if (osType.isWindowsLike() || osType.isPosixHostedOnWindows()) ";" else ":" - val pathEnvVariableCalculatedPath: String = "${execPath.convert(osType)}$pathEnvVariableSeparator$pathEnvVariableValue" + val pathEnvVariableCalculatedPath: String = "${execPath.convert(osType).path}$pathEnvVariableSeparator$pathEnvVariableValue" val nl: String = when { osType.isPosixHostedOnWindows() -> "\n" diff --git a/src/main/kotlin/io/github/kscripting/shell/model/OsPath.kt b/src/main/kotlin/io/github/kscripting/shell/model/OsPath.kt index 3586760..c1eef9a 100644 --- a/src/main/kotlin/io/github/kscripting/shell/model/OsPath.kt +++ b/src/main/kotlin/io/github/kscripting/shell/model/OsPath.kt @@ -14,11 +14,11 @@ data class OsPath(val osType: OsType, val root: String, val pathParts: List result.value is Either.Left -> throw IllegalArgumentException(result.value) } - return OsPath(osType, this.root, normalizedPath) + return OsPath(osType, root, normalizedPath) } //Not all conversions make sense: only Windows to CygWin and Msys and vice versa //TODO: conversion of paths like /usr /opt etc. is wrong; it needs also windows root of installation cygwin/msys // base path: cygpath -w / fun convert(targetOsType: OsType /*nativeRootPath: OsPath = emptyOsPath*/): OsPath { - if (this.osType == targetOsType) { + if (osType == targetOsType) { return this } - if ((this.osType.isPosixLike() && targetOsType.isPosixLike()) || (this.osType.isWindowsLike() && targetOsType.isWindowsLike())) { - return OsPath(targetOsType, this.root, pathParts) + if ((osType.isPosixLike() && targetOsType.isPosixLike()) || (osType.isWindowsLike() && targetOsType.isWindowsLike())) { + return OsPath(targetOsType, root, pathParts) } val toHosted = osType.isWindowsLike() && targetOsType.isPosixHostedOnWindows() @@ -75,10 +75,9 @@ data class OsPath(val osType: OsType, val root: String, val pathParts: List { - val drive: String - if (isAbsolute) { - drive = root.dropLast(2).lowercase() + //root is like 'C:\' + val drive = root.dropLast(2).lowercase() newRoot = "/" From 6dd6ad488985ffbf8e981ce427b3800acae54456 Mon Sep 17 00:00:00 2001 From: Marcin Kuszczak <1508798+aartiPl@users.noreply.github.com> Date: Thu, 6 Jul 2023 23:12:49 +0200 Subject: [PATCH 25/50] Improvements --- .../github/kscripting/shell/model/OsPath.kt | 34 +++++++++---------- .../kscripting/shell/model/OsPathExt.kt | 3 +- 2 files changed, 17 insertions(+), 20 deletions(-) diff --git a/src/main/kotlin/io/github/kscripting/shell/model/OsPath.kt b/src/main/kotlin/io/github/kscripting/shell/model/OsPath.kt index c1eef9a..1d5e6bb 100644 --- a/src/main/kotlin/io/github/kscripting/shell/model/OsPath.kt +++ b/src/main/kotlin/io/github/kscripting/shell/model/OsPath.kt @@ -13,17 +13,12 @@ data class OsPath(val osType: OsType, val root: String, val pathParts: List throw IllegalArgumentException(result.value) } - return OsPath(osType, root, normalizedPath) + return createPathOrEmpty(osType, root, normalizedPath) } //Not all conversions make sense: only Windows to CygWin and Msys and vice versa //TODO: conversion of paths like /usr /opt etc. is wrong; it needs also windows root of installation cygwin/msys // base path: cygpath -w / - fun convert(targetOsType: OsType /*nativeRootPath: OsPath = emptyOsPath*/): OsPath { + fun convert(targetOsType: OsType /*nativeRootPath: OsPath = emptyPath*/): OsPath { if (osType == targetOsType) { return this } @@ -190,10 +185,6 @@ data class OsPath(val osType: OsType, val root: String, val pathParts: List return result.value.left() } - if (root.isEmpty() && normalizedPath.isEmpty()) { - return emptyPath.right() - } - //Validate root element of path and find out if it is absolute or relative val forbiddenCharacter = path.substring(root.length).find { forbiddenCharacters.contains(it) } @@ -201,7 +192,15 @@ data class OsPath(val osType: OsType, val root: String, val pathParts: List): OsPath { + if (root.isEmpty() && pathParts.isEmpty()) { + return emptyPath + } + + return OsPath(osType, root, pathParts) } fun normalize(root: String, pathParts: List): Either> { @@ -225,8 +224,7 @@ data class OsPath(val osType: OsType, val root: String, val pathParts: List Date: Mon, 10 Jul 2023 21:03:23 +0200 Subject: [PATCH 26/50] Improvements --- .../shell/integration/tools/TestContext.kt | 2 +- .../github/kscripting/shell/model/OsPath.kt | 253 +---------------- .../kscripting/shell/model/OsPathExt.kt | 255 +++++++++++++++++- .../github/kscripting/shell/model/OsType.kt | 2 +- .../kscripting/shell/model/OsPathTest.kt | 134 ++++----- 5 files changed, 313 insertions(+), 333 deletions(-) diff --git a/src/itest/kotlin/io/github/kscripting/shell/integration/tools/TestContext.kt b/src/itest/kotlin/io/github/kscripting/shell/integration/tools/TestContext.kt index 102636e..21fe7fb 100644 --- a/src/itest/kotlin/io/github/kscripting/shell/integration/tools/TestContext.kt +++ b/src/itest/kotlin/io/github/kscripting/shell/integration/tools/TestContext.kt @@ -8,7 +8,7 @@ import io.github.kscripting.shell.util.Sanitizer object TestContext { val osType: OsType = OsType.find(System.getProperty("osType")) ?: OsType.native - val projectPath: OsPath = OsPath.createOrThrow(OsType.native, System.getProperty("projectPath")).convert(osType) + val projectPath: OsPath = OsPath.of(OsType.native, System.getProperty("projectPath")).convert(osType) val execPath: OsPath = projectPath.resolve("build/shell_test/bin") val testPath: OsPath = projectPath.resolve("build/shell_test/tmp") diff --git a/src/main/kotlin/io/github/kscripting/shell/model/OsPath.kt b/src/main/kotlin/io/github/kscripting/shell/model/OsPath.kt index 1d5e6bb..00625a0 100644 --- a/src/main/kotlin/io/github/kscripting/shell/model/OsPath.kt +++ b/src/main/kotlin/io/github/kscripting/shell/model/OsPath.kt @@ -1,260 +1,9 @@ package io.github.kscripting.shell.model -import arrow.core.Either -import arrow.core.left -import arrow.core.right - //Path representation for different OSes -@Suppress("MemberVisibilityCanBePrivate") data class OsPath(val osType: OsType, val root: String, val pathParts: List) { - val isEmpty: Boolean get() = this === emptyPath val isRelative: Boolean get() = root.isEmpty() val isAbsolute: Boolean get() = !isRelative - val pathSeparator: Char get() = if (osType.isWindowsLike()) '\\' else '/' - val path get(): String = root + pathParts.joinToString(pathSeparator.toString()) { it } - - val OsPath.leaf get(): String = if (pathParts.isEmpty()) root else pathParts.last() - - operator fun div(osPath: OsPath): OsPath = resolve(osPath) - operator fun div(path: String): OsPath = resolve(path) - - fun resolve(vararg pathParts: String): OsPath = resolve(createOrThrow(osType, *pathParts)) - - fun resolve(osPath: OsPath): OsPath { - if (osPath.isEmpty) { - return this - } - - require(osType == osPath.osType || osType == OsType.ANY) { - "Paths from different OS's: '${osType.name}' path can not be resolved with '${osPath.osType.name}' path" - } - - require(osPath.isRelative) { - "Can not resolve absolute or relative path '${path}' using absolute path '${osPath.path}'" - } - - val newPathParts = buildList { - addAll(pathParts) - addAll(osPath.pathParts) - } - - val normalizedPath = when (val result = normalize(root, newPathParts)) { - is Either.Right -> result.value - is Either.Left -> throw IllegalArgumentException(result.value) - } - - return createPathOrEmpty(osType, root, normalizedPath) - } - - //Not all conversions make sense: only Windows to CygWin and Msys and vice versa - //TODO: conversion of paths like /usr /opt etc. is wrong; it needs also windows root of installation cygwin/msys - // base path: cygpath -w / - fun convert(targetOsType: OsType /*nativeRootPath: OsPath = emptyPath*/): OsPath { - if (osType == targetOsType) { - return this - } - - if ((osType.isPosixLike() && targetOsType.isPosixLike()) || (osType.isWindowsLike() && targetOsType.isWindowsLike())) { - return OsPath(targetOsType, root, pathParts) - } - - val toHosted = osType.isWindowsLike() && targetOsType.isPosixHostedOnWindows() - val toNative = osType.isPosixHostedOnWindows() && targetOsType.isWindowsLike() - - require(toHosted || toNative) { - "Only paths conversion between Windows and Posix hosted on Windows are supported" - } - - val newParts = mutableListOf() - var newRoot = "" - - when { - toHosted -> { - if (isAbsolute) { - //root is like 'C:\' - val drive = root.dropLast(2).lowercase() - - newRoot = "/" - - if (targetOsType == OsType.CYGWIN) { - newParts.add("cygdrive") - newParts.add(drive) - } else { - newParts.add(drive) - } - } - - newParts.addAll(pathParts) - } - - toNative -> { - if (isAbsolute) { - if (osType == OsType.CYGWIN) { - newRoot = pathParts[1] + ":\\" - newParts.addAll(pathParts.subList(2, pathParts.size)) - } else { - newRoot = pathParts[0] + ":\\" - newParts.addAll(pathParts.subList(1, pathParts.size)) - } - } else { - newParts.addAll(pathParts) - } - } - - else -> throw IllegalArgumentException("Invalid conversion: $this to ${targetOsType.name}") - } - - return OsPath(targetOsType, newRoot, newParts) - } - - override fun toString(): String = "$path [${osType.name}]" - - companion object { - val emptyPath = OsPath(OsType.ANY, "", emptyList()) - - //https://stackoverflow.com/questions/1976007/what-characters-are-forbidden-in-windows-and-linux-directory-names - //The rule here is more strict than necessary, but it is at least good practice to follow such a rule. - //TODO: should I remove validation all together? It allows e.g. for globbing with asterisks and question marks - // maybe separate function in FileSystem: validate? - private val forbiddenCharacters = buildSet { - add('<') - add('>') - add(':') - add('"') - add('|') - add('?') - add('*') - for (i in 0 until 32) { - add(i.toChar()) - } - } - - private val windowsDriveRegex = - "^([a-zA-Z]:(?=[\\\\/])|\\\\\\\\(?:[^*:<>?\\\\/|]+\\\\[^*:<>?\\\\/|]+|\\?\\\\(?:[a-zA-Z]:(?=\\\\)|(?:UNC\\\\)?[^*:<>?\\\\/|]+\\\\[^*:<>?\\\\/|]+)))".toRegex() - - - fun createOrThrow(vararg pathParts: String): OsPath = createOrThrow(OsType.native, pathParts.toList()) - - fun createOrThrow(osType: OsType, vararg pathParts: String): OsPath = createOrThrow(osType, pathParts.toList()) - - fun createOrThrow(osType: OsType, pathParts: List): OsPath { - return when (val result = internalCreate(osType, pathParts)) { - is Either.Right -> result.value - is Either.Left -> throw IllegalArgumentException(result.value) - } - } - - fun create(vararg pathParts: String): OsPath? = create(OsType.native, pathParts.toList()) - - fun create(osType: OsType, vararg pathParts: String): OsPath? = create(osType, pathParts.toList()) - - fun create(osType: OsType, pathParts: List): OsPath? { - return when (val result = internalCreate(osType, pathParts)) { - is Either.Right -> result.value - is Either.Left -> null - } - } - - //Relaxed validation: - //1. It doesn't matter if there is '/' or '\' used as path separator - both are treated the same - //2. Duplicated or trailing slashes '/' and backslashes '\' are just ignored - private fun internalCreate(osType: OsType, pathParts: List): Either { - val path = pathParts.joinToString("/") - - //Detect root - val root: String = when { - path.startsWith("~/") || path.startsWith("~\\") -> "~" - osType.isPosixLike() && path.startsWith("/") -> "/" - osType.isWindowsLike() -> { - val match = windowsDriveRegex.find(path) - val matchedRoot = match?.groupValues?.get(1) - if (matchedRoot != null) matchedRoot + "\\" else "" - } - - else -> "" - } - - //TODO: https://learn.microsoft.com/pl-pl/dotnet/standard/io/file-path-formats - // https://regex101.com/r/aU4yZ7/1 - - //Remove also empty path parts - there were duplicated or trailing slashes / backslashes in initial path - val pathPartsResolved = path.drop(root.length).split('/', '\\').filter { it.isNotBlank() } - - val normalizedPath = when (val result = normalize(root, pathPartsResolved)) { - is Either.Right -> result.value - is Either.Left -> return result.value.left() - } - - //Validate root element of path and find out if it is absolute or relative - val forbiddenCharacter = path.substring(root.length).find { forbiddenCharacters.contains(it) } - - if (forbiddenCharacter != null) { - return "Invalid character '$forbiddenCharacter' in path '$path'".left() - } - - return createPathOrEmpty(osType, root, normalizedPath).right() - } - - private fun createPathOrEmpty(osType: OsType, root: String, pathParts: List): OsPath { - if (root.isEmpty() && pathParts.isEmpty()) { - return emptyPath - } - - return OsPath(osType, root, pathParts) - } - - fun normalize(root: String, pathParts: List): Either> { - //Relative: - // ./../ --> ../ - // ./a/../ --> ./ - // ./a/ --> ./a - // ../a --> ../a - // ../../a --> ../../a - - //Absolute: - // /../ --> invalid (above root) - // /a/../ --> / - - val isAbsolute: Boolean = root.isNotEmpty() - - val newParts = mutableListOf() - var index = 0 - - while (index < pathParts.size) { - if (pathParts[index] == ".") { - //Just skip . without adding it to newParts - } else if (pathParts[index] == "..") { - if (isAbsolute && newParts.size == 0) { - return "Path after normalization goes beyond root element: '$root'".left() - } - - if (newParts.size > 0) { - when (newParts.last()) { - "." -> { - //It's the first element - other dots should be already removed before - newParts.removeAt(newParts.size - 1) - newParts.add("..") - } - - ".." -> { - newParts.add("..") - } - - else -> { - newParts.removeAt(newParts.size - 1) - } - } - } else { - newParts.add("..") - } - } else { - newParts.add(pathParts[index]) - } - - index += 1 - } - return newParts.right() - } - } + companion object } diff --git a/src/main/kotlin/io/github/kscripting/shell/model/OsPathExt.kt b/src/main/kotlin/io/github/kscripting/shell/model/OsPathExt.kt index 934cdc1..7e0c9d3 100644 --- a/src/main/kotlin/io/github/kscripting/shell/model/OsPathExt.kt +++ b/src/main/kotlin/io/github/kscripting/shell/model/OsPathExt.kt @@ -1,5 +1,8 @@ package io.github.kscripting.shell.model +import arrow.core.Either +import arrow.core.left +import arrow.core.right import java.io.File import java.net.URI import java.nio.charset.Charset @@ -8,22 +11,248 @@ import java.nio.file.Path import java.nio.file.Paths import kotlin.io.path.* +//Create OsPath +//https://stackoverflow.com/questions/1976007/what-characters-are-forbidden-in-windows-and-linux-directory-names +//The rule here is more strict than necessary, but it is at least good practice to follow such a rule. +//TODO: should I remove validation all together? It allows e.g. for globbing with asterisks and question marks +// maybe separate function in FileSystem: validate? +private val forbiddenCharacters = buildSet { + add('<') + add('>') + add(':') + add('"') + add('|') + add('?') + add('*') + for (i in 0 until 32) { + add(i.toChar()) + } +} -// Conversion to OsPath +private val windowsDriveRegex = + "^([a-zA-Z]:(?=[\\\\/])|\\\\\\\\(?:[^*:<>?\\\\/|]+\\\\[^*:<>?\\\\/|]+|\\?\\\\(?:[a-zA-Z]:(?=\\\\)|(?:UNC\\\\)?[^*:<>?\\\\/|]+\\\\[^*:<>?\\\\/|]+)))".toRegex() -fun File.toOsPath(): OsPath = OsPath.createOrThrow(OsType.native, absolutePath) -fun Path.toOsPath(): OsPath = OsPath.createOrThrow(OsType.native, absolutePathString()) +fun OsPath.Companion.of(vararg pathParts: String): OsPath = of(OsType.native, pathParts.toList()) + +fun OsPath.Companion.of(osType: OsType, vararg pathParts: String): OsPath = of(osType, pathParts.toList()) + +fun OsPath.Companion.of(osType: OsType, pathParts: List): OsPath { + return when (val result = ofWithEither(osType, pathParts)) { + is Either.Right -> result.value + is Either.Left -> throw result.value + } +} + +fun OsPath.Companion.ofOrNull(vararg pathParts: String): OsPath? = ofOrNull(OsType.native, pathParts.toList()) + +fun OsPath.Companion.ofOrNull(osType: OsType, vararg pathParts: String): OsPath? = ofOrNull(osType, pathParts.toList()) + +fun OsPath.Companion.ofOrNull(osType: OsType, pathParts: List): OsPath? { + return when (val result = ofWithEither(osType, pathParts)) { + is Either.Right -> result.value + is Either.Left -> null + } +} + +//Relaxed validation: +//1. It doesn't matter if there is '/' or '\' used as path separator - both are treated the same +//2. Duplicated trailing slashes '/' and backslashes '\' are just ignored +fun OsPath.Companion.ofWithEither(osType: OsType, pathParts: List): Either { + val path = pathParts.joinToString("/") + + //Detect root + val root: String = when { + path.startsWith("~/") || path.startsWith("~\\") -> "~" + osType.isPosixLike() && path.startsWith("/") -> "/" + osType.isWindowsLike() -> { + val match = windowsDriveRegex.find(path) + val matchedRoot = match?.groupValues?.get(1) + if (matchedRoot != null) matchedRoot + "\\" else "" + } + else -> "" + } + + //TODO: https://learn.microsoft.com/pl-pl/dotnet/standard/io/file-path-formats + // https://regex101.com/r/aU4yZ7/1 + + //Remove also empty path parts - there were duplicated or trailing slashes / backslashes in initial path + val pathPartsResolved = path.drop(root.length).split('/', '\\').filter { it.isNotBlank() } + + val normalizedPath = when (val result = normalize(root, pathPartsResolved)) { + is Either.Right -> result.value + is Either.Left -> return result.value.left() + } + + //Validate root element of path and find out if it is absolute or relative + val forbiddenCharacter = path.substring(root.length).find { forbiddenCharacters.contains(it) } + + if (forbiddenCharacter != null) { + return IllegalArgumentException("Invalid character '$forbiddenCharacter' in path '$path'").left() + } + + return OsPath(osType, root, normalizedPath).right() +} + +fun OsPath.Companion.normalize(root: String, pathParts: List): Either> { + //Relative: + // ./../ --> ../ + // ./a/../ --> ./ + // ./a/ --> ./a + // ../a --> ../a + // ../../a --> ../../a + + //Absolute: + // /../ --> invalid (above root) + // /a/../ --> / + + val isAbsolute: Boolean = root.isNotEmpty() + + val newParts = mutableListOf() + var index = 0 + + while (index < pathParts.size) { + if (pathParts[index] == ".") { + //Just skip . without adding it to newParts + } else if (pathParts[index] == "..") { + if (isAbsolute && newParts.size == 0) { + return IllegalArgumentException("Path after normalization goes beyond root element: '$root'").left() + } + + if (newParts.size > 0) { + when (newParts.last()) { + "." -> { + //It's the first element - other dots should be already removed before + newParts.removeAt(newParts.size - 1) + newParts.add("..") + } + + ".." -> { + newParts.add("..") + } + + else -> { + newParts.removeAt(newParts.size - 1) + } + } + } else { + newParts.add("..") + } + } else { + newParts.add(pathParts[index]) + } + + index += 1 + } + + return newParts.right() +} + +// Resolve OsPath +operator fun OsPath.div(osPath: OsPath): OsPath = resolve(osPath) +operator fun OsPath.div(path: String): OsPath = resolve(path) + +fun OsPath.resolve(vararg pathParts: String): OsPath = resolve(OsPath.of(osType, *pathParts)) + +fun OsPath.resolve(osPath: OsPath): OsPath { + require(osType == osPath.osType) { + "Paths from different OS's: '${osType.name}' path can not be resolved with '${osPath.osType.name}' path" + } + + require(osPath.isRelative) { + "Can not resolve absolute or relative path '${path}' using absolute path '${osPath.path}'" + } + + val newPathParts = buildList { + addAll(pathParts) + addAll(osPath.pathParts) + } + + val normalizedPath = when (val result = OsPath.normalize(root, newPathParts)) { + is Either.Right -> result.value + is Either.Left -> throw result.value + } + + return OsPath(osType, root, normalizedPath) +} + +// Convert OsPath + +//Not all conversions make sense: only Windows to CygWin and Msys and vice versa +//TODO: conversion of paths like /usr /opt etc. is wrong; it needs also windows root of installation cygwin/msys +// base path: cygpath -w / +fun OsPath.convert(targetOsType: OsType /*nativeRootPath: OsPath = emptyPath*/): OsPath { + if (osType == targetOsType) { + return this + } + + if ((osType.isPosixLike() && targetOsType.isPosixLike()) || (osType.isWindowsLike() && targetOsType.isWindowsLike())) { + return OsPath(targetOsType, root, pathParts) + } + + val toHosted = osType.isWindowsLike() && targetOsType.isPosixHostedOnWindows() + val toNative = osType.isPosixHostedOnWindows() && targetOsType.isWindowsLike() + + require(toHosted || toNative) { + "Only paths conversion between Windows and Posix hosted on Windows are supported" + } + + val newParts = mutableListOf() + var newRoot = "" + + when { + toHosted -> { + if (isAbsolute) { + //root is like 'C:\' + val drive = root.dropLast(2).lowercase() + + newRoot = "/" + + if (targetOsType == OsType.CYGWIN) { + newParts.add("cygdrive") + newParts.add(drive) + } else { + newParts.add(drive) + } + } + + newParts.addAll(pathParts) + } + + toNative -> { + if (isAbsolute) { + if (osType == OsType.CYGWIN) { + newRoot = pathParts[1] + ":\\" + newParts.addAll(pathParts.subList(2, pathParts.size)) + } else { + newRoot = pathParts[0] + ":\\" + newParts.addAll(pathParts.subList(1, pathParts.size)) + } + } else { + newParts.addAll(pathParts) + } + } + + else -> throw IllegalArgumentException("Invalid conversion: $this to ${targetOsType.name}") + } + + return OsPath(targetOsType, newRoot, newParts) +} + + +// Interact with file system +fun File.toOsPath(): OsPath = OsPath.of(OsType.native, absolutePath) + +fun Path.toOsPath(): OsPath = OsPath.of(OsType.native, absolutePathString()) fun URI.toOsPath(): OsPath = if (this.scheme == "file") File(this).toOsPath() else throw IllegalArgumentException("Invalid conversion from URL to OsPath") fun String.toOsPath(osType: OsType = OsType.native): OsPath = - OsPath.createOrThrow(osType, this) + OsPath.of(osType, this) // Conversion from OsPath - fun OsPath.toNativePath(): Path = Paths.get(toNativeOsPath().path) fun OsPath.toNativeFile(): File = toNativePath().toFile() @@ -32,15 +261,14 @@ fun OsPath.toNativeOsPath(): OsPath = if (osType.isPosixHostedOnWindows()) conve // OsPath operations - fun OsPath.exists(): Boolean = toNativePath().exists() -fun OsPath.createDirectories(): OsPath = OsPath.createOrThrow(nativeType, toNativePath().createDirectories().pathString) +fun OsPath.createDirectories(): OsPath = OsPath.of(nativeType, toNativePath().createDirectories().pathString) fun OsPath.deleteRecursively(): Boolean = this.toNativeFile().deleteRecursively() fun OsPath.readBytes(): ByteArray = this.toNativeFile().readBytes() fun OsPath.copyTo(target: OsPath, overwrite: Boolean = false): OsPath = - OsPath.createOrThrow(nativeType, toNativePath().copyTo(target.toNativePath(), overwrite).pathString) + OsPath.of(nativeType, toNativePath().copyTo(target.toNativePath(), overwrite).pathString) fun OsPath.writeText(text: CharSequence, charset: Charset = Charsets.UTF_8, vararg options: OpenOption): Unit = toNativePath().writeText(text, charset, *options) @@ -49,16 +277,19 @@ fun OsPath.readText(charset: Charset = Charsets.UTF_8): String = toNativePath(). // OsPath accessors +val OsPath.leaf get(): String = if (pathParts.isEmpty()) root else pathParts.last() - -val OsPath.rootOsPath - get():OsPath = if (isRelative) OsPath.emptyPath else OsPath.createOrThrow(osType, root) +//val OsPath.rootOsPath +// get():OsPath = if (isRelative) OsPath.emptyPath else OsPath.of(osType, root) val OsPath.parent - get(): OsPath = OsPath.createOrThrow(osType, pathParts.dropLast(1)) + get(): OsPath = OsPath.of(osType, pathParts.dropLast(1)) val OsPath.nativeType get(): OsType = if (osType.isPosixHostedOnWindows()) OsType.WINDOWS else osType val OsPath.extension get(): String? = leaf.substringAfterLast('.', "") + +val OsPath.path: String + get() = root + pathParts.joinToString((if (osType.isWindowsLike()) "\\" else "/")) { it } diff --git a/src/main/kotlin/io/github/kscripting/shell/model/OsType.kt b/src/main/kotlin/io/github/kscripting/shell/model/OsType.kt index 31229be..016f26d 100644 --- a/src/main/kotlin/io/github/kscripting/shell/model/OsType.kt +++ b/src/main/kotlin/io/github/kscripting/shell/model/OsType.kt @@ -3,7 +3,7 @@ package io.github.kscripting.shell.model import org.apache.commons.lang3.SystemUtils enum class OsType(val osName: String) { - ANY("any"), LINUX("linux"), FREEBSD("freebsd"), MACOS("darwin"), CYGWIN("cygwin"), MSYS("msys"), WINDOWS("windows"); + LINUX("linux"), FREEBSD("freebsd"), MACOS("darwin"), CYGWIN("cygwin"), MSYS("msys"), WINDOWS("windows"); fun isPosixLike() = (this == LINUX || this == MACOS || this == FREEBSD || this == CYGWIN || this == MSYS) fun isPosixHostedOnWindows() = (this == CYGWIN || this == MSYS) diff --git a/src/test/kotlin/io/github/kscripting/shell/model/OsPathTest.kt b/src/test/kotlin/io/github/kscripting/shell/model/OsPathTest.kt index bb6fb1c..8c5ad1f 100644 --- a/src/test/kotlin/io/github/kscripting/shell/model/OsPathTest.kt +++ b/src/test/kotlin/io/github/kscripting/shell/model/OsPathTest.kt @@ -8,7 +8,7 @@ class OsPathTest { // ************************************************** LINUX PATHS ************************************************** @Test fun `Test Linux paths`() { - assertThat(OsPath.createOrThrow(OsType.LINUX, "/")).let { + assertThat(OsPath.of(OsType.LINUX, "/")).let { it.prop(OsPath::root).isEqualTo("/") it.prop(OsPath::pathParts).isEqualTo(emptyList()) it.prop(OsPath::isAbsolute).isTrue() @@ -16,50 +16,50 @@ class OsPathTest { it.prop(OsPath::osType).isEqualTo(OsType.LINUX) } - assertThat(OsPath.createOrThrow(OsType.LINUX, "/home/admin/.kscript")).let { + assertThat(OsPath.of(OsType.LINUX, "/home/admin/.kscript")).let { it.prop(OsPath::root).isEqualTo("/") it.prop(OsPath::pathParts).isEqualTo(listOf("home", "admin", ".kscript")) it.prop(OsPath::isAbsolute).isTrue() it.prop(OsPath::osType).isEqualTo(OsType.LINUX) } - assertThat(OsPath.createOrThrow(OsType.LINUX, "./home/admin/.kscript")).let { + assertThat(OsPath.of(OsType.LINUX, "./home/admin/.kscript")).let { it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(listOf("home", "admin", ".kscript")) it.prop(OsPath::isRelative).isTrue() it.prop(OsPath::osType).isEqualTo(OsType.LINUX) } - assertThat(OsPath.createOrThrow(OsType.LINUX, "")).let { + assertThat(OsPath.of(OsType.LINUX, "")).let { it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(emptyList()) it.prop(OsPath::isRelative).isTrue() it.prop(OsPath::isAbsolute).isFalse() - it.prop(OsPath::osType).isEqualTo(OsType.ANY) + it.prop(OsPath::osType).isEqualTo(OsType.LINUX) } - assertThat(OsPath.createOrThrow(OsType.LINUX, "file.txt")).let { + assertThat(OsPath.of(OsType.LINUX, "file.txt")).let { it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(listOf("file.txt")) it.prop(OsPath::isRelative).isTrue() it.prop(OsPath::osType).isEqualTo(OsType.LINUX) } - assertThat(OsPath.createOrThrow(OsType.LINUX, ".")).let { + assertThat(OsPath.of(OsType.LINUX, ".")).let { it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(emptyList()) it.prop(OsPath::isRelative).isTrue() - it.prop(OsPath::osType).isEqualTo(OsType.ANY) + it.prop(OsPath::osType).isEqualTo(OsType.LINUX) } - assertThat(OsPath.createOrThrow(OsType.LINUX, "../home/admin/.kscript")).let { + assertThat(OsPath.of(OsType.LINUX, "../home/admin/.kscript")).let { it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(listOf("..", "home", "admin", ".kscript")) it.prop(OsPath::isRelative).isTrue() it.prop(OsPath::osType).isEqualTo(OsType.LINUX) } - assertThat(OsPath.createOrThrow(OsType.LINUX, "..")).let { + assertThat(OsPath.of(OsType.LINUX, "..")).let { it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(listOf("..")) it.prop(OsPath::isRelative).isTrue() @@ -67,7 +67,7 @@ class OsPathTest { } //Duplicated separators are accepted - assertThat(OsPath.createOrThrow(OsType.LINUX, "..//home////admin/.kscript/")).let { + assertThat(OsPath.of(OsType.LINUX, "..//home////admin/.kscript/")).let { it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(listOf("..", "home", "admin", ".kscript")) it.prop(OsPath::isRelative).isTrue() @@ -75,7 +75,7 @@ class OsPathTest { } //Both types of separator are accepted - assertThat(OsPath.createOrThrow(OsType.LINUX, "..//home\\admin\\.kscript/")).let { + assertThat(OsPath.of(OsType.LINUX, "..//home\\admin\\.kscript/")).let { it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(listOf("..", "home", "admin", ".kscript")) it.prop(OsPath::isRelative).isTrue() @@ -83,7 +83,7 @@ class OsPathTest { } //Home dir is correctly handled - assertThat(OsPath.createOrThrow(OsType.LINUX, "~/admin/.git")).let { + assertThat(OsPath.of(OsType.LINUX, "~/admin/.git")).let { it.prop(OsPath::root).isEqualTo("~") it.prop(OsPath::pathParts).isEqualTo(listOf("admin", ".git")) it.prop(OsPath::isAbsolute).isTrue() @@ -93,84 +93,84 @@ class OsPathTest { @Test fun `Normalization of Linux paths`() { - assertThat(OsPath.createOrThrow(OsType.LINUX, "/home/admin/.kscript/../../")).let { + assertThat(OsPath.of(OsType.LINUX, "/home/admin/.kscript/../../")).let { it.prop(OsPath::root).isEqualTo("/") it.prop(OsPath::pathParts).isEqualTo(listOf("home")) it.prop(OsPath::isAbsolute).isTrue() it.prop(OsPath::osType).isEqualTo(OsType.LINUX) } - assertThat(OsPath.createOrThrow(OsType.LINUX, "./././../../script")).let { + assertThat(OsPath.of(OsType.LINUX, "./././../../script")).let { it.prop(OsPath::pathParts).isEqualTo(listOf("..", "..", "script")) it.prop(OsPath::isRelative).isTrue() it.prop(OsPath::osType).isEqualTo(OsType.LINUX) } - assertThat(OsPath.createOrThrow(OsType.LINUX, "/a/b/c/../d/script")).let { + assertThat(OsPath.of(OsType.LINUX, "/a/b/c/../d/script")).let { it.prop(OsPath::root).isEqualTo("/") it.prop(OsPath::pathParts).isEqualTo(listOf("a", "b", "d", "script")) it.prop(OsPath::isAbsolute).isTrue() it.prop(OsPath::osType).isEqualTo(OsType.LINUX) } - assertThat { OsPath.createOrThrow(OsType.LINUX, "/.kscript/../../") }.isFailure() + assertThat { OsPath.of(OsType.LINUX, "/.kscript/../../") }.isFailure() .isInstanceOf(IllegalArgumentException::class.java) .hasMessage("Path after normalization goes beyond root element: '/'") } @Test fun `Test invalid Linux paths`() { - assertThat { OsPath.createOrThrow(OsType.LINUX, "/ad*asdf") }.isFailure() + assertThat { OsPath.of(OsType.LINUX, "/ad*asdf") }.isFailure() .isInstanceOf(IllegalArgumentException::class.java).hasMessage("Invalid character '*' in path '/ad*asdf'") } @Test fun `Test Linux stringPath`() { - assertThat(OsPath.createOrThrow(OsType.LINUX, "/home/admin/.kscript").path) + assertThat(OsPath.of(OsType.LINUX, "/home/admin/.kscript").path) .isEqualTo("/home/admin/.kscript") - assertThat(OsPath.createOrThrow(OsType.LINUX, "/a/b/c/../d/script").path).isEqualTo("/a/b/d/script") - assertThat(OsPath.createOrThrow(OsType.LINUX, "./././../../script").path).isEqualTo("../../script") - assertThat(OsPath.createOrThrow(OsType.LINUX, "script/file.txt").path).isEqualTo("script/file.txt") + assertThat(OsPath.of(OsType.LINUX, "/a/b/c/../d/script").path).isEqualTo("/a/b/d/script") + assertThat(OsPath.of(OsType.LINUX, "./././../../script").path).isEqualTo("../../script") + assertThat(OsPath.of(OsType.LINUX, "script/file.txt").path).isEqualTo("script/file.txt") } @Test fun `Test Linux resolve`() { assertThat( - OsPath.createOrThrow(OsType.LINUX, "/").resolve(OsPath.createOrThrow(OsType.LINUX, "./.kscript/")) + OsPath.of(OsType.LINUX, "/").resolve(OsPath.of(OsType.LINUX, "./.kscript/")) .path ).isEqualTo("/.kscript") assertThat( - OsPath.createOrThrow(OsType.LINUX, "/home/admin/") - .resolve(OsPath.createOrThrow(OsType.LINUX, "./.kscript/")).path + OsPath.of(OsType.LINUX, "/home/admin/") + .resolve(OsPath.of(OsType.LINUX, "./.kscript/")).path ).isEqualTo("/home/admin/.kscript") assertThat( - OsPath.createOrThrow(OsType.LINUX, "./home/admin/") - .resolve(OsPath.createOrThrow(OsType.LINUX, "./.kscript/")).path + OsPath.of(OsType.LINUX, "./home/admin/") + .resolve(OsPath.of(OsType.LINUX, "./.kscript/")).path ).isEqualTo("home/admin/.kscript") assertThat( - OsPath.createOrThrow(OsType.LINUX, "../home/admin/") - .resolve(OsPath.createOrThrow(OsType.LINUX, "./.kscript/")).path + OsPath.of(OsType.LINUX, "../home/admin/") + .resolve(OsPath.of(OsType.LINUX, "./.kscript/")).path ).isEqualTo("../home/admin/.kscript") assertThat( - OsPath.createOrThrow(OsType.LINUX, "..").resolve(OsPath.createOrThrow(OsType.LINUX, "./.kscript/")) + OsPath.of(OsType.LINUX, "..").resolve(OsPath.of(OsType.LINUX, "./.kscript/")) .path ).isEqualTo("../.kscript") assertThat( - OsPath.createOrThrow(OsType.LINUX, ".").resolve(OsPath.createOrThrow(OsType.LINUX, "./.kscript/")).path + OsPath.of(OsType.LINUX, ".").resolve(OsPath.of(OsType.LINUX, "./.kscript/")).path ).isEqualTo(".kscript") assertThat { - OsPath.createOrThrow(OsType.LINUX, "./home/admin").resolve(OsPath.createOrThrow(OsType.WINDOWS, ".\\run")) + OsPath.of(OsType.LINUX, "./home/admin").resolve(OsPath.of(OsType.WINDOWS, ".\\run")) }.isFailure().isInstanceOf(IllegalArgumentException::class.java) .hasMessage("Paths from different OS's: 'LINUX' path can not be resolved with 'WINDOWS' path") assertThat { - OsPath.createOrThrow(OsType.LINUX, "./home/admin").resolve(OsPath.createOrThrow(OsType.LINUX, "/run")) + OsPath.of(OsType.LINUX, "./home/admin").resolve(OsPath.of(OsType.LINUX, "/run")) }.isFailure().isInstanceOf(IllegalArgumentException::class.java) .hasMessage("Can not resolve absolute or relative path 'home/admin' using absolute path '/run'") } @@ -179,56 +179,56 @@ class OsPathTest { @Test fun `Test Windows paths`() { - assertThat(OsPath.createOrThrow(OsType.WINDOWS, "C:\\")).let { + assertThat(OsPath.of(OsType.WINDOWS, "C:\\")).let { it.prop(OsPath::root).isEqualTo("C:\\") it.prop(OsPath::pathParts).isEqualTo(emptyList()) it.prop(OsPath::isAbsolute).isTrue() it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) } - assertThat(OsPath.createOrThrow(OsType.WINDOWS, "C:\\home\\admin\\.kscript")).let { + assertThat(OsPath.of(OsType.WINDOWS, "C:\\home\\admin\\.kscript")).let { it.prop(OsPath::root).isEqualTo("C:\\") it.prop(OsPath::pathParts).isEqualTo(listOf("home", "admin", ".kscript")) it.prop(OsPath::isAbsolute).isTrue() it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) } - assertThat(OsPath.createOrThrow(OsType.WINDOWS, "")).let { + assertThat(OsPath.of(OsType.WINDOWS, "")).let { it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(emptyList()) it.prop(OsPath::isRelative).isTrue() - it.prop(OsPath::osType).isEqualTo(OsType.ANY) + it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) } - assertThat(OsPath.createOrThrow(OsType.WINDOWS, "file.txt")).let { + assertThat(OsPath.of(OsType.WINDOWS, "file.txt")).let { it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(listOf("file.txt")) it.prop(OsPath::isRelative).isTrue() it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) } - assertThat(OsPath.createOrThrow(OsType.WINDOWS, ".")).let { + assertThat(OsPath.of(OsType.WINDOWS, ".")).let { it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(emptyList()) it.prop(OsPath::isRelative).isTrue() - it.prop(OsPath::osType).isEqualTo(OsType.ANY) + it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) } - assertThat(OsPath.createOrThrow(OsType.WINDOWS, ".\\home\\admin\\.kscript")).let { + assertThat(OsPath.of(OsType.WINDOWS, ".\\home\\admin\\.kscript")).let { it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(listOf("home", "admin", ".kscript")) it.prop(OsPath::isRelative).isTrue() it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) } - assertThat(OsPath.createOrThrow(OsType.WINDOWS, "..\\home\\admin\\.kscript")).let { + assertThat(OsPath.of(OsType.WINDOWS, "..\\home\\admin\\.kscript")).let { it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(listOf("..", "home", "admin", ".kscript")) it.prop(OsPath::isRelative).isTrue() it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) } - assertThat(OsPath.createOrThrow(OsType.WINDOWS, "..")).let { + assertThat(OsPath.of(OsType.WINDOWS, "..")).let { it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(listOf("..")) it.prop(OsPath::isRelative).isTrue() @@ -236,7 +236,7 @@ class OsPathTest { } //Duplicated separators are accepted - assertThat(OsPath.createOrThrow(OsType.WINDOWS, "C:\\home\\\\\\\\admin\\.kscript\\")).let { + assertThat(OsPath.of(OsType.WINDOWS, "C:\\home\\\\\\\\admin\\.kscript\\")).let { it.prop(OsPath::root).isEqualTo("C:\\") it.prop(OsPath::pathParts).isEqualTo(listOf("home", "admin", ".kscript")) it.prop(OsPath::isAbsolute).isTrue() @@ -244,7 +244,7 @@ class OsPathTest { } //Both types of separator are accepted - assertThat(OsPath.createOrThrow(OsType.WINDOWS, "C:/home\\admin/.kscript////")).let { + assertThat(OsPath.of(OsType.WINDOWS, "C:/home\\admin/.kscript////")).let { it.prop(OsPath::root).isEqualTo("C:\\") it.prop(OsPath::pathParts).isEqualTo(listOf("home", "admin", ".kscript")) it.prop(OsPath::isAbsolute).isTrue() @@ -254,39 +254,39 @@ class OsPathTest { @Test fun `Normalization of Windows paths`() { - assertThat(OsPath.createOrThrow(OsType.WINDOWS, "C:\\home\\admin\\.kscript\\..\\..\\")).let { + assertThat(OsPath.of(OsType.WINDOWS, "C:\\home\\admin\\.kscript\\..\\..\\")).let { it.prop(OsPath::root).isEqualTo("C:\\") it.prop(OsPath::pathParts).isEqualTo(listOf("home")) it.prop(OsPath::isAbsolute).isTrue() it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) } - assertThat(OsPath.createOrThrow(OsType.WINDOWS, ".\\.\\.\\..\\..\\script")).let { + assertThat(OsPath.of(OsType.WINDOWS, ".\\.\\.\\..\\..\\script")).let { it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(listOf("..", "..", "script")) it.prop(OsPath::isRelative).isTrue() it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) } - assertThat(OsPath.createOrThrow(OsType.WINDOWS, "C:\\a\\b\\c\\..\\d\\script")).let { + assertThat(OsPath.of(OsType.WINDOWS, "C:\\a\\b\\c\\..\\d\\script")).let { it.prop(OsPath::root).isEqualTo("C:\\") it.prop(OsPath::pathParts).isEqualTo(listOf("a", "b", "d", "script")) it.prop(OsPath::isAbsolute).isTrue() it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) } - assertThat { OsPath.createOrThrow(OsType.WINDOWS, "C:\\.kscript\\..\\..\\") }.isFailure() + assertThat { OsPath.of(OsType.WINDOWS, "C:\\.kscript\\..\\..\\") }.isFailure() .isInstanceOf(IllegalArgumentException::class.java) .hasMessage("Path after normalization goes beyond root element: 'C:\\'") } @Test fun `Test invalid Windows paths`() { - assertThat { OsPath.createOrThrow(OsType.WINDOWS, "C:\\adas?df") }.isFailure() + assertThat { OsPath.of(OsType.WINDOWS, "C:\\adas?df") }.isFailure() .isInstanceOf(IllegalArgumentException::class.java) .hasMessage("Invalid character '?' in path 'C:\\adas?df'") - assertThat { OsPath.createOrThrow(OsType.WINDOWS, "home:\\vagrant") }.isFailure() + assertThat { OsPath.of(OsType.WINDOWS, "home:\\vagrant") }.isFailure() .isInstanceOf(IllegalArgumentException::class.java) .hasMessage("Invalid character ':' in path 'home:\\vagrant'") } @@ -294,16 +294,16 @@ class OsPathTest { @Test fun `Test Windows stringPath`() { assertThat( - OsPath.createOrThrow(OsType.WINDOWS, "C:\\home\\admin\\.kscript").path + OsPath.of(OsType.WINDOWS, "C:\\home\\admin\\.kscript").path ).isEqualTo("C:\\home\\admin\\.kscript") assertThat( - OsPath.createOrThrow(OsType.WINDOWS, "c:\\a\\b\\c\\..\\d\\script").path + OsPath.of(OsType.WINDOWS, "c:\\a\\b\\c\\..\\d\\script").path ).isEqualTo("c:\\a\\b\\d\\script") assertThat( - OsPath.createOrThrow(OsType.WINDOWS, ".\\.\\.\\..\\..\\script").path + OsPath.of(OsType.WINDOWS, ".\\.\\.\\..\\..\\script").path ).isEqualTo("..\\..\\script") assertThat( - OsPath.createOrThrow(OsType.WINDOWS, "script\\file.txt").path + OsPath.of(OsType.WINDOWS, "script\\file.txt").path ).isEqualTo("script\\file.txt") } @@ -312,44 +312,44 @@ class OsPathTest { @Test fun `Test Windows to Cygwin`() { assertThat( - OsPath.createOrThrow(OsType.WINDOWS, "C:\\home\\admin\\.kscript").convert(OsType.CYGWIN).path + OsPath.of(OsType.WINDOWS, "C:\\home\\admin\\.kscript").convert(OsType.CYGWIN).path ).isEqualTo("/cygdrive/c/home/admin/.kscript") assertThat( - OsPath.createOrThrow(OsType.WINDOWS, "..\\home\\admin\\.kscript").convert(OsType.CYGWIN).path + OsPath.of(OsType.WINDOWS, "..\\home\\admin\\.kscript").convert(OsType.CYGWIN).path ).isEqualTo("../home/admin/.kscript") } @Test fun `Test Cygwin to Windows`() { assertThat( - OsPath.createOrThrow(OsType.CYGWIN, "/cygdrive/c/home/admin/.kscript").convert(OsType.WINDOWS).path + OsPath.of(OsType.CYGWIN, "/cygdrive/c/home/admin/.kscript").convert(OsType.WINDOWS).path ).isEqualTo("c:\\home\\admin\\.kscript") assertThat( - OsPath.createOrThrow(OsType.CYGWIN, "../home/admin/.kscript").convert(OsType.WINDOWS).path + OsPath.of(OsType.CYGWIN, "../home/admin/.kscript").convert(OsType.WINDOWS).path ).isEqualTo("..\\home\\admin\\.kscript") } @Test fun `Test Windows to MSys`() { assertThat( - OsPath.createOrThrow(OsType.WINDOWS, "C:\\home\\admin\\.kscript").convert(OsType.MSYS).path + OsPath.of(OsType.WINDOWS, "C:\\home\\admin\\.kscript").convert(OsType.MSYS).path ).isEqualTo("/c/home/admin/.kscript") assertThat( - OsPath.createOrThrow(OsType.WINDOWS, "..\\home\\admin\\.kscript").convert(OsType.MSYS).path + OsPath.of(OsType.WINDOWS, "..\\home\\admin\\.kscript").convert(OsType.MSYS).path ).isEqualTo("../home/admin/.kscript") } @Test fun `Test MSys to Windows`() { assertThat( - OsPath.createOrThrow(OsType.MSYS, "/c/home/admin/.kscript").convert(OsType.WINDOWS).path + OsPath.of(OsType.MSYS, "/c/home/admin/.kscript").convert(OsType.WINDOWS).path ).isEqualTo("c:\\home\\admin\\.kscript") assertThat( - OsPath.createOrThrow(OsType.MSYS, "../home/admin/.kscript").convert(OsType.WINDOWS).path + OsPath.of(OsType.MSYS, "../home/admin/.kscript").convert(OsType.WINDOWS).path ).isEqualTo("..\\home\\admin\\.kscript") } @@ -358,12 +358,12 @@ class OsPathTest { @Test fun `Special cases`() { //Empty path - assertThat(OsPath.createOrThrow(OsType.LINUX)).let { + assertThat(OsPath.of(OsType.LINUX)).let { it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(emptyList()) it.prop(OsPath::isRelative).isTrue() it.prop(OsPath::isAbsolute).isFalse() - it.prop(OsPath::osType).isEqualTo(OsType.ANY) + it.prop(OsPath::osType).isEqualTo(OsType.LINUX) } } @@ -371,8 +371,8 @@ class OsPathTest { @Test fun `Concatenate paths`() { - val p = OsPath.createOrThrow(OsType.MSYS, "/c/home") - val p1 = OsPath.createOrThrow(OsType.MSYS, "admin/.kscript") + val p = OsPath.of(OsType.MSYS, "/c/home") + val p1 = OsPath.of(OsType.MSYS, "admin/.kscript") assertThat(p / p1).let { it.prop(OsPath::root).isEqualTo("/") From 23b8a359222124e4b65b4784e9361d86e752ee06 Mon Sep 17 00:00:00 2001 From: Marcin Kuszczak <1508798+aartiPl@users.noreply.github.com> Date: Thu, 3 Aug 2023 00:18:50 +0200 Subject: [PATCH 27/50] Reverted OsPath changes --- build.gradle.kts | 5 +- .../shell/integration/ShellExecutorTest.kt | 2 +- .../shell/integration/tools/TestContext.kt | 4 +- src/main/kotlin/io/github/kscripting/os/Os.kt | 19 ++ .../github/kscripting/os/instance/CygwinOs.kt | 15 + .../github/kscripting/os/instance/HostedOs.kt | 14 + .../github/kscripting/os/instance/LinuxOs.kt | 13 + .../github/kscripting/os/instance/MsysOs.kt | 15 + .../kscripting/os/instance/WindowsOs.kt | 14 + .../io/github/kscripting/os/model/OsPath.kt | 245 +++++++++++++++ .../github/kscripting/os/model/OsPathExt.kt | 64 ++++ .../kscripting/{shell => os}/model/OsType.kt | 4 +- .../io/github/kscripting/os/model/PathType.kt | 6 + .../github/kscripting/shell/ShellExecutor.kt | 4 +- .../github/kscripting/shell/model/OsPath.kt | 9 - .../kscripting/shell/model/OsPathExt.kt | 295 ------------------ .../kscripting/shell/process/ProcessRunner.kt | 4 +- .../{shell => os}/model/OsPathTest.kt | 243 ++++++--------- 18 files changed, 505 insertions(+), 470 deletions(-) create mode 100644 src/main/kotlin/io/github/kscripting/os/Os.kt create mode 100644 src/main/kotlin/io/github/kscripting/os/instance/CygwinOs.kt create mode 100644 src/main/kotlin/io/github/kscripting/os/instance/HostedOs.kt create mode 100644 src/main/kotlin/io/github/kscripting/os/instance/LinuxOs.kt create mode 100644 src/main/kotlin/io/github/kscripting/os/instance/MsysOs.kt create mode 100644 src/main/kotlin/io/github/kscripting/os/instance/WindowsOs.kt create mode 100644 src/main/kotlin/io/github/kscripting/os/model/OsPath.kt create mode 100644 src/main/kotlin/io/github/kscripting/os/model/OsPathExt.kt rename src/main/kotlin/io/github/kscripting/{shell => os}/model/OsType.kt (89%) create mode 100644 src/main/kotlin/io/github/kscripting/os/model/PathType.kt delete mode 100644 src/main/kotlin/io/github/kscripting/shell/model/OsPath.kt delete mode 100644 src/main/kotlin/io/github/kscripting/shell/model/OsPathExt.kt rename src/test/kotlin/io/github/kscripting/{shell => os}/model/OsPathTest.kt (54%) diff --git a/build.gradle.kts b/build.gradle.kts index 2415fa0..4c9932e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -10,6 +10,7 @@ plugins { } repositories { + mavenLocal() mavenCentral() } @@ -154,13 +155,13 @@ signing { } dependencies { + api("net.igsoft:typeutils:0.6.0-SNAPSHOT") + implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlinVersion") implementation("org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4") - implementation("io.arrow-kt:arrow-core:1.1.2") implementation("org.apache.commons:commons-lang3:3.12.0") - implementation("org.slf4j:slf4j-nop:2.0.5") testImplementation("org.junit.platform:junit-platform-suite-engine:1.9.0") diff --git a/src/itest/kotlin/io/github/kscripting/shell/integration/ShellExecutorTest.kt b/src/itest/kotlin/io/github/kscripting/shell/integration/ShellExecutorTest.kt index 9d554d0..02c571f 100644 --- a/src/itest/kotlin/io/github/kscripting/shell/integration/ShellExecutorTest.kt +++ b/src/itest/kotlin/io/github/kscripting/shell/integration/ShellExecutorTest.kt @@ -1,10 +1,10 @@ package io.github.kscripting.shell.integration +import io.github.kscripting.os.model.readText import io.github.kscripting.shell.integration.tools.ShellTestBase import io.github.kscripting.shell.integration.tools.TestContext import io.github.kscripting.shell.integration.tools.TestContext.execPath import io.github.kscripting.shell.integration.tools.TestContext.testPath -import io.github.kscripting.shell.model.readText import io.github.kscripting.shell.util.Sanitizer import org.junit.jupiter.api.Tag import org.junit.jupiter.api.Test diff --git a/src/itest/kotlin/io/github/kscripting/shell/integration/tools/TestContext.kt b/src/itest/kotlin/io/github/kscripting/shell/integration/tools/TestContext.kt index 21fe7fb..b3fc977 100644 --- a/src/itest/kotlin/io/github/kscripting/shell/integration/tools/TestContext.kt +++ b/src/itest/kotlin/io/github/kscripting/shell/integration/tools/TestContext.kt @@ -1,14 +1,14 @@ package io.github.kscripting.shell.integration.tools +import io.github.kscripting.os.model.* import io.github.kscripting.shell.ShellExecutor -import io.github.kscripting.shell.model.* import io.github.kscripting.shell.util.Sanitizer @Suppress("MemberVisibilityCanBePrivate") object TestContext { val osType: OsType = OsType.find(System.getProperty("osType")) ?: OsType.native - val projectPath: OsPath = OsPath.of(OsType.native, System.getProperty("projectPath")).convert(osType) + val projectPath: OsPath = OsPath.createOrThrow(OsType.native, System.getProperty("projectPath")).convert(osType) val execPath: OsPath = projectPath.resolve("build/shell_test/bin") val testPath: OsPath = projectPath.resolve("build/shell_test/tmp") diff --git a/src/main/kotlin/io/github/kscripting/os/Os.kt b/src/main/kotlin/io/github/kscripting/os/Os.kt new file mode 100644 index 0000000..4a22155 --- /dev/null +++ b/src/main/kotlin/io/github/kscripting/os/Os.kt @@ -0,0 +1,19 @@ +package io.github.kscripting.os + +import io.github.kscripting.os.model.OsPath +import io.github.kscripting.os.model.OsType +import net.igsoft.typeutils.marker.AutoTypedMarker +import net.igsoft.typeutils.marker.TypedMarker + +interface Os { + val type: OsType + + val userHome: OsPath + val pathSeparator: String + + fun path(vararg pathParts: String): OsPath + + companion object { + val CURRENT_OS = AutoTypedMarker.create() + } +} diff --git a/src/main/kotlin/io/github/kscripting/os/instance/CygwinOs.kt b/src/main/kotlin/io/github/kscripting/os/instance/CygwinOs.kt new file mode 100644 index 0000000..52f055e --- /dev/null +++ b/src/main/kotlin/io/github/kscripting/os/instance/CygwinOs.kt @@ -0,0 +1,15 @@ +package io.github.kscripting.os.instance + +import io.github.kscripting.os.model.OsPath +import io.github.kscripting.os.model.OsType + +class CygwinOs(userHome: String, nativeFileSystemRoot: String) : HostedOs { + override val type: OsType = OsType.CYGWIN + override val userHome: OsPath = path(userHome) + override val nativeType: OsType = OsType.WINDOWS + override val nativeFileSystemRoot: OsPath = TODO() + + override fun path(vararg pathParts: String): OsPath = TODO() + override fun toNativePath(osPath: OsPath): OsPath = TODO() + override fun toHostedPath(osPath: OsPath): OsPath = TODO() +} diff --git a/src/main/kotlin/io/github/kscripting/os/instance/HostedOs.kt b/src/main/kotlin/io/github/kscripting/os/instance/HostedOs.kt new file mode 100644 index 0000000..5824179 --- /dev/null +++ b/src/main/kotlin/io/github/kscripting/os/instance/HostedOs.kt @@ -0,0 +1,14 @@ +package io.github.kscripting.os.instance + +import io.github.kscripting.os.Os +import io.github.kscripting.os.model.OsPath +import io.github.kscripting.os.model.OsType + +interface HostedOs : Os { + val nativeType: OsType + val nativeFileSystemRoot: OsPath + override val pathSeparator get(): String = "/" + + fun toNativePath(osPath: OsPath): OsPath + fun toHostedPath(osPath: OsPath): OsPath +} diff --git a/src/main/kotlin/io/github/kscripting/os/instance/LinuxOs.kt b/src/main/kotlin/io/github/kscripting/os/instance/LinuxOs.kt new file mode 100644 index 0000000..55efbe1 --- /dev/null +++ b/src/main/kotlin/io/github/kscripting/os/instance/LinuxOs.kt @@ -0,0 +1,13 @@ +package io.github.kscripting.os.instance + +import io.github.kscripting.os.Os +import io.github.kscripting.os.model.OsPath +import io.github.kscripting.os.model.OsType + +class LinuxOs(userHome: String) : Os { + override val type: OsType = OsType.LINUX + override val pathSeparator get() = "/" + override val userHome: OsPath = path(userHome) + + override fun path(vararg pathParts: String): OsPath = TODO() +} diff --git a/src/main/kotlin/io/github/kscripting/os/instance/MsysOs.kt b/src/main/kotlin/io/github/kscripting/os/instance/MsysOs.kt new file mode 100644 index 0000000..6e341d8 --- /dev/null +++ b/src/main/kotlin/io/github/kscripting/os/instance/MsysOs.kt @@ -0,0 +1,15 @@ +package io.github.kscripting.os.instance + +import io.github.kscripting.os.model.OsPath +import io.github.kscripting.os.model.OsType + +class MsysOs(userHome: String, nativeFileSystemRoot: String) : HostedOs { + override val type: OsType = OsType.MSYS + override val userHome: OsPath = path(userHome) + override val nativeType: OsType = OsType.WINDOWS + override val nativeFileSystemRoot: OsPath = TODO() + + override fun path(vararg pathParts: String): OsPath = TODO() + override fun toNativePath(osPath: OsPath): OsPath = TODO() + override fun toHostedPath(osPath: OsPath): OsPath = TODO() +} diff --git a/src/main/kotlin/io/github/kscripting/os/instance/WindowsOs.kt b/src/main/kotlin/io/github/kscripting/os/instance/WindowsOs.kt new file mode 100644 index 0000000..0eb7531 --- /dev/null +++ b/src/main/kotlin/io/github/kscripting/os/instance/WindowsOs.kt @@ -0,0 +1,14 @@ +package io.github.kscripting.os.instance + +import io.github.kscripting.os.Os +import io.github.kscripting.os.model.OsPath +import io.github.kscripting.os.model.OsType + + +class WindowsOs(userHome: String) : Os { + override val type: OsType = OsType.WINDOWS + override val pathSeparator get() = "\\" + override val userHome: OsPath = path(userHome) + + override fun path(vararg pathParts: String): OsPath = TODO() +} diff --git a/src/main/kotlin/io/github/kscripting/os/model/OsPath.kt b/src/main/kotlin/io/github/kscripting/os/model/OsPath.kt new file mode 100644 index 0000000..bb416fc --- /dev/null +++ b/src/main/kotlin/io/github/kscripting/os/model/OsPath.kt @@ -0,0 +1,245 @@ +package io.github.kscripting.os.model + +import arrow.core.Either +import arrow.core.left +import arrow.core.right + +//Path representation for different OSes +@Suppress("MemberVisibilityCanBePrivate") +data class OsPath(val osType: OsType, val root: String, val pathParts: List) { + val isRelative: Boolean get() = root.isEmpty() + val isAbsolute: Boolean get() = !isRelative + val pathSeparator: Char get() = if (osType.isWindowsLike()) '\\' else '/' + val path get(): String = root + pathParts.joinToString(pathSeparator.toString()) { it } + + val OsPath.leaf get(): String = if (pathParts.isEmpty()) root else pathParts.last() + + operator fun div(osPath: OsPath): OsPath = resolve(osPath) + operator fun div(path: String): OsPath = resolve(path) + + fun resolve(vararg pathParts: String): OsPath = resolve(createOrThrow(osType, *pathParts)) + + fun resolve(osPath: OsPath): OsPath { + require(osType == osPath.osType) { + "Paths from different OS's: '${osType.name}' path can not be resolved with '${osPath.osType.name}' path" + } + + require(osPath.isRelative) { + "Can not resolve absolute or relative path '${path}' using absolute path '${osPath.path}'" + } + + val newPathParts = buildList { + addAll(pathParts) + addAll(osPath.pathParts) + } + + val normalizedPath = when (val result = normalize(root, newPathParts)) { + is Either.Right -> result.value + is Either.Left -> throw IllegalArgumentException(result.value) + } + + return OsPath(osType, root, normalizedPath) + } + + //Not all conversions make sense: only Windows to CygWin and Msys and vice versa + //TODO: conversion of paths like /usr /opt etc. is wrong; it needs also windows root of installation cygwin/msys + // base path: cygpath -w / + fun convert(targetOsType: OsType /*nativeRootPath: OsPath = emptyPath*/): OsPath { + if (osType == targetOsType) { + return this + } + + if ((osType.isPosixLike() && targetOsType.isPosixLike()) || (osType.isWindowsLike() && targetOsType.isWindowsLike())) { + return OsPath(targetOsType, root, pathParts) + } + + val toHosted = osType.isWindowsLike() && targetOsType.isPosixHostedOnWindows() + val toNative = osType.isPosixHostedOnWindows() && targetOsType.isWindowsLike() + + require(toHosted || toNative) { + "Only paths conversion between Windows and Posix hosted on Windows are supported" + } + + val newParts = mutableListOf() + var newRoot = "" + + when { + toHosted -> { + if (isAbsolute) { + //root is like 'C:\' + val drive = root.dropLast(2).lowercase() + + newRoot = "/" + + if (targetOsType == OsType.CYGWIN) { + newParts.add("cygdrive") + newParts.add(drive) + } else { + newParts.add(drive) + } + } + + newParts.addAll(pathParts) + } + + toNative -> { + if (isAbsolute) { + if (osType == OsType.CYGWIN) { + newRoot = pathParts[1] + ":\\" + newParts.addAll(pathParts.subList(2, pathParts.size)) + } else { + newRoot = pathParts[0] + ":\\" + newParts.addAll(pathParts.subList(1, pathParts.size)) + } + } else { + newParts.addAll(pathParts) + } + } + + else -> throw IllegalArgumentException("Invalid conversion: $this to ${targetOsType.name}") + } + + return OsPath(targetOsType, newRoot, newParts) + } + + override fun toString(): String = "$path [${osType.name}]" + + companion object { + //https://stackoverflow.com/questions/1976007/what-characters-are-forbidden-in-windows-and-linux-directory-names + //The rule here is more strict than necessary, but it is at least good practice to follow such a rule. + //TODO: should I remove validation all together? It allows e.g. for globbing with asterisks and question marks + // maybe separate function in FileSystem: validate? + private val forbiddenCharacters = buildSet { + add('<') + add('>') + add(':') + add('"') + add('|') + add('?') + add('*') + for (i in 0 until 32) { + add(i.toChar()) + } + } + + private val windowsDriveRegex = + "^([a-zA-Z]:(?=[\\\\/])|\\\\\\\\(?:[^*:<>?\\\\/|]+\\\\[^*:<>?\\\\/|]+|\\?\\\\(?:[a-zA-Z]:(?=\\\\)|(?:UNC\\\\)?[^*:<>?\\\\/|]+\\\\[^*:<>?\\\\/|]+)))".toRegex() + + + fun createOrThrow(vararg pathParts: String): OsPath = createOrThrow(OsType.native, pathParts.toList()) + + fun createOrThrow(osType: OsType, vararg pathParts: String): OsPath = createOrThrow(osType, pathParts.toList()) + + fun createOrThrow(osType: OsType, pathParts: List): OsPath { + return when (val result = internalCreate(osType, pathParts)) { + is Either.Right -> result.value + is Either.Left -> throw IllegalArgumentException(result.value) + } + } + + fun create(vararg pathParts: String): OsPath? = create(OsType.native, pathParts.toList()) + + fun create(osType: OsType, vararg pathParts: String): OsPath? = create(osType, pathParts.toList()) + + fun create(osType: OsType, pathParts: List): OsPath? { + return when (val result = internalCreate(osType, pathParts)) { + is Either.Right -> result.value + is Either.Left -> null + } + } + + //Relaxed validation: + //1. It doesn't matter if there is '/' or '\' used as path separator - both are treated the same + //2. Duplicated or trailing slashes '/' and backslashes '\' are just ignored + private fun internalCreate(osType: OsType, pathParts: List): Either { + val path = pathParts.joinToString("/") + + //Detect root + val root: String = when { + path.startsWith("~/") || path.startsWith("~\\") -> "~" + osType.isPosixLike() && path.startsWith("/") -> "/" + osType.isWindowsLike() -> { + val match = windowsDriveRegex.find(path) + val matchedRoot = match?.groupValues?.get(1) + if (matchedRoot != null) matchedRoot + "\\" else "" + } + + else -> "" + } + + //TODO: https://learn.microsoft.com/pl-pl/dotnet/standard/io/file-path-formats + // https://regex101.com/r/aU4yZ7/1 + + //Remove also empty path parts - there were duplicated or trailing slashes / backslashes in initial path + val pathPartsResolved = path.drop(root.length).split('/', '\\').filter { it.isNotBlank() } + + val normalizedPath = when (val result = normalize(root, pathPartsResolved)) { + is Either.Right -> result.value + is Either.Left -> return result.value.left() + } + + //Validate root element of path and find out if it is absolute or relative + val forbiddenCharacter = path.substring(root.length).find { forbiddenCharacters.contains(it) } + + if (forbiddenCharacter != null) { + return "Invalid character '$forbiddenCharacter' in path '$path'".left() + } + + return OsPath(osType, root, normalizedPath).right() + } + + fun normalize(root: String, pathParts: List): Either> { + //Relative: + // ./../ --> ../ + // ./a/../ --> ./ + // ./a/ --> ./a + // ../a --> ../a + // ../../a --> ../../a + + //Absolute: + // /../ --> invalid (above root) + // /a/../ --> / + + val isAbsolute: Boolean = root.isNotEmpty() + + val newParts = mutableListOf() + var index = 0 + + while (index < pathParts.size) { + if (pathParts[index] == ".") { + //Just skip . without adding it to newParts + } else if (pathParts[index] == "..") { + if (isAbsolute && newParts.size == 0) { + return "Path after normalization goes beyond root element: '$root'".left() + } + + if (newParts.size > 0) { + when (newParts.last()) { + "." -> { + //It's the first element - other dots should be already removed before + newParts.removeAt(newParts.size - 1) + newParts.add("..") + } + + ".." -> { + newParts.add("..") + } + + else -> { + newParts.removeAt(newParts.size - 1) + } + } + } else { + newParts.add("..") + } + } else { + newParts.add(pathParts[index]) + } + + index += 1 + } + + return newParts.right() + } + } +} diff --git a/src/main/kotlin/io/github/kscripting/os/model/OsPathExt.kt b/src/main/kotlin/io/github/kscripting/os/model/OsPathExt.kt new file mode 100644 index 0000000..dee436f --- /dev/null +++ b/src/main/kotlin/io/github/kscripting/os/model/OsPathExt.kt @@ -0,0 +1,64 @@ +package io.github.kscripting.os.model + +import java.io.File +import java.net.URI +import java.nio.charset.Charset +import java.nio.file.OpenOption +import java.nio.file.Path +import java.nio.file.Paths +import kotlin.io.path.* + + +// Conversion to OsPath + +fun File.toOsPath(): OsPath = OsPath.createOrThrow(OsType.native, absolutePath) + +fun Path.toOsPath(): OsPath = OsPath.createOrThrow(OsType.native, absolutePathString()) + +fun URI.toOsPath(): OsPath = + if (this.scheme == "file") File(this).toOsPath() else throw IllegalArgumentException("Invalid conversion from URL to OsPath") + + +// Conversion from OsPath + +fun OsPath.toNativePath(): Path = Paths.get(toNativeOsPath().path) + +fun OsPath.toNativeOsPath() = if (osType.isPosixHostedOnWindows()) convert(OsType.WINDOWS) else this + +fun OsPath.toNativeFile(): File = toNativePath().toFile() + + +// OsPath operations + +fun OsPath.exists() = toNativePath().exists() + +fun OsPath.createDirectories(): OsPath = OsPath.createOrThrow(nativeType, toNativePath().createDirectories().pathString) + +fun OsPath.copyTo(target: OsPath, overwrite: Boolean = false): OsPath = + OsPath.createOrThrow(nativeType, toNativePath().copyTo(target.toNativePath(), overwrite).pathString) + +fun OsPath.writeText(text: CharSequence, charset: Charset = Charsets.UTF_8, vararg options: OpenOption): Unit = + toNativePath().writeText(text, charset, *options) + +fun OsPath.readText(charset: Charset = Charsets.UTF_8): String = toNativePath().readText(charset) + + +// OsPath accessors + +val OsPath.leaf + get() = if (pathParts.isEmpty()) "" else pathParts.last() + +//val OsPath.root +// get() = if (pathParts.isEmpty()) "" else pathParts.first() + +//val OsPath.rootOsPath +// get() = OsPath.createOrThrow(osType, root) + +val OsPath.parent + get() = toNativePath().parent + +val OsPath.nativeType + get() = if (osType.isPosixHostedOnWindows()) OsType.WINDOWS else osType + +val OsPath.extension + get() = leaf.substringAfterLast('.', "") diff --git a/src/main/kotlin/io/github/kscripting/shell/model/OsType.kt b/src/main/kotlin/io/github/kscripting/os/model/OsType.kt similarity index 89% rename from src/main/kotlin/io/github/kscripting/shell/model/OsType.kt rename to src/main/kotlin/io/github/kscripting/os/model/OsType.kt index 016f26d..313da6d 100644 --- a/src/main/kotlin/io/github/kscripting/shell/model/OsType.kt +++ b/src/main/kotlin/io/github/kscripting/os/model/OsType.kt @@ -1,9 +1,9 @@ -package io.github.kscripting.shell.model +package io.github.kscripting.os.model import org.apache.commons.lang3.SystemUtils enum class OsType(val osName: String) { - LINUX("linux"), FREEBSD("freebsd"), MACOS("darwin"), CYGWIN("cygwin"), MSYS("msys"), WINDOWS("windows"); + LINUX("linux"), MACOS("darwin"), WINDOWS("windows"), CYGWIN("cygwin"), MSYS("msys"), FREEBSD("freebsd"); fun isPosixLike() = (this == LINUX || this == MACOS || this == FREEBSD || this == CYGWIN || this == MSYS) fun isPosixHostedOnWindows() = (this == CYGWIN || this == MSYS) diff --git a/src/main/kotlin/io/github/kscripting/os/model/PathType.kt b/src/main/kotlin/io/github/kscripting/os/model/PathType.kt new file mode 100644 index 0000000..4bb9b31 --- /dev/null +++ b/src/main/kotlin/io/github/kscripting/os/model/PathType.kt @@ -0,0 +1,6 @@ +package io.github.kscripting.os.model + + +enum class PathType { + ABSOLUTE, RELATIVE, UNDEFINED +} diff --git a/src/main/kotlin/io/github/kscripting/shell/ShellExecutor.kt b/src/main/kotlin/io/github/kscripting/shell/ShellExecutor.kt index 391a4dc..2f02f21 100644 --- a/src/main/kotlin/io/github/kscripting/shell/ShellExecutor.kt +++ b/src/main/kotlin/io/github/kscripting/shell/ShellExecutor.kt @@ -1,7 +1,7 @@ package io.github.kscripting.shell -import io.github.kscripting.shell.model.OsPath -import io.github.kscripting.shell.model.OsType +import io.github.kscripting.os.model.OsPath +import io.github.kscripting.os.model.OsType import io.github.kscripting.shell.model.ProcessResult import io.github.kscripting.shell.model.ShellType import io.github.kscripting.shell.process.EnvAdjuster diff --git a/src/main/kotlin/io/github/kscripting/shell/model/OsPath.kt b/src/main/kotlin/io/github/kscripting/shell/model/OsPath.kt deleted file mode 100644 index 00625a0..0000000 --- a/src/main/kotlin/io/github/kscripting/shell/model/OsPath.kt +++ /dev/null @@ -1,9 +0,0 @@ -package io.github.kscripting.shell.model - -//Path representation for different OSes -data class OsPath(val osType: OsType, val root: String, val pathParts: List) { - val isRelative: Boolean get() = root.isEmpty() - val isAbsolute: Boolean get() = !isRelative - - companion object -} diff --git a/src/main/kotlin/io/github/kscripting/shell/model/OsPathExt.kt b/src/main/kotlin/io/github/kscripting/shell/model/OsPathExt.kt deleted file mode 100644 index 7e0c9d3..0000000 --- a/src/main/kotlin/io/github/kscripting/shell/model/OsPathExt.kt +++ /dev/null @@ -1,295 +0,0 @@ -package io.github.kscripting.shell.model - -import arrow.core.Either -import arrow.core.left -import arrow.core.right -import java.io.File -import java.net.URI -import java.nio.charset.Charset -import java.nio.file.OpenOption -import java.nio.file.Path -import java.nio.file.Paths -import kotlin.io.path.* - -//Create OsPath -//https://stackoverflow.com/questions/1976007/what-characters-are-forbidden-in-windows-and-linux-directory-names -//The rule here is more strict than necessary, but it is at least good practice to follow such a rule. -//TODO: should I remove validation all together? It allows e.g. for globbing with asterisks and question marks -// maybe separate function in FileSystem: validate? -private val forbiddenCharacters = buildSet { - add('<') - add('>') - add(':') - add('"') - add('|') - add('?') - add('*') - for (i in 0 until 32) { - add(i.toChar()) - } -} - -private val windowsDriveRegex = - "^([a-zA-Z]:(?=[\\\\/])|\\\\\\\\(?:[^*:<>?\\\\/|]+\\\\[^*:<>?\\\\/|]+|\\?\\\\(?:[a-zA-Z]:(?=\\\\)|(?:UNC\\\\)?[^*:<>?\\\\/|]+\\\\[^*:<>?\\\\/|]+)))".toRegex() - - -fun OsPath.Companion.of(vararg pathParts: String): OsPath = of(OsType.native, pathParts.toList()) - -fun OsPath.Companion.of(osType: OsType, vararg pathParts: String): OsPath = of(osType, pathParts.toList()) - -fun OsPath.Companion.of(osType: OsType, pathParts: List): OsPath { - return when (val result = ofWithEither(osType, pathParts)) { - is Either.Right -> result.value - is Either.Left -> throw result.value - } -} - -fun OsPath.Companion.ofOrNull(vararg pathParts: String): OsPath? = ofOrNull(OsType.native, pathParts.toList()) - -fun OsPath.Companion.ofOrNull(osType: OsType, vararg pathParts: String): OsPath? = ofOrNull(osType, pathParts.toList()) - -fun OsPath.Companion.ofOrNull(osType: OsType, pathParts: List): OsPath? { - return when (val result = ofWithEither(osType, pathParts)) { - is Either.Right -> result.value - is Either.Left -> null - } -} - -//Relaxed validation: -//1. It doesn't matter if there is '/' or '\' used as path separator - both are treated the same -//2. Duplicated trailing slashes '/' and backslashes '\' are just ignored -fun OsPath.Companion.ofWithEither(osType: OsType, pathParts: List): Either { - val path = pathParts.joinToString("/") - - //Detect root - val root: String = when { - path.startsWith("~/") || path.startsWith("~\\") -> "~" - osType.isPosixLike() && path.startsWith("/") -> "/" - osType.isWindowsLike() -> { - val match = windowsDriveRegex.find(path) - val matchedRoot = match?.groupValues?.get(1) - if (matchedRoot != null) matchedRoot + "\\" else "" - } - else -> "" - } - - //TODO: https://learn.microsoft.com/pl-pl/dotnet/standard/io/file-path-formats - // https://regex101.com/r/aU4yZ7/1 - - //Remove also empty path parts - there were duplicated or trailing slashes / backslashes in initial path - val pathPartsResolved = path.drop(root.length).split('/', '\\').filter { it.isNotBlank() } - - val normalizedPath = when (val result = normalize(root, pathPartsResolved)) { - is Either.Right -> result.value - is Either.Left -> return result.value.left() - } - - //Validate root element of path and find out if it is absolute or relative - val forbiddenCharacter = path.substring(root.length).find { forbiddenCharacters.contains(it) } - - if (forbiddenCharacter != null) { - return IllegalArgumentException("Invalid character '$forbiddenCharacter' in path '$path'").left() - } - - return OsPath(osType, root, normalizedPath).right() -} - -fun OsPath.Companion.normalize(root: String, pathParts: List): Either> { - //Relative: - // ./../ --> ../ - // ./a/../ --> ./ - // ./a/ --> ./a - // ../a --> ../a - // ../../a --> ../../a - - //Absolute: - // /../ --> invalid (above root) - // /a/../ --> / - - val isAbsolute: Boolean = root.isNotEmpty() - - val newParts = mutableListOf() - var index = 0 - - while (index < pathParts.size) { - if (pathParts[index] == ".") { - //Just skip . without adding it to newParts - } else if (pathParts[index] == "..") { - if (isAbsolute && newParts.size == 0) { - return IllegalArgumentException("Path after normalization goes beyond root element: '$root'").left() - } - - if (newParts.size > 0) { - when (newParts.last()) { - "." -> { - //It's the first element - other dots should be already removed before - newParts.removeAt(newParts.size - 1) - newParts.add("..") - } - - ".." -> { - newParts.add("..") - } - - else -> { - newParts.removeAt(newParts.size - 1) - } - } - } else { - newParts.add("..") - } - } else { - newParts.add(pathParts[index]) - } - - index += 1 - } - - return newParts.right() -} - -// Resolve OsPath -operator fun OsPath.div(osPath: OsPath): OsPath = resolve(osPath) -operator fun OsPath.div(path: String): OsPath = resolve(path) - -fun OsPath.resolve(vararg pathParts: String): OsPath = resolve(OsPath.of(osType, *pathParts)) - -fun OsPath.resolve(osPath: OsPath): OsPath { - require(osType == osPath.osType) { - "Paths from different OS's: '${osType.name}' path can not be resolved with '${osPath.osType.name}' path" - } - - require(osPath.isRelative) { - "Can not resolve absolute or relative path '${path}' using absolute path '${osPath.path}'" - } - - val newPathParts = buildList { - addAll(pathParts) - addAll(osPath.pathParts) - } - - val normalizedPath = when (val result = OsPath.normalize(root, newPathParts)) { - is Either.Right -> result.value - is Either.Left -> throw result.value - } - - return OsPath(osType, root, normalizedPath) -} - -// Convert OsPath - -//Not all conversions make sense: only Windows to CygWin and Msys and vice versa -//TODO: conversion of paths like /usr /opt etc. is wrong; it needs also windows root of installation cygwin/msys -// base path: cygpath -w / -fun OsPath.convert(targetOsType: OsType /*nativeRootPath: OsPath = emptyPath*/): OsPath { - if (osType == targetOsType) { - return this - } - - if ((osType.isPosixLike() && targetOsType.isPosixLike()) || (osType.isWindowsLike() && targetOsType.isWindowsLike())) { - return OsPath(targetOsType, root, pathParts) - } - - val toHosted = osType.isWindowsLike() && targetOsType.isPosixHostedOnWindows() - val toNative = osType.isPosixHostedOnWindows() && targetOsType.isWindowsLike() - - require(toHosted || toNative) { - "Only paths conversion between Windows and Posix hosted on Windows are supported" - } - - val newParts = mutableListOf() - var newRoot = "" - - when { - toHosted -> { - if (isAbsolute) { - //root is like 'C:\' - val drive = root.dropLast(2).lowercase() - - newRoot = "/" - - if (targetOsType == OsType.CYGWIN) { - newParts.add("cygdrive") - newParts.add(drive) - } else { - newParts.add(drive) - } - } - - newParts.addAll(pathParts) - } - - toNative -> { - if (isAbsolute) { - if (osType == OsType.CYGWIN) { - newRoot = pathParts[1] + ":\\" - newParts.addAll(pathParts.subList(2, pathParts.size)) - } else { - newRoot = pathParts[0] + ":\\" - newParts.addAll(pathParts.subList(1, pathParts.size)) - } - } else { - newParts.addAll(pathParts) - } - } - - else -> throw IllegalArgumentException("Invalid conversion: $this to ${targetOsType.name}") - } - - return OsPath(targetOsType, newRoot, newParts) -} - - -// Interact with file system -fun File.toOsPath(): OsPath = OsPath.of(OsType.native, absolutePath) - -fun Path.toOsPath(): OsPath = OsPath.of(OsType.native, absolutePathString()) - -fun URI.toOsPath(): OsPath = - if (this.scheme == "file") File(this).toOsPath() else throw IllegalArgumentException("Invalid conversion from URL to OsPath") - -fun String.toOsPath(osType: OsType = OsType.native): OsPath = - OsPath.of(osType, this) - - -// Conversion from OsPath -fun OsPath.toNativePath(): Path = Paths.get(toNativeOsPath().path) - -fun OsPath.toNativeFile(): File = toNativePath().toFile() - -fun OsPath.toNativeOsPath(): OsPath = if (osType.isPosixHostedOnWindows()) convert(OsType.WINDOWS) else this - - -// OsPath operations -fun OsPath.exists(): Boolean = toNativePath().exists() - -fun OsPath.createDirectories(): OsPath = OsPath.of(nativeType, toNativePath().createDirectories().pathString) -fun OsPath.deleteRecursively(): Boolean = this.toNativeFile().deleteRecursively() -fun OsPath.readBytes(): ByteArray = this.toNativeFile().readBytes() - -fun OsPath.copyTo(target: OsPath, overwrite: Boolean = false): OsPath = - OsPath.of(nativeType, toNativePath().copyTo(target.toNativePath(), overwrite).pathString) - -fun OsPath.writeText(text: CharSequence, charset: Charset = Charsets.UTF_8, vararg options: OpenOption): Unit = - toNativePath().writeText(text, charset, *options) - -fun OsPath.readText(charset: Charset = Charsets.UTF_8): String = toNativePath().readText(charset) - - -// OsPath accessors -val OsPath.leaf get(): String = if (pathParts.isEmpty()) root else pathParts.last() - -//val OsPath.rootOsPath -// get():OsPath = if (isRelative) OsPath.emptyPath else OsPath.of(osType, root) - -val OsPath.parent - get(): OsPath = OsPath.of(osType, pathParts.dropLast(1)) - -val OsPath.nativeType - get(): OsType = if (osType.isPosixHostedOnWindows()) OsType.WINDOWS else osType - -val OsPath.extension - get(): String? = leaf.substringAfterLast('.', "") - -val OsPath.path: String - get() = root + pathParts.joinToString((if (osType.isWindowsLike()) "\\" else "/")) { it } diff --git a/src/main/kotlin/io/github/kscripting/shell/process/ProcessRunner.kt b/src/main/kotlin/io/github/kscripting/shell/process/ProcessRunner.kt index 8ab88bc..92bf4f2 100644 --- a/src/main/kotlin/io/github/kscripting/shell/process/ProcessRunner.kt +++ b/src/main/kotlin/io/github/kscripting/shell/process/ProcessRunner.kt @@ -1,8 +1,8 @@ package io.github.kscripting.shell.process import io.github.kscripting.shell.model.CommandTimeoutException -import io.github.kscripting.shell.model.OsPath -import io.github.kscripting.shell.model.toNativeFile +import io.github.kscripting.os.model.OsPath +import io.github.kscripting.os.model.toNativeFile import io.github.kscripting.shell.util.Sanitizer import io.github.kscripting.shell.util.Sanitizer.Companion.EMPTY_SANITIZER import java.io.InputStream diff --git a/src/test/kotlin/io/github/kscripting/shell/model/OsPathTest.kt b/src/test/kotlin/io/github/kscripting/os/model/OsPathTest.kt similarity index 54% rename from src/test/kotlin/io/github/kscripting/shell/model/OsPathTest.kt rename to src/test/kotlin/io/github/kscripting/os/model/OsPathTest.kt index 8c5ad1f..556af6d 100644 --- a/src/test/kotlin/io/github/kscripting/shell/model/OsPathTest.kt +++ b/src/test/kotlin/io/github/kscripting/os/model/OsPathTest.kt @@ -1,4 +1,4 @@ -package io.github.kscripting.shell.model +package io.github.kscripting.os.model import assertk.assertThat import assertk.assertions.* @@ -8,169 +8,146 @@ class OsPathTest { // ************************************************** LINUX PATHS ************************************************** @Test fun `Test Linux paths`() { - assertThat(OsPath.of(OsType.LINUX, "/")).let { + assertThat(OsPath.createOrThrow(OsType.LINUX, "/")).let { + it.prop(OsPath::osType).isEqualTo(OsType.LINUX) it.prop(OsPath::root).isEqualTo("/") it.prop(OsPath::pathParts).isEqualTo(emptyList()) - it.prop(OsPath::isAbsolute).isTrue() - it.prop(OsPath::isRelative).isFalse() - it.prop(OsPath::osType).isEqualTo(OsType.LINUX) } - assertThat(OsPath.of(OsType.LINUX, "/home/admin/.kscript")).let { + assertThat(OsPath.createOrThrow(OsType.LINUX, "/home/admin/.kscript")).let { + it.prop(OsPath::osType).isEqualTo(OsType.LINUX) it.prop(OsPath::root).isEqualTo("/") it.prop(OsPath::pathParts).isEqualTo(listOf("home", "admin", ".kscript")) - it.prop(OsPath::isAbsolute).isTrue() - it.prop(OsPath::osType).isEqualTo(OsType.LINUX) } - assertThat(OsPath.of(OsType.LINUX, "./home/admin/.kscript")).let { + assertThat(OsPath.createOrThrow(OsType.LINUX, "./home/admin/.kscript")).let { + it.prop(OsPath::osType).isEqualTo(OsType.LINUX) it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(listOf("home", "admin", ".kscript")) - it.prop(OsPath::isRelative).isTrue() - it.prop(OsPath::osType).isEqualTo(OsType.LINUX) } - assertThat(OsPath.of(OsType.LINUX, "")).let { + assertThat(OsPath.createOrThrow(OsType.LINUX, "")).let { + it.prop(OsPath::osType).isEqualTo(OsType.LINUX) it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(emptyList()) - it.prop(OsPath::isRelative).isTrue() - it.prop(OsPath::isAbsolute).isFalse() - it.prop(OsPath::osType).isEqualTo(OsType.LINUX) } - assertThat(OsPath.of(OsType.LINUX, "file.txt")).let { + assertThat(OsPath.createOrThrow(OsType.LINUX, "file.txt")).let { + it.prop(OsPath::osType).isEqualTo(OsType.LINUX) it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(listOf("file.txt")) - it.prop(OsPath::isRelative).isTrue() - it.prop(OsPath::osType).isEqualTo(OsType.LINUX) } - assertThat(OsPath.of(OsType.LINUX, ".")).let { + assertThat(OsPath.createOrThrow(OsType.LINUX, ".")).let { + it.prop(OsPath::osType).isEqualTo(OsType.LINUX) it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(emptyList()) - it.prop(OsPath::isRelative).isTrue() - it.prop(OsPath::osType).isEqualTo(OsType.LINUX) } - assertThat(OsPath.of(OsType.LINUX, "../home/admin/.kscript")).let { + assertThat(OsPath.createOrThrow(OsType.LINUX, "../home/admin/.kscript")).let { + it.prop(OsPath::osType).isEqualTo(OsType.LINUX) it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(listOf("..", "home", "admin", ".kscript")) - it.prop(OsPath::isRelative).isTrue() - it.prop(OsPath::osType).isEqualTo(OsType.LINUX) } - assertThat(OsPath.of(OsType.LINUX, "..")).let { + assertThat(OsPath.createOrThrow(OsType.LINUX, "..")).let { + it.prop(OsPath::osType).isEqualTo(OsType.LINUX) it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(listOf("..")) - it.prop(OsPath::isRelative).isTrue() - it.prop(OsPath::osType).isEqualTo(OsType.LINUX) } //Duplicated separators are accepted - assertThat(OsPath.of(OsType.LINUX, "..//home////admin/.kscript/")).let { + assertThat(OsPath.createOrThrow(OsType.LINUX, "..//home////admin/.kscript/")).let { + it.prop(OsPath::osType).isEqualTo(OsType.LINUX) it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(listOf("..", "home", "admin", ".kscript")) - it.prop(OsPath::isRelative).isTrue() - it.prop(OsPath::osType).isEqualTo(OsType.LINUX) } //Both types of separator are accepted - assertThat(OsPath.of(OsType.LINUX, "..//home\\admin\\.kscript/")).let { + assertThat(OsPath.createOrThrow(OsType.LINUX, "..//home\\admin\\.kscript/")).let { + it.prop(OsPath::osType).isEqualTo(OsType.LINUX) it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(listOf("..", "home", "admin", ".kscript")) - it.prop(OsPath::isRelative).isTrue() - it.prop(OsPath::osType).isEqualTo(OsType.LINUX) - } - - //Home dir is correctly handled - assertThat(OsPath.of(OsType.LINUX, "~/admin/.git")).let { - it.prop(OsPath::root).isEqualTo("~") - it.prop(OsPath::pathParts).isEqualTo(listOf("admin", ".git")) - it.prop(OsPath::isAbsolute).isTrue() - it.prop(OsPath::osType).isEqualTo(OsType.LINUX) } } @Test fun `Normalization of Linux paths`() { - assertThat(OsPath.of(OsType.LINUX, "/home/admin/.kscript/../../")).let { + assertThat(OsPath.createOrThrow(OsType.LINUX, "/home/admin/.kscript/../../")).let { + it.prop(OsPath::osType).isEqualTo(OsType.LINUX) it.prop(OsPath::root).isEqualTo("/") it.prop(OsPath::pathParts).isEqualTo(listOf("home")) - it.prop(OsPath::isAbsolute).isTrue() - it.prop(OsPath::osType).isEqualTo(OsType.LINUX) } - assertThat(OsPath.of(OsType.LINUX, "./././../../script")).let { - it.prop(OsPath::pathParts).isEqualTo(listOf("..", "..", "script")) - it.prop(OsPath::isRelative).isTrue() + assertThat(OsPath.createOrThrow(OsType.LINUX, "./././../../script")).let { it.prop(OsPath::osType).isEqualTo(OsType.LINUX) + it.prop(OsPath::root).isEqualTo("") + it.prop(OsPath::pathParts).isEqualTo(listOf("..", "..", "script")) } - assertThat(OsPath.of(OsType.LINUX, "/a/b/c/../d/script")).let { + assertThat(OsPath.createOrThrow(OsType.LINUX, "/a/b/c/../d/script")).let { + it.prop(OsPath::osType).isEqualTo(OsType.LINUX) it.prop(OsPath::root).isEqualTo("/") it.prop(OsPath::pathParts).isEqualTo(listOf("a", "b", "d", "script")) - it.prop(OsPath::isAbsolute).isTrue() - it.prop(OsPath::osType).isEqualTo(OsType.LINUX) } - assertThat { OsPath.of(OsType.LINUX, "/.kscript/../../") }.isFailure() + assertThat { OsPath.createOrThrow(OsType.LINUX, "/.kscript/../../") }.isFailure() .isInstanceOf(IllegalArgumentException::class.java) .hasMessage("Path after normalization goes beyond root element: '/'") } @Test fun `Test invalid Linux paths`() { - assertThat { OsPath.of(OsType.LINUX, "/ad*asdf") }.isFailure() + assertThat { OsPath.createOrThrow(OsType.LINUX, "/ad*asdf") }.isFailure() .isInstanceOf(IllegalArgumentException::class.java).hasMessage("Invalid character '*' in path '/ad*asdf'") } @Test fun `Test Linux stringPath`() { - assertThat(OsPath.of(OsType.LINUX, "/home/admin/.kscript").path) - .isEqualTo("/home/admin/.kscript") - assertThat(OsPath.of(OsType.LINUX, "/a/b/c/../d/script").path).isEqualTo("/a/b/d/script") - assertThat(OsPath.of(OsType.LINUX, "./././../../script").path).isEqualTo("../../script") - assertThat(OsPath.of(OsType.LINUX, "script/file.txt").path).isEqualTo("script/file.txt") + assertThat( + OsPath.createOrThrow(OsType.LINUX, "/home/admin/.kscript").path + ).isEqualTo("/home/admin/.kscript") + assertThat(OsPath.createOrThrow(OsType.LINUX, "/a/b/c/../d/script").path).isEqualTo("/a/b/d/script") + assertThat(OsPath.createOrThrow(OsType.LINUX, "./././../../script").path).isEqualTo("../../script") + assertThat(OsPath.createOrThrow(OsType.LINUX, "script/file.txt").path).isEqualTo("script/file.txt") } @Test fun `Test Linux resolve`() { assertThat( - OsPath.of(OsType.LINUX, "/").resolve(OsPath.of(OsType.LINUX, "./.kscript/")) - .path + OsPath.createOrThrow(OsType.LINUX, "/").resolve(OsPath.createOrThrow(OsType.LINUX, "./.kscript/")).path ).isEqualTo("/.kscript") assertThat( - OsPath.of(OsType.LINUX, "/home/admin/") - .resolve(OsPath.of(OsType.LINUX, "./.kscript/")).path + OsPath.createOrThrow(OsType.LINUX, "/home/admin/") + .resolve(OsPath.createOrThrow(OsType.LINUX, "./.kscript/")).path ).isEqualTo("/home/admin/.kscript") assertThat( - OsPath.of(OsType.LINUX, "./home/admin/") - .resolve(OsPath.of(OsType.LINUX, "./.kscript/")).path + OsPath.createOrThrow(OsType.LINUX, "./home/admin/") + .resolve(OsPath.createOrThrow(OsType.LINUX, "./.kscript/")).path ).isEqualTo("home/admin/.kscript") assertThat( - OsPath.of(OsType.LINUX, "../home/admin/") - .resolve(OsPath.of(OsType.LINUX, "./.kscript/")).path + OsPath.createOrThrow(OsType.LINUX, "../home/admin/") + .resolve(OsPath.createOrThrow(OsType.LINUX, "./.kscript/")).path ).isEqualTo("../home/admin/.kscript") assertThat( - OsPath.of(OsType.LINUX, "..").resolve(OsPath.of(OsType.LINUX, "./.kscript/")) - .path + OsPath.createOrThrow(OsType.LINUX, "..").resolve(OsPath.createOrThrow(OsType.LINUX, "./.kscript/")).path ).isEqualTo("../.kscript") assertThat( - OsPath.of(OsType.LINUX, ".").resolve(OsPath.of(OsType.LINUX, "./.kscript/")).path + OsPath.createOrThrow(OsType.LINUX, ".").resolve(OsPath.createOrThrow(OsType.LINUX, "./.kscript/")).path ).isEqualTo(".kscript") assertThat { - OsPath.of(OsType.LINUX, "./home/admin").resolve(OsPath.of(OsType.WINDOWS, ".\\run")) + OsPath.createOrThrow(OsType.LINUX, "./home/admin").resolve(OsPath.createOrThrow(OsType.WINDOWS, ".\\run")) }.isFailure().isInstanceOf(IllegalArgumentException::class.java) .hasMessage("Paths from different OS's: 'LINUX' path can not be resolved with 'WINDOWS' path") assertThat { - OsPath.of(OsType.LINUX, "./home/admin").resolve(OsPath.of(OsType.LINUX, "/run")) + OsPath.createOrThrow(OsType.LINUX, "./home/admin").resolve(OsPath.createOrThrow(OsType.LINUX, "/run")) }.isFailure().isInstanceOf(IllegalArgumentException::class.java) .hasMessage("Can not resolve absolute or relative path 'home/admin' using absolute path '/run'") } @@ -179,114 +156,101 @@ class OsPathTest { @Test fun `Test Windows paths`() { - assertThat(OsPath.of(OsType.WINDOWS, "C:\\")).let { + assertThat(OsPath.createOrThrow(OsType.WINDOWS, "C:\\")).let { + it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) it.prop(OsPath::root).isEqualTo("C:\\") it.prop(OsPath::pathParts).isEqualTo(emptyList()) - it.prop(OsPath::isAbsolute).isTrue() - it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) } - assertThat(OsPath.of(OsType.WINDOWS, "C:\\home\\admin\\.kscript")).let { + assertThat(OsPath.createOrThrow(OsType.WINDOWS, "C:\\home\\admin\\.kscript")).let { + it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) it.prop(OsPath::root).isEqualTo("C:\\") it.prop(OsPath::pathParts).isEqualTo(listOf("home", "admin", ".kscript")) - it.prop(OsPath::isAbsolute).isTrue() - it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) } - assertThat(OsPath.of(OsType.WINDOWS, "")).let { + assertThat(OsPath.createOrThrow(OsType.WINDOWS, "")).let { + it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(emptyList()) - it.prop(OsPath::isRelative).isTrue() - it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) } - assertThat(OsPath.of(OsType.WINDOWS, "file.txt")).let { + assertThat(OsPath.createOrThrow(OsType.WINDOWS, "file.txt")).let { + it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(listOf("file.txt")) - it.prop(OsPath::isRelative).isTrue() - it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) } - assertThat(OsPath.of(OsType.WINDOWS, ".")).let { + assertThat(OsPath.createOrThrow(OsType.WINDOWS, ".")).let { + it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(emptyList()) - it.prop(OsPath::isRelative).isTrue() - it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) } - assertThat(OsPath.of(OsType.WINDOWS, ".\\home\\admin\\.kscript")).let { + assertThat(OsPath.createOrThrow(OsType.WINDOWS, ".\\home\\admin\\.kscript")).let { + it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(listOf("home", "admin", ".kscript")) - it.prop(OsPath::isRelative).isTrue() - it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) } - assertThat(OsPath.of(OsType.WINDOWS, "..\\home\\admin\\.kscript")).let { + assertThat(OsPath.createOrThrow(OsType.WINDOWS, "..\\home\\admin\\.kscript")).let { + it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(listOf("..", "home", "admin", ".kscript")) - it.prop(OsPath::isRelative).isTrue() - it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) } - assertThat(OsPath.of(OsType.WINDOWS, "..")).let { + assertThat(OsPath.createOrThrow(OsType.WINDOWS, "..")).let { + it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(listOf("..")) - it.prop(OsPath::isRelative).isTrue() - it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) } //Duplicated separators are accepted - assertThat(OsPath.of(OsType.WINDOWS, "C:\\home\\\\\\\\admin\\.kscript\\")).let { + assertThat(OsPath.createOrThrow(OsType.WINDOWS, "C:\\home\\\\\\\\admin\\.kscript\\")).let { + it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) it.prop(OsPath::root).isEqualTo("C:\\") it.prop(OsPath::pathParts).isEqualTo(listOf("home", "admin", ".kscript")) - it.prop(OsPath::isAbsolute).isTrue() - it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) } //Both types of separator are accepted - assertThat(OsPath.of(OsType.WINDOWS, "C:/home\\admin/.kscript////")).let { + assertThat(OsPath.createOrThrow(OsType.WINDOWS, "C:/home\\admin/.kscript////")).let { + it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) it.prop(OsPath::root).isEqualTo("C:\\") it.prop(OsPath::pathParts).isEqualTo(listOf("home", "admin", ".kscript")) - it.prop(OsPath::isAbsolute).isTrue() - it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) } } @Test fun `Normalization of Windows paths`() { - assertThat(OsPath.of(OsType.WINDOWS, "C:\\home\\admin\\.kscript\\..\\..\\")).let { + assertThat(OsPath.createOrThrow(OsType.WINDOWS, "C:\\home\\admin\\.kscript\\..\\..\\")).let { + it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) it.prop(OsPath::root).isEqualTo("C:\\") it.prop(OsPath::pathParts).isEqualTo(listOf("home")) - it.prop(OsPath::isAbsolute).isTrue() - it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) } - assertThat(OsPath.of(OsType.WINDOWS, ".\\.\\.\\..\\..\\script")).let { + assertThat(OsPath.createOrThrow(OsType.WINDOWS, ".\\.\\.\\..\\..\\script")).let { + it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(listOf("..", "..", "script")) - it.prop(OsPath::isRelative).isTrue() - it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) } - assertThat(OsPath.of(OsType.WINDOWS, "C:\\a\\b\\c\\..\\d\\script")).let { + assertThat(OsPath.createOrThrow(OsType.WINDOWS, "C:\\a\\b\\c\\..\\d\\script")).let { + it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) it.prop(OsPath::root).isEqualTo("C:\\") it.prop(OsPath::pathParts).isEqualTo(listOf("a", "b", "d", "script")) - it.prop(OsPath::isAbsolute).isTrue() - it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) } - assertThat { OsPath.of(OsType.WINDOWS, "C:\\.kscript\\..\\..\\") }.isFailure() + assertThat { OsPath.createOrThrow(OsType.WINDOWS, "C:\\.kscript\\..\\..\\") }.isFailure() .isInstanceOf(IllegalArgumentException::class.java) .hasMessage("Path after normalization goes beyond root element: 'C:\\'") } @Test fun `Test invalid Windows paths`() { - assertThat { OsPath.of(OsType.WINDOWS, "C:\\adas?df") }.isFailure() + assertThat { OsPath.createOrThrow(OsType.WINDOWS, "C:\\adas?df") }.isFailure() .isInstanceOf(IllegalArgumentException::class.java) .hasMessage("Invalid character '?' in path 'C:\\adas?df'") - assertThat { OsPath.of(OsType.WINDOWS, "home:\\vagrant") }.isFailure() + assertThat { OsPath.createOrThrow(OsType.WINDOWS, "home:\\vagrant") }.isFailure() .isInstanceOf(IllegalArgumentException::class.java) .hasMessage("Invalid character ':' in path 'home:\\vagrant'") } @@ -294,17 +258,15 @@ class OsPathTest { @Test fun `Test Windows stringPath`() { assertThat( - OsPath.of(OsType.WINDOWS, "C:\\home\\admin\\.kscript").path + OsPath.createOrThrow(OsType.WINDOWS, "C:\\home\\admin\\.kscript").path ).isEqualTo("C:\\home\\admin\\.kscript") assertThat( - OsPath.of(OsType.WINDOWS, "c:\\a\\b\\c\\..\\d\\script").path + OsPath.createOrThrow(OsType.WINDOWS, "c:\\a\\b\\c\\..\\d\\script").path ).isEqualTo("c:\\a\\b\\d\\script") assertThat( - OsPath.of(OsType.WINDOWS, ".\\.\\.\\..\\..\\script").path + OsPath.createOrThrow(OsType.WINDOWS, ".\\.\\.\\..\\..\\script").path ).isEqualTo("..\\..\\script") - assertThat( - OsPath.of(OsType.WINDOWS, "script\\file.txt").path - ).isEqualTo("script\\file.txt") + assertThat(OsPath.createOrThrow(OsType.WINDOWS, "script\\file.txt").path).isEqualTo("script\\file.txt") } // ****************************************** WINDOWS <-> CYGWIN <-> MSYS ****************************************** @@ -312,73 +274,44 @@ class OsPathTest { @Test fun `Test Windows to Cygwin`() { assertThat( - OsPath.of(OsType.WINDOWS, "C:\\home\\admin\\.kscript").convert(OsType.CYGWIN).path + OsPath.createOrThrow(OsType.WINDOWS, "C:\\home\\admin\\.kscript").convert(OsType.CYGWIN).path ).isEqualTo("/cygdrive/c/home/admin/.kscript") assertThat( - OsPath.of(OsType.WINDOWS, "..\\home\\admin\\.kscript").convert(OsType.CYGWIN).path + OsPath.createOrThrow(OsType.WINDOWS, "..\\home\\admin\\.kscript").convert(OsType.CYGWIN).path ).isEqualTo("../home/admin/.kscript") } @Test fun `Test Cygwin to Windows`() { assertThat( - OsPath.of(OsType.CYGWIN, "/cygdrive/c/home/admin/.kscript").convert(OsType.WINDOWS).path + OsPath.createOrThrow(OsType.CYGWIN, "/cygdrive/c/home/admin/.kscript").convert(OsType.WINDOWS).path ).isEqualTo("c:\\home\\admin\\.kscript") assertThat( - OsPath.of(OsType.CYGWIN, "../home/admin/.kscript").convert(OsType.WINDOWS).path + OsPath.createOrThrow(OsType.CYGWIN, "../home/admin/.kscript").convert(OsType.WINDOWS).path ).isEqualTo("..\\home\\admin\\.kscript") } @Test fun `Test Windows to MSys`() { assertThat( - OsPath.of(OsType.WINDOWS, "C:\\home\\admin\\.kscript").convert(OsType.MSYS).path + OsPath.createOrThrow(OsType.WINDOWS, "C:\\home\\admin\\.kscript").convert(OsType.MSYS).path ).isEqualTo("/c/home/admin/.kscript") assertThat( - OsPath.of(OsType.WINDOWS, "..\\home\\admin\\.kscript").convert(OsType.MSYS).path + OsPath.createOrThrow(OsType.WINDOWS, "..\\home\\admin\\.kscript").convert(OsType.MSYS).path ).isEqualTo("../home/admin/.kscript") } @Test fun `Test MSys to Windows`() { assertThat( - OsPath.of(OsType.MSYS, "/c/home/admin/.kscript").convert(OsType.WINDOWS).path + OsPath.createOrThrow(OsType.MSYS, "/c/home/admin/.kscript").convert(OsType.WINDOWS).path ).isEqualTo("c:\\home\\admin\\.kscript") assertThat( - OsPath.of(OsType.MSYS, "../home/admin/.kscript").convert(OsType.WINDOWS).path + OsPath.createOrThrow(OsType.MSYS, "../home/admin/.kscript").convert(OsType.WINDOWS).path ).isEqualTo("..\\home\\admin\\.kscript") } - - // ************************************************* Special cases ************************************************* - - @Test - fun `Special cases`() { - //Empty path - assertThat(OsPath.of(OsType.LINUX)).let { - it.prop(OsPath::root).isEqualTo("") - it.prop(OsPath::pathParts).isEqualTo(emptyList()) - it.prop(OsPath::isRelative).isTrue() - it.prop(OsPath::isAbsolute).isFalse() - it.prop(OsPath::osType).isEqualTo(OsType.LINUX) - } - } - - // ************************************* Shorthand for creating composite paths ************************************ - - @Test - fun `Concatenate paths`() { - val p = OsPath.of(OsType.MSYS, "/c/home") - val p1 = OsPath.of(OsType.MSYS, "admin/.kscript") - - assertThat(p / p1).let { - it.prop(OsPath::root).isEqualTo("/") - it.prop(OsPath::pathParts).isEqualTo(listOf("c", "home", "admin", ".kscript")) - it.prop(OsPath::isAbsolute).isTrue() - it.prop(OsPath::osType).isEqualTo(OsType.MSYS) - } - } } From a71ad05d184ffb57606531fe0b3c6d1d010a7f40 Mon Sep 17 00:00:00 2001 From: Marcin Kuszczak <1508798+aartiPl@users.noreply.github.com> Date: Thu, 3 Aug 2023 00:21:16 +0200 Subject: [PATCH 28/50] * Removed PathType enum * Renamed creation method to 'of' --- .../shell/integration/tools/TestContext.kt | 2 +- .../io/github/kscripting/os/model/OsPath.kt | 14 +-- .../github/kscripting/os/model/OsPathExt.kt | 8 +- .../io/github/kscripting/os/model/PathType.kt | 6 - .../github/kscripting/os/model/OsPathTest.kt | 116 +++++++++--------- 5 files changed, 70 insertions(+), 76 deletions(-) delete mode 100644 src/main/kotlin/io/github/kscripting/os/model/PathType.kt diff --git a/src/itest/kotlin/io/github/kscripting/shell/integration/tools/TestContext.kt b/src/itest/kotlin/io/github/kscripting/shell/integration/tools/TestContext.kt index b3fc977..6b7a196 100644 --- a/src/itest/kotlin/io/github/kscripting/shell/integration/tools/TestContext.kt +++ b/src/itest/kotlin/io/github/kscripting/shell/integration/tools/TestContext.kt @@ -8,7 +8,7 @@ import io.github.kscripting.shell.util.Sanitizer object TestContext { val osType: OsType = OsType.find(System.getProperty("osType")) ?: OsType.native - val projectPath: OsPath = OsPath.createOrThrow(OsType.native, System.getProperty("projectPath")).convert(osType) + val projectPath: OsPath = OsPath.of(OsType.native, System.getProperty("projectPath")).convert(osType) val execPath: OsPath = projectPath.resolve("build/shell_test/bin") val testPath: OsPath = projectPath.resolve("build/shell_test/tmp") diff --git a/src/main/kotlin/io/github/kscripting/os/model/OsPath.kt b/src/main/kotlin/io/github/kscripting/os/model/OsPath.kt index bb416fc..54e102c 100644 --- a/src/main/kotlin/io/github/kscripting/os/model/OsPath.kt +++ b/src/main/kotlin/io/github/kscripting/os/model/OsPath.kt @@ -17,7 +17,7 @@ data class OsPath(val osType: OsType, val root: String, val pathParts: List?\\\\/|]+\\\\[^*:<>?\\\\/|]+|\\?\\\\(?:[a-zA-Z]:(?=\\\\)|(?:UNC\\\\)?[^*:<>?\\\\/|]+\\\\[^*:<>?\\\\/|]+)))".toRegex() - fun createOrThrow(vararg pathParts: String): OsPath = createOrThrow(OsType.native, pathParts.toList()) + fun of(vararg pathParts: String): OsPath = of(OsType.native, pathParts.toList()) - fun createOrThrow(osType: OsType, vararg pathParts: String): OsPath = createOrThrow(osType, pathParts.toList()) + fun of(osType: OsType, vararg pathParts: String): OsPath = of(osType, pathParts.toList()) - fun createOrThrow(osType: OsType, pathParts: List): OsPath { + fun of(osType: OsType, pathParts: List): OsPath { return when (val result = internalCreate(osType, pathParts)) { is Either.Right -> result.value is Either.Left -> throw IllegalArgumentException(result.value) } } - fun create(vararg pathParts: String): OsPath? = create(OsType.native, pathParts.toList()) + fun ofOrNull(vararg pathParts: String): OsPath? = ofOrNull(OsType.native, pathParts.toList()) - fun create(osType: OsType, vararg pathParts: String): OsPath? = create(osType, pathParts.toList()) + fun ofOrNull(osType: OsType, vararg pathParts: String): OsPath? = ofOrNull(osType, pathParts.toList()) - fun create(osType: OsType, pathParts: List): OsPath? { + fun ofOrNull(osType: OsType, pathParts: List): OsPath? { return when (val result = internalCreate(osType, pathParts)) { is Either.Right -> result.value is Either.Left -> null diff --git a/src/main/kotlin/io/github/kscripting/os/model/OsPathExt.kt b/src/main/kotlin/io/github/kscripting/os/model/OsPathExt.kt index dee436f..924072c 100644 --- a/src/main/kotlin/io/github/kscripting/os/model/OsPathExt.kt +++ b/src/main/kotlin/io/github/kscripting/os/model/OsPathExt.kt @@ -11,9 +11,9 @@ import kotlin.io.path.* // Conversion to OsPath -fun File.toOsPath(): OsPath = OsPath.createOrThrow(OsType.native, absolutePath) +fun File.toOsPath(): OsPath = OsPath.of(OsType.native, absolutePath) -fun Path.toOsPath(): OsPath = OsPath.createOrThrow(OsType.native, absolutePathString()) +fun Path.toOsPath(): OsPath = OsPath.of(OsType.native, absolutePathString()) fun URI.toOsPath(): OsPath = if (this.scheme == "file") File(this).toOsPath() else throw IllegalArgumentException("Invalid conversion from URL to OsPath") @@ -32,10 +32,10 @@ fun OsPath.toNativeFile(): File = toNativePath().toFile() fun OsPath.exists() = toNativePath().exists() -fun OsPath.createDirectories(): OsPath = OsPath.createOrThrow(nativeType, toNativePath().createDirectories().pathString) +fun OsPath.createDirectories(): OsPath = OsPath.of(nativeType, toNativePath().createDirectories().pathString) fun OsPath.copyTo(target: OsPath, overwrite: Boolean = false): OsPath = - OsPath.createOrThrow(nativeType, toNativePath().copyTo(target.toNativePath(), overwrite).pathString) + OsPath.of(nativeType, toNativePath().copyTo(target.toNativePath(), overwrite).pathString) fun OsPath.writeText(text: CharSequence, charset: Charset = Charsets.UTF_8, vararg options: OpenOption): Unit = toNativePath().writeText(text, charset, *options) diff --git a/src/main/kotlin/io/github/kscripting/os/model/PathType.kt b/src/main/kotlin/io/github/kscripting/os/model/PathType.kt deleted file mode 100644 index 4bb9b31..0000000 --- a/src/main/kotlin/io/github/kscripting/os/model/PathType.kt +++ /dev/null @@ -1,6 +0,0 @@ -package io.github.kscripting.os.model - - -enum class PathType { - ABSOLUTE, RELATIVE, UNDEFINED -} diff --git a/src/test/kotlin/io/github/kscripting/os/model/OsPathTest.kt b/src/test/kotlin/io/github/kscripting/os/model/OsPathTest.kt index 556af6d..8ae748a 100644 --- a/src/test/kotlin/io/github/kscripting/os/model/OsPathTest.kt +++ b/src/test/kotlin/io/github/kscripting/os/model/OsPathTest.kt @@ -8,63 +8,63 @@ class OsPathTest { // ************************************************** LINUX PATHS ************************************************** @Test fun `Test Linux paths`() { - assertThat(OsPath.createOrThrow(OsType.LINUX, "/")).let { + assertThat(OsPath.of(OsType.LINUX, "/")).let { it.prop(OsPath::osType).isEqualTo(OsType.LINUX) it.prop(OsPath::root).isEqualTo("/") it.prop(OsPath::pathParts).isEqualTo(emptyList()) } - assertThat(OsPath.createOrThrow(OsType.LINUX, "/home/admin/.kscript")).let { + assertThat(OsPath.of(OsType.LINUX, "/home/admin/.kscript")).let { it.prop(OsPath::osType).isEqualTo(OsType.LINUX) it.prop(OsPath::root).isEqualTo("/") it.prop(OsPath::pathParts).isEqualTo(listOf("home", "admin", ".kscript")) } - assertThat(OsPath.createOrThrow(OsType.LINUX, "./home/admin/.kscript")).let { + assertThat(OsPath.of(OsType.LINUX, "./home/admin/.kscript")).let { it.prop(OsPath::osType).isEqualTo(OsType.LINUX) it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(listOf("home", "admin", ".kscript")) } - assertThat(OsPath.createOrThrow(OsType.LINUX, "")).let { + assertThat(OsPath.of(OsType.LINUX, "")).let { it.prop(OsPath::osType).isEqualTo(OsType.LINUX) it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(emptyList()) } - assertThat(OsPath.createOrThrow(OsType.LINUX, "file.txt")).let { + assertThat(OsPath.of(OsType.LINUX, "file.txt")).let { it.prop(OsPath::osType).isEqualTo(OsType.LINUX) it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(listOf("file.txt")) } - assertThat(OsPath.createOrThrow(OsType.LINUX, ".")).let { + assertThat(OsPath.of(OsType.LINUX, ".")).let { it.prop(OsPath::osType).isEqualTo(OsType.LINUX) it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(emptyList()) } - assertThat(OsPath.createOrThrow(OsType.LINUX, "../home/admin/.kscript")).let { + assertThat(OsPath.of(OsType.LINUX, "../home/admin/.kscript")).let { it.prop(OsPath::osType).isEqualTo(OsType.LINUX) it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(listOf("..", "home", "admin", ".kscript")) } - assertThat(OsPath.createOrThrow(OsType.LINUX, "..")).let { + assertThat(OsPath.of(OsType.LINUX, "..")).let { it.prop(OsPath::osType).isEqualTo(OsType.LINUX) it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(listOf("..")) } //Duplicated separators are accepted - assertThat(OsPath.createOrThrow(OsType.LINUX, "..//home////admin/.kscript/")).let { + assertThat(OsPath.of(OsType.LINUX, "..//home////admin/.kscript/")).let { it.prop(OsPath::osType).isEqualTo(OsType.LINUX) it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(listOf("..", "home", "admin", ".kscript")) } //Both types of separator are accepted - assertThat(OsPath.createOrThrow(OsType.LINUX, "..//home\\admin\\.kscript/")).let { + assertThat(OsPath.of(OsType.LINUX, "..//home\\admin\\.kscript/")).let { it.prop(OsPath::osType).isEqualTo(OsType.LINUX) it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(listOf("..", "home", "admin", ".kscript")) @@ -73,81 +73,81 @@ class OsPathTest { @Test fun `Normalization of Linux paths`() { - assertThat(OsPath.createOrThrow(OsType.LINUX, "/home/admin/.kscript/../../")).let { + assertThat(OsPath.of(OsType.LINUX, "/home/admin/.kscript/../../")).let { it.prop(OsPath::osType).isEqualTo(OsType.LINUX) it.prop(OsPath::root).isEqualTo("/") it.prop(OsPath::pathParts).isEqualTo(listOf("home")) } - assertThat(OsPath.createOrThrow(OsType.LINUX, "./././../../script")).let { + assertThat(OsPath.of(OsType.LINUX, "./././../../script")).let { it.prop(OsPath::osType).isEqualTo(OsType.LINUX) it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(listOf("..", "..", "script")) } - assertThat(OsPath.createOrThrow(OsType.LINUX, "/a/b/c/../d/script")).let { + assertThat(OsPath.of(OsType.LINUX, "/a/b/c/../d/script")).let { it.prop(OsPath::osType).isEqualTo(OsType.LINUX) it.prop(OsPath::root).isEqualTo("/") it.prop(OsPath::pathParts).isEqualTo(listOf("a", "b", "d", "script")) } - assertThat { OsPath.createOrThrow(OsType.LINUX, "/.kscript/../../") }.isFailure() + assertThat { OsPath.of(OsType.LINUX, "/.kscript/../../") }.isFailure() .isInstanceOf(IllegalArgumentException::class.java) .hasMessage("Path after normalization goes beyond root element: '/'") } @Test fun `Test invalid Linux paths`() { - assertThat { OsPath.createOrThrow(OsType.LINUX, "/ad*asdf") }.isFailure() + assertThat { OsPath.of(OsType.LINUX, "/ad*asdf") }.isFailure() .isInstanceOf(IllegalArgumentException::class.java).hasMessage("Invalid character '*' in path '/ad*asdf'") } @Test fun `Test Linux stringPath`() { assertThat( - OsPath.createOrThrow(OsType.LINUX, "/home/admin/.kscript").path + OsPath.of(OsType.LINUX, "/home/admin/.kscript").path ).isEqualTo("/home/admin/.kscript") - assertThat(OsPath.createOrThrow(OsType.LINUX, "/a/b/c/../d/script").path).isEqualTo("/a/b/d/script") - assertThat(OsPath.createOrThrow(OsType.LINUX, "./././../../script").path).isEqualTo("../../script") - assertThat(OsPath.createOrThrow(OsType.LINUX, "script/file.txt").path).isEqualTo("script/file.txt") + assertThat(OsPath.of(OsType.LINUX, "/a/b/c/../d/script").path).isEqualTo("/a/b/d/script") + assertThat(OsPath.of(OsType.LINUX, "./././../../script").path).isEqualTo("../../script") + assertThat(OsPath.of(OsType.LINUX, "script/file.txt").path).isEqualTo("script/file.txt") } @Test fun `Test Linux resolve`() { assertThat( - OsPath.createOrThrow(OsType.LINUX, "/").resolve(OsPath.createOrThrow(OsType.LINUX, "./.kscript/")).path + OsPath.of(OsType.LINUX, "/").resolve(OsPath.of(OsType.LINUX, "./.kscript/")).path ).isEqualTo("/.kscript") assertThat( - OsPath.createOrThrow(OsType.LINUX, "/home/admin/") - .resolve(OsPath.createOrThrow(OsType.LINUX, "./.kscript/")).path + OsPath.of(OsType.LINUX, "/home/admin/") + .resolve(OsPath.of(OsType.LINUX, "./.kscript/")).path ).isEqualTo("/home/admin/.kscript") assertThat( - OsPath.createOrThrow(OsType.LINUX, "./home/admin/") - .resolve(OsPath.createOrThrow(OsType.LINUX, "./.kscript/")).path + OsPath.of(OsType.LINUX, "./home/admin/") + .resolve(OsPath.of(OsType.LINUX, "./.kscript/")).path ).isEqualTo("home/admin/.kscript") assertThat( - OsPath.createOrThrow(OsType.LINUX, "../home/admin/") - .resolve(OsPath.createOrThrow(OsType.LINUX, "./.kscript/")).path + OsPath.of(OsType.LINUX, "../home/admin/") + .resolve(OsPath.of(OsType.LINUX, "./.kscript/")).path ).isEqualTo("../home/admin/.kscript") assertThat( - OsPath.createOrThrow(OsType.LINUX, "..").resolve(OsPath.createOrThrow(OsType.LINUX, "./.kscript/")).path + OsPath.of(OsType.LINUX, "..").resolve(OsPath.of(OsType.LINUX, "./.kscript/")).path ).isEqualTo("../.kscript") assertThat( - OsPath.createOrThrow(OsType.LINUX, ".").resolve(OsPath.createOrThrow(OsType.LINUX, "./.kscript/")).path + OsPath.of(OsType.LINUX, ".").resolve(OsPath.of(OsType.LINUX, "./.kscript/")).path ).isEqualTo(".kscript") assertThat { - OsPath.createOrThrow(OsType.LINUX, "./home/admin").resolve(OsPath.createOrThrow(OsType.WINDOWS, ".\\run")) + OsPath.of(OsType.LINUX, "./home/admin").resolve(OsPath.of(OsType.WINDOWS, ".\\run")) }.isFailure().isInstanceOf(IllegalArgumentException::class.java) .hasMessage("Paths from different OS's: 'LINUX' path can not be resolved with 'WINDOWS' path") assertThat { - OsPath.createOrThrow(OsType.LINUX, "./home/admin").resolve(OsPath.createOrThrow(OsType.LINUX, "/run")) + OsPath.of(OsType.LINUX, "./home/admin").resolve(OsPath.of(OsType.LINUX, "/run")) }.isFailure().isInstanceOf(IllegalArgumentException::class.java) .hasMessage("Can not resolve absolute or relative path 'home/admin' using absolute path '/run'") } @@ -156,63 +156,63 @@ class OsPathTest { @Test fun `Test Windows paths`() { - assertThat(OsPath.createOrThrow(OsType.WINDOWS, "C:\\")).let { + assertThat(OsPath.of(OsType.WINDOWS, "C:\\")).let { it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) it.prop(OsPath::root).isEqualTo("C:\\") it.prop(OsPath::pathParts).isEqualTo(emptyList()) } - assertThat(OsPath.createOrThrow(OsType.WINDOWS, "C:\\home\\admin\\.kscript")).let { + assertThat(OsPath.of(OsType.WINDOWS, "C:\\home\\admin\\.kscript")).let { it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) it.prop(OsPath::root).isEqualTo("C:\\") it.prop(OsPath::pathParts).isEqualTo(listOf("home", "admin", ".kscript")) } - assertThat(OsPath.createOrThrow(OsType.WINDOWS, "")).let { + assertThat(OsPath.of(OsType.WINDOWS, "")).let { it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(emptyList()) } - assertThat(OsPath.createOrThrow(OsType.WINDOWS, "file.txt")).let { + assertThat(OsPath.of(OsType.WINDOWS, "file.txt")).let { it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(listOf("file.txt")) } - assertThat(OsPath.createOrThrow(OsType.WINDOWS, ".")).let { + assertThat(OsPath.of(OsType.WINDOWS, ".")).let { it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(emptyList()) } - assertThat(OsPath.createOrThrow(OsType.WINDOWS, ".\\home\\admin\\.kscript")).let { + assertThat(OsPath.of(OsType.WINDOWS, ".\\home\\admin\\.kscript")).let { it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(listOf("home", "admin", ".kscript")) } - assertThat(OsPath.createOrThrow(OsType.WINDOWS, "..\\home\\admin\\.kscript")).let { + assertThat(OsPath.of(OsType.WINDOWS, "..\\home\\admin\\.kscript")).let { it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(listOf("..", "home", "admin", ".kscript")) } - assertThat(OsPath.createOrThrow(OsType.WINDOWS, "..")).let { + assertThat(OsPath.of(OsType.WINDOWS, "..")).let { it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(listOf("..")) } //Duplicated separators are accepted - assertThat(OsPath.createOrThrow(OsType.WINDOWS, "C:\\home\\\\\\\\admin\\.kscript\\")).let { + assertThat(OsPath.of(OsType.WINDOWS, "C:\\home\\\\\\\\admin\\.kscript\\")).let { it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) it.prop(OsPath::root).isEqualTo("C:\\") it.prop(OsPath::pathParts).isEqualTo(listOf("home", "admin", ".kscript")) } //Both types of separator are accepted - assertThat(OsPath.createOrThrow(OsType.WINDOWS, "C:/home\\admin/.kscript////")).let { + assertThat(OsPath.of(OsType.WINDOWS, "C:/home\\admin/.kscript////")).let { it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) it.prop(OsPath::root).isEqualTo("C:\\") it.prop(OsPath::pathParts).isEqualTo(listOf("home", "admin", ".kscript")) @@ -221,36 +221,36 @@ class OsPathTest { @Test fun `Normalization of Windows paths`() { - assertThat(OsPath.createOrThrow(OsType.WINDOWS, "C:\\home\\admin\\.kscript\\..\\..\\")).let { + assertThat(OsPath.of(OsType.WINDOWS, "C:\\home\\admin\\.kscript\\..\\..\\")).let { it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) it.prop(OsPath::root).isEqualTo("C:\\") it.prop(OsPath::pathParts).isEqualTo(listOf("home")) } - assertThat(OsPath.createOrThrow(OsType.WINDOWS, ".\\.\\.\\..\\..\\script")).let { + assertThat(OsPath.of(OsType.WINDOWS, ".\\.\\.\\..\\..\\script")).let { it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(listOf("..", "..", "script")) } - assertThat(OsPath.createOrThrow(OsType.WINDOWS, "C:\\a\\b\\c\\..\\d\\script")).let { + assertThat(OsPath.of(OsType.WINDOWS, "C:\\a\\b\\c\\..\\d\\script")).let { it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) it.prop(OsPath::root).isEqualTo("C:\\") it.prop(OsPath::pathParts).isEqualTo(listOf("a", "b", "d", "script")) } - assertThat { OsPath.createOrThrow(OsType.WINDOWS, "C:\\.kscript\\..\\..\\") }.isFailure() + assertThat { OsPath.of(OsType.WINDOWS, "C:\\.kscript\\..\\..\\") }.isFailure() .isInstanceOf(IllegalArgumentException::class.java) .hasMessage("Path after normalization goes beyond root element: 'C:\\'") } @Test fun `Test invalid Windows paths`() { - assertThat { OsPath.createOrThrow(OsType.WINDOWS, "C:\\adas?df") }.isFailure() + assertThat { OsPath.of(OsType.WINDOWS, "C:\\adas?df") }.isFailure() .isInstanceOf(IllegalArgumentException::class.java) .hasMessage("Invalid character '?' in path 'C:\\adas?df'") - assertThat { OsPath.createOrThrow(OsType.WINDOWS, "home:\\vagrant") }.isFailure() + assertThat { OsPath.of(OsType.WINDOWS, "home:\\vagrant") }.isFailure() .isInstanceOf(IllegalArgumentException::class.java) .hasMessage("Invalid character ':' in path 'home:\\vagrant'") } @@ -258,15 +258,15 @@ class OsPathTest { @Test fun `Test Windows stringPath`() { assertThat( - OsPath.createOrThrow(OsType.WINDOWS, "C:\\home\\admin\\.kscript").path + OsPath.of(OsType.WINDOWS, "C:\\home\\admin\\.kscript").path ).isEqualTo("C:\\home\\admin\\.kscript") assertThat( - OsPath.createOrThrow(OsType.WINDOWS, "c:\\a\\b\\c\\..\\d\\script").path + OsPath.of(OsType.WINDOWS, "c:\\a\\b\\c\\..\\d\\script").path ).isEqualTo("c:\\a\\b\\d\\script") assertThat( - OsPath.createOrThrow(OsType.WINDOWS, ".\\.\\.\\..\\..\\script").path + OsPath.of(OsType.WINDOWS, ".\\.\\.\\..\\..\\script").path ).isEqualTo("..\\..\\script") - assertThat(OsPath.createOrThrow(OsType.WINDOWS, "script\\file.txt").path).isEqualTo("script\\file.txt") + assertThat(OsPath.of(OsType.WINDOWS, "script\\file.txt").path).isEqualTo("script\\file.txt") } // ****************************************** WINDOWS <-> CYGWIN <-> MSYS ****************************************** @@ -274,44 +274,44 @@ class OsPathTest { @Test fun `Test Windows to Cygwin`() { assertThat( - OsPath.createOrThrow(OsType.WINDOWS, "C:\\home\\admin\\.kscript").convert(OsType.CYGWIN).path + OsPath.of(OsType.WINDOWS, "C:\\home\\admin\\.kscript").convert(OsType.CYGWIN).path ).isEqualTo("/cygdrive/c/home/admin/.kscript") assertThat( - OsPath.createOrThrow(OsType.WINDOWS, "..\\home\\admin\\.kscript").convert(OsType.CYGWIN).path + OsPath.of(OsType.WINDOWS, "..\\home\\admin\\.kscript").convert(OsType.CYGWIN).path ).isEqualTo("../home/admin/.kscript") } @Test fun `Test Cygwin to Windows`() { assertThat( - OsPath.createOrThrow(OsType.CYGWIN, "/cygdrive/c/home/admin/.kscript").convert(OsType.WINDOWS).path + OsPath.of(OsType.CYGWIN, "/cygdrive/c/home/admin/.kscript").convert(OsType.WINDOWS).path ).isEqualTo("c:\\home\\admin\\.kscript") assertThat( - OsPath.createOrThrow(OsType.CYGWIN, "../home/admin/.kscript").convert(OsType.WINDOWS).path + OsPath.of(OsType.CYGWIN, "../home/admin/.kscript").convert(OsType.WINDOWS).path ).isEqualTo("..\\home\\admin\\.kscript") } @Test fun `Test Windows to MSys`() { assertThat( - OsPath.createOrThrow(OsType.WINDOWS, "C:\\home\\admin\\.kscript").convert(OsType.MSYS).path + OsPath.of(OsType.WINDOWS, "C:\\home\\admin\\.kscript").convert(OsType.MSYS).path ).isEqualTo("/c/home/admin/.kscript") assertThat( - OsPath.createOrThrow(OsType.WINDOWS, "..\\home\\admin\\.kscript").convert(OsType.MSYS).path + OsPath.of(OsType.WINDOWS, "..\\home\\admin\\.kscript").convert(OsType.MSYS).path ).isEqualTo("../home/admin/.kscript") } @Test fun `Test MSys to Windows`() { assertThat( - OsPath.createOrThrow(OsType.MSYS, "/c/home/admin/.kscript").convert(OsType.WINDOWS).path + OsPath.of(OsType.MSYS, "/c/home/admin/.kscript").convert(OsType.WINDOWS).path ).isEqualTo("c:\\home\\admin\\.kscript") assertThat( - OsPath.createOrThrow(OsType.MSYS, "../home/admin/.kscript").convert(OsType.WINDOWS).path + OsPath.of(OsType.MSYS, "../home/admin/.kscript").convert(OsType.WINDOWS).path ).isEqualTo("..\\home\\admin\\.kscript") } } From 600bc78715ae8d88929a1a7c0ac564fd3807991d Mon Sep 17 00:00:00 2001 From: Marcin Kuszczak <1508798+aartiPl@users.noreply.github.com> Date: Thu, 3 Aug 2023 00:32:26 +0200 Subject: [PATCH 29/50] * Small improvements --- src/main/kotlin/io/github/kscripting/os/Os.kt | 1 - src/main/kotlin/io/github/kscripting/os/model/OsPath.kt | 5 +++-- .../kotlin/io/github/kscripting/os/model/OsPathTest.kt | 9 +++------ 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/src/main/kotlin/io/github/kscripting/os/Os.kt b/src/main/kotlin/io/github/kscripting/os/Os.kt index 4a22155..45024fb 100644 --- a/src/main/kotlin/io/github/kscripting/os/Os.kt +++ b/src/main/kotlin/io/github/kscripting/os/Os.kt @@ -3,7 +3,6 @@ package io.github.kscripting.os import io.github.kscripting.os.model.OsPath import io.github.kscripting.os.model.OsType import net.igsoft.typeutils.marker.AutoTypedMarker -import net.igsoft.typeutils.marker.TypedMarker interface Os { val type: OsType diff --git a/src/main/kotlin/io/github/kscripting/os/model/OsPath.kt b/src/main/kotlin/io/github/kscripting/os/model/OsPath.kt index 54e102c..f00b84d 100644 --- a/src/main/kotlin/io/github/kscripting/os/model/OsPath.kt +++ b/src/main/kotlin/io/github/kscripting/os/model/OsPath.kt @@ -9,8 +9,9 @@ import arrow.core.right data class OsPath(val osType: OsType, val root: String, val pathParts: List) { val isRelative: Boolean get() = root.isEmpty() val isAbsolute: Boolean get() = !isRelative - val pathSeparator: Char get() = if (osType.isWindowsLike()) '\\' else '/' - val path get(): String = root + pathParts.joinToString(pathSeparator.toString()) { it } + + val pathSeparator: String get() = if (osType.isWindowsLike()) "\\" else "/" + val path get(): String = root + pathParts.joinToString(pathSeparator) { it } val OsPath.leaf get(): String = if (pathParts.isEmpty()) root else pathParts.last() diff --git a/src/test/kotlin/io/github/kscripting/os/model/OsPathTest.kt b/src/test/kotlin/io/github/kscripting/os/model/OsPathTest.kt index 8ae748a..2b0d474 100644 --- a/src/test/kotlin/io/github/kscripting/os/model/OsPathTest.kt +++ b/src/test/kotlin/io/github/kscripting/os/model/OsPathTest.kt @@ -119,18 +119,15 @@ class OsPathTest { ).isEqualTo("/.kscript") assertThat( - OsPath.of(OsType.LINUX, "/home/admin/") - .resolve(OsPath.of(OsType.LINUX, "./.kscript/")).path + OsPath.of(OsType.LINUX, "/home/admin/").resolve(OsPath.of(OsType.LINUX, "./.kscript/")).path ).isEqualTo("/home/admin/.kscript") assertThat( - OsPath.of(OsType.LINUX, "./home/admin/") - .resolve(OsPath.of(OsType.LINUX, "./.kscript/")).path + OsPath.of(OsType.LINUX, "./home/admin/").resolve(OsPath.of(OsType.LINUX, "./.kscript/")).path ).isEqualTo("home/admin/.kscript") assertThat( - OsPath.of(OsType.LINUX, "../home/admin/") - .resolve(OsPath.of(OsType.LINUX, "./.kscript/")).path + OsPath.of(OsType.LINUX, "../home/admin/").resolve(OsPath.of(OsType.LINUX, "./.kscript/")).path ).isEqualTo("../home/admin/.kscript") assertThat( From 4011fcf26a5153bab598e3b6cd6c36284cd0c0f8 Mon Sep 17 00:00:00 2001 From: Marcin Kuszczak <1508798+aartiPl@users.noreply.github.com> Date: Sat, 5 Aug 2023 19:59:20 +0200 Subject: [PATCH 30/50] Design cd. --- src/main/kotlin/io/github/kscripting/os/Os.kt | 9 ++------- .../kotlin/io/github/kscripting/os/instance/CygwinOs.kt | 6 +----- .../kotlin/io/github/kscripting/os/instance/HostedOs.kt | 3 --- .../kotlin/io/github/kscripting/os/instance/LinuxOs.kt | 4 +--- .../kotlin/io/github/kscripting/os/instance/MsysOs.kt | 6 +----- .../kotlin/io/github/kscripting/os/instance/WindowsOs.kt | 4 +--- src/main/kotlin/io/github/kscripting/os/model/OsPath.kt | 9 +++++++++ .../kotlin/io/github/kscripting/os/model/OsPathExt.kt | 6 +----- 8 files changed, 16 insertions(+), 31 deletions(-) diff --git a/src/main/kotlin/io/github/kscripting/os/Os.kt b/src/main/kotlin/io/github/kscripting/os/Os.kt index 45024fb..97a7840 100644 --- a/src/main/kotlin/io/github/kscripting/os/Os.kt +++ b/src/main/kotlin/io/github/kscripting/os/Os.kt @@ -4,15 +4,10 @@ import io.github.kscripting.os.model.OsPath import io.github.kscripting.os.model.OsType import net.igsoft.typeutils.marker.AutoTypedMarker +val CURRENT_OS = AutoTypedMarker.create() + interface Os { val type: OsType - val userHome: OsPath val pathSeparator: String - - fun path(vararg pathParts: String): OsPath - - companion object { - val CURRENT_OS = AutoTypedMarker.create() - } } diff --git a/src/main/kotlin/io/github/kscripting/os/instance/CygwinOs.kt b/src/main/kotlin/io/github/kscripting/os/instance/CygwinOs.kt index 52f055e..482843a 100644 --- a/src/main/kotlin/io/github/kscripting/os/instance/CygwinOs.kt +++ b/src/main/kotlin/io/github/kscripting/os/instance/CygwinOs.kt @@ -5,11 +5,7 @@ import io.github.kscripting.os.model.OsType class CygwinOs(userHome: String, nativeFileSystemRoot: String) : HostedOs { override val type: OsType = OsType.CYGWIN - override val userHome: OsPath = path(userHome) + override val userHome: OsPath = TODO() override val nativeType: OsType = OsType.WINDOWS override val nativeFileSystemRoot: OsPath = TODO() - - override fun path(vararg pathParts: String): OsPath = TODO() - override fun toNativePath(osPath: OsPath): OsPath = TODO() - override fun toHostedPath(osPath: OsPath): OsPath = TODO() } diff --git a/src/main/kotlin/io/github/kscripting/os/instance/HostedOs.kt b/src/main/kotlin/io/github/kscripting/os/instance/HostedOs.kt index 5824179..f0c231f 100644 --- a/src/main/kotlin/io/github/kscripting/os/instance/HostedOs.kt +++ b/src/main/kotlin/io/github/kscripting/os/instance/HostedOs.kt @@ -8,7 +8,4 @@ interface HostedOs : Os { val nativeType: OsType val nativeFileSystemRoot: OsPath override val pathSeparator get(): String = "/" - - fun toNativePath(osPath: OsPath): OsPath - fun toHostedPath(osPath: OsPath): OsPath } diff --git a/src/main/kotlin/io/github/kscripting/os/instance/LinuxOs.kt b/src/main/kotlin/io/github/kscripting/os/instance/LinuxOs.kt index 55efbe1..f8ae167 100644 --- a/src/main/kotlin/io/github/kscripting/os/instance/LinuxOs.kt +++ b/src/main/kotlin/io/github/kscripting/os/instance/LinuxOs.kt @@ -7,7 +7,5 @@ import io.github.kscripting.os.model.OsType class LinuxOs(userHome: String) : Os { override val type: OsType = OsType.LINUX override val pathSeparator get() = "/" - override val userHome: OsPath = path(userHome) - - override fun path(vararg pathParts: String): OsPath = TODO() + override val userHome: OsPath = OsPath.of(OsType.LINUX, userHome) } diff --git a/src/main/kotlin/io/github/kscripting/os/instance/MsysOs.kt b/src/main/kotlin/io/github/kscripting/os/instance/MsysOs.kt index 6e341d8..1e6b963 100644 --- a/src/main/kotlin/io/github/kscripting/os/instance/MsysOs.kt +++ b/src/main/kotlin/io/github/kscripting/os/instance/MsysOs.kt @@ -5,11 +5,7 @@ import io.github.kscripting.os.model.OsType class MsysOs(userHome: String, nativeFileSystemRoot: String) : HostedOs { override val type: OsType = OsType.MSYS - override val userHome: OsPath = path(userHome) + override val userHome: OsPath = TODO() override val nativeType: OsType = OsType.WINDOWS override val nativeFileSystemRoot: OsPath = TODO() - - override fun path(vararg pathParts: String): OsPath = TODO() - override fun toNativePath(osPath: OsPath): OsPath = TODO() - override fun toHostedPath(osPath: OsPath): OsPath = TODO() } diff --git a/src/main/kotlin/io/github/kscripting/os/instance/WindowsOs.kt b/src/main/kotlin/io/github/kscripting/os/instance/WindowsOs.kt index 0eb7531..b532a59 100644 --- a/src/main/kotlin/io/github/kscripting/os/instance/WindowsOs.kt +++ b/src/main/kotlin/io/github/kscripting/os/instance/WindowsOs.kt @@ -8,7 +8,5 @@ import io.github.kscripting.os.model.OsType class WindowsOs(userHome: String) : Os { override val type: OsType = OsType.WINDOWS override val pathSeparator get() = "\\" - override val userHome: OsPath = path(userHome) - - override fun path(vararg pathParts: String): OsPath = TODO() + override val userHome: OsPath = TODO() } diff --git a/src/main/kotlin/io/github/kscripting/os/model/OsPath.kt b/src/main/kotlin/io/github/kscripting/os/model/OsPath.kt index f00b84d..75ff3fa 100644 --- a/src/main/kotlin/io/github/kscripting/os/model/OsPath.kt +++ b/src/main/kotlin/io/github/kscripting/os/model/OsPath.kt @@ -42,9 +42,18 @@ data class OsPath(val osType: OsType, val root: String, val pathParts: List Date: Sat, 19 Aug 2023 17:10:25 +0200 Subject: [PATCH 31/50] Design cd. --- .../kscripting/os/instance/FreeBsdOs.kt | 11 +++ .../io/github/kscripting/os/instance/MacOs.kt | 11 +++ .../github/kscripting/os/model/OsTypeNew.kt | 77 +++++++++++++++++++ 3 files changed, 99 insertions(+) create mode 100644 src/main/kotlin/io/github/kscripting/os/instance/FreeBsdOs.kt create mode 100644 src/main/kotlin/io/github/kscripting/os/instance/MacOs.kt create mode 100644 src/main/kotlin/io/github/kscripting/os/model/OsTypeNew.kt diff --git a/src/main/kotlin/io/github/kscripting/os/instance/FreeBsdOs.kt b/src/main/kotlin/io/github/kscripting/os/instance/FreeBsdOs.kt new file mode 100644 index 0000000..dd20b75 --- /dev/null +++ b/src/main/kotlin/io/github/kscripting/os/instance/FreeBsdOs.kt @@ -0,0 +1,11 @@ +package io.github.kscripting.os.instance + +import io.github.kscripting.os.Os +import io.github.kscripting.os.model.OsPath +import io.github.kscripting.os.model.OsType + +class FreeBsdOs(userHome: String) : Os { + override val type: OsType = OsType.LINUX + override val pathSeparator get() = "/" + override val userHome: OsPath = OsPath.of(OsType.LINUX, userHome) +} diff --git a/src/main/kotlin/io/github/kscripting/os/instance/MacOs.kt b/src/main/kotlin/io/github/kscripting/os/instance/MacOs.kt new file mode 100644 index 0000000..51f4330 --- /dev/null +++ b/src/main/kotlin/io/github/kscripting/os/instance/MacOs.kt @@ -0,0 +1,11 @@ +package io.github.kscripting.os.instance + +import io.github.kscripting.os.Os +import io.github.kscripting.os.model.OsPath +import io.github.kscripting.os.model.OsType + +class MacOs(userHome: String) : Os { + override val type: OsType = OsType.LINUX + override val pathSeparator get() = "/" + override val userHome: OsPath = OsPath.of(OsType.LINUX, userHome) +} diff --git a/src/main/kotlin/io/github/kscripting/os/model/OsTypeNew.kt b/src/main/kotlin/io/github/kscripting/os/model/OsTypeNew.kt new file mode 100644 index 0000000..1deafc5 --- /dev/null +++ b/src/main/kotlin/io/github/kscripting/os/model/OsTypeNew.kt @@ -0,0 +1,77 @@ +package io.github.kscripting.os.model + +import io.github.kscripting.os.Os +import io.github.kscripting.os.instance.* +import net.igsoft.typeutils.globalcontext.GlobalContext +import net.igsoft.typeutils.marker.AutoTypedMarker +import net.igsoft.typeutils.marker.TypedMarker +import org.apache.commons.lang3.SystemUtils +import kotlin.properties.ReadOnlyProperty + + +abstract class TypedEnumCompanion { + private val registry: MutableMap = mutableMapOf() + + fun find(name: String): T? = registry[name] + fun findOrThrow(name: String): T = + find(name) ?: throw IllegalArgumentException("Can not find enum value for: '$name'") + + protected fun register(name: String, instance: V): V { + registry[name] = instance + return instance + } + + protected fun register(instance: V): ReadOnlyProperty { + return ReadOnlyProperty { _, property -> + registry[property.name] = instance + instance + } + } + + protected fun findName(instance: T) = + registry.entries.firstOrNull { it.value == instance }?.key ?: error("Enum not registered") +} + +class OsTypeNew private constructor(private val marker: TypedMarker) : TypedMarker { + val value get() = GlobalContext.getValue(marker) + + fun isPosixLike() = + (this == LINUX || this == MACOS || this == FREEBSD || this == CYGWIN || this == MSYS) + + fun isPosixHostedOnWindows() = (this == CYGWIN || this == MSYS) + fun isWindowsLike() = (this == WINDOWS) + + companion object : TypedEnumCompanion>() { + val LINUX by register(OsTypeNew(AutoTypedMarker.create())) + val WINDOWS by register(OsTypeNew(AutoTypedMarker.create())) + val CYGWIN by register(OsTypeNew(AutoTypedMarker.create())) + val MSYS by register(OsTypeNew(AutoTypedMarker.create())) + val MACOS by register(OsTypeNew(AutoTypedMarker.create())) + val FREEBSD by register(OsTypeNew(AutoTypedMarker.create())) + + val native: OsTypeNew = guessNativeType() + + private fun guessNativeType(): OsTypeNew { + when { + SystemUtils.IS_OS_LINUX -> return LINUX + SystemUtils.IS_OS_MAC -> return MACOS + SystemUtils.IS_OS_WINDOWS -> return WINDOWS + SystemUtils.IS_OS_FREE_BSD -> return FREEBSD + } + + return LINUX + } + } + + override val clazz: Class get() = marker.clazz + override val id: Any get() = marker.id + override fun toString(): String = findName(this) +} + +fun main() { + GlobalContext.register(OsTypeNew.LINUX, LinuxOs("home")) + val test = OsTypeNew.find("WINDOWS") + println(test) + + println(OsTypeNew.LINUX) +} From 4c495e2dcce6c59825c5b33613de6660b60b80c3 Mon Sep 17 00:00:00 2001 From: Marcin Kuszczak <1508798+aartiPl@users.noreply.github.com> Date: Sat, 19 Aug 2023 17:17:33 +0200 Subject: [PATCH 32/50] Design cd. --- .../github/kscripting/os/model/OsTypeNew.kt | 25 +------------------ 1 file changed, 1 insertion(+), 24 deletions(-) diff --git a/src/main/kotlin/io/github/kscripting/os/model/OsTypeNew.kt b/src/main/kotlin/io/github/kscripting/os/model/OsTypeNew.kt index 1deafc5..58b793d 100644 --- a/src/main/kotlin/io/github/kscripting/os/model/OsTypeNew.kt +++ b/src/main/kotlin/io/github/kscripting/os/model/OsTypeNew.kt @@ -2,36 +2,13 @@ package io.github.kscripting.os.model import io.github.kscripting.os.Os import io.github.kscripting.os.instance.* +import net.igsoft.typeutils.enum.TypedEnumCompanion import net.igsoft.typeutils.globalcontext.GlobalContext import net.igsoft.typeutils.marker.AutoTypedMarker import net.igsoft.typeutils.marker.TypedMarker import org.apache.commons.lang3.SystemUtils -import kotlin.properties.ReadOnlyProperty -abstract class TypedEnumCompanion { - private val registry: MutableMap = mutableMapOf() - - fun find(name: String): T? = registry[name] - fun findOrThrow(name: String): T = - find(name) ?: throw IllegalArgumentException("Can not find enum value for: '$name'") - - protected fun register(name: String, instance: V): V { - registry[name] = instance - return instance - } - - protected fun register(instance: V): ReadOnlyProperty { - return ReadOnlyProperty { _, property -> - registry[property.name] = instance - instance - } - } - - protected fun findName(instance: T) = - registry.entries.firstOrNull { it.value == instance }?.key ?: error("Enum not registered") -} - class OsTypeNew private constructor(private val marker: TypedMarker) : TypedMarker { val value get() = GlobalContext.getValue(marker) From f7dae85fe4a68b7955cc66ea8aa6320ce7fc6480 Mon Sep 17 00:00:00 2001 From: Marcin Kuszczak <1508798+aartiPl@users.noreply.github.com> Date: Sat, 19 Aug 2023 17:43:28 +0200 Subject: [PATCH 33/50] Ability to handle osType string --- src/main/kotlin/io/github/kscripting/os/Os.kt | 5 +++++ src/main/kotlin/io/github/kscripting/os/instance/CygwinOs.kt | 1 + .../kotlin/io/github/kscripting/os/instance/FreeBsdOs.kt | 1 + src/main/kotlin/io/github/kscripting/os/instance/LinuxOs.kt | 1 + src/main/kotlin/io/github/kscripting/os/instance/MacOs.kt | 1 + src/main/kotlin/io/github/kscripting/os/instance/MsysOs.kt | 1 + .../kotlin/io/github/kscripting/os/instance/WindowsOs.kt | 1 + src/main/kotlin/io/github/kscripting/os/model/OsTypeNew.kt | 3 +++ 8 files changed, 14 insertions(+) diff --git a/src/main/kotlin/io/github/kscripting/os/Os.kt b/src/main/kotlin/io/github/kscripting/os/Os.kt index 97a7840..25e89bd 100644 --- a/src/main/kotlin/io/github/kscripting/os/Os.kt +++ b/src/main/kotlin/io/github/kscripting/os/Os.kt @@ -7,6 +7,11 @@ import net.igsoft.typeutils.marker.AutoTypedMarker val CURRENT_OS = AutoTypedMarker.create() interface Os { + //LINUX("linux"), MACOS("darwin"), WINDOWS("windows"), CYGWIN("cygwin"), MSYS("msys"), FREEBSD("freebsd"); + // Exact comparison (it.osName.equals(name, true)) seems to be not feasible as there is also e.g. "darwin21" + // "darwin19", "linux-musl" (for Docker Alpine), "linux-gnu" and maybe even other osTypes. But it seems that + // startsWith() covers all cases. + val osTypePrefix: String val type: OsType val userHome: OsPath val pathSeparator: String diff --git a/src/main/kotlin/io/github/kscripting/os/instance/CygwinOs.kt b/src/main/kotlin/io/github/kscripting/os/instance/CygwinOs.kt index 482843a..404619d 100644 --- a/src/main/kotlin/io/github/kscripting/os/instance/CygwinOs.kt +++ b/src/main/kotlin/io/github/kscripting/os/instance/CygwinOs.kt @@ -4,6 +4,7 @@ import io.github.kscripting.os.model.OsPath import io.github.kscripting.os.model.OsType class CygwinOs(userHome: String, nativeFileSystemRoot: String) : HostedOs { + override val osTypePrefix: String = "cygwin" override val type: OsType = OsType.CYGWIN override val userHome: OsPath = TODO() override val nativeType: OsType = OsType.WINDOWS diff --git a/src/main/kotlin/io/github/kscripting/os/instance/FreeBsdOs.kt b/src/main/kotlin/io/github/kscripting/os/instance/FreeBsdOs.kt index dd20b75..552d07f 100644 --- a/src/main/kotlin/io/github/kscripting/os/instance/FreeBsdOs.kt +++ b/src/main/kotlin/io/github/kscripting/os/instance/FreeBsdOs.kt @@ -5,6 +5,7 @@ import io.github.kscripting.os.model.OsPath import io.github.kscripting.os.model.OsType class FreeBsdOs(userHome: String) : Os { + override val osTypePrefix: String = "freebsd" override val type: OsType = OsType.LINUX override val pathSeparator get() = "/" override val userHome: OsPath = OsPath.of(OsType.LINUX, userHome) diff --git a/src/main/kotlin/io/github/kscripting/os/instance/LinuxOs.kt b/src/main/kotlin/io/github/kscripting/os/instance/LinuxOs.kt index f8ae167..751fea0 100644 --- a/src/main/kotlin/io/github/kscripting/os/instance/LinuxOs.kt +++ b/src/main/kotlin/io/github/kscripting/os/instance/LinuxOs.kt @@ -5,6 +5,7 @@ import io.github.kscripting.os.model.OsPath import io.github.kscripting.os.model.OsType class LinuxOs(userHome: String) : Os { + override val osTypePrefix: String = "linux" override val type: OsType = OsType.LINUX override val pathSeparator get() = "/" override val userHome: OsPath = OsPath.of(OsType.LINUX, userHome) diff --git a/src/main/kotlin/io/github/kscripting/os/instance/MacOs.kt b/src/main/kotlin/io/github/kscripting/os/instance/MacOs.kt index 51f4330..faf013f 100644 --- a/src/main/kotlin/io/github/kscripting/os/instance/MacOs.kt +++ b/src/main/kotlin/io/github/kscripting/os/instance/MacOs.kt @@ -5,6 +5,7 @@ import io.github.kscripting.os.model.OsPath import io.github.kscripting.os.model.OsType class MacOs(userHome: String) : Os { + override val osTypePrefix: String = "darwin" override val type: OsType = OsType.LINUX override val pathSeparator get() = "/" override val userHome: OsPath = OsPath.of(OsType.LINUX, userHome) diff --git a/src/main/kotlin/io/github/kscripting/os/instance/MsysOs.kt b/src/main/kotlin/io/github/kscripting/os/instance/MsysOs.kt index 1e6b963..fe38767 100644 --- a/src/main/kotlin/io/github/kscripting/os/instance/MsysOs.kt +++ b/src/main/kotlin/io/github/kscripting/os/instance/MsysOs.kt @@ -4,6 +4,7 @@ import io.github.kscripting.os.model.OsPath import io.github.kscripting.os.model.OsType class MsysOs(userHome: String, nativeFileSystemRoot: String) : HostedOs { + override val osTypePrefix: String = "msys" override val type: OsType = OsType.MSYS override val userHome: OsPath = TODO() override val nativeType: OsType = OsType.WINDOWS diff --git a/src/main/kotlin/io/github/kscripting/os/instance/WindowsOs.kt b/src/main/kotlin/io/github/kscripting/os/instance/WindowsOs.kt index b532a59..ff7d18d 100644 --- a/src/main/kotlin/io/github/kscripting/os/instance/WindowsOs.kt +++ b/src/main/kotlin/io/github/kscripting/os/instance/WindowsOs.kt @@ -6,6 +6,7 @@ import io.github.kscripting.os.model.OsType class WindowsOs(userHome: String) : Os { + override val osTypePrefix: String = "windows" override val type: OsType = OsType.WINDOWS override val pathSeparator get() = "\\" override val userHome: OsPath = TODO() diff --git a/src/main/kotlin/io/github/kscripting/os/model/OsTypeNew.kt b/src/main/kotlin/io/github/kscripting/os/model/OsTypeNew.kt index 58b793d..8d3c1ff 100644 --- a/src/main/kotlin/io/github/kscripting/os/model/OsTypeNew.kt +++ b/src/main/kotlin/io/github/kscripting/os/model/OsTypeNew.kt @@ -28,6 +28,9 @@ class OsTypeNew private constructor(private val marker: TypedMarker) val native: OsTypeNew = guessNativeType() + fun findByOsTypeString(osTypeString: String): OsTypeNew? = + find { osTypeString.startsWith(it.value.osTypePrefix, true) } + private fun guessNativeType(): OsTypeNew { when { SystemUtils.IS_OS_LINUX -> return LINUX From e06e067a1c637f269a577185860f1ae961b80511 Mon Sep 17 00:00:00 2001 From: Marcin Kuszczak <1508798+aartiPl@users.noreply.github.com> Date: Sat, 19 Aug 2023 20:04:46 +0200 Subject: [PATCH 34/50] Reimplemented OsType, so that it can simplify implementation of OsType --- build.gradle.kts | 5 ++ .../shell/integration/tools/TestContext.kt | 3 +- src/main/kotlin/io/github/kscripting/os/Os.kt | 2 +- .../github/kscripting/os/instance/CygwinOs.kt | 8 +-- .../kscripting/os/instance/FreeBsdOs.kt | 6 +- .../github/kscripting/os/instance/HostedOs.kt | 4 +- .../github/kscripting/os/instance/LinuxOs.kt | 4 +- .../io/github/kscripting/os/instance/MacOs.kt | 6 +- .../github/kscripting/os/instance/MsysOs.kt | 8 +-- .../kscripting/os/instance/WindowsOs.kt | 6 +- .../io/github/kscripting/os/model/OsPath.kt | 23 ++++---- .../io/github/kscripting/os/model/OsType.kt | 37 ++++++++---- .../github/kscripting/os/model/OsTypeNew.kt | 57 ------------------- .../github/kscripting/shell/ShellExecutor.kt | 11 ++-- .../github/kscripting/os/model/OsPathTest.kt | 17 +++++- 15 files changed, 86 insertions(+), 111 deletions(-) delete mode 100644 src/main/kotlin/io/github/kscripting/os/model/OsTypeNew.kt diff --git a/build.gradle.kts b/build.gradle.kts index 4c9932e..47cd3fe 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -35,6 +35,11 @@ configurations { get("itestImplementation").apply { extendsFrom(get("testImplementation")) } } +configurations.all { + resolutionStrategy.cacheDynamicVersionsFor(0, "seconds") + resolutionStrategy.cacheChangingModulesFor(0, "seconds") +} + tasks.create("itest") { val itags = System.getProperty("includeTags") ?: "" val etags = System.getProperty("excludeTags") ?: "" diff --git a/src/itest/kotlin/io/github/kscripting/shell/integration/tools/TestContext.kt b/src/itest/kotlin/io/github/kscripting/shell/integration/tools/TestContext.kt index 6b7a196..66f240d 100644 --- a/src/itest/kotlin/io/github/kscripting/shell/integration/tools/TestContext.kt +++ b/src/itest/kotlin/io/github/kscripting/shell/integration/tools/TestContext.kt @@ -1,12 +1,13 @@ package io.github.kscripting.shell.integration.tools +import io.github.kscripting.os.Os import io.github.kscripting.os.model.* import io.github.kscripting.shell.ShellExecutor import io.github.kscripting.shell.util.Sanitizer @Suppress("MemberVisibilityCanBePrivate") object TestContext { - val osType: OsType = OsType.find(System.getProperty("osType")) ?: OsType.native + val osType: OsType = OsType.find(System.getProperty("osType")) ?: OsType.native val projectPath: OsPath = OsPath.of(OsType.native, System.getProperty("projectPath")).convert(osType) val execPath: OsPath = projectPath.resolve("build/shell_test/bin") diff --git a/src/main/kotlin/io/github/kscripting/os/Os.kt b/src/main/kotlin/io/github/kscripting/os/Os.kt index 25e89bd..70fb3c2 100644 --- a/src/main/kotlin/io/github/kscripting/os/Os.kt +++ b/src/main/kotlin/io/github/kscripting/os/Os.kt @@ -12,7 +12,7 @@ interface Os { // "darwin19", "linux-musl" (for Docker Alpine), "linux-gnu" and maybe even other osTypes. But it seems that // startsWith() covers all cases. val osTypePrefix: String - val type: OsType + val type: OsType val userHome: OsPath val pathSeparator: String } diff --git a/src/main/kotlin/io/github/kscripting/os/instance/CygwinOs.kt b/src/main/kotlin/io/github/kscripting/os/instance/CygwinOs.kt index 404619d..9f4766f 100644 --- a/src/main/kotlin/io/github/kscripting/os/instance/CygwinOs.kt +++ b/src/main/kotlin/io/github/kscripting/os/instance/CygwinOs.kt @@ -5,8 +5,8 @@ import io.github.kscripting.os.model.OsType class CygwinOs(userHome: String, nativeFileSystemRoot: String) : HostedOs { override val osTypePrefix: String = "cygwin" - override val type: OsType = OsType.CYGWIN - override val userHome: OsPath = TODO() - override val nativeType: OsType = OsType.WINDOWS - override val nativeFileSystemRoot: OsPath = TODO() + override val type: OsType = OsType.CYGWIN + override val userHome: OsPath = OsPath.of(type, userHome) + override val nativeType: OsType = OsType.WINDOWS + override val nativeFileSystemRoot: OsPath = OsPath.of(nativeType, nativeFileSystemRoot) } diff --git a/src/main/kotlin/io/github/kscripting/os/instance/FreeBsdOs.kt b/src/main/kotlin/io/github/kscripting/os/instance/FreeBsdOs.kt index 552d07f..43afe8f 100644 --- a/src/main/kotlin/io/github/kscripting/os/instance/FreeBsdOs.kt +++ b/src/main/kotlin/io/github/kscripting/os/instance/FreeBsdOs.kt @@ -6,7 +6,7 @@ import io.github.kscripting.os.model.OsType class FreeBsdOs(userHome: String) : Os { override val osTypePrefix: String = "freebsd" - override val type: OsType = OsType.LINUX - override val pathSeparator get() = "/" - override val userHome: OsPath = OsPath.of(OsType.LINUX, userHome) + override val type: OsType = OsType.FREEBSD + override val pathSeparator: String get() = "/" + override val userHome: OsPath = OsPath.of(OsType.FREEBSD, userHome) } diff --git a/src/main/kotlin/io/github/kscripting/os/instance/HostedOs.kt b/src/main/kotlin/io/github/kscripting/os/instance/HostedOs.kt index f0c231f..e0cb5ef 100644 --- a/src/main/kotlin/io/github/kscripting/os/instance/HostedOs.kt +++ b/src/main/kotlin/io/github/kscripting/os/instance/HostedOs.kt @@ -5,7 +5,7 @@ import io.github.kscripting.os.model.OsPath import io.github.kscripting.os.model.OsType interface HostedOs : Os { - val nativeType: OsType + val nativeType: OsType val nativeFileSystemRoot: OsPath - override val pathSeparator get(): String = "/" + override val pathSeparator: String get() = "/" } diff --git a/src/main/kotlin/io/github/kscripting/os/instance/LinuxOs.kt b/src/main/kotlin/io/github/kscripting/os/instance/LinuxOs.kt index 751fea0..a661a86 100644 --- a/src/main/kotlin/io/github/kscripting/os/instance/LinuxOs.kt +++ b/src/main/kotlin/io/github/kscripting/os/instance/LinuxOs.kt @@ -6,7 +6,7 @@ import io.github.kscripting.os.model.OsType class LinuxOs(userHome: String) : Os { override val osTypePrefix: String = "linux" - override val type: OsType = OsType.LINUX - override val pathSeparator get() = "/" + override val type: OsType = OsType.LINUX + override val pathSeparator: String get() = "/" override val userHome: OsPath = OsPath.of(OsType.LINUX, userHome) } diff --git a/src/main/kotlin/io/github/kscripting/os/instance/MacOs.kt b/src/main/kotlin/io/github/kscripting/os/instance/MacOs.kt index faf013f..1e200a0 100644 --- a/src/main/kotlin/io/github/kscripting/os/instance/MacOs.kt +++ b/src/main/kotlin/io/github/kscripting/os/instance/MacOs.kt @@ -6,7 +6,7 @@ import io.github.kscripting.os.model.OsType class MacOs(userHome: String) : Os { override val osTypePrefix: String = "darwin" - override val type: OsType = OsType.LINUX - override val pathSeparator get() = "/" - override val userHome: OsPath = OsPath.of(OsType.LINUX, userHome) + override val type: OsType = OsType.MACOS + override val pathSeparator: String get() = "/" + override val userHome: OsPath = OsPath.of(OsType.MACOS, userHome) } diff --git a/src/main/kotlin/io/github/kscripting/os/instance/MsysOs.kt b/src/main/kotlin/io/github/kscripting/os/instance/MsysOs.kt index fe38767..1fc1101 100644 --- a/src/main/kotlin/io/github/kscripting/os/instance/MsysOs.kt +++ b/src/main/kotlin/io/github/kscripting/os/instance/MsysOs.kt @@ -5,8 +5,8 @@ import io.github.kscripting.os.model.OsType class MsysOs(userHome: String, nativeFileSystemRoot: String) : HostedOs { override val osTypePrefix: String = "msys" - override val type: OsType = OsType.MSYS - override val userHome: OsPath = TODO() - override val nativeType: OsType = OsType.WINDOWS - override val nativeFileSystemRoot: OsPath = TODO() + override val type: OsType = OsType.MSYS + override val userHome: OsPath = OsPath.of(type, userHome) + override val nativeType: OsType = OsType.WINDOWS + override val nativeFileSystemRoot: OsPath = OsPath.of(nativeType, nativeFileSystemRoot) } diff --git a/src/main/kotlin/io/github/kscripting/os/instance/WindowsOs.kt b/src/main/kotlin/io/github/kscripting/os/instance/WindowsOs.kt index ff7d18d..f32eed6 100644 --- a/src/main/kotlin/io/github/kscripting/os/instance/WindowsOs.kt +++ b/src/main/kotlin/io/github/kscripting/os/instance/WindowsOs.kt @@ -7,7 +7,7 @@ import io.github.kscripting.os.model.OsType class WindowsOs(userHome: String) : Os { override val osTypePrefix: String = "windows" - override val type: OsType = OsType.WINDOWS - override val pathSeparator get() = "\\" - override val userHome: OsPath = TODO() + override val type: OsType = OsType.WINDOWS + override val pathSeparator: String get() = "\\" + override val userHome: OsPath = OsPath.of(type, userHome) } diff --git a/src/main/kotlin/io/github/kscripting/os/model/OsPath.kt b/src/main/kotlin/io/github/kscripting/os/model/OsPath.kt index 75ff3fa..1ef397d 100644 --- a/src/main/kotlin/io/github/kscripting/os/model/OsPath.kt +++ b/src/main/kotlin/io/github/kscripting/os/model/OsPath.kt @@ -6,12 +6,11 @@ import arrow.core.right //Path representation for different OSes @Suppress("MemberVisibilityCanBePrivate") -data class OsPath(val osType: OsType, val root: String, val pathParts: List) { +data class OsPath(val osType: OsType<*>, val root: String, val pathParts: List) { val isRelative: Boolean get() = root.isEmpty() val isAbsolute: Boolean get() = !isRelative - val pathSeparator: String get() = if (osType.isWindowsLike()) "\\" else "/" - val path get(): String = root + pathParts.joinToString(pathSeparator) { it } + val path get(): String = root + pathParts.joinToString(osType.value.pathSeparator) { it } val OsPath.leaf get(): String = if (pathParts.isEmpty()) root else pathParts.last() @@ -22,7 +21,7 @@ data class OsPath(val osType: OsType, val root: String, val pathParts: List /*nativeRootPath: OsPath = emptyPath*/): OsPath { if (osType == targetOsType) { return this } @@ -106,13 +105,13 @@ data class OsPath(val osType: OsType, val root: String, val pathParts: List throw IllegalArgumentException("Invalid conversion: $this to ${targetOsType.name}") + else -> throw IllegalArgumentException("Invalid conversion: $this to $targetOsType") } return OsPath(targetOsType, newRoot, newParts) } - override fun toString(): String = "$path [${osType.name}]" + override fun toString(): String = "$path [$osType]" companion object { //https://stackoverflow.com/questions/1976007/what-characters-are-forbidden-in-windows-and-linux-directory-names @@ -138,9 +137,9 @@ data class OsPath(val osType: OsType, val root: String, val pathParts: List, vararg pathParts: String): OsPath = of(osType, pathParts.toList()) - fun of(osType: OsType, pathParts: List): OsPath { + fun of(osType: OsType<*>, pathParts: List): OsPath { return when (val result = internalCreate(osType, pathParts)) { is Either.Right -> result.value is Either.Left -> throw IllegalArgumentException(result.value) @@ -149,9 +148,9 @@ data class OsPath(val osType: OsType, val root: String, val pathParts: List, vararg pathParts: String): OsPath? = ofOrNull(osType, pathParts.toList()) - fun ofOrNull(osType: OsType, pathParts: List): OsPath? { + fun ofOrNull(osType: OsType<*>, pathParts: List): OsPath? { return when (val result = internalCreate(osType, pathParts)) { is Either.Right -> result.value is Either.Left -> null @@ -161,7 +160,7 @@ data class OsPath(val osType: OsType, val root: String, val pathParts: List): Either { + private fun internalCreate(osType: OsType<*>, pathParts: List): Either { val path = pathParts.joinToString("/") //Detect root diff --git a/src/main/kotlin/io/github/kscripting/os/model/OsType.kt b/src/main/kotlin/io/github/kscripting/os/model/OsType.kt index 313da6d..da45049 100644 --- a/src/main/kotlin/io/github/kscripting/os/model/OsType.kt +++ b/src/main/kotlin/io/github/kscripting/os/model/OsType.kt @@ -1,25 +1,38 @@ package io.github.kscripting.os.model +import io.github.kscripting.os.Os +import io.github.kscripting.os.instance.* +import net.igsoft.typeutils.enum.TypedEnumCompanion +import net.igsoft.typeutils.globalcontext.GlobalContext +import net.igsoft.typeutils.marker.AutoTypedMarker +import net.igsoft.typeutils.marker.DefaultTypedMarker +import net.igsoft.typeutils.marker.TypedMarker import org.apache.commons.lang3.SystemUtils -enum class OsType(val osName: String) { - LINUX("linux"), MACOS("darwin"), WINDOWS("windows"), CYGWIN("cygwin"), MSYS("msys"), FREEBSD("freebsd"); - fun isPosixLike() = (this == LINUX || this == MACOS || this == FREEBSD || this == CYGWIN || this == MSYS) +class OsType private constructor(private val marker: TypedMarker) : DefaultTypedMarker(marker) { + val value: T get() = GlobalContext.getValue(marker) + + fun isPosixLike() = + (this == LINUX || this == MACOS || this == FREEBSD || this == CYGWIN || this == MSYS) + fun isPosixHostedOnWindows() = (this == CYGWIN || this == MSYS) fun isWindowsLike() = (this == WINDOWS) - companion object { - val native: OsType = guessNativeType() + companion object : TypedEnumCompanion>() { + val LINUX by register(OsType(AutoTypedMarker.create())) + val WINDOWS by register(OsType(AutoTypedMarker.create())) + val CYGWIN by register(OsType(AutoTypedMarker.create())) + val MSYS by register(OsType(AutoTypedMarker.create())) + val MACOS by register(OsType(AutoTypedMarker.create())) + val FREEBSD by register(OsType(AutoTypedMarker.create())) - fun findOrThrow(name: String): OsType = find(name) ?: throw IllegalArgumentException("Unsupported OS: '$name'") + val native: OsType = guessNativeType() - // Exact comparison (it.osName.equals(name, true)) seems to be not feasible as there is also e.g. "darwin21" - // "darwin19", "linux-musl" (for Docker Alpine), "linux-gnu" and maybe even other osTypes. But it seems that - // startsWith() covers all cases. - fun find(name: String): OsType? = values().find { name.startsWith(it.osName, true) } + fun findByOsTypeString(osTypeString: String): OsType? = + find { osTypeString.startsWith(it.value.osTypePrefix, true) } - private fun guessNativeType(): OsType { + private fun guessNativeType(): OsType { when { SystemUtils.IS_OS_LINUX -> return LINUX SystemUtils.IS_OS_MAC -> return MACOS @@ -30,4 +43,6 @@ enum class OsType(val osName: String) { return LINUX } } + + override fun toString(): String = findName(this) } diff --git a/src/main/kotlin/io/github/kscripting/os/model/OsTypeNew.kt b/src/main/kotlin/io/github/kscripting/os/model/OsTypeNew.kt deleted file mode 100644 index 8d3c1ff..0000000 --- a/src/main/kotlin/io/github/kscripting/os/model/OsTypeNew.kt +++ /dev/null @@ -1,57 +0,0 @@ -package io.github.kscripting.os.model - -import io.github.kscripting.os.Os -import io.github.kscripting.os.instance.* -import net.igsoft.typeutils.enum.TypedEnumCompanion -import net.igsoft.typeutils.globalcontext.GlobalContext -import net.igsoft.typeutils.marker.AutoTypedMarker -import net.igsoft.typeutils.marker.TypedMarker -import org.apache.commons.lang3.SystemUtils - - -class OsTypeNew private constructor(private val marker: TypedMarker) : TypedMarker { - val value get() = GlobalContext.getValue(marker) - - fun isPosixLike() = - (this == LINUX || this == MACOS || this == FREEBSD || this == CYGWIN || this == MSYS) - - fun isPosixHostedOnWindows() = (this == CYGWIN || this == MSYS) - fun isWindowsLike() = (this == WINDOWS) - - companion object : TypedEnumCompanion>() { - val LINUX by register(OsTypeNew(AutoTypedMarker.create())) - val WINDOWS by register(OsTypeNew(AutoTypedMarker.create())) - val CYGWIN by register(OsTypeNew(AutoTypedMarker.create())) - val MSYS by register(OsTypeNew(AutoTypedMarker.create())) - val MACOS by register(OsTypeNew(AutoTypedMarker.create())) - val FREEBSD by register(OsTypeNew(AutoTypedMarker.create())) - - val native: OsTypeNew = guessNativeType() - - fun findByOsTypeString(osTypeString: String): OsTypeNew? = - find { osTypeString.startsWith(it.value.osTypePrefix, true) } - - private fun guessNativeType(): OsTypeNew { - when { - SystemUtils.IS_OS_LINUX -> return LINUX - SystemUtils.IS_OS_MAC -> return MACOS - SystemUtils.IS_OS_WINDOWS -> return WINDOWS - SystemUtils.IS_OS_FREE_BSD -> return FREEBSD - } - - return LINUX - } - } - - override val clazz: Class get() = marker.clazz - override val id: Any get() = marker.id - override fun toString(): String = findName(this) -} - -fun main() { - GlobalContext.register(OsTypeNew.LINUX, LinuxOs("home")) - val test = OsTypeNew.find("WINDOWS") - println(test) - - println(OsTypeNew.LINUX) -} diff --git a/src/main/kotlin/io/github/kscripting/shell/ShellExecutor.kt b/src/main/kotlin/io/github/kscripting/shell/ShellExecutor.kt index 2f02f21..4ad0de8 100644 --- a/src/main/kotlin/io/github/kscripting/shell/ShellExecutor.kt +++ b/src/main/kotlin/io/github/kscripting/shell/ShellExecutor.kt @@ -1,5 +1,6 @@ package io.github.kscripting.shell +import io.github.kscripting.os.Os import io.github.kscripting.os.model.OsPath import io.github.kscripting.os.model.OsType import io.github.kscripting.shell.model.ProcessResult @@ -20,7 +21,7 @@ import java.util.regex.Pattern object ShellExecutor { private val SPLIT_PATTERN = Pattern.compile("([^\"]\\S*|\".+?\")\\s*") private val UTF_8 = StandardCharsets.UTF_8.name() - private val DEFAULT_SHELL_MAPPER: Map = mapOf( + private val DEFAULT_SHELL_MAPPER: Map, ShellType> = mapOf( OsType.WINDOWS to ShellType.CMD, OsType.LINUX to ShellType.BASH, OsType.FREEBSD to ShellType.BASH, @@ -31,7 +32,7 @@ object ShellExecutor { fun evalAndGobble( command: String, - osType: OsType = OsType.native, + osType: OsType = OsType.native, workingDirectory: OsPath? = null, waitTimeMinutes: Int = 10, inheritInput: Boolean = false, @@ -40,7 +41,7 @@ object ShellExecutor { outPrinter: List = emptyList(), errPrinter: List = emptyList(), inputStream: InputStream? = null, - shellMapper: Map = DEFAULT_SHELL_MAPPER, + shellMapper: Map, ShellType> = DEFAULT_SHELL_MAPPER, envAdjuster: EnvAdjuster = {} ): ProcessResult { val outStream = ByteArrayOutputStream(1024) @@ -72,7 +73,7 @@ object ShellExecutor { fun eval( command: String, - osType: OsType = OsType.native, + osType: OsType = OsType.native, workingDirectory: OsPath? = null, waitTimeMinutes: Int = 10, inheritInput: Boolean = false, @@ -81,7 +82,7 @@ object ShellExecutor { outPrinter: List = DEFAULT_OUT_PRINTERS, errPrinter: List = DEFAULT_ERR_PRINTERS, inputStream: InputStream? = null, - shellMapper: Map = DEFAULT_SHELL_MAPPER, + shellMapper: Map, ShellType> = DEFAULT_SHELL_MAPPER, envAdjuster: EnvAdjuster = {} ): Int { val sanitizedCommand = inputSanitizer.sanitize(command) diff --git a/src/test/kotlin/io/github/kscripting/os/model/OsPathTest.kt b/src/test/kotlin/io/github/kscripting/os/model/OsPathTest.kt index 2b0d474..eaa684e 100644 --- a/src/test/kotlin/io/github/kscripting/os/model/OsPathTest.kt +++ b/src/test/kotlin/io/github/kscripting/os/model/OsPathTest.kt @@ -2,6 +2,11 @@ package io.github.kscripting.os.model import assertk.assertThat import assertk.assertions.* +import io.github.kscripting.os.instance.CygwinOs +import io.github.kscripting.os.instance.LinuxOs +import io.github.kscripting.os.instance.MsysOs +import io.github.kscripting.os.instance.WindowsOs +import net.igsoft.typeutils.globalcontext.GlobalContext import org.junit.jupiter.api.Test class OsPathTest { @@ -104,9 +109,9 @@ class OsPathTest { @Test fun `Test Linux stringPath`() { - assertThat( - OsPath.of(OsType.LINUX, "/home/admin/.kscript").path - ).isEqualTo("/home/admin/.kscript") + GlobalContext.registerOrReplace(OsType.LINUX, LinuxOs("userhome")) + + assertThat(OsPath.of(OsType.LINUX, "/home/admin/.kscript").path).isEqualTo("/home/admin/.kscript") assertThat(OsPath.of(OsType.LINUX, "/a/b/c/../d/script").path).isEqualTo("/a/b/d/script") assertThat(OsPath.of(OsType.LINUX, "./././../../script").path).isEqualTo("../../script") assertThat(OsPath.of(OsType.LINUX, "script/file.txt").path).isEqualTo("script/file.txt") @@ -270,6 +275,9 @@ class OsPathTest { @Test fun `Test Windows to Cygwin`() { + GlobalContext.registerOrReplace(OsType.CYGWIN, CygwinOs("userhome", "nativeFileSystemRoot")) + GlobalContext.registerOrReplace(OsType.WINDOWS, WindowsOs("userhome")) + assertThat( OsPath.of(OsType.WINDOWS, "C:\\home\\admin\\.kscript").convert(OsType.CYGWIN).path ).isEqualTo("/cygdrive/c/home/admin/.kscript") @@ -303,6 +311,9 @@ class OsPathTest { @Test fun `Test MSys to Windows`() { + GlobalContext.registerOrReplace(OsType.MSYS, MsysOs("userhome", "nativeFileSystemRoot")) + GlobalContext.registerOrReplace(OsType.WINDOWS, WindowsOs("userhome")) + assertThat( OsPath.of(OsType.MSYS, "/c/home/admin/.kscript").convert(OsType.WINDOWS).path ).isEqualTo("c:\\home\\admin\\.kscript") From 16f5d16be23c4d7d51655e7fa3745684cc659f8b Mon Sep 17 00:00:00 2001 From: Marcin Kuszczak <1508798+aartiPl@users.noreply.github.com> Date: Mon, 21 Aug 2023 19:23:07 +0200 Subject: [PATCH 35/50] Improved implementation of hosted paths --- .../io/github/kscripting/os/model/OsPath.kt | 111 +++--- .../github/kscripting/os/model/OsPathExt.kt | 2 +- .../kscripting/os/model/HostedOsPathTest.kt | 75 ++++ .../github/kscripting/os/model/OsPathTest.kt | 325 ------------------ .../kscripting/os/model/PosixOsPathTest.kt | 159 +++++++++ .../kscripting/os/model/WindowsOsPathTest.kt | 122 +++++++ 6 files changed, 417 insertions(+), 377 deletions(-) create mode 100644 src/test/kotlin/io/github/kscripting/os/model/HostedOsPathTest.kt delete mode 100644 src/test/kotlin/io/github/kscripting/os/model/OsPathTest.kt create mode 100644 src/test/kotlin/io/github/kscripting/os/model/PosixOsPathTest.kt create mode 100644 src/test/kotlin/io/github/kscripting/os/model/WindowsOsPathTest.kt diff --git a/src/main/kotlin/io/github/kscripting/os/model/OsPath.kt b/src/main/kotlin/io/github/kscripting/os/model/OsPath.kt index 1ef397d..a48ad05 100644 --- a/src/main/kotlin/io/github/kscripting/os/model/OsPath.kt +++ b/src/main/kotlin/io/github/kscripting/os/model/OsPath.kt @@ -3,6 +3,7 @@ package io.github.kscripting.os.model import arrow.core.Either import arrow.core.left import arrow.core.right +import io.github.kscripting.os.instance.HostedOs //Path representation for different OSes @Suppress("MemberVisibilityCanBePrivate") @@ -41,74 +42,82 @@ data class OsPath(val osType: OsType<*>, val root: String, val pathParts: List /*nativeRootPath: OsPath = emptyPath*/): OsPath { - if (osType == targetOsType) { - return this + fun toNative(): OsPath { + check(osType.isPosixHostedOnWindows()) { + "You can convert only paths on hosted OS-es" } - if ((osType.isPosixLike() && targetOsType.isPosixLike()) || (osType.isWindowsLike() && targetOsType.isWindowsLike())) { - return OsPath(targetOsType, root, pathParts) - } - - val toHosted = osType.isWindowsLike() && targetOsType.isPosixHostedOnWindows() - val toNative = osType.isPosixHostedOnWindows() && targetOsType.isWindowsLike() - - require(toHosted || toNative) { - "Only paths conversion between Windows and Posix hosted on Windows are supported" - } + val hostedOs = osType.value as HostedOs val newParts = mutableListOf() var newRoot = "" - when { - toHosted -> { - if (isAbsolute) { - //root is like 'C:\' - val drive = root.dropLast(2).lowercase() - - newRoot = "/" - - if (targetOsType == OsType.CYGWIN) { - newParts.add("cygdrive") - newParts.add(drive) - } else { - newParts.add(drive) + if (isAbsolute) { + when (osType) { + OsType.CYGWIN -> { + if (pathParts[0].equals("cygdrive", true)) { //Paths referring /cygdrive + newRoot = pathParts[1] + ":\\" + newParts.addAll(pathParts.subList(2, pathParts.size)) + } else if (root == "~") { //Paths starting with ~ + newRoot = hostedOs.nativeFileSystemRoot.root + newParts.addAll(hostedOs.nativeFileSystemRoot.pathParts) + newParts.addAll(hostedOs.userHome.pathParts) + newParts.addAll(pathParts) + } else { //Any other path like: /usr/bin + newRoot = hostedOs.nativeFileSystemRoot.root + newParts.addAll(hostedOs.nativeFileSystemRoot.pathParts) + newParts.addAll(pathParts) } } - newParts.addAll(pathParts) - } - - toNative -> { - if (isAbsolute) { - if (osType == OsType.CYGWIN) { - newRoot = pathParts[1] + ":\\" - newParts.addAll(pathParts.subList(2, pathParts.size)) - } else { + OsType.MSYS -> { + if (pathParts[0].length == 1 && (pathParts[0][0].code in 65..90 || pathParts[0][0].code in 97..122)) { //Paths referring with drive letter at the beginning newRoot = pathParts[0] + ":\\" newParts.addAll(pathParts.subList(1, pathParts.size)) + } else if (root == "~") { //Paths starting with ~ + newRoot = hostedOs.nativeFileSystemRoot.root + newParts.addAll(hostedOs.nativeFileSystemRoot.pathParts) + newParts.addAll(hostedOs.userHome.pathParts) + newParts.addAll(pathParts) + } else { //Any other path like: /usr/bin + newRoot = hostedOs.nativeFileSystemRoot.root + newParts.addAll(hostedOs.nativeFileSystemRoot.pathParts) + newParts.addAll(pathParts) } - } else { - newParts.addAll(pathParts) } } + } else { + newParts.addAll(pathParts) + } + + return OsPath(hostedOs.nativeType, newRoot, newParts) + } - else -> throw IllegalArgumentException("Invalid conversion: $this to $targetOsType") + fun toHosted(targetOs: OsType<*>): OsPath { + check(targetOs.isPosixHostedOnWindows() && ((targetOs.value) as HostedOs).nativeType == osType) { + "You can convert only paths to hosted OS-es" } - return OsPath(targetOsType, newRoot, newParts) + val newParts = mutableListOf() + var newRoot = "" + + if (isAbsolute) { + //root is like 'C:\' + val drive = root.dropLast(2).lowercase() + + newRoot = "/" + + if (targetOs.value.type == OsType.CYGWIN) { + newParts.add("cygdrive") + newParts.add(drive) + } else { + newParts.add(drive) + } + } + + newParts.addAll(pathParts) + + return OsPath(targetOs, newRoot, newParts) } override fun toString(): String = "$path [$osType]" diff --git a/src/main/kotlin/io/github/kscripting/os/model/OsPathExt.kt b/src/main/kotlin/io/github/kscripting/os/model/OsPathExt.kt index 7dbe49a..fc648b6 100644 --- a/src/main/kotlin/io/github/kscripting/os/model/OsPathExt.kt +++ b/src/main/kotlin/io/github/kscripting/os/model/OsPathExt.kt @@ -21,7 +21,7 @@ fun URI.toOsPath(): OsPath = // Conversion from OsPath fun OsPath.toNativePath(): Path = Paths.get(toNativeOsPath().path) -fun OsPath.toNativeOsPath() = if (osType.isPosixHostedOnWindows()) convert(OsType.WINDOWS) else this +fun OsPath.toNativeOsPath() = if (osType.isPosixHostedOnWindows()) toNative() else this fun OsPath.toNativeFile(): File = toNativePath().toFile() diff --git a/src/test/kotlin/io/github/kscripting/os/model/HostedOsPathTest.kt b/src/test/kotlin/io/github/kscripting/os/model/HostedOsPathTest.kt new file mode 100644 index 0000000..b8144fb --- /dev/null +++ b/src/test/kotlin/io/github/kscripting/os/model/HostedOsPathTest.kt @@ -0,0 +1,75 @@ +package io.github.kscripting.os.model + +import assertk.assertThat +import assertk.assertions.isEqualTo +import io.github.kscripting.os.instance.CygwinOs +import io.github.kscripting.os.instance.MsysOs +import io.github.kscripting.os.instance.WindowsOs +import net.igsoft.typeutils.globalcontext.GlobalContext +import org.junit.jupiter.api.BeforeAll +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.TestInstance + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class HostedOsPathTest { + + @BeforeAll + fun beforeAll() { + //Installation cygwin/msys base path: cygpath -w / + GlobalContext.registerOrReplace(OsType.CYGWIN, CygwinOs("/home/admin", "C:\\Programs\\Cygwin\\")) + GlobalContext.registerOrReplace(OsType.MSYS, MsysOs("/home/admin", "C:\\Programs\\Msys\\")) + GlobalContext.registerOrReplace(OsType.WINDOWS, WindowsOs("C:\\Users\\Admin\\.kscript")) + } + + @Test + fun `Test Cygwin to Windows`() { + assertThat(OsPath.of(OsType.CYGWIN, "/cygdrive/c/home/admin/.kscript").toNative().path) + .isEqualTo("c:\\home\\admin\\.kscript") + + assertThat(OsPath.of(OsType.CYGWIN, "~/.kscript").toNative().path) + .isEqualTo("C:\\Programs\\Cygwin\\home\\admin\\.kscript") + + assertThat(OsPath.of(OsType.CYGWIN, "/usr/local/bin/sdk").toNative().path) + .isEqualTo("C:\\Programs\\Cygwin\\usr\\local\\bin\\sdk") + + assertThat(OsPath.of(OsType.CYGWIN, "../home/admin/.kscript").toNative().path) + .isEqualTo("..\\home\\admin\\.kscript") + } + + @Test + fun `Test Windows to Cygwin`() { + assertThat( + OsPath.of(OsType.WINDOWS, "C:\\home\\admin\\.kscript").toHosted(OsType.CYGWIN).path + ).isEqualTo("/cygdrive/c/home/admin/.kscript") + + assertThat( + OsPath.of(OsType.WINDOWS, "..\\home\\admin\\.kscript").toHosted(OsType.CYGWIN).path + ).isEqualTo("../home/admin/.kscript") + } + + @Test + fun `Test MSys to Windows`() { + assertThat(OsPath.of(OsType.MSYS, "/c/home/admin/.kscript").toNative().path) + .isEqualTo("c:\\home\\admin\\.kscript") + + assertThat(OsPath.of(OsType.MSYS, "~/.kscript").toNative().path) + .isEqualTo("C:\\Programs\\Msys\\home\\admin\\.kscript") + + assertThat(OsPath.of(OsType.MSYS, "/usr/local/bin/sdk").toNative().path) + .isEqualTo("C:\\Programs\\Msys\\usr\\local\\bin\\sdk") + + assertThat(OsPath.of(OsType.MSYS, "../home/admin/.kscript").toNative().path) + .isEqualTo("..\\home\\admin\\.kscript") + } + + @Test + fun `Test Windows to MSys`() { + assertThat( + OsPath.of(OsType.WINDOWS, "C:\\home\\admin\\.kscript").toHosted(OsType.MSYS).path + ).isEqualTo("/c/home/admin/.kscript") + + assertThat( + OsPath.of(OsType.WINDOWS, "..\\home\\admin\\.kscript").toHosted(OsType.MSYS).path + ).isEqualTo("../home/admin/.kscript") + } +} diff --git a/src/test/kotlin/io/github/kscripting/os/model/OsPathTest.kt b/src/test/kotlin/io/github/kscripting/os/model/OsPathTest.kt deleted file mode 100644 index eaa684e..0000000 --- a/src/test/kotlin/io/github/kscripting/os/model/OsPathTest.kt +++ /dev/null @@ -1,325 +0,0 @@ -package io.github.kscripting.os.model - -import assertk.assertThat -import assertk.assertions.* -import io.github.kscripting.os.instance.CygwinOs -import io.github.kscripting.os.instance.LinuxOs -import io.github.kscripting.os.instance.MsysOs -import io.github.kscripting.os.instance.WindowsOs -import net.igsoft.typeutils.globalcontext.GlobalContext -import org.junit.jupiter.api.Test - -class OsPathTest { - // ************************************************** LINUX PATHS ************************************************** - @Test - fun `Test Linux paths`() { - assertThat(OsPath.of(OsType.LINUX, "/")).let { - it.prop(OsPath::osType).isEqualTo(OsType.LINUX) - it.prop(OsPath::root).isEqualTo("/") - it.prop(OsPath::pathParts).isEqualTo(emptyList()) - } - - assertThat(OsPath.of(OsType.LINUX, "/home/admin/.kscript")).let { - it.prop(OsPath::osType).isEqualTo(OsType.LINUX) - it.prop(OsPath::root).isEqualTo("/") - it.prop(OsPath::pathParts).isEqualTo(listOf("home", "admin", ".kscript")) - } - - assertThat(OsPath.of(OsType.LINUX, "./home/admin/.kscript")).let { - it.prop(OsPath::osType).isEqualTo(OsType.LINUX) - it.prop(OsPath::root).isEqualTo("") - it.prop(OsPath::pathParts).isEqualTo(listOf("home", "admin", ".kscript")) - } - - assertThat(OsPath.of(OsType.LINUX, "")).let { - it.prop(OsPath::osType).isEqualTo(OsType.LINUX) - it.prop(OsPath::root).isEqualTo("") - it.prop(OsPath::pathParts).isEqualTo(emptyList()) - } - - assertThat(OsPath.of(OsType.LINUX, "file.txt")).let { - it.prop(OsPath::osType).isEqualTo(OsType.LINUX) - it.prop(OsPath::root).isEqualTo("") - it.prop(OsPath::pathParts).isEqualTo(listOf("file.txt")) - } - - assertThat(OsPath.of(OsType.LINUX, ".")).let { - it.prop(OsPath::osType).isEqualTo(OsType.LINUX) - it.prop(OsPath::root).isEqualTo("") - it.prop(OsPath::pathParts).isEqualTo(emptyList()) - } - - assertThat(OsPath.of(OsType.LINUX, "../home/admin/.kscript")).let { - it.prop(OsPath::osType).isEqualTo(OsType.LINUX) - it.prop(OsPath::root).isEqualTo("") - it.prop(OsPath::pathParts).isEqualTo(listOf("..", "home", "admin", ".kscript")) - } - - assertThat(OsPath.of(OsType.LINUX, "..")).let { - it.prop(OsPath::osType).isEqualTo(OsType.LINUX) - it.prop(OsPath::root).isEqualTo("") - it.prop(OsPath::pathParts).isEqualTo(listOf("..")) - } - - //Duplicated separators are accepted - assertThat(OsPath.of(OsType.LINUX, "..//home////admin/.kscript/")).let { - it.prop(OsPath::osType).isEqualTo(OsType.LINUX) - it.prop(OsPath::root).isEqualTo("") - it.prop(OsPath::pathParts).isEqualTo(listOf("..", "home", "admin", ".kscript")) - } - - //Both types of separator are accepted - assertThat(OsPath.of(OsType.LINUX, "..//home\\admin\\.kscript/")).let { - it.prop(OsPath::osType).isEqualTo(OsType.LINUX) - it.prop(OsPath::root).isEqualTo("") - it.prop(OsPath::pathParts).isEqualTo(listOf("..", "home", "admin", ".kscript")) - } - } - - @Test - fun `Normalization of Linux paths`() { - assertThat(OsPath.of(OsType.LINUX, "/home/admin/.kscript/../../")).let { - it.prop(OsPath::osType).isEqualTo(OsType.LINUX) - it.prop(OsPath::root).isEqualTo("/") - it.prop(OsPath::pathParts).isEqualTo(listOf("home")) - } - - assertThat(OsPath.of(OsType.LINUX, "./././../../script")).let { - it.prop(OsPath::osType).isEqualTo(OsType.LINUX) - it.prop(OsPath::root).isEqualTo("") - it.prop(OsPath::pathParts).isEqualTo(listOf("..", "..", "script")) - } - - assertThat(OsPath.of(OsType.LINUX, "/a/b/c/../d/script")).let { - it.prop(OsPath::osType).isEqualTo(OsType.LINUX) - it.prop(OsPath::root).isEqualTo("/") - it.prop(OsPath::pathParts).isEqualTo(listOf("a", "b", "d", "script")) - } - - assertThat { OsPath.of(OsType.LINUX, "/.kscript/../../") }.isFailure() - .isInstanceOf(IllegalArgumentException::class.java) - .hasMessage("Path after normalization goes beyond root element: '/'") - } - - @Test - fun `Test invalid Linux paths`() { - assertThat { OsPath.of(OsType.LINUX, "/ad*asdf") }.isFailure() - .isInstanceOf(IllegalArgumentException::class.java).hasMessage("Invalid character '*' in path '/ad*asdf'") - } - - @Test - fun `Test Linux stringPath`() { - GlobalContext.registerOrReplace(OsType.LINUX, LinuxOs("userhome")) - - assertThat(OsPath.of(OsType.LINUX, "/home/admin/.kscript").path).isEqualTo("/home/admin/.kscript") - assertThat(OsPath.of(OsType.LINUX, "/a/b/c/../d/script").path).isEqualTo("/a/b/d/script") - assertThat(OsPath.of(OsType.LINUX, "./././../../script").path).isEqualTo("../../script") - assertThat(OsPath.of(OsType.LINUX, "script/file.txt").path).isEqualTo("script/file.txt") - } - - @Test - fun `Test Linux resolve`() { - assertThat( - OsPath.of(OsType.LINUX, "/").resolve(OsPath.of(OsType.LINUX, "./.kscript/")).path - ).isEqualTo("/.kscript") - - assertThat( - OsPath.of(OsType.LINUX, "/home/admin/").resolve(OsPath.of(OsType.LINUX, "./.kscript/")).path - ).isEqualTo("/home/admin/.kscript") - - assertThat( - OsPath.of(OsType.LINUX, "./home/admin/").resolve(OsPath.of(OsType.LINUX, "./.kscript/")).path - ).isEqualTo("home/admin/.kscript") - - assertThat( - OsPath.of(OsType.LINUX, "../home/admin/").resolve(OsPath.of(OsType.LINUX, "./.kscript/")).path - ).isEqualTo("../home/admin/.kscript") - - assertThat( - OsPath.of(OsType.LINUX, "..").resolve(OsPath.of(OsType.LINUX, "./.kscript/")).path - ).isEqualTo("../.kscript") - - assertThat( - OsPath.of(OsType.LINUX, ".").resolve(OsPath.of(OsType.LINUX, "./.kscript/")).path - ).isEqualTo(".kscript") - - assertThat { - OsPath.of(OsType.LINUX, "./home/admin").resolve(OsPath.of(OsType.WINDOWS, ".\\run")) - }.isFailure().isInstanceOf(IllegalArgumentException::class.java) - .hasMessage("Paths from different OS's: 'LINUX' path can not be resolved with 'WINDOWS' path") - - assertThat { - OsPath.of(OsType.LINUX, "./home/admin").resolve(OsPath.of(OsType.LINUX, "/run")) - }.isFailure().isInstanceOf(IllegalArgumentException::class.java) - .hasMessage("Can not resolve absolute or relative path 'home/admin' using absolute path '/run'") - } - - // ************************************************* WINDOWS PATHS ************************************************* - - @Test - fun `Test Windows paths`() { - assertThat(OsPath.of(OsType.WINDOWS, "C:\\")).let { - it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) - it.prop(OsPath::root).isEqualTo("C:\\") - it.prop(OsPath::pathParts).isEqualTo(emptyList()) - } - - assertThat(OsPath.of(OsType.WINDOWS, "C:\\home\\admin\\.kscript")).let { - it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) - it.prop(OsPath::root).isEqualTo("C:\\") - it.prop(OsPath::pathParts).isEqualTo(listOf("home", "admin", ".kscript")) - } - - assertThat(OsPath.of(OsType.WINDOWS, "")).let { - it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) - it.prop(OsPath::root).isEqualTo("") - it.prop(OsPath::pathParts).isEqualTo(emptyList()) - } - - assertThat(OsPath.of(OsType.WINDOWS, "file.txt")).let { - it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) - it.prop(OsPath::root).isEqualTo("") - it.prop(OsPath::pathParts).isEqualTo(listOf("file.txt")) - } - - assertThat(OsPath.of(OsType.WINDOWS, ".")).let { - it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) - it.prop(OsPath::root).isEqualTo("") - it.prop(OsPath::pathParts).isEqualTo(emptyList()) - } - - assertThat(OsPath.of(OsType.WINDOWS, ".\\home\\admin\\.kscript")).let { - it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) - it.prop(OsPath::root).isEqualTo("") - it.prop(OsPath::pathParts).isEqualTo(listOf("home", "admin", ".kscript")) - } - - assertThat(OsPath.of(OsType.WINDOWS, "..\\home\\admin\\.kscript")).let { - it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) - it.prop(OsPath::root).isEqualTo("") - it.prop(OsPath::pathParts).isEqualTo(listOf("..", "home", "admin", ".kscript")) - } - - assertThat(OsPath.of(OsType.WINDOWS, "..")).let { - it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) - it.prop(OsPath::root).isEqualTo("") - it.prop(OsPath::pathParts).isEqualTo(listOf("..")) - } - - //Duplicated separators are accepted - assertThat(OsPath.of(OsType.WINDOWS, "C:\\home\\\\\\\\admin\\.kscript\\")).let { - it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) - it.prop(OsPath::root).isEqualTo("C:\\") - it.prop(OsPath::pathParts).isEqualTo(listOf("home", "admin", ".kscript")) - } - - //Both types of separator are accepted - assertThat(OsPath.of(OsType.WINDOWS, "C:/home\\admin/.kscript////")).let { - it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) - it.prop(OsPath::root).isEqualTo("C:\\") - it.prop(OsPath::pathParts).isEqualTo(listOf("home", "admin", ".kscript")) - } - } - - @Test - fun `Normalization of Windows paths`() { - assertThat(OsPath.of(OsType.WINDOWS, "C:\\home\\admin\\.kscript\\..\\..\\")).let { - it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) - it.prop(OsPath::root).isEqualTo("C:\\") - it.prop(OsPath::pathParts).isEqualTo(listOf("home")) - } - - assertThat(OsPath.of(OsType.WINDOWS, ".\\.\\.\\..\\..\\script")).let { - it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) - it.prop(OsPath::root).isEqualTo("") - it.prop(OsPath::pathParts).isEqualTo(listOf("..", "..", "script")) - } - - assertThat(OsPath.of(OsType.WINDOWS, "C:\\a\\b\\c\\..\\d\\script")).let { - it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) - it.prop(OsPath::root).isEqualTo("C:\\") - it.prop(OsPath::pathParts).isEqualTo(listOf("a", "b", "d", "script")) - } - - assertThat { OsPath.of(OsType.WINDOWS, "C:\\.kscript\\..\\..\\") }.isFailure() - .isInstanceOf(IllegalArgumentException::class.java) - .hasMessage("Path after normalization goes beyond root element: 'C:\\'") - } - - @Test - fun `Test invalid Windows paths`() { - assertThat { OsPath.of(OsType.WINDOWS, "C:\\adas?df") }.isFailure() - .isInstanceOf(IllegalArgumentException::class.java) - .hasMessage("Invalid character '?' in path 'C:\\adas?df'") - - assertThat { OsPath.of(OsType.WINDOWS, "home:\\vagrant") }.isFailure() - .isInstanceOf(IllegalArgumentException::class.java) - .hasMessage("Invalid character ':' in path 'home:\\vagrant'") - } - - @Test - fun `Test Windows stringPath`() { - assertThat( - OsPath.of(OsType.WINDOWS, "C:\\home\\admin\\.kscript").path - ).isEqualTo("C:\\home\\admin\\.kscript") - assertThat( - OsPath.of(OsType.WINDOWS, "c:\\a\\b\\c\\..\\d\\script").path - ).isEqualTo("c:\\a\\b\\d\\script") - assertThat( - OsPath.of(OsType.WINDOWS, ".\\.\\.\\..\\..\\script").path - ).isEqualTo("..\\..\\script") - assertThat(OsPath.of(OsType.WINDOWS, "script\\file.txt").path).isEqualTo("script\\file.txt") - } - - // ****************************************** WINDOWS <-> CYGWIN <-> MSYS ****************************************** - - @Test - fun `Test Windows to Cygwin`() { - GlobalContext.registerOrReplace(OsType.CYGWIN, CygwinOs("userhome", "nativeFileSystemRoot")) - GlobalContext.registerOrReplace(OsType.WINDOWS, WindowsOs("userhome")) - - assertThat( - OsPath.of(OsType.WINDOWS, "C:\\home\\admin\\.kscript").convert(OsType.CYGWIN).path - ).isEqualTo("/cygdrive/c/home/admin/.kscript") - - assertThat( - OsPath.of(OsType.WINDOWS, "..\\home\\admin\\.kscript").convert(OsType.CYGWIN).path - ).isEqualTo("../home/admin/.kscript") - } - - @Test - fun `Test Cygwin to Windows`() { - assertThat( - OsPath.of(OsType.CYGWIN, "/cygdrive/c/home/admin/.kscript").convert(OsType.WINDOWS).path - ).isEqualTo("c:\\home\\admin\\.kscript") - - assertThat( - OsPath.of(OsType.CYGWIN, "../home/admin/.kscript").convert(OsType.WINDOWS).path - ).isEqualTo("..\\home\\admin\\.kscript") - } - - @Test - fun `Test Windows to MSys`() { - assertThat( - OsPath.of(OsType.WINDOWS, "C:\\home\\admin\\.kscript").convert(OsType.MSYS).path - ).isEqualTo("/c/home/admin/.kscript") - - assertThat( - OsPath.of(OsType.WINDOWS, "..\\home\\admin\\.kscript").convert(OsType.MSYS).path - ).isEqualTo("../home/admin/.kscript") - } - - @Test - fun `Test MSys to Windows`() { - GlobalContext.registerOrReplace(OsType.MSYS, MsysOs("userhome", "nativeFileSystemRoot")) - GlobalContext.registerOrReplace(OsType.WINDOWS, WindowsOs("userhome")) - - assertThat( - OsPath.of(OsType.MSYS, "/c/home/admin/.kscript").convert(OsType.WINDOWS).path - ).isEqualTo("c:\\home\\admin\\.kscript") - - assertThat( - OsPath.of(OsType.MSYS, "../home/admin/.kscript").convert(OsType.WINDOWS).path - ).isEqualTo("..\\home\\admin\\.kscript") - } -} diff --git a/src/test/kotlin/io/github/kscripting/os/model/PosixOsPathTest.kt b/src/test/kotlin/io/github/kscripting/os/model/PosixOsPathTest.kt new file mode 100644 index 0000000..89287a8 --- /dev/null +++ b/src/test/kotlin/io/github/kscripting/os/model/PosixOsPathTest.kt @@ -0,0 +1,159 @@ +package io.github.kscripting.os.model + +import assertk.assertThat +import assertk.assertions.* +import io.github.kscripting.os.instance.LinuxOs +import net.igsoft.typeutils.globalcontext.GlobalContext +import org.junit.jupiter.api.BeforeAll +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.TestInstance + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class PosixOsPathTest { + + @BeforeAll + fun beforeAll() { + GlobalContext.registerOrReplace(OsType.LINUX, LinuxOs("userhome")) + } + + @Test + fun `Test Posix paths`() { + assertThat(OsPath.of(OsType.LINUX, "/")).let { + it.prop(OsPath::osType).isEqualTo(OsType.LINUX) + it.prop(OsPath::root).isEqualTo("/") + it.prop(OsPath::pathParts).isEqualTo(emptyList()) + } + + assertThat(OsPath.of(OsType.LINUX, "/home/admin/.kscript")).let { + it.prop(OsPath::osType).isEqualTo(OsType.LINUX) + it.prop(OsPath::root).isEqualTo("/") + it.prop(OsPath::pathParts).isEqualTo(listOf("home", "admin", ".kscript")) + } + + assertThat(OsPath.of(OsType.LINUX, "./home/admin/.kscript")).let { + it.prop(OsPath::osType).isEqualTo(OsType.LINUX) + it.prop(OsPath::root).isEqualTo("") + it.prop(OsPath::pathParts).isEqualTo(listOf("home", "admin", ".kscript")) + } + + assertThat(OsPath.of(OsType.LINUX, "")).let { + it.prop(OsPath::osType).isEqualTo(OsType.LINUX) + it.prop(OsPath::root).isEqualTo("") + it.prop(OsPath::pathParts).isEqualTo(emptyList()) + } + + assertThat(OsPath.of(OsType.LINUX, "file.txt")).let { + it.prop(OsPath::osType).isEqualTo(OsType.LINUX) + it.prop(OsPath::root).isEqualTo("") + it.prop(OsPath::pathParts).isEqualTo(listOf("file.txt")) + } + + assertThat(OsPath.of(OsType.LINUX, ".")).let { + it.prop(OsPath::osType).isEqualTo(OsType.LINUX) + it.prop(OsPath::root).isEqualTo("") + it.prop(OsPath::pathParts).isEqualTo(emptyList()) + } + + assertThat(OsPath.of(OsType.LINUX, "../home/admin/.kscript")).let { + it.prop(OsPath::osType).isEqualTo(OsType.LINUX) + it.prop(OsPath::root).isEqualTo("") + it.prop(OsPath::pathParts).isEqualTo(listOf("..", "home", "admin", ".kscript")) + } + + assertThat(OsPath.of(OsType.LINUX, "..")).let { + it.prop(OsPath::osType).isEqualTo(OsType.LINUX) + it.prop(OsPath::root).isEqualTo("") + it.prop(OsPath::pathParts).isEqualTo(listOf("..")) + } + + //Duplicated separators are accepted + assertThat(OsPath.of(OsType.LINUX, "..//home////admin/.kscript/")).let { + it.prop(OsPath::osType).isEqualTo(OsType.LINUX) + it.prop(OsPath::root).isEqualTo("") + it.prop(OsPath::pathParts).isEqualTo(listOf("..", "home", "admin", ".kscript")) + } + + //Both types of separator are accepted + assertThat(OsPath.of(OsType.LINUX, "..//home\\admin\\.kscript/")).let { + it.prop(OsPath::osType).isEqualTo(OsType.LINUX) + it.prop(OsPath::root).isEqualTo("") + it.prop(OsPath::pathParts).isEqualTo(listOf("..", "home", "admin", ".kscript")) + } + } + + @Test + fun `Normalization of Posix paths`() { + assertThat(OsPath.of(OsType.LINUX, "/home/admin/.kscript/../../")).let { + it.prop(OsPath::osType).isEqualTo(OsType.LINUX) + it.prop(OsPath::root).isEqualTo("/") + it.prop(OsPath::pathParts).isEqualTo(listOf("home")) + } + + assertThat(OsPath.of(OsType.LINUX, "./././../../script")).let { + it.prop(OsPath::osType).isEqualTo(OsType.LINUX) + it.prop(OsPath::root).isEqualTo("") + it.prop(OsPath::pathParts).isEqualTo(listOf("..", "..", "script")) + } + + assertThat(OsPath.of(OsType.LINUX, "/a/b/c/../d/script")).let { + it.prop(OsPath::osType).isEqualTo(OsType.LINUX) + it.prop(OsPath::root).isEqualTo("/") + it.prop(OsPath::pathParts).isEqualTo(listOf("a", "b", "d", "script")) + } + + assertThat { OsPath.of(OsType.LINUX, "/.kscript/../../") }.isFailure() + .isInstanceOf(IllegalArgumentException::class.java) + .hasMessage("Path after normalization goes beyond root element: '/'") + } + + @Test + fun `Test invalid Posix paths`() { + assertThat { OsPath.of(OsType.LINUX, "/ad*asdf") }.isFailure() + .isInstanceOf(IllegalArgumentException::class.java).hasMessage("Invalid character '*' in path '/ad*asdf'") + } + + @Test + fun `Test Posix stringPath`() { + assertThat(OsPath.of(OsType.LINUX, "/home/admin/.kscript").path).isEqualTo("/home/admin/.kscript") + assertThat(OsPath.of(OsType.LINUX, "/a/b/c/../d/script").path).isEqualTo("/a/b/d/script") + assertThat(OsPath.of(OsType.LINUX, "./././../../script").path).isEqualTo("../../script") + assertThat(OsPath.of(OsType.LINUX, "script/file.txt").path).isEqualTo("script/file.txt") + } + + @Test + fun `Test Posix resolve`() { + assertThat( + OsPath.of(OsType.LINUX, "/").resolve(OsPath.of(OsType.LINUX, "./.kscript/")).path + ).isEqualTo("/.kscript") + + assertThat( + OsPath.of(OsType.LINUX, "/home/admin/").resolve(OsPath.of(OsType.LINUX, "./.kscript/")).path + ).isEqualTo("/home/admin/.kscript") + + assertThat( + OsPath.of(OsType.LINUX, "./home/admin/").resolve(OsPath.of(OsType.LINUX, "./.kscript/")).path + ).isEqualTo("home/admin/.kscript") + + assertThat( + OsPath.of(OsType.LINUX, "../home/admin/").resolve(OsPath.of(OsType.LINUX, "./.kscript/")).path + ).isEqualTo("../home/admin/.kscript") + + assertThat( + OsPath.of(OsType.LINUX, "..").resolve(OsPath.of(OsType.LINUX, "./.kscript/")).path + ).isEqualTo("../.kscript") + + assertThat( + OsPath.of(OsType.LINUX, ".").resolve(OsPath.of(OsType.LINUX, "./.kscript/")).path + ).isEqualTo(".kscript") + + assertThat { + OsPath.of(OsType.LINUX, "./home/admin").resolve(OsPath.of(OsType.WINDOWS, ".\\run")) + }.isFailure().isInstanceOf(IllegalArgumentException::class.java) + .hasMessage("Paths from different OS's: 'LINUX' path can not be resolved with 'WINDOWS' path") + + assertThat { + OsPath.of(OsType.LINUX, "./home/admin").resolve(OsPath.of(OsType.LINUX, "/run")) + }.isFailure().isInstanceOf(IllegalArgumentException::class.java) + .hasMessage("Can not resolve absolute or relative path 'home/admin' using absolute path '/run'") + } +} diff --git a/src/test/kotlin/io/github/kscripting/os/model/WindowsOsPathTest.kt b/src/test/kotlin/io/github/kscripting/os/model/WindowsOsPathTest.kt new file mode 100644 index 0000000..053f59d --- /dev/null +++ b/src/test/kotlin/io/github/kscripting/os/model/WindowsOsPathTest.kt @@ -0,0 +1,122 @@ +package io.github.kscripting.os.model + +import assertk.assertThat +import assertk.assertions.* +import org.junit.jupiter.api.Test + +class WindowsOsPathTest { + @Test + fun `Test Windows paths`() { + assertThat(OsPath.of(OsType.WINDOWS, "C:\\")).let { + it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) + it.prop(OsPath::root).isEqualTo("C:\\") + it.prop(OsPath::pathParts).isEqualTo(emptyList()) + } + + assertThat(OsPath.of(OsType.WINDOWS, "C:\\home\\admin\\.kscript")).let { + it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) + it.prop(OsPath::root).isEqualTo("C:\\") + it.prop(OsPath::pathParts).isEqualTo(listOf("home", "admin", ".kscript")) + } + + assertThat(OsPath.of(OsType.WINDOWS, "")).let { + it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) + it.prop(OsPath::root).isEqualTo("") + it.prop(OsPath::pathParts).isEqualTo(emptyList()) + } + + assertThat(OsPath.of(OsType.WINDOWS, "file.txt")).let { + it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) + it.prop(OsPath::root).isEqualTo("") + it.prop(OsPath::pathParts).isEqualTo(listOf("file.txt")) + } + + assertThat(OsPath.of(OsType.WINDOWS, ".")).let { + it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) + it.prop(OsPath::root).isEqualTo("") + it.prop(OsPath::pathParts).isEqualTo(emptyList()) + } + + assertThat(OsPath.of(OsType.WINDOWS, ".\\home\\admin\\.kscript")).let { + it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) + it.prop(OsPath::root).isEqualTo("") + it.prop(OsPath::pathParts).isEqualTo(listOf("home", "admin", ".kscript")) + } + + assertThat(OsPath.of(OsType.WINDOWS, "..\\home\\admin\\.kscript")).let { + it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) + it.prop(OsPath::root).isEqualTo("") + it.prop(OsPath::pathParts).isEqualTo(listOf("..", "home", "admin", ".kscript")) + } + + assertThat(OsPath.of(OsType.WINDOWS, "..")).let { + it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) + it.prop(OsPath::root).isEqualTo("") + it.prop(OsPath::pathParts).isEqualTo(listOf("..")) + } + + //Duplicated separators are accepted + assertThat(OsPath.of(OsType.WINDOWS, "C:\\home\\\\\\\\admin\\.kscript\\")).let { + it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) + it.prop(OsPath::root).isEqualTo("C:\\") + it.prop(OsPath::pathParts).isEqualTo(listOf("home", "admin", ".kscript")) + } + + //Both types of separator are accepted + assertThat(OsPath.of(OsType.WINDOWS, "C:/home\\admin/.kscript////")).let { + it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) + it.prop(OsPath::root).isEqualTo("C:\\") + it.prop(OsPath::pathParts).isEqualTo(listOf("home", "admin", ".kscript")) + } + } + + @Test + fun `Normalization of Windows paths`() { + assertThat(OsPath.of(OsType.WINDOWS, "C:\\home\\admin\\.kscript\\..\\..\\")).let { + it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) + it.prop(OsPath::root).isEqualTo("C:\\") + it.prop(OsPath::pathParts).isEqualTo(listOf("home")) + } + + assertThat(OsPath.of(OsType.WINDOWS, ".\\.\\.\\..\\..\\script")).let { + it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) + it.prop(OsPath::root).isEqualTo("") + it.prop(OsPath::pathParts).isEqualTo(listOf("..", "..", "script")) + } + + assertThat(OsPath.of(OsType.WINDOWS, "C:\\a\\b\\c\\..\\d\\script")).let { + it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) + it.prop(OsPath::root).isEqualTo("C:\\") + it.prop(OsPath::pathParts).isEqualTo(listOf("a", "b", "d", "script")) + } + + assertThat { OsPath.of(OsType.WINDOWS, "C:\\.kscript\\..\\..\\") }.isFailure() + .isInstanceOf(IllegalArgumentException::class.java) + .hasMessage("Path after normalization goes beyond root element: 'C:\\'") + } + + @Test + fun `Test invalid Windows paths`() { + assertThat { OsPath.of(OsType.WINDOWS, "C:\\adas?df") }.isFailure() + .isInstanceOf(IllegalArgumentException::class.java) + .hasMessage("Invalid character '?' in path 'C:\\adas?df'") + + assertThat { OsPath.of(OsType.WINDOWS, "home:\\vagrant") }.isFailure() + .isInstanceOf(IllegalArgumentException::class.java) + .hasMessage("Invalid character ':' in path 'home:\\vagrant'") + } + + @Test + fun `Test Windows stringPath`() { + assertThat( + OsPath.of(OsType.WINDOWS, "C:\\home\\admin\\.kscript").path + ).isEqualTo("C:\\home\\admin\\.kscript") + assertThat( + OsPath.of(OsType.WINDOWS, "c:\\a\\b\\c\\..\\d\\script").path + ).isEqualTo("c:\\a\\b\\d\\script") + assertThat( + OsPath.of(OsType.WINDOWS, ".\\.\\.\\..\\..\\script").path + ).isEqualTo("..\\..\\script") + assertThat(OsPath.of(OsType.WINDOWS, "script\\file.txt").path).isEqualTo("script\\file.txt") + } +} From baec18804e5cf97b4e1d99556030795eb1854037 Mon Sep 17 00:00:00 2001 From: Marcin Kuszczak <1508798+aartiPl@users.noreply.github.com> Date: Tue, 22 Aug 2023 11:27:35 +0200 Subject: [PATCH 36/50] Improved implementation of hosted paths (shortening home and root paths) --- .../io/github/kscripting/os/model/OsPath.kt | 54 ++++++++++++++----- .../github/kscripting/os/model/OsPathExt.kt | 9 ++-- .../kscripting/os/model/HostedOsPathTest.kt | 32 ++++++----- .../kscripting/os/model/WindowsOsPathTest.kt | 12 ++--- 4 files changed, 67 insertions(+), 40 deletions(-) diff --git a/src/main/kotlin/io/github/kscripting/os/model/OsPath.kt b/src/main/kotlin/io/github/kscripting/os/model/OsPath.kt index a48ad05..f97d21c 100644 --- a/src/main/kotlin/io/github/kscripting/os/model/OsPath.kt +++ b/src/main/kotlin/io/github/kscripting/os/model/OsPath.kt @@ -13,6 +13,7 @@ data class OsPath(val osType: OsType<*>, val root: String, val pathParts: List, val root: String, val pathParts: List, val root: String, val pathParts: List List.startsWith(list: List): Boolean = (this.size >= list.size && this.subList(0, list.size) == list) + + fun startsWith(osPath: OsPath): Boolean = root == osPath.root && pathParts.startsWith(osPath.pathParts) + fun toHosted(targetOs: OsType<*>): OsPath { - check(targetOs.isPosixHostedOnWindows() && ((targetOs.value) as HostedOs).nativeType == osType) { + if (osType == targetOs) { + //This is already targetOs... + return this + } + + check(targetOs.isPosixHostedOnWindows() && osType == ((targetOs.value) as HostedOs).nativeType) { "You can convert only paths to hosted OS-es" } @@ -102,21 +113,38 @@ data class OsPath(val osType: OsType<*>, val root: String, val pathParts: List Date: Fri, 15 Sep 2023 19:40:56 +0200 Subject: [PATCH 37/50] Cleanup. --- .../kotlin/io/github/kscripting/os/model/OsPath.kt | 6 +++--- .../kotlin/io/github/kscripting/os/model/OsType.kt | 14 +++++++------- .../github/kscripting/os/model/HostedOsPathTest.kt | 2 +- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/main/kotlin/io/github/kscripting/os/model/OsPath.kt b/src/main/kotlin/io/github/kscripting/os/model/OsPath.kt index f97d21c..39b163a 100644 --- a/src/main/kotlin/io/github/kscripting/os/model/OsPath.kt +++ b/src/main/kotlin/io/github/kscripting/os/model/OsPath.kt @@ -11,10 +11,10 @@ data class OsPath(val osType: OsType<*>, val root: String, val pathParts: List private constructor(private val marker: TypedMarker) : D fun isWindowsLike() = (this == WINDOWS) companion object : TypedEnumCompanion>() { - val LINUX by register(OsType(AutoTypedMarker.create())) - val WINDOWS by register(OsType(AutoTypedMarker.create())) - val CYGWIN by register(OsType(AutoTypedMarker.create())) - val MSYS by register(OsType(AutoTypedMarker.create())) - val MACOS by register(OsType(AutoTypedMarker.create())) - val FREEBSD by register(OsType(AutoTypedMarker.create())) + val LINUX = OsType(AutoTypedMarker.create()) + val WINDOWS = OsType(AutoTypedMarker.create()) + val CYGWIN = OsType(AutoTypedMarker.create()) + val MSYS = OsType(AutoTypedMarker.create()) + val MACOS = OsType(AutoTypedMarker.create()) + val FREEBSD = OsType(AutoTypedMarker.create()) val native: OsType = guessNativeType() diff --git a/src/test/kotlin/io/github/kscripting/os/model/HostedOsPathTest.kt b/src/test/kotlin/io/github/kscripting/os/model/HostedOsPathTest.kt index 259ffef..b5c7d73 100644 --- a/src/test/kotlin/io/github/kscripting/os/model/HostedOsPathTest.kt +++ b/src/test/kotlin/io/github/kscripting/os/model/HostedOsPathTest.kt @@ -15,7 +15,7 @@ class HostedOsPathTest { @BeforeAll fun beforeAll() { - //Installation cygwin/msys base path: cygpath -w / + //Installation cygwin/msys base path: cygpath -w /, cygpath ~ GlobalContext.registerOrReplace(OsType.CYGWIN, CygwinOs("/home/admin", "C:\\Programs\\Cygwin\\")) GlobalContext.registerOrReplace(OsType.MSYS, MsysOs("/home/admin", "C:\\Programs\\Msys\\")) GlobalContext.registerOrReplace(OsType.WINDOWS, WindowsOs("C:\\Users\\Admin\\.kscript")) From a76e7c81cdf808a8a82a3b0bfc19662aeb5b2a6c Mon Sep 17 00:00:00 2001 From: Marcin Kuszczak <1508798+aartiPl@users.noreply.github.com> Date: Fri, 15 Sep 2023 21:28:37 +0200 Subject: [PATCH 38/50] Removed Either. --- build.gradle.kts | 1 - .../io/github/kscripting/os/model/OsPath.kt | 52 +++++++++---------- .../kscripting/os/model/PosixOsPathTest.kt | 8 +-- .../kscripting/os/model/WindowsOsPathTest.kt | 20 +++++-- 4 files changed, 47 insertions(+), 34 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 47cd3fe..0b6c134 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -165,7 +165,6 @@ dependencies { implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlinVersion") implementation("org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4") - implementation("io.arrow-kt:arrow-core:1.1.2") implementation("org.apache.commons:commons-lang3:3.12.0") implementation("org.slf4j:slf4j-nop:2.0.5") diff --git a/src/main/kotlin/io/github/kscripting/os/model/OsPath.kt b/src/main/kotlin/io/github/kscripting/os/model/OsPath.kt index 39b163a..93e1e31 100644 --- a/src/main/kotlin/io/github/kscripting/os/model/OsPath.kt +++ b/src/main/kotlin/io/github/kscripting/os/model/OsPath.kt @@ -1,8 +1,5 @@ package io.github.kscripting.os.model -import arrow.core.Either -import arrow.core.left -import arrow.core.right import io.github.kscripting.os.instance.HostedOs //Path representation for different OSes @@ -35,11 +32,7 @@ data class OsPath(val osType: OsType<*>, val root: String, val pathParts: List result.value - is Either.Left -> throw IllegalArgumentException(result.value) - } - + val normalizedPath = normalize(root, newPathParts).getOrThrow() return OsPath(osType, root, normalizedPath) } @@ -116,10 +109,17 @@ data class OsPath(val osType: OsType<*>, val root: String, val pathParts: List, val root: String, val pathParts: List, vararg pathParts: String): OsPath = of(osType, pathParts.toList()) fun of(osType: OsType<*>, pathParts: List): OsPath { - return when (val result = internalCreate(osType, pathParts)) { - is Either.Right -> result.value - is Either.Left -> throw IllegalArgumentException(result.value) - } + return create(osType, pathParts).getOrThrow() } fun ofOrNull(vararg pathParts: String): OsPath? = ofOrNull(OsType.native, pathParts.toList()) @@ -188,16 +185,18 @@ data class OsPath(val osType: OsType<*>, val root: String, val pathParts: List, vararg pathParts: String): OsPath? = ofOrNull(osType, pathParts.toList()) fun ofOrNull(osType: OsType<*>, pathParts: List): OsPath? { - return when (val result = internalCreate(osType, pathParts)) { - is Either.Right -> result.value - is Either.Left -> null - } + return create(osType, pathParts).getOrNull() } //Relaxed validation: //1. It doesn't matter if there is '/' or '\' used as path separator - both are treated the same //2. Duplicated or trailing slashes '/' and backslashes '\' are just ignored - private fun internalCreate(osType: OsType<*>, pathParts: List): Either { + + fun create(osType: OsType<*>, vararg pathParts: String): Result { + return create(osType, pathParts.toList()) + } + + fun create(osType: OsType<*>, pathParts: List): Result { val path = pathParts.joinToString("/") //Detect root @@ -219,22 +218,21 @@ data class OsPath(val osType: OsType<*>, val root: String, val pathParts: List result.value - is Either.Left -> return result.value.left() + val normalizedPath = normalize(root, pathPartsResolved).getOrElse { + return Result.failure(it) } //Validate root element of path and find out if it is absolute or relative val forbiddenCharacter = path.substring(root.length).find { forbiddenCharacters.contains(it) } if (forbiddenCharacter != null) { - return "Invalid character '$forbiddenCharacter' in path '$path'".left() + return Result.failure(IllegalArgumentException("Invalid character '$forbiddenCharacter' in path '$path'")) } - return OsPath(osType, root, normalizedPath).right() + return Result.success(OsPath(osType, root, normalizedPath)) } - fun normalize(root: String, pathParts: List): Either> { + fun normalize(root: String, pathParts: List): Result> { //Relative: // ./../ --> ../ // ./a/../ --> ./ @@ -256,7 +254,7 @@ data class OsPath(val osType: OsType<*>, val root: String, val pathParts: List 0) { @@ -285,7 +283,7 @@ data class OsPath(val osType: OsType<*>, val root: String, val pathParts: List Date: Tue, 31 Oct 2023 18:37:09 +0100 Subject: [PATCH 39/50] Use Result<> --- build.gradle.kts | 4 +- .../shell/integration/ShellExecutorTest.kt | 3 +- .../shell/integration/tools/TestContext.kt | 14 +- .../github/kscripting/os/instance/CygwinOs.kt | 4 +- .../kscripting/os/instance/FreeBsdOs.kt | 2 +- .../github/kscripting/os/instance/LinuxOs.kt | 2 +- .../io/github/kscripting/os/instance/MacOs.kt | 2 +- .../github/kscripting/os/instance/MsysOs.kt | 4 +- .../kscripting/os/instance/WindowsOs.kt | 2 +- .../io/github/kscripting/os/model/OsPath.kt | 158 +------------ .../github/kscripting/os/model/OsPathExt.kt | 214 ++++++++++++++++-- .../kscripting/shell/process/ProcessRunner.kt | 2 +- .../kscripting/os/model/HostedOsPathTest.kt | 32 +-- .../kscripting/os/model/PosixOsPathTest.kt | 63 +++--- .../kscripting/os/model/WindowsOsPathTest.kt | 41 ++-- 15 files changed, 294 insertions(+), 253 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 0b6c134..c5a7de2 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -160,7 +160,7 @@ signing { } dependencies { - api("net.igsoft:typeutils:0.6.0-SNAPSHOT") + api("net.igsoft:typeutils:0.7.0-SNAPSHOT") implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlinVersion") implementation("org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion") @@ -173,7 +173,7 @@ dependencies { testImplementation("org.junit.platform:junit-platform-suite-commons:1.9.0") testImplementation("org.junit.jupiter:junit-jupiter-engine:5.9.0") testImplementation("org.junit.jupiter:junit-jupiter-params:5.9.0") - testImplementation("com.willowtreeapps.assertk:assertk-jvm:0.25") + testImplementation("com.willowtreeapps.assertk:assertk-jvm:0.27.0") testImplementation("io.mockk:mockk:1.13.2") testImplementation(kotlin("script-runtime")) diff --git a/src/itest/kotlin/io/github/kscripting/shell/integration/ShellExecutorTest.kt b/src/itest/kotlin/io/github/kscripting/shell/integration/ShellExecutorTest.kt index 02c571f..cf23e36 100644 --- a/src/itest/kotlin/io/github/kscripting/shell/integration/ShellExecutorTest.kt +++ b/src/itest/kotlin/io/github/kscripting/shell/integration/ShellExecutorTest.kt @@ -1,6 +1,7 @@ package io.github.kscripting.shell.integration import io.github.kscripting.os.model.readText +import io.github.kscripting.os.model.resolve import io.github.kscripting.shell.integration.tools.ShellTestBase import io.github.kscripting.shell.integration.tools.TestContext import io.github.kscripting.shell.integration.tools.TestContext.execPath @@ -26,7 +27,7 @@ class ShellExecutorTest : ShellTestBase { verify( "doEcho -f $path", 0, - path.readText(), + path.readText().getOrThrow(), "", inputSanitizer = Sanitizer.EMPTY_SANITIZER, outputSanitizer = Sanitizer.EMPTY_SANITIZER diff --git a/src/itest/kotlin/io/github/kscripting/shell/integration/tools/TestContext.kt b/src/itest/kotlin/io/github/kscripting/shell/integration/tools/TestContext.kt index 66f240d..9bf75a8 100644 --- a/src/itest/kotlin/io/github/kscripting/shell/integration/tools/TestContext.kt +++ b/src/itest/kotlin/io/github/kscripting/shell/integration/tools/TestContext.kt @@ -9,14 +9,14 @@ import io.github.kscripting.shell.util.Sanitizer object TestContext { val osType: OsType = OsType.find(System.getProperty("osType")) ?: OsType.native - val projectPath: OsPath = OsPath.of(OsType.native, System.getProperty("projectPath")).convert(osType) - val execPath: OsPath = projectPath.resolve("build/shell_test/bin") - val testPath: OsPath = projectPath.resolve("build/shell_test/tmp") + val projectPath: OsPath = OsPath.of(OsType.native, System.getProperty("projectPath")).toHosted(osType).getOrThrow() + val execPath: OsPath = projectPath.resolve("build/shell_test/bin").getOrThrow() + val testPath: OsPath = projectPath.resolve("build/shell_test/tmp").getOrThrow() val pathEnvVariableName = if (osType.isWindowsLike()) "Path" else "PATH" val pathEnvVariableValue: String = System.getenv()[pathEnvVariableName]!! val pathEnvVariableSeparator: String = if (osType.isWindowsLike() || osType.isPosixHostedOnWindows()) ";" else ":" - val pathEnvVariableCalculatedPath: String = "${execPath.convert(osType).path}$pathEnvVariableSeparator$pathEnvVariableValue" + val pathEnvVariableCalculatedPath: String = "${execPath.toHosted(osType).path}$pathEnvVariableSeparator$pathEnvVariableValue" val nl: String = when { osType.isPosixHostedOnWindows() -> "\n" @@ -30,7 +30,7 @@ object TestContext { println("osType : $osType") println("nativeType : ${OsType.native}") println("projectDir : $projectPath") - println("execDir : ${execPath.convert(osType)}") + println("execDir : ${execPath.toHosted(osType)}") println("Kotlin version : ${ShellExecutor.evalAndGobble("kotlin -version", osType).stdout}") println("Env path : $pathEnvVariableCalculatedPath") @@ -38,8 +38,8 @@ object TestContext { } fun copyFile(source: String, target: OsPath) { - val sourceFile = projectPath.resolve(source).toNativeFile() - val targetFile = target.resolve(sourceFile.name).toNativeFile() + val sourceFile = projectPath.resolve(source).toNativeFile().getOrThrow() + val targetFile = target.resolve(sourceFile.name).toNativeFile().getOrThrow() sourceFile.copyTo(targetFile, overwrite = true) if (target == execPath) { diff --git a/src/main/kotlin/io/github/kscripting/os/instance/CygwinOs.kt b/src/main/kotlin/io/github/kscripting/os/instance/CygwinOs.kt index 9f4766f..8572af3 100644 --- a/src/main/kotlin/io/github/kscripting/os/instance/CygwinOs.kt +++ b/src/main/kotlin/io/github/kscripting/os/instance/CygwinOs.kt @@ -6,7 +6,7 @@ import io.github.kscripting.os.model.OsType class CygwinOs(userHome: String, nativeFileSystemRoot: String) : HostedOs { override val osTypePrefix: String = "cygwin" override val type: OsType = OsType.CYGWIN - override val userHome: OsPath = OsPath.of(type, userHome) + override val userHome: OsPath = OsPath.of(type, userHome).getOrThrow() override val nativeType: OsType = OsType.WINDOWS - override val nativeFileSystemRoot: OsPath = OsPath.of(nativeType, nativeFileSystemRoot) + override val nativeFileSystemRoot: OsPath = OsPath.of(nativeType, nativeFileSystemRoot).getOrThrow() } diff --git a/src/main/kotlin/io/github/kscripting/os/instance/FreeBsdOs.kt b/src/main/kotlin/io/github/kscripting/os/instance/FreeBsdOs.kt index 43afe8f..219a71c 100644 --- a/src/main/kotlin/io/github/kscripting/os/instance/FreeBsdOs.kt +++ b/src/main/kotlin/io/github/kscripting/os/instance/FreeBsdOs.kt @@ -8,5 +8,5 @@ class FreeBsdOs(userHome: String) : Os { override val osTypePrefix: String = "freebsd" override val type: OsType = OsType.FREEBSD override val pathSeparator: String get() = "/" - override val userHome: OsPath = OsPath.of(OsType.FREEBSD, userHome) + override val userHome: OsPath = OsPath.of(OsType.FREEBSD, userHome).getOrThrow() } diff --git a/src/main/kotlin/io/github/kscripting/os/instance/LinuxOs.kt b/src/main/kotlin/io/github/kscripting/os/instance/LinuxOs.kt index a661a86..45cab46 100644 --- a/src/main/kotlin/io/github/kscripting/os/instance/LinuxOs.kt +++ b/src/main/kotlin/io/github/kscripting/os/instance/LinuxOs.kt @@ -8,5 +8,5 @@ class LinuxOs(userHome: String) : Os { override val osTypePrefix: String = "linux" override val type: OsType = OsType.LINUX override val pathSeparator: String get() = "/" - override val userHome: OsPath = OsPath.of(OsType.LINUX, userHome) + override val userHome: OsPath = OsPath.of(OsType.LINUX, userHome).getOrThrow() } diff --git a/src/main/kotlin/io/github/kscripting/os/instance/MacOs.kt b/src/main/kotlin/io/github/kscripting/os/instance/MacOs.kt index 1e200a0..0304554 100644 --- a/src/main/kotlin/io/github/kscripting/os/instance/MacOs.kt +++ b/src/main/kotlin/io/github/kscripting/os/instance/MacOs.kt @@ -8,5 +8,5 @@ class MacOs(userHome: String) : Os { override val osTypePrefix: String = "darwin" override val type: OsType = OsType.MACOS override val pathSeparator: String get() = "/" - override val userHome: OsPath = OsPath.of(OsType.MACOS, userHome) + override val userHome: OsPath = OsPath.of(OsType.MACOS, userHome).getOrThrow() } diff --git a/src/main/kotlin/io/github/kscripting/os/instance/MsysOs.kt b/src/main/kotlin/io/github/kscripting/os/instance/MsysOs.kt index 1fc1101..dacf054 100644 --- a/src/main/kotlin/io/github/kscripting/os/instance/MsysOs.kt +++ b/src/main/kotlin/io/github/kscripting/os/instance/MsysOs.kt @@ -6,7 +6,7 @@ import io.github.kscripting.os.model.OsType class MsysOs(userHome: String, nativeFileSystemRoot: String) : HostedOs { override val osTypePrefix: String = "msys" override val type: OsType = OsType.MSYS - override val userHome: OsPath = OsPath.of(type, userHome) + override val userHome: OsPath = OsPath.of(type, userHome).getOrThrow() override val nativeType: OsType = OsType.WINDOWS - override val nativeFileSystemRoot: OsPath = OsPath.of(nativeType, nativeFileSystemRoot) + override val nativeFileSystemRoot: OsPath = OsPath.of(nativeType, nativeFileSystemRoot).getOrThrow() } diff --git a/src/main/kotlin/io/github/kscripting/os/instance/WindowsOs.kt b/src/main/kotlin/io/github/kscripting/os/instance/WindowsOs.kt index f32eed6..2afee8b 100644 --- a/src/main/kotlin/io/github/kscripting/os/instance/WindowsOs.kt +++ b/src/main/kotlin/io/github/kscripting/os/instance/WindowsOs.kt @@ -9,5 +9,5 @@ class WindowsOs(userHome: String) : Os { override val osTypePrefix: String = "windows" override val type: OsType = OsType.WINDOWS override val pathSeparator: String get() = "\\" - override val userHome: OsPath = OsPath.of(type, userHome) + override val userHome: OsPath = OsPath.of(type, userHome).getOrThrow() } diff --git a/src/main/kotlin/io/github/kscripting/os/model/OsPath.kt b/src/main/kotlin/io/github/kscripting/os/model/OsPath.kt index 93e1e31..3036dde 100644 --- a/src/main/kotlin/io/github/kscripting/os/model/OsPath.kt +++ b/src/main/kotlin/io/github/kscripting/os/model/OsPath.kt @@ -1,7 +1,5 @@ package io.github.kscripting.os.model -import io.github.kscripting.os.instance.HostedOs - //Path representation for different OSes @Suppress("MemberVisibilityCanBePrivate") data class OsPath(val osType: OsType<*>, val root: String, val pathParts: List) { @@ -13,141 +11,11 @@ data class OsPath(val osType: OsType<*>, val root: String, val pathParts: List() - var newRoot = "" - - if (isAbsolute) { - when (osType) { - OsType.CYGWIN -> { - if (pathParts[0].equals("cygdrive", true)) { //Paths referring /cygdrive - newRoot = pathParts[1] + ":\\" - newParts.addAll(pathParts.subList(2, pathParts.size)) - } else if (root == "~") { //Paths starting with ~ - newRoot = hostedOs.nativeFileSystemRoot.root - newParts.addAll(hostedOs.nativeFileSystemRoot.pathParts) - newParts.addAll(hostedOs.userHome.pathParts) - newParts.addAll(pathParts) - } else { //Any other path like: /usr/bin - newRoot = hostedOs.nativeFileSystemRoot.root - newParts.addAll(hostedOs.nativeFileSystemRoot.pathParts) - newParts.addAll(pathParts) - } - } - - OsType.MSYS -> { - if (pathParts[0].length == 1 && (pathParts[0][0].code in 65..90 || pathParts[0][0].code in 97..122)) { //Paths referring with drive letter at the beginning - newRoot = pathParts[0] + ":\\" - newParts.addAll(pathParts.subList(1, pathParts.size)) - } else if (root == "~") { //Paths starting with ~ - newRoot = hostedOs.nativeFileSystemRoot.root - newParts.addAll(hostedOs.nativeFileSystemRoot.pathParts) - newParts.addAll(hostedOs.userHome.pathParts) - newParts.addAll(pathParts) - } else { //Any other path like: /usr/bin - newRoot = hostedOs.nativeFileSystemRoot.root - newParts.addAll(hostedOs.nativeFileSystemRoot.pathParts) - newParts.addAll(pathParts) - } - } - } - } else { - newParts.addAll(pathParts) - } - - return OsPath(hostedOs.nativeType, newRoot, newParts) - } fun List.startsWith(list: List): Boolean = (this.size >= list.size && this.subList(0, list.size) == list) fun startsWith(osPath: OsPath): Boolean = root == osPath.root && pathParts.startsWith(osPath.pathParts) - fun toHosted(targetOs: OsType<*>): OsPath { - if (osType == targetOs) { - //This is already targetOs... - return this - } - - check(targetOs.isPosixHostedOnWindows() && osType == ((targetOs.value) as HostedOs).nativeType) { - "You can convert only paths to hosted OS-es" - } - - val newParts = mutableListOf() - var newRoot = "" - - if (isAbsolute) { - val hostedOs = targetOs.value as HostedOs - - if (this.startsWith(hostedOs.nativeFileSystemRoot)) { - if (pathParts.subList(hostedOs.nativeFileSystemRoot.pathParts.size, pathParts.size) - .startsWith(hostedOs.userHome.pathParts) - ) { - //It is user home: ~ - newRoot = "~/" - newParts.addAll( - pathParts.subList( - hostedOs.nativeFileSystemRoot.pathParts.size + hostedOs.userHome.pathParts.size, - pathParts.size - ) - ) - } else { - //It is hostedOs root: / - newRoot = "/" - newParts.addAll(pathParts.subList(hostedOs.nativeFileSystemRoot.pathParts.size, pathParts.size)) - } - } else { - //Otherwise: - //root is like 'C:\' - val drive = root.dropLast(2).lowercase() - - newRoot = "/" - - if (targetOs.value.type == OsType.CYGWIN) { - newParts.add("cygdrive") - newParts.add(drive) - } else { - newParts.add(drive) - } - - newParts.addAll(pathParts) - } - } else { - newParts.addAll(pathParts) - } - - return OsPath(targetOs, newRoot, newParts) - } - override fun toString(): String = "$path [$osType]" companion object { @@ -172,31 +40,19 @@ data class OsPath(val osType: OsType<*>, val root: String, val pathParts: List?\\\\/|]+\\\\[^*:<>?\\\\/|]+|\\?\\\\(?:[a-zA-Z]:(?=\\\\)|(?:UNC\\\\)?[^*:<>?\\\\/|]+\\\\[^*:<>?\\\\/|]+)))".toRegex() - fun of(vararg pathParts: String): OsPath = of(OsType.native, pathParts.toList()) - - fun of(osType: OsType<*>, vararg pathParts: String): OsPath = of(osType, pathParts.toList()) - - fun of(osType: OsType<*>, pathParts: List): OsPath { - return create(osType, pathParts).getOrThrow() - } - - fun ofOrNull(vararg pathParts: String): OsPath? = ofOrNull(OsType.native, pathParts.toList()) - - fun ofOrNull(osType: OsType<*>, vararg pathParts: String): OsPath? = ofOrNull(osType, pathParts.toList()) - - fun ofOrNull(osType: OsType<*>, pathParts: List): OsPath? { - return create(osType, pathParts).getOrNull() - } - //Relaxed validation: //1. It doesn't matter if there is '/' or '\' used as path separator - both are treated the same //2. Duplicated or trailing slashes '/' and backslashes '\' are just ignored - fun create(osType: OsType<*>, vararg pathParts: String): Result { - return create(osType, pathParts.toList()) + fun of(vararg pathParts: String): Result { + return of(OsType.native, pathParts.toList()) + } + + fun of(osType: OsType<*>, vararg pathParts: String): Result { + return of(osType, pathParts.toList()) } - fun create(osType: OsType<*>, pathParts: List): Result { + fun of(osType: OsType<*>, pathParts: List): Result { val path = pathParts.joinToString("/") //Detect root diff --git a/src/main/kotlin/io/github/kscripting/os/model/OsPathExt.kt b/src/main/kotlin/io/github/kscripting/os/model/OsPathExt.kt index 8a9dfc1..d77db77 100644 --- a/src/main/kotlin/io/github/kscripting/os/model/OsPathExt.kt +++ b/src/main/kotlin/io/github/kscripting/os/model/OsPathExt.kt @@ -1,5 +1,6 @@ package io.github.kscripting.os.model +import io.github.kscripting.os.instance.HostedOs import java.io.File import java.net.URI import java.nio.charset.Charset @@ -8,36 +9,217 @@ import java.nio.file.Path import java.nio.file.Paths import kotlin.io.path.* +//Wrappers +fun Result

.flatMap(fn: (P) -> Result): Result = fold( + onSuccess = { fn(it) }, + onFailure = { Result.failure(it) } +) + +fun OsPath.toNative(): Result { + if (!osType.isPosixHostedOnWindows()) { + //Everything besides Cygwin/Msys is native... + return Result.success(this) + } + + val hostedOs = osType.value as HostedOs + + val newParts = mutableListOf() + var newRoot = "" + + if (isAbsolute) { + when (osType) { + OsType.CYGWIN -> { + if (pathParts[0].equals("cygdrive", true)) { //Paths referring /cygdrive + newRoot = pathParts[1] + ":\\" + newParts.addAll(pathParts.subList(2, pathParts.size)) + } else if (root == "~") { //Paths starting with ~ + newRoot = hostedOs.nativeFileSystemRoot.root + newParts.addAll(hostedOs.nativeFileSystemRoot.pathParts) + newParts.addAll(hostedOs.userHome.pathParts) + newParts.addAll(pathParts) + } else { //Any other path like: /usr/bin + newRoot = hostedOs.nativeFileSystemRoot.root + newParts.addAll(hostedOs.nativeFileSystemRoot.pathParts) + newParts.addAll(pathParts) + } + } + + OsType.MSYS -> { + if (pathParts[0].length == 1 && (pathParts[0][0].code in 65..90 || pathParts[0][0].code in 97..122)) { //Paths referring with drive letter at the beginning + newRoot = pathParts[0] + ":\\" + newParts.addAll(pathParts.subList(1, pathParts.size)) + } else if (root == "~") { //Paths starting with ~ + newRoot = hostedOs.nativeFileSystemRoot.root + newParts.addAll(hostedOs.nativeFileSystemRoot.pathParts) + newParts.addAll(hostedOs.userHome.pathParts) + newParts.addAll(pathParts) + } else { //Any other path like: /usr/bin + newRoot = hostedOs.nativeFileSystemRoot.root + newParts.addAll(hostedOs.nativeFileSystemRoot.pathParts) + newParts.addAll(pathParts) + } + } + } + } else { + newParts.addAll(pathParts) + } + + return Result.success(OsPath(hostedOs.nativeType, newRoot, newParts)) +} + +fun Result.toNative(): Result = flatMap { it.toNative() } + + +fun OsPath.toHosted(targetOs: OsType<*>): Result { + if (osType == targetOs) { + //This is already targetOs... + return Result.success(this) + } + + if (!(targetOs.isPosixHostedOnWindows() && osType == ((targetOs.value) as HostedOs).nativeType)) { + return Result.failure(IllegalArgumentException("You can convert only paths to hosted OS-es")) + } + + val newParts = mutableListOf() + var newRoot = "" + + if (isAbsolute) { + val hostedOs = targetOs.value as HostedOs + + if (this.startsWith(hostedOs.nativeFileSystemRoot)) { + if (pathParts.subList(hostedOs.nativeFileSystemRoot.pathParts.size, pathParts.size) + .startsWith(hostedOs.userHome.pathParts) + ) { + //It is user home: ~ + newRoot = "~/" + newParts.addAll( + pathParts.subList( + hostedOs.nativeFileSystemRoot.pathParts.size + hostedOs.userHome.pathParts.size, + pathParts.size + ) + ) + } else { + //It is hostedOs root: / + newRoot = "/" + newParts.addAll(pathParts.subList(hostedOs.nativeFileSystemRoot.pathParts.size, pathParts.size)) + } + } else { + //Otherwise: + //root is like 'C:\' + val drive = root.dropLast(2).lowercase() + + newRoot = "/" + + if (targetOs.value.type == OsType.CYGWIN) { + newParts.add("cygdrive") + newParts.add(drive) + } else { + newParts.add(drive) + } + + newParts.addAll(pathParts) + } + } else { + newParts.addAll(pathParts) + } + + return Result.success(OsPath(targetOs, newRoot, newParts)) +} + +fun Result.toHosted(targetOs: OsType<*>): Result = flatMap { it.toHosted(targetOs) } + + +val Result.path: Result get() = map { it.path } // Conversion to OsPath -fun File.toOsPath(): OsPath = OsPath.of(OsType.native, absolutePath) +fun File.toOsPath(): Result = OsPath.of(OsType.native, absolutePath) -fun Path.toOsPath(): OsPath = OsPath.of(OsType.native, absolutePathString()) +fun Path.toOsPath(): Result = OsPath.of(OsType.native, absolutePathString()) -fun URI.toOsPath(): OsPath = - if (this.scheme == "file") File(this).toOsPath() else error("Invalid conversion from URL to OsPath") +fun URI.toOsPath(): Result = + if (this.scheme == "file") File(this).toOsPath() else Result.failure(IllegalArgumentException("Invalid conversion from URL to OsPath")) // Conversion from OsPath -fun OsPath.toNativePath(): Path = Paths.get(toNative().path) +fun OsPath.toNativePath(): Result = toNative().path.map { Paths.get(it) } -fun OsPath.toNativeFile(): File = File(toNative().path) +fun OsPath.toNativeFile(): Result = toNative().path.map { File(it) } +fun Result.toNativeFile(): Result = toNative().path.map { File(it) } -fun OsPath.toNativeUri(): URI = File(toNative().path).toURI() +fun OsPath.toNativeUri(): Result = toNative().path.map { File(it).toURI() } // OsPath operations -fun OsPath.exists() = toNativePath().exists() +fun OsPath.exists(): Result = toNativePath().map { it.exists() } +val Result.exists: Result get() = flatMap { it.exists() } -fun OsPath.createDirectories(): OsPath = OsPath.of(nativeType, toNativePath().createDirectories().pathString) +fun OsPath.createDirectories(): Result { + val path = toNativePath().getOrElse { return Result.failure(it) } + val createPath = Result.runCatching { path.createDirectories().pathString }.getOrElse { return Result.failure(it) } -fun OsPath.copyTo(target: OsPath, overwrite: Boolean = false): OsPath = - OsPath.of(nativeType, toNativePath().copyTo(target.toNativePath(), overwrite).pathString) + return OsPath.of(nativeType, createPath) +} -fun OsPath.writeText(text: CharSequence, charset: Charset = Charsets.UTF_8, vararg options: OpenOption): Unit = - toNativePath().writeText(text, charset, *options) +fun Result.createDirectories(): Result = flatMap { it.createDirectories() } -fun OsPath.readText(charset: Charset = Charsets.UTF_8): String = toNativePath().readText(charset) + +fun OsPath.copyTo(target: OsPath, overwrite: Boolean = false): Result { + val sourcePath = toNativePath().getOrElse { return Result.failure(it) } + val targetPath = target.toNativePath().getOrElse { return Result.failure(it) } + val copyPath = Result.runCatching { sourcePath.copyTo(targetPath, overwrite).pathString } + .getOrElse { return Result.failure(it) } + + return OsPath.of(nativeType, copyPath) +} + +fun Result.copyTo(target: OsPath, overwrite: Boolean = false): Result = + flatMap { it.copyTo(target, overwrite) } + +fun OsPath.writeText(text: CharSequence, charset: Charset = Charsets.UTF_8, vararg options: OpenOption): Result { + val path = toNativePath().getOrElse { return Result.failure(it) } + return Result.runCatching { path.writeText(text, charset, *options) } +} + +fun OsPath.readText(charset: Charset = Charsets.UTF_8): Result { + val path = toNativePath().getOrElse { return Result.failure(it) } + return Result.runCatching { path.readText(charset) } +} +fun Result.readText(charset: Charset = Charsets.UTF_8): Result = flatMap { it.readText(charset) } + + +operator fun OsPath.div(osPath: OsPath): Result = resolve(osPath) +operator fun Result.div(osPath: OsPath): Result = flatMap { it.div(osPath) } + +operator fun OsPath.div(path: String): Result = resolve(path) +operator fun Result.div(path: String): Result = flatMap { it.div(path) } + + +fun OsPath.resolve(vararg pathParts: String): Result = resolve(OsPath.of(osType, *pathParts).getOrThrow()) +fun Result.resolve(vararg pathParts: String): Result = flatMap { it.resolve(*pathParts) } + +fun OsPath.resolve(osPath: OsPath): Result { + if (osType != osPath.osType) { + return Result.failure(IllegalArgumentException("Paths from different OS's: '${osType}' path can not be resolved with '${osPath.osType}' path")) + } + + if (osPath.isAbsolute) { + return Result.failure(IllegalArgumentException("Can not resolve absolute or relative path '${path}' using absolute path '${osPath.path}'")) + } + + val newPathParts = buildList { + addAll(pathParts) + addAll(osPath.pathParts) + } + + val normalizedPath = OsPath.normalize(root, newPathParts).getOrElse { return Result.failure(it) } + return Result.success(OsPath(osType, root, normalizedPath)) +} +fun Result.resolve(osPath: OsPath): Result = flatMap { it.resolve(osPath) } +fun Result.resolve(osPath: Result): Result { + val currentPath = this.getOrElse { return Result.failure(it) } + val newPath = osPath.getOrElse { return Result.failure(it) } + return currentPath.resolve(newPath) +} // OsPath accessors @@ -47,8 +229,8 @@ val OsPath.leaf //val OsPath.rootOsPath // get() = OsPath.createOrThrow(osType, root) -val OsPath.parent - get() = toNativePath().parent +//val OsPath.parent +// get() = toNativePath().parent val OsPath.nativeType get() = if (osType.isPosixHostedOnWindows()) OsType.WINDOWS else osType diff --git a/src/main/kotlin/io/github/kscripting/shell/process/ProcessRunner.kt b/src/main/kotlin/io/github/kscripting/shell/process/ProcessRunner.kt index 92bf4f2..d9cdd22 100644 --- a/src/main/kotlin/io/github/kscripting/shell/process/ProcessRunner.kt +++ b/src/main/kotlin/io/github/kscripting/shell/process/ProcessRunner.kt @@ -53,7 +53,7 @@ object ProcessRunner { try { // simplify with https://stackoverflow.com/questions/35421699/how-to-invoke-external-command-from-within-kotlin-code val process = ProcessBuilder(command) - .directory(workingDirectory?.toNativeFile()) + .directory(workingDirectory?.toNativeFile()?.getOrNull()) .redirectInput(if (inheritInput) ProcessBuilder.Redirect.INHERIT else ProcessBuilder.Redirect.PIPE) .redirectOutput(ProcessBuilder.Redirect.PIPE) .apply { diff --git a/src/test/kotlin/io/github/kscripting/os/model/HostedOsPathTest.kt b/src/test/kotlin/io/github/kscripting/os/model/HostedOsPathTest.kt index b5c7d73..a357a3f 100644 --- a/src/test/kotlin/io/github/kscripting/os/model/HostedOsPathTest.kt +++ b/src/test/kotlin/io/github/kscripting/os/model/HostedOsPathTest.kt @@ -23,61 +23,61 @@ class HostedOsPathTest { @Test fun `Test Cygwin to Windows`() { - assertThat(OsPath.of(OsType.CYGWIN, "/cygdrive/c/home/admin/.kscript").toNative().path) + assertThat(OsPath.of(OsType.CYGWIN, "/cygdrive/c/home/admin/.kscript").toNative().path.getOrThrow()) .isEqualTo("c:\\home\\admin\\.kscript") - assertThat(OsPath.of(OsType.CYGWIN, "~/.kscript").toNative().path) + assertThat(OsPath.of(OsType.CYGWIN, "~/.kscript").toNative().path.getOrThrow()) .isEqualTo("C:\\Programs\\Cygwin\\home\\admin\\.kscript") - assertThat(OsPath.of(OsType.CYGWIN, "/usr/local/bin/sdk").toNative().path) + assertThat(OsPath.of(OsType.CYGWIN, "/usr/local/bin/sdk").toNative().path.getOrThrow()) .isEqualTo("C:\\Programs\\Cygwin\\usr\\local\\bin\\sdk") - assertThat(OsPath.of(OsType.CYGWIN, "../home/admin/.kscript").toNative().path) + assertThat(OsPath.of(OsType.CYGWIN, "../home/admin/.kscript").toNative().path.getOrThrow()) .isEqualTo("..\\home\\admin\\.kscript") } @Test fun `Test Windows to Cygwin`() { - assertThat(OsPath.of(OsType.WINDOWS, "C:\\home\\admin\\.kscript").toHosted(OsType.CYGWIN).path) + assertThat(OsPath.of(OsType.WINDOWS, "C:\\home\\admin\\.kscript").toHosted(OsType.CYGWIN).path.getOrThrow()) .isEqualTo("/cygdrive/c/home/admin/.kscript") - assertThat(OsPath.of(OsType.WINDOWS, "..\\home\\admin\\.kscript").toHosted(OsType.CYGWIN).path) + assertThat(OsPath.of(OsType.WINDOWS, "..\\home\\admin\\.kscript").toHosted(OsType.CYGWIN).path.getOrThrow()) .isEqualTo("../home/admin/.kscript") - assertThat(OsPath.of(OsType.WINDOWS, "C:\\Programs\\Cygwin\\home\\admin\\.kscript").toHosted(OsType.CYGWIN).path) + assertThat(OsPath.of(OsType.WINDOWS, "C:\\Programs\\Cygwin\\home\\admin\\.kscript").toHosted(OsType.CYGWIN).path.getOrThrow()) .isEqualTo("~/.kscript") - assertThat(OsPath.of(OsType.WINDOWS, "C:\\Programs\\Cygwin\\usr\\local\\sdk").toHosted(OsType.CYGWIN).path) + assertThat(OsPath.of(OsType.WINDOWS, "C:\\Programs\\Cygwin\\usr\\local\\sdk").toHosted(OsType.CYGWIN).path.getOrThrow()) .isEqualTo("/usr/local/sdk") } @Test fun `Test MSys to Windows`() { - assertThat(OsPath.of(OsType.MSYS, "/c/home/admin/.kscript").toNative().path) + assertThat(OsPath.of(OsType.MSYS, "/c/home/admin/.kscript").toNative().path.getOrThrow()) .isEqualTo("c:\\home\\admin\\.kscript") - assertThat(OsPath.of(OsType.MSYS, "~/.kscript").toNative().path) + assertThat(OsPath.of(OsType.MSYS, "~/.kscript").toNative().path.getOrThrow()) .isEqualTo("C:\\Programs\\Msys\\home\\admin\\.kscript") - assertThat(OsPath.of(OsType.MSYS, "/usr/local/bin/sdk").toNative().path) + assertThat(OsPath.of(OsType.MSYS, "/usr/local/bin/sdk").toNative().path.getOrThrow()) .isEqualTo("C:\\Programs\\Msys\\usr\\local\\bin\\sdk") - assertThat(OsPath.of(OsType.MSYS, "../home/admin/.kscript").toNative().path) + assertThat(OsPath.of(OsType.MSYS, "../home/admin/.kscript").toNative().path.getOrThrow()) .isEqualTo("..\\home\\admin\\.kscript") } @Test fun `Test Windows to MSys`() { - assertThat(OsPath.of(OsType.WINDOWS, "C:\\home\\admin\\.kscript").toHosted(OsType.MSYS).path) + assertThat(OsPath.of(OsType.WINDOWS, "C:\\home\\admin\\.kscript").toHosted(OsType.MSYS).path.getOrThrow()) .isEqualTo("/c/home/admin/.kscript") - assertThat(OsPath.of(OsType.WINDOWS, "..\\home\\admin\\.kscript").toHosted(OsType.MSYS).path) + assertThat(OsPath.of(OsType.WINDOWS, "..\\home\\admin\\.kscript").toHosted(OsType.MSYS).path.getOrThrow()) .isEqualTo("../home/admin/.kscript") - assertThat(OsPath.of(OsType.WINDOWS, "C:\\Programs\\Msys\\home\\admin\\.kscript").toHosted(OsType.MSYS).path) + assertThat(OsPath.of(OsType.WINDOWS, "C:\\Programs\\Msys\\home\\admin\\.kscript").toHosted(OsType.MSYS).path.getOrThrow()) .isEqualTo("~/.kscript") - assertThat(OsPath.of(OsType.WINDOWS, "C:\\Programs\\Msys\\usr\\local\\sdk").toHosted(OsType.MSYS).path) + assertThat(OsPath.of(OsType.WINDOWS, "C:\\Programs\\Msys\\usr\\local\\sdk").toHosted(OsType.MSYS).path.getOrThrow()) .isEqualTo("/usr/local/sdk") } } diff --git a/src/test/kotlin/io/github/kscripting/os/model/PosixOsPathTest.kt b/src/test/kotlin/io/github/kscripting/os/model/PosixOsPathTest.kt index 2c7ec8b..1a8d7f6 100644 --- a/src/test/kotlin/io/github/kscripting/os/model/PosixOsPathTest.kt +++ b/src/test/kotlin/io/github/kscripting/os/model/PosixOsPathTest.kt @@ -1,5 +1,6 @@ package io.github.kscripting.os.model +import assertk.assertFailure import assertk.assertThat import assertk.assertions.* import io.github.kscripting.os.instance.LinuxOs @@ -18,63 +19,63 @@ class PosixOsPathTest { @Test fun `Test Posix paths`() { - assertThat(OsPath.of(OsType.LINUX, "/")).let { + assertThat(OsPath.of(OsType.LINUX, "/").getOrThrow()).let { it.prop(OsPath::osType).isEqualTo(OsType.LINUX) it.prop(OsPath::root).isEqualTo("/") it.prop(OsPath::pathParts).isEqualTo(emptyList()) } - assertThat(OsPath.of(OsType.LINUX, "/home/admin/.kscript")).let { + assertThat(OsPath.of(OsType.LINUX, "/home/admin/.kscript").getOrThrow()).let { it.prop(OsPath::osType).isEqualTo(OsType.LINUX) it.prop(OsPath::root).isEqualTo("/") it.prop(OsPath::pathParts).isEqualTo(listOf("home", "admin", ".kscript")) } - assertThat(OsPath.of(OsType.LINUX, "./home/admin/.kscript")).let { + assertThat(OsPath.of(OsType.LINUX, "./home/admin/.kscript").getOrThrow()).let { it.prop(OsPath::osType).isEqualTo(OsType.LINUX) it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(listOf("home", "admin", ".kscript")) } - assertThat(OsPath.of(OsType.LINUX, "")).let { + assertThat(OsPath.of(OsType.LINUX, "").getOrThrow()).let { it.prop(OsPath::osType).isEqualTo(OsType.LINUX) it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(emptyList()) } - assertThat(OsPath.of(OsType.LINUX, "file.txt")).let { + assertThat(OsPath.of(OsType.LINUX, "file.txt").getOrThrow()).let { it.prop(OsPath::osType).isEqualTo(OsType.LINUX) it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(listOf("file.txt")) } - assertThat(OsPath.of(OsType.LINUX, ".")).let { + assertThat(OsPath.of(OsType.LINUX, ".").getOrThrow()).let { it.prop(OsPath::osType).isEqualTo(OsType.LINUX) it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(emptyList()) } - assertThat(OsPath.of(OsType.LINUX, "../home/admin/.kscript")).let { + assertThat(OsPath.of(OsType.LINUX, "../home/admin/.kscript").getOrThrow()).let { it.prop(OsPath::osType).isEqualTo(OsType.LINUX) it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(listOf("..", "home", "admin", ".kscript")) } - assertThat(OsPath.of(OsType.LINUX, "..")).let { + assertThat(OsPath.of(OsType.LINUX, "..").getOrThrow()).let { it.prop(OsPath::osType).isEqualTo(OsType.LINUX) it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(listOf("..")) } //Duplicated separators are accepted - assertThat(OsPath.of(OsType.LINUX, "..//home////admin/.kscript/")).let { + assertThat(OsPath.of(OsType.LINUX, "..//home////admin/.kscript/").getOrThrow()).let { it.prop(OsPath::osType).isEqualTo(OsType.LINUX) it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(listOf("..", "home", "admin", ".kscript")) } //Both types of separator are accepted - assertThat(OsPath.of(OsType.LINUX, "..//home\\admin\\.kscript/")).let { + assertThat(OsPath.of(OsType.LINUX, "..//home\\admin\\.kscript/").getOrThrow()).let { it.prop(OsPath::osType).isEqualTo(OsType.LINUX) it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(listOf("..", "home", "admin", ".kscript")) @@ -83,25 +84,25 @@ class PosixOsPathTest { @Test fun `Normalization of Posix paths`() { - assertThat(OsPath.of(OsType.LINUX, "/home/admin/.kscript/../../")).let { + assertThat(OsPath.of(OsType.LINUX, "/home/admin/.kscript/../../").getOrThrow()).let { it.prop(OsPath::osType).isEqualTo(OsType.LINUX) it.prop(OsPath::root).isEqualTo("/") it.prop(OsPath::pathParts).isEqualTo(listOf("home")) } - assertThat(OsPath.of(OsType.LINUX, "./././../../script")).let { + assertThat(OsPath.of(OsType.LINUX, "./././../../script").getOrThrow()).let { it.prop(OsPath::osType).isEqualTo(OsType.LINUX) it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(listOf("..", "..", "script")) } - assertThat(OsPath.of(OsType.LINUX, "/a/b/c/../d/script")).let { + assertThat(OsPath.of(OsType.LINUX, "/a/b/c/../d/script").getOrThrow()).let { it.prop(OsPath::osType).isEqualTo(OsType.LINUX) it.prop(OsPath::root).isEqualTo("/") it.prop(OsPath::pathParts).isEqualTo(listOf("a", "b", "d", "script")) } - OsPath.create(OsType.LINUX, "/.kscript/../../").let { + OsPath.of(OsType.LINUX, "/.kscript/../../").let { assertThat(it.isFailure).isTrue() assertThat(it.exceptionOrNull()).isNotNull().isInstanceOf(IllegalArgumentException::class.java) .hasMessage("Path after normalization goes beyond root element: '/'") @@ -110,52 +111,52 @@ class PosixOsPathTest { @Test fun `Test invalid Posix paths`() { - assertThat { OsPath.of(OsType.LINUX, "/ad*asdf") }.isFailure() + assertFailure { OsPath.of(OsType.LINUX, "/ad*asdf").getOrThrow() } .isInstanceOf(IllegalArgumentException::class.java).hasMessage("Invalid character '*' in path '/ad*asdf'") } @Test fun `Test Posix stringPath`() { - assertThat(OsPath.of(OsType.LINUX, "/home/admin/.kscript").path).isEqualTo("/home/admin/.kscript") - assertThat(OsPath.of(OsType.LINUX, "/a/b/c/../d/script").path).isEqualTo("/a/b/d/script") - assertThat(OsPath.of(OsType.LINUX, "./././../../script").path).isEqualTo("../../script") - assertThat(OsPath.of(OsType.LINUX, "script/file.txt").path).isEqualTo("script/file.txt") + assertThat(OsPath.of(OsType.LINUX, "/home/admin/.kscript").path.getOrThrow()).isEqualTo("/home/admin/.kscript") + assertThat(OsPath.of(OsType.LINUX, "/a/b/c/../d/script").path.getOrThrow()).isEqualTo("/a/b/d/script") + assertThat(OsPath.of(OsType.LINUX, "./././../../script").path.getOrThrow()).isEqualTo("../../script") + assertThat(OsPath.of(OsType.LINUX, "script/file.txt").path.getOrThrow()).isEqualTo("script/file.txt") } @Test fun `Test Posix resolve`() { assertThat( - OsPath.of(OsType.LINUX, "/").resolve(OsPath.of(OsType.LINUX, "./.kscript/")).path + OsPath.of(OsType.LINUX, "/").resolve(OsPath.of(OsType.LINUX, "./.kscript/")).path.getOrThrow() ).isEqualTo("/.kscript") assertThat( - OsPath.of(OsType.LINUX, "/home/admin/").resolve(OsPath.of(OsType.LINUX, "./.kscript/")).path + OsPath.of(OsType.LINUX, "/home/admin/").resolve(OsPath.of(OsType.LINUX, "./.kscript/")).path.getOrThrow() ).isEqualTo("/home/admin/.kscript") assertThat( - OsPath.of(OsType.LINUX, "./home/admin/").resolve(OsPath.of(OsType.LINUX, "./.kscript/")).path + OsPath.of(OsType.LINUX, "./home/admin/").resolve(OsPath.of(OsType.LINUX, "./.kscript/")).path.getOrThrow() ).isEqualTo("home/admin/.kscript") assertThat( - OsPath.of(OsType.LINUX, "../home/admin/").resolve(OsPath.of(OsType.LINUX, "./.kscript/")).path + OsPath.of(OsType.LINUX, "../home/admin/").resolve(OsPath.of(OsType.LINUX, "./.kscript/")).path.getOrThrow() ).isEqualTo("../home/admin/.kscript") assertThat( - OsPath.of(OsType.LINUX, "..").resolve(OsPath.of(OsType.LINUX, "./.kscript/")).path + OsPath.of(OsType.LINUX, "..").resolve(OsPath.of(OsType.LINUX, "./.kscript/")).path.getOrThrow() ).isEqualTo("../.kscript") assertThat( - OsPath.of(OsType.LINUX, ".").resolve(OsPath.of(OsType.LINUX, "./.kscript/")).path + OsPath.of(OsType.LINUX, ".").resolve(OsPath.of(OsType.LINUX, "./.kscript/")).path.getOrThrow() ).isEqualTo(".kscript") - assertThat { - OsPath.of(OsType.LINUX, "./home/admin").resolve(OsPath.of(OsType.WINDOWS, ".\\run")) - }.isFailure().isInstanceOf(IllegalArgumentException::class.java) + assertFailure { + OsPath.of(OsType.LINUX, "./home/admin").resolve(OsPath.of(OsType.WINDOWS, ".\\run")).getOrThrow() + }.isInstanceOf(IllegalArgumentException::class.java) .hasMessage("Paths from different OS's: 'LINUX' path can not be resolved with 'WINDOWS' path") - assertThat { - OsPath.of(OsType.LINUX, "./home/admin").resolve(OsPath.of(OsType.LINUX, "/run")) - }.isFailure().isInstanceOf(IllegalArgumentException::class.java) + assertFailure { + OsPath.of(OsType.LINUX, "./home/admin").resolve(OsPath.of(OsType.LINUX, "/run")).getOrThrow() + }.isInstanceOf(IllegalArgumentException::class.java) .hasMessage("Can not resolve absolute or relative path 'home/admin' using absolute path '/run'") } } diff --git a/src/test/kotlin/io/github/kscripting/os/model/WindowsOsPathTest.kt b/src/test/kotlin/io/github/kscripting/os/model/WindowsOsPathTest.kt index 0b181c7..db336d0 100644 --- a/src/test/kotlin/io/github/kscripting/os/model/WindowsOsPathTest.kt +++ b/src/test/kotlin/io/github/kscripting/os/model/WindowsOsPathTest.kt @@ -1,5 +1,6 @@ package io.github.kscripting.os.model +import assertk.assertFailure import assertk.assertThat import assertk.assertions.* import io.github.kscripting.os.instance.CygwinOs @@ -19,63 +20,63 @@ class WindowsOsPathTest { @Test fun `Test Windows paths`() { - assertThat(OsPath.of(OsType.WINDOWS, "C:\\")).let { + assertThat(OsPath.of(OsType.WINDOWS, "C:\\").getOrThrow()).let { it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) it.prop(OsPath::root).isEqualTo("C:\\") it.prop(OsPath::pathParts).isEqualTo(emptyList()) } - assertThat(OsPath.of(OsType.WINDOWS, "C:\\home\\admin\\.kscript")).let { + assertThat(OsPath.of(OsType.WINDOWS, "C:\\home\\admin\\.kscript").getOrThrow()).let { it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) it.prop(OsPath::root).isEqualTo("C:\\") it.prop(OsPath::pathParts).isEqualTo(listOf("home", "admin", ".kscript")) } - assertThat(OsPath.of(OsType.WINDOWS, "")).let { + assertThat(OsPath.of(OsType.WINDOWS, "").getOrThrow()).let { it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(emptyList()) } - assertThat(OsPath.of(OsType.WINDOWS, "file.txt")).let { + assertThat(OsPath.of(OsType.WINDOWS, "file.txt").getOrThrow()).let { it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(listOf("file.txt")) } - assertThat(OsPath.of(OsType.WINDOWS, ".")).let { + assertThat(OsPath.of(OsType.WINDOWS, ".").getOrThrow()).let { it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(emptyList()) } - assertThat(OsPath.of(OsType.WINDOWS, ".\\home\\admin\\.kscript")).let { + assertThat(OsPath.of(OsType.WINDOWS, ".\\home\\admin\\.kscript").getOrThrow()).let { it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(listOf("home", "admin", ".kscript")) } - assertThat(OsPath.of(OsType.WINDOWS, "..\\home\\admin\\.kscript")).let { + assertThat(OsPath.of(OsType.WINDOWS, "..\\home\\admin\\.kscript").getOrThrow()).let { it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(listOf("..", "home", "admin", ".kscript")) } - assertThat(OsPath.of(OsType.WINDOWS, "..")).let { + assertThat(OsPath.of(OsType.WINDOWS, "..").getOrThrow()).let { it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(listOf("..")) } //Duplicated separators are accepted - assertThat(OsPath.of(OsType.WINDOWS, "C:\\home\\\\\\\\admin\\.kscript\\")).let { + assertThat(OsPath.of(OsType.WINDOWS, "C:\\home\\\\\\\\admin\\.kscript\\").getOrThrow()).let { it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) it.prop(OsPath::root).isEqualTo("C:\\") it.prop(OsPath::pathParts).isEqualTo(listOf("home", "admin", ".kscript")) } //Both types of separator are accepted - assertThat(OsPath.of(OsType.WINDOWS, "C:/home\\admin/.kscript////")).let { + assertThat(OsPath.of(OsType.WINDOWS, "C:/home\\admin/.kscript////").getOrThrow()).let { it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) it.prop(OsPath::root).isEqualTo("C:\\") it.prop(OsPath::pathParts).isEqualTo(listOf("home", "admin", ".kscript")) @@ -84,25 +85,25 @@ class WindowsOsPathTest { @Test fun `Normalization of Windows paths`() { - assertThat(OsPath.of(OsType.WINDOWS, "C:\\home\\admin\\.kscript\\..\\..\\")).let { + assertThat(OsPath.of(OsType.WINDOWS, "C:\\home\\admin\\.kscript\\..\\..\\").getOrThrow()).let { it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) it.prop(OsPath::root).isEqualTo("C:\\") it.prop(OsPath::pathParts).isEqualTo(listOf("home")) } - assertThat(OsPath.of(OsType.WINDOWS, ".\\.\\.\\..\\..\\script")).let { + assertThat(OsPath.of(OsType.WINDOWS, ".\\.\\.\\..\\..\\script").getOrThrow()).let { it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(listOf("..", "..", "script")) } - assertThat(OsPath.of(OsType.WINDOWS, "C:\\a\\b\\c\\..\\d\\script")).let { + assertThat(OsPath.of(OsType.WINDOWS, "C:\\a\\b\\c\\..\\d\\script").getOrThrow()).let { it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) it.prop(OsPath::root).isEqualTo("C:\\") it.prop(OsPath::pathParts).isEqualTo(listOf("a", "b", "d", "script")) } - OsPath.create(OsType.WINDOWS, "C:\\.kscript\\..\\..\\").let { + OsPath.of(OsType.WINDOWS, "C:\\.kscript\\..\\..\\").let { assertThat(it.isFailure).isTrue() assertThat(it.exceptionOrNull()).isNotNull().isInstanceOf(IllegalArgumentException::class.java) .hasMessage("Path after normalization goes beyond root element: 'C:\\'") @@ -111,20 +112,20 @@ class WindowsOsPathTest { @Test fun `Test invalid Windows paths`() { - assertThat { OsPath.of(OsType.WINDOWS, "C:\\adas?df") }.isFailure() + assertFailure { OsPath.of(OsType.WINDOWS, "C:\\adas?df").getOrThrow() } .isInstanceOf(IllegalArgumentException::class.java) .hasMessage("Invalid character '?' in path 'C:\\adas?df'") - assertThat { OsPath.of(OsType.WINDOWS, "home:\\vagrant") }.isFailure() + assertFailure { OsPath.of(OsType.WINDOWS, "home:\\vagrant").getOrThrow() } .isInstanceOf(IllegalArgumentException::class.java) .hasMessage("Invalid character ':' in path 'home:\\vagrant'") } @Test fun `Test Windows stringPath`() { - assertThat(OsPath.of(OsType.WINDOWS, "C:\\home\\admin\\.kscript").path).isEqualTo("C:\\home\\admin\\.kscript") - assertThat(OsPath.of(OsType.WINDOWS, "c:\\a\\b\\c\\..\\d\\script").path).isEqualTo("c:\\a\\b\\d\\script") - assertThat(OsPath.of(OsType.WINDOWS, ".\\.\\.\\..\\..\\script").path).isEqualTo("..\\..\\script") - assertThat(OsPath.of(OsType.WINDOWS, "script\\file.txt").path).isEqualTo("script\\file.txt") + assertThat(OsPath.of(OsType.WINDOWS, "C:\\home\\admin\\.kscript").path.getOrThrow()).isEqualTo("C:\\home\\admin\\.kscript") + assertThat(OsPath.of(OsType.WINDOWS, "c:\\a\\b\\c\\..\\d\\script").path.getOrThrow()).isEqualTo("c:\\a\\b\\d\\script") + assertThat(OsPath.of(OsType.WINDOWS, ".\\.\\.\\..\\..\\script").path.getOrThrow()).isEqualTo("..\\..\\script") + assertThat(OsPath.of(OsType.WINDOWS, "script\\file.txt").path.getOrThrow()).isEqualTo("script\\file.txt") } } From 61c6f96cc36d1a8934ebf51cb65f6efc52c07be1 Mon Sep 17 00:00:00 2001 From: Marcin Kuszczak <1508798+aartiPl@users.noreply.github.com> Date: Tue, 31 Oct 2023 22:05:01 +0100 Subject: [PATCH 40/50] Improvements --- .../github/kscripting/os/instance/CygwinOs.kt | 4 +- .../kscripting/os/instance/FreeBsdOs.kt | 2 +- .../github/kscripting/os/instance/LinuxOs.kt | 2 +- .../io/github/kscripting/os/instance/MacOs.kt | 2 +- .../github/kscripting/os/instance/MsysOs.kt | 4 +- .../kscripting/os/instance/WindowsOs.kt | 2 +- .../io/github/kscripting/os/model/OsPath.kt | 37 ++++++------ .../github/kscripting/os/model/OsPathExt.kt | 56 +++++++++---------- .../kscripting/os/model/GenericOsPathTest.kt | 41 ++++++++++++++ .../kscripting/os/model/HostedOsPathTest.kt | 32 +++++------ .../kscripting/os/model/PosixOsPathTest.kt | 54 +++++++++--------- .../kscripting/os/model/WindowsOsPathTest.kt | 40 ++++++------- 12 files changed, 159 insertions(+), 117 deletions(-) create mode 100644 src/test/kotlin/io/github/kscripting/os/model/GenericOsPathTest.kt diff --git a/src/main/kotlin/io/github/kscripting/os/instance/CygwinOs.kt b/src/main/kotlin/io/github/kscripting/os/instance/CygwinOs.kt index 8572af3..0f2f684 100644 --- a/src/main/kotlin/io/github/kscripting/os/instance/CygwinOs.kt +++ b/src/main/kotlin/io/github/kscripting/os/instance/CygwinOs.kt @@ -6,7 +6,7 @@ import io.github.kscripting.os.model.OsType class CygwinOs(userHome: String, nativeFileSystemRoot: String) : HostedOs { override val osTypePrefix: String = "cygwin" override val type: OsType = OsType.CYGWIN - override val userHome: OsPath = OsPath.of(type, userHome).getOrThrow() + override val userHome: OsPath = OsPath(type, userHome).getOrThrow() override val nativeType: OsType = OsType.WINDOWS - override val nativeFileSystemRoot: OsPath = OsPath.of(nativeType, nativeFileSystemRoot).getOrThrow() + override val nativeFileSystemRoot: OsPath = OsPath(nativeType, nativeFileSystemRoot).getOrThrow() } diff --git a/src/main/kotlin/io/github/kscripting/os/instance/FreeBsdOs.kt b/src/main/kotlin/io/github/kscripting/os/instance/FreeBsdOs.kt index 219a71c..3f97491 100644 --- a/src/main/kotlin/io/github/kscripting/os/instance/FreeBsdOs.kt +++ b/src/main/kotlin/io/github/kscripting/os/instance/FreeBsdOs.kt @@ -8,5 +8,5 @@ class FreeBsdOs(userHome: String) : Os { override val osTypePrefix: String = "freebsd" override val type: OsType = OsType.FREEBSD override val pathSeparator: String get() = "/" - override val userHome: OsPath = OsPath.of(OsType.FREEBSD, userHome).getOrThrow() + override val userHome: OsPath = OsPath(OsType.FREEBSD, userHome).getOrThrow() } diff --git a/src/main/kotlin/io/github/kscripting/os/instance/LinuxOs.kt b/src/main/kotlin/io/github/kscripting/os/instance/LinuxOs.kt index 45cab46..7fb1df0 100644 --- a/src/main/kotlin/io/github/kscripting/os/instance/LinuxOs.kt +++ b/src/main/kotlin/io/github/kscripting/os/instance/LinuxOs.kt @@ -8,5 +8,5 @@ class LinuxOs(userHome: String) : Os { override val osTypePrefix: String = "linux" override val type: OsType = OsType.LINUX override val pathSeparator: String get() = "/" - override val userHome: OsPath = OsPath.of(OsType.LINUX, userHome).getOrThrow() + override val userHome: OsPath = OsPath(OsType.LINUX, userHome).getOrThrow() } diff --git a/src/main/kotlin/io/github/kscripting/os/instance/MacOs.kt b/src/main/kotlin/io/github/kscripting/os/instance/MacOs.kt index 0304554..90ad66e 100644 --- a/src/main/kotlin/io/github/kscripting/os/instance/MacOs.kt +++ b/src/main/kotlin/io/github/kscripting/os/instance/MacOs.kt @@ -8,5 +8,5 @@ class MacOs(userHome: String) : Os { override val osTypePrefix: String = "darwin" override val type: OsType = OsType.MACOS override val pathSeparator: String get() = "/" - override val userHome: OsPath = OsPath.of(OsType.MACOS, userHome).getOrThrow() + override val userHome: OsPath = OsPath(OsType.MACOS, userHome).getOrThrow() } diff --git a/src/main/kotlin/io/github/kscripting/os/instance/MsysOs.kt b/src/main/kotlin/io/github/kscripting/os/instance/MsysOs.kt index dacf054..ab97d8e 100644 --- a/src/main/kotlin/io/github/kscripting/os/instance/MsysOs.kt +++ b/src/main/kotlin/io/github/kscripting/os/instance/MsysOs.kt @@ -6,7 +6,7 @@ import io.github.kscripting.os.model.OsType class MsysOs(userHome: String, nativeFileSystemRoot: String) : HostedOs { override val osTypePrefix: String = "msys" override val type: OsType = OsType.MSYS - override val userHome: OsPath = OsPath.of(type, userHome).getOrThrow() + override val userHome: OsPath = OsPath(type, userHome).getOrThrow() override val nativeType: OsType = OsType.WINDOWS - override val nativeFileSystemRoot: OsPath = OsPath.of(nativeType, nativeFileSystemRoot).getOrThrow() + override val nativeFileSystemRoot: OsPath = OsPath(nativeType, nativeFileSystemRoot).getOrThrow() } diff --git a/src/main/kotlin/io/github/kscripting/os/instance/WindowsOs.kt b/src/main/kotlin/io/github/kscripting/os/instance/WindowsOs.kt index 2afee8b..8e925ca 100644 --- a/src/main/kotlin/io/github/kscripting/os/instance/WindowsOs.kt +++ b/src/main/kotlin/io/github/kscripting/os/instance/WindowsOs.kt @@ -9,5 +9,5 @@ class WindowsOs(userHome: String) : Os { override val osTypePrefix: String = "windows" override val type: OsType = OsType.WINDOWS override val pathSeparator: String get() = "\\" - override val userHome: OsPath = OsPath.of(type, userHome).getOrThrow() + override val userHome: OsPath = OsPath(type, userHome).getOrThrow() } diff --git a/src/main/kotlin/io/github/kscripting/os/model/OsPath.kt b/src/main/kotlin/io/github/kscripting/os/model/OsPath.kt index 3036dde..9f720b6 100644 --- a/src/main/kotlin/io/github/kscripting/os/model/OsPath.kt +++ b/src/main/kotlin/io/github/kscripting/os/model/OsPath.kt @@ -1,22 +1,21 @@ package io.github.kscripting.os.model +sealed interface OsPathError { + object EmptyPath : OsPathError, RuntimeException() + data class InvalidConversion(val errorMessage: String) : OsPathError, RuntimeException(errorMessage) +} + //Path representation for different OSes @Suppress("MemberVisibilityCanBePrivate") -data class OsPath(val osType: OsType<*>, val root: String, val pathParts: List) { - val isRelative: Boolean get() = root.isEmpty() - val isAbsolute: Boolean get() = !isRelative +data class OsPath private constructor(val osType: OsType<*>, val root: String, val pathParts: List) { + val isRelative: Boolean get() = root.isEmpty() && pathParts.isNotEmpty() + val isAbsolute: Boolean get() = root.isNotEmpty() + val isEmpty: Boolean get() = root.isEmpty() && pathParts.isEmpty() val path: String get() = root + pathParts.joinToString(osType.value.pathSeparator) { it } + val leaf: String get() = if (pathParts.isEmpty()) root else pathParts.last() - //TODO: maybe we should signalise errors with null root? But what then in path? - val OsPath.leaf: String get() = if (pathParts.isEmpty()) root else pathParts.last() - - - fun List.startsWith(list: List): Boolean = (this.size >= list.size && this.subList(0, list.size) == list) - - fun startsWith(osPath: OsPath): Boolean = root == osPath.root && pathParts.startsWith(osPath.pathParts) - - override fun toString(): String = "$path [$osType]" + override fun toString(): String = if (isEmpty) "[$osType]" else "$path [$osType]" companion object { //https://stackoverflow.com/questions/1976007/what-characters-are-forbidden-in-windows-and-linux-directory-names @@ -44,15 +43,19 @@ data class OsPath(val osType: OsType<*>, val root: String, val pathParts: List { - return of(OsType.native, pathParts.toList()) + operator fun invoke(osType: OsType<*>, root: String, pathParts: List): Result { + return Result.success(OsPath(osType, root, pathParts)) + } + + operator fun invoke(vararg pathParts: String): Result { + return invoke(OsType.native, pathParts.toList()) } - fun of(osType: OsType<*>, vararg pathParts: String): Result { - return of(osType, pathParts.toList()) + operator fun invoke(osType: OsType<*>, vararg pathParts: String): Result { + return invoke(osType, pathParts.toList()) } - fun of(osType: OsType<*>, pathParts: List): Result { + operator fun invoke(osType: OsType<*>, pathParts: List): Result { val path = pathParts.joinToString("/") //Detect root diff --git a/src/main/kotlin/io/github/kscripting/os/model/OsPathExt.kt b/src/main/kotlin/io/github/kscripting/os/model/OsPathExt.kt index d77db77..84f855d 100644 --- a/src/main/kotlin/io/github/kscripting/os/model/OsPathExt.kt +++ b/src/main/kotlin/io/github/kscripting/os/model/OsPathExt.kt @@ -64,20 +64,24 @@ fun OsPath.toNative(): Result { newParts.addAll(pathParts) } - return Result.success(OsPath(hostedOs.nativeType, newRoot, newParts)) + return OsPath(hostedOs.nativeType, newRoot, newParts) } fun Result.toNative(): Result = flatMap { it.toNative() } -fun OsPath.toHosted(targetOs: OsType<*>): Result { +fun List.startsWith(list: List): Boolean = (this.size >= list.size && this.subList(0, list.size) == list) + +fun OsPath.startsWith(osPath: OsPath): Boolean = root == osPath.root && pathParts.startsWith(osPath.pathParts) + +fun OsPath.toHosted(targetOs: OsType<*>): Result = runCatching{ if (osType == targetOs) { //This is already targetOs... return Result.success(this) } if (!(targetOs.isPosixHostedOnWindows() && osType == ((targetOs.value) as HostedOs).nativeType)) { - return Result.failure(IllegalArgumentException("You can convert only paths to hosted OS-es")) + throw OsPathError.InvalidConversion("You can convert only paths to hosted OS-es") } val newParts = mutableListOf() @@ -123,7 +127,7 @@ fun OsPath.toHosted(targetOs: OsType<*>): Result { newParts.addAll(pathParts) } - return Result.success(OsPath(targetOs, newRoot, newParts)) + return OsPath(targetOs, newRoot, newParts) } fun Result.toHosted(targetOs: OsType<*>): Result = flatMap { it.toHosted(targetOs) } @@ -132,9 +136,9 @@ fun Result.toHosted(targetOs: OsType<*>): Result = flatMap { it. val Result.path: Result get() = map { it.path } // Conversion to OsPath -fun File.toOsPath(): Result = OsPath.of(OsType.native, absolutePath) +fun File.toOsPath(): Result = OsPath(OsType.native, absolutePath) -fun Path.toOsPath(): Result = OsPath.of(OsType.native, absolutePathString()) +fun Path.toOsPath(): Result = OsPath(OsType.native, absolutePathString()) fun URI.toOsPath(): Result = if (this.scheme == "file") File(this).toOsPath() else Result.failure(IllegalArgumentException("Invalid conversion from URL to OsPath")) @@ -153,37 +157,32 @@ fun OsPath.toNativeUri(): Result = toNative().path.map { File(it).toURI() } fun OsPath.exists(): Result = toNativePath().map { it.exists() } val Result.exists: Result get() = flatMap { it.exists() } -fun OsPath.createDirectories(): Result { - val path = toNativePath().getOrElse { return Result.failure(it) } - val createPath = Result.runCatching { path.createDirectories().pathString }.getOrElse { return Result.failure(it) } - - return OsPath.of(nativeType, createPath) +fun OsPath.createDirectories(): Result = Result.runCatching { + return OsPath(nativeType, toNativePath().getOrThrow().createDirectories().pathString) } fun Result.createDirectories(): Result = flatMap { it.createDirectories() } -fun OsPath.copyTo(target: OsPath, overwrite: Boolean = false): Result { - val sourcePath = toNativePath().getOrElse { return Result.failure(it) } - val targetPath = target.toNativePath().getOrElse { return Result.failure(it) } - val copyPath = Result.runCatching { sourcePath.copyTo(targetPath, overwrite).pathString } - .getOrElse { return Result.failure(it) } - - return OsPath.of(nativeType, copyPath) +fun OsPath.copyTo(target: OsPath, overwrite: Boolean = false): Result = Result.run { + return OsPath( + nativeType, + toNativePath().getOrThrow().copyTo(target.toNativePath().getOrThrow(), overwrite).pathString + ) } fun Result.copyTo(target: OsPath, overwrite: Boolean = false): Result = flatMap { it.copyTo(target, overwrite) } -fun OsPath.writeText(text: CharSequence, charset: Charset = Charsets.UTF_8, vararg options: OpenOption): Result { - val path = toNativePath().getOrElse { return Result.failure(it) } - return Result.runCatching { path.writeText(text, charset, *options) } -} +fun OsPath.writeText(text: CharSequence, charset: Charset = Charsets.UTF_8, vararg options: OpenOption): Result = + runCatching { + toNativePath().getOrThrow().writeText(text, charset, *options) + } -fun OsPath.readText(charset: Charset = Charsets.UTF_8): Result { - val path = toNativePath().getOrElse { return Result.failure(it) } - return Result.runCatching { path.readText(charset) } +fun OsPath.readText(charset: Charset = Charsets.UTF_8): Result = runCatching { + toNativePath().getOrThrow().readText(charset) } + fun Result.readText(charset: Charset = Charsets.UTF_8): Result = flatMap { it.readText(charset) } @@ -194,7 +193,7 @@ operator fun OsPath.div(path: String): Result = resolve(path) operator fun Result.div(path: String): Result = flatMap { it.div(path) } -fun OsPath.resolve(vararg pathParts: String): Result = resolve(OsPath.of(osType, *pathParts).getOrThrow()) +fun OsPath.resolve(vararg pathParts: String): Result = resolve(OsPath(osType, *pathParts).getOrThrow()) fun Result.resolve(vararg pathParts: String): Result = flatMap { it.resolve(*pathParts) } fun OsPath.resolve(osPath: OsPath): Result { @@ -212,8 +211,9 @@ fun OsPath.resolve(osPath: OsPath): Result { } val normalizedPath = OsPath.normalize(root, newPathParts).getOrElse { return Result.failure(it) } - return Result.success(OsPath(osType, root, normalizedPath)) + return OsPath(osType, root, normalizedPath) } + fun Result.resolve(osPath: OsPath): Result = flatMap { it.resolve(osPath) } fun Result.resolve(osPath: Result): Result { val currentPath = this.getOrElse { return Result.failure(it) } @@ -223,8 +223,6 @@ fun Result.resolve(osPath: Result): Result { // OsPath accessors -val OsPath.leaf - get() = if (pathParts.isEmpty()) "" else pathParts.last() //val OsPath.rootOsPath // get() = OsPath.createOrThrow(osType, root) diff --git a/src/test/kotlin/io/github/kscripting/os/model/GenericOsPathTest.kt b/src/test/kotlin/io/github/kscripting/os/model/GenericOsPathTest.kt new file mode 100644 index 0000000..44fb5b0 --- /dev/null +++ b/src/test/kotlin/io/github/kscripting/os/model/GenericOsPathTest.kt @@ -0,0 +1,41 @@ +package io.github.kscripting.os.model + +import assertk.assertThat +import assertk.assertions.isEqualTo +import assertk.assertions.isTrue +import io.github.kscripting.os.instance.LinuxOs +import net.igsoft.typeutils.globalcontext.GlobalContext +import org.junit.jupiter.api.BeforeAll +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.TestInstance +import kotlin.Result.Companion.success + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class GenericOsPathTest { + private lateinit var emptyPath: OsPath + private lateinit var simplePath: OsPath + + @BeforeAll + fun beforeAll() { + GlobalContext.registerOrReplace(OsType.LINUX, LinuxOs("/home/admin")) + emptyPath = OsPath(OsType.LINUX, "", emptyList()).getOrThrow() + simplePath = OsPath(OsType.LINUX, "", listOf("home", "admin")).getOrThrow() + } + + @Test + fun `Test Empty paths`() { + assertThat(emptyPath.isEmpty).isTrue() + + assertThat(emptyPath.resolve(emptyPath)).isEqualTo(success(emptyPath)) + assertThat(emptyPath / emptyPath).isEqualTo(success(emptyPath)) + + assertThat(simplePath / emptyPath).isEqualTo(success(simplePath)) + assertThat(emptyPath / simplePath).isEqualTo(success(simplePath)) + + assertThat(emptyPath.toString()).isEqualTo("[LINUX]") + assertThat(simplePath.toString()).isEqualTo("home/admin [LINUX]") + + assertThat(emptyPath.leaf).isEqualTo("") + assertThat(simplePath.leaf).isEqualTo("admin") + } +} diff --git a/src/test/kotlin/io/github/kscripting/os/model/HostedOsPathTest.kt b/src/test/kotlin/io/github/kscripting/os/model/HostedOsPathTest.kt index a357a3f..c89ed01 100644 --- a/src/test/kotlin/io/github/kscripting/os/model/HostedOsPathTest.kt +++ b/src/test/kotlin/io/github/kscripting/os/model/HostedOsPathTest.kt @@ -23,61 +23,61 @@ class HostedOsPathTest { @Test fun `Test Cygwin to Windows`() { - assertThat(OsPath.of(OsType.CYGWIN, "/cygdrive/c/home/admin/.kscript").toNative().path.getOrThrow()) + assertThat(OsPath(OsType.CYGWIN, "/cygdrive/c/home/admin/.kscript").toNative().path.getOrThrow()) .isEqualTo("c:\\home\\admin\\.kscript") - assertThat(OsPath.of(OsType.CYGWIN, "~/.kscript").toNative().path.getOrThrow()) + assertThat(OsPath(OsType.CYGWIN, "~/.kscript").toNative().path.getOrThrow()) .isEqualTo("C:\\Programs\\Cygwin\\home\\admin\\.kscript") - assertThat(OsPath.of(OsType.CYGWIN, "/usr/local/bin/sdk").toNative().path.getOrThrow()) + assertThat(OsPath(OsType.CYGWIN, "/usr/local/bin/sdk").toNative().path.getOrThrow()) .isEqualTo("C:\\Programs\\Cygwin\\usr\\local\\bin\\sdk") - assertThat(OsPath.of(OsType.CYGWIN, "../home/admin/.kscript").toNative().path.getOrThrow()) + assertThat(OsPath(OsType.CYGWIN, "../home/admin/.kscript").toNative().path.getOrThrow()) .isEqualTo("..\\home\\admin\\.kscript") } @Test fun `Test Windows to Cygwin`() { - assertThat(OsPath.of(OsType.WINDOWS, "C:\\home\\admin\\.kscript").toHosted(OsType.CYGWIN).path.getOrThrow()) + assertThat(OsPath(OsType.WINDOWS, "C:\\home\\admin\\.kscript").toHosted(OsType.CYGWIN).path.getOrThrow()) .isEqualTo("/cygdrive/c/home/admin/.kscript") - assertThat(OsPath.of(OsType.WINDOWS, "..\\home\\admin\\.kscript").toHosted(OsType.CYGWIN).path.getOrThrow()) + assertThat(OsPath(OsType.WINDOWS, "..\\home\\admin\\.kscript").toHosted(OsType.CYGWIN).path.getOrThrow()) .isEqualTo("../home/admin/.kscript") - assertThat(OsPath.of(OsType.WINDOWS, "C:\\Programs\\Cygwin\\home\\admin\\.kscript").toHosted(OsType.CYGWIN).path.getOrThrow()) + assertThat(OsPath(OsType.WINDOWS, "C:\\Programs\\Cygwin\\home\\admin\\.kscript").toHosted(OsType.CYGWIN).path.getOrThrow()) .isEqualTo("~/.kscript") - assertThat(OsPath.of(OsType.WINDOWS, "C:\\Programs\\Cygwin\\usr\\local\\sdk").toHosted(OsType.CYGWIN).path.getOrThrow()) + assertThat(OsPath(OsType.WINDOWS, "C:\\Programs\\Cygwin\\usr\\local\\sdk").toHosted(OsType.CYGWIN).path.getOrThrow()) .isEqualTo("/usr/local/sdk") } @Test fun `Test MSys to Windows`() { - assertThat(OsPath.of(OsType.MSYS, "/c/home/admin/.kscript").toNative().path.getOrThrow()) + assertThat(OsPath(OsType.MSYS, "/c/home/admin/.kscript").toNative().path.getOrThrow()) .isEqualTo("c:\\home\\admin\\.kscript") - assertThat(OsPath.of(OsType.MSYS, "~/.kscript").toNative().path.getOrThrow()) + assertThat(OsPath(OsType.MSYS, "~/.kscript").toNative().path.getOrThrow()) .isEqualTo("C:\\Programs\\Msys\\home\\admin\\.kscript") - assertThat(OsPath.of(OsType.MSYS, "/usr/local/bin/sdk").toNative().path.getOrThrow()) + assertThat(OsPath(OsType.MSYS, "/usr/local/bin/sdk").toNative().path.getOrThrow()) .isEqualTo("C:\\Programs\\Msys\\usr\\local\\bin\\sdk") - assertThat(OsPath.of(OsType.MSYS, "../home/admin/.kscript").toNative().path.getOrThrow()) + assertThat(OsPath(OsType.MSYS, "../home/admin/.kscript").toNative().path.getOrThrow()) .isEqualTo("..\\home\\admin\\.kscript") } @Test fun `Test Windows to MSys`() { - assertThat(OsPath.of(OsType.WINDOWS, "C:\\home\\admin\\.kscript").toHosted(OsType.MSYS).path.getOrThrow()) + assertThat(OsPath(OsType.WINDOWS, "C:\\home\\admin\\.kscript").toHosted(OsType.MSYS).path.getOrThrow()) .isEqualTo("/c/home/admin/.kscript") - assertThat(OsPath.of(OsType.WINDOWS, "..\\home\\admin\\.kscript").toHosted(OsType.MSYS).path.getOrThrow()) + assertThat(OsPath(OsType.WINDOWS, "..\\home\\admin\\.kscript").toHosted(OsType.MSYS).path.getOrThrow()) .isEqualTo("../home/admin/.kscript") - assertThat(OsPath.of(OsType.WINDOWS, "C:\\Programs\\Msys\\home\\admin\\.kscript").toHosted(OsType.MSYS).path.getOrThrow()) + assertThat(OsPath(OsType.WINDOWS, "C:\\Programs\\Msys\\home\\admin\\.kscript").toHosted(OsType.MSYS).path.getOrThrow()) .isEqualTo("~/.kscript") - assertThat(OsPath.of(OsType.WINDOWS, "C:\\Programs\\Msys\\usr\\local\\sdk").toHosted(OsType.MSYS).path.getOrThrow()) + assertThat(OsPath(OsType.WINDOWS, "C:\\Programs\\Msys\\usr\\local\\sdk").toHosted(OsType.MSYS).path.getOrThrow()) .isEqualTo("/usr/local/sdk") } } diff --git a/src/test/kotlin/io/github/kscripting/os/model/PosixOsPathTest.kt b/src/test/kotlin/io/github/kscripting/os/model/PosixOsPathTest.kt index 1a8d7f6..44db66c 100644 --- a/src/test/kotlin/io/github/kscripting/os/model/PosixOsPathTest.kt +++ b/src/test/kotlin/io/github/kscripting/os/model/PosixOsPathTest.kt @@ -19,63 +19,63 @@ class PosixOsPathTest { @Test fun `Test Posix paths`() { - assertThat(OsPath.of(OsType.LINUX, "/").getOrThrow()).let { + assertThat(OsPath(OsType.LINUX, "/").getOrThrow()).let { it.prop(OsPath::osType).isEqualTo(OsType.LINUX) it.prop(OsPath::root).isEqualTo("/") it.prop(OsPath::pathParts).isEqualTo(emptyList()) } - assertThat(OsPath.of(OsType.LINUX, "/home/admin/.kscript").getOrThrow()).let { + assertThat(OsPath(OsType.LINUX, "/home/admin/.kscript").getOrThrow()).let { it.prop(OsPath::osType).isEqualTo(OsType.LINUX) it.prop(OsPath::root).isEqualTo("/") it.prop(OsPath::pathParts).isEqualTo(listOf("home", "admin", ".kscript")) } - assertThat(OsPath.of(OsType.LINUX, "./home/admin/.kscript").getOrThrow()).let { + assertThat(OsPath(OsType.LINUX, "./home/admin/.kscript").getOrThrow()).let { it.prop(OsPath::osType).isEqualTo(OsType.LINUX) it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(listOf("home", "admin", ".kscript")) } - assertThat(OsPath.of(OsType.LINUX, "").getOrThrow()).let { + assertThat(OsPath(OsType.LINUX, "").getOrThrow()).let { it.prop(OsPath::osType).isEqualTo(OsType.LINUX) it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(emptyList()) } - assertThat(OsPath.of(OsType.LINUX, "file.txt").getOrThrow()).let { + assertThat(OsPath(OsType.LINUX, "file.txt").getOrThrow()).let { it.prop(OsPath::osType).isEqualTo(OsType.LINUX) it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(listOf("file.txt")) } - assertThat(OsPath.of(OsType.LINUX, ".").getOrThrow()).let { + assertThat(OsPath(OsType.LINUX, ".").getOrThrow()).let { it.prop(OsPath::osType).isEqualTo(OsType.LINUX) it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(emptyList()) } - assertThat(OsPath.of(OsType.LINUX, "../home/admin/.kscript").getOrThrow()).let { + assertThat(OsPath(OsType.LINUX, "../home/admin/.kscript").getOrThrow()).let { it.prop(OsPath::osType).isEqualTo(OsType.LINUX) it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(listOf("..", "home", "admin", ".kscript")) } - assertThat(OsPath.of(OsType.LINUX, "..").getOrThrow()).let { + assertThat(OsPath(OsType.LINUX, "..").getOrThrow()).let { it.prop(OsPath::osType).isEqualTo(OsType.LINUX) it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(listOf("..")) } //Duplicated separators are accepted - assertThat(OsPath.of(OsType.LINUX, "..//home////admin/.kscript/").getOrThrow()).let { + assertThat(OsPath(OsType.LINUX, "..//home////admin/.kscript/").getOrThrow()).let { it.prop(OsPath::osType).isEqualTo(OsType.LINUX) it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(listOf("..", "home", "admin", ".kscript")) } //Both types of separator are accepted - assertThat(OsPath.of(OsType.LINUX, "..//home\\admin\\.kscript/").getOrThrow()).let { + assertThat(OsPath(OsType.LINUX, "..//home\\admin\\.kscript/").getOrThrow()).let { it.prop(OsPath::osType).isEqualTo(OsType.LINUX) it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(listOf("..", "home", "admin", ".kscript")) @@ -84,25 +84,25 @@ class PosixOsPathTest { @Test fun `Normalization of Posix paths`() { - assertThat(OsPath.of(OsType.LINUX, "/home/admin/.kscript/../../").getOrThrow()).let { + assertThat(OsPath(OsType.LINUX, "/home/admin/.kscript/../../").getOrThrow()).let { it.prop(OsPath::osType).isEqualTo(OsType.LINUX) it.prop(OsPath::root).isEqualTo("/") it.prop(OsPath::pathParts).isEqualTo(listOf("home")) } - assertThat(OsPath.of(OsType.LINUX, "./././../../script").getOrThrow()).let { + assertThat(OsPath(OsType.LINUX, "./././../../script").getOrThrow()).let { it.prop(OsPath::osType).isEqualTo(OsType.LINUX) it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(listOf("..", "..", "script")) } - assertThat(OsPath.of(OsType.LINUX, "/a/b/c/../d/script").getOrThrow()).let { + assertThat(OsPath(OsType.LINUX, "/a/b/c/../d/script").getOrThrow()).let { it.prop(OsPath::osType).isEqualTo(OsType.LINUX) it.prop(OsPath::root).isEqualTo("/") it.prop(OsPath::pathParts).isEqualTo(listOf("a", "b", "d", "script")) } - OsPath.of(OsType.LINUX, "/.kscript/../../").let { + OsPath(OsType.LINUX, "/.kscript/../../").let { assertThat(it.isFailure).isTrue() assertThat(it.exceptionOrNull()).isNotNull().isInstanceOf(IllegalArgumentException::class.java) .hasMessage("Path after normalization goes beyond root element: '/'") @@ -111,51 +111,51 @@ class PosixOsPathTest { @Test fun `Test invalid Posix paths`() { - assertFailure { OsPath.of(OsType.LINUX, "/ad*asdf").getOrThrow() } + assertFailure { OsPath(OsType.LINUX, "/ad*asdf").getOrThrow() } .isInstanceOf(IllegalArgumentException::class.java).hasMessage("Invalid character '*' in path '/ad*asdf'") } @Test fun `Test Posix stringPath`() { - assertThat(OsPath.of(OsType.LINUX, "/home/admin/.kscript").path.getOrThrow()).isEqualTo("/home/admin/.kscript") - assertThat(OsPath.of(OsType.LINUX, "/a/b/c/../d/script").path.getOrThrow()).isEqualTo("/a/b/d/script") - assertThat(OsPath.of(OsType.LINUX, "./././../../script").path.getOrThrow()).isEqualTo("../../script") - assertThat(OsPath.of(OsType.LINUX, "script/file.txt").path.getOrThrow()).isEqualTo("script/file.txt") + assertThat(OsPath(OsType.LINUX, "/home/admin/.kscript").path.getOrThrow()).isEqualTo("/home/admin/.kscript") + assertThat(OsPath(OsType.LINUX, "/a/b/c/../d/script").path.getOrThrow()).isEqualTo("/a/b/d/script") + assertThat(OsPath(OsType.LINUX, "./././../../script").path.getOrThrow()).isEqualTo("../../script") + assertThat(OsPath(OsType.LINUX, "script/file.txt").path.getOrThrow()).isEqualTo("script/file.txt") } @Test fun `Test Posix resolve`() { assertThat( - OsPath.of(OsType.LINUX, "/").resolve(OsPath.of(OsType.LINUX, "./.kscript/")).path.getOrThrow() + OsPath(OsType.LINUX, "/").resolve(OsPath(OsType.LINUX, "./.kscript/")).path.getOrThrow() ).isEqualTo("/.kscript") assertThat( - OsPath.of(OsType.LINUX, "/home/admin/").resolve(OsPath.of(OsType.LINUX, "./.kscript/")).path.getOrThrow() + OsPath(OsType.LINUX, "/home/admin/").resolve(OsPath(OsType.LINUX, "./.kscript/")).path.getOrThrow() ).isEqualTo("/home/admin/.kscript") assertThat( - OsPath.of(OsType.LINUX, "./home/admin/").resolve(OsPath.of(OsType.LINUX, "./.kscript/")).path.getOrThrow() + OsPath(OsType.LINUX, "./home/admin/").resolve(OsPath(OsType.LINUX, "./.kscript/")).path.getOrThrow() ).isEqualTo("home/admin/.kscript") assertThat( - OsPath.of(OsType.LINUX, "../home/admin/").resolve(OsPath.of(OsType.LINUX, "./.kscript/")).path.getOrThrow() + OsPath(OsType.LINUX, "../home/admin/").resolve(OsPath(OsType.LINUX, "./.kscript/")).path.getOrThrow() ).isEqualTo("../home/admin/.kscript") assertThat( - OsPath.of(OsType.LINUX, "..").resolve(OsPath.of(OsType.LINUX, "./.kscript/")).path.getOrThrow() + OsPath(OsType.LINUX, "..").resolve(OsPath(OsType.LINUX, "./.kscript/")).path.getOrThrow() ).isEqualTo("../.kscript") assertThat( - OsPath.of(OsType.LINUX, ".").resolve(OsPath.of(OsType.LINUX, "./.kscript/")).path.getOrThrow() + OsPath(OsType.LINUX, ".").resolve(OsPath(OsType.LINUX, "./.kscript/")).path.getOrThrow() ).isEqualTo(".kscript") assertFailure { - OsPath.of(OsType.LINUX, "./home/admin").resolve(OsPath.of(OsType.WINDOWS, ".\\run")).getOrThrow() + OsPath(OsType.LINUX, "./home/admin").resolve(OsPath(OsType.WINDOWS, ".\\run")).getOrThrow() }.isInstanceOf(IllegalArgumentException::class.java) .hasMessage("Paths from different OS's: 'LINUX' path can not be resolved with 'WINDOWS' path") assertFailure { - OsPath.of(OsType.LINUX, "./home/admin").resolve(OsPath.of(OsType.LINUX, "/run")).getOrThrow() + OsPath(OsType.LINUX, "./home/admin").resolve(OsPath(OsType.LINUX, "/run")).getOrThrow() }.isInstanceOf(IllegalArgumentException::class.java) .hasMessage("Can not resolve absolute or relative path 'home/admin' using absolute path '/run'") } diff --git a/src/test/kotlin/io/github/kscripting/os/model/WindowsOsPathTest.kt b/src/test/kotlin/io/github/kscripting/os/model/WindowsOsPathTest.kt index db336d0..f937d75 100644 --- a/src/test/kotlin/io/github/kscripting/os/model/WindowsOsPathTest.kt +++ b/src/test/kotlin/io/github/kscripting/os/model/WindowsOsPathTest.kt @@ -20,63 +20,63 @@ class WindowsOsPathTest { @Test fun `Test Windows paths`() { - assertThat(OsPath.of(OsType.WINDOWS, "C:\\").getOrThrow()).let { + assertThat(OsPath(OsType.WINDOWS, "C:\\").getOrThrow()).let { it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) it.prop(OsPath::root).isEqualTo("C:\\") it.prop(OsPath::pathParts).isEqualTo(emptyList()) } - assertThat(OsPath.of(OsType.WINDOWS, "C:\\home\\admin\\.kscript").getOrThrow()).let { + assertThat(OsPath(OsType.WINDOWS, "C:\\home\\admin\\.kscript").getOrThrow()).let { it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) it.prop(OsPath::root).isEqualTo("C:\\") it.prop(OsPath::pathParts).isEqualTo(listOf("home", "admin", ".kscript")) } - assertThat(OsPath.of(OsType.WINDOWS, "").getOrThrow()).let { + assertThat(OsPath(OsType.WINDOWS, "").getOrThrow()).let { it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(emptyList()) } - assertThat(OsPath.of(OsType.WINDOWS, "file.txt").getOrThrow()).let { + assertThat(OsPath(OsType.WINDOWS, "file.txt").getOrThrow()).let { it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(listOf("file.txt")) } - assertThat(OsPath.of(OsType.WINDOWS, ".").getOrThrow()).let { + assertThat(OsPath(OsType.WINDOWS, ".").getOrThrow()).let { it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(emptyList()) } - assertThat(OsPath.of(OsType.WINDOWS, ".\\home\\admin\\.kscript").getOrThrow()).let { + assertThat(OsPath(OsType.WINDOWS, ".\\home\\admin\\.kscript").getOrThrow()).let { it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(listOf("home", "admin", ".kscript")) } - assertThat(OsPath.of(OsType.WINDOWS, "..\\home\\admin\\.kscript").getOrThrow()).let { + assertThat(OsPath(OsType.WINDOWS, "..\\home\\admin\\.kscript").getOrThrow()).let { it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(listOf("..", "home", "admin", ".kscript")) } - assertThat(OsPath.of(OsType.WINDOWS, "..").getOrThrow()).let { + assertThat(OsPath(OsType.WINDOWS, "..").getOrThrow()).let { it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(listOf("..")) } //Duplicated separators are accepted - assertThat(OsPath.of(OsType.WINDOWS, "C:\\home\\\\\\\\admin\\.kscript\\").getOrThrow()).let { + assertThat(OsPath(OsType.WINDOWS, "C:\\home\\\\\\\\admin\\.kscript\\").getOrThrow()).let { it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) it.prop(OsPath::root).isEqualTo("C:\\") it.prop(OsPath::pathParts).isEqualTo(listOf("home", "admin", ".kscript")) } //Both types of separator are accepted - assertThat(OsPath.of(OsType.WINDOWS, "C:/home\\admin/.kscript////").getOrThrow()).let { + assertThat(OsPath(OsType.WINDOWS, "C:/home\\admin/.kscript////").getOrThrow()).let { it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) it.prop(OsPath::root).isEqualTo("C:\\") it.prop(OsPath::pathParts).isEqualTo(listOf("home", "admin", ".kscript")) @@ -85,25 +85,25 @@ class WindowsOsPathTest { @Test fun `Normalization of Windows paths`() { - assertThat(OsPath.of(OsType.WINDOWS, "C:\\home\\admin\\.kscript\\..\\..\\").getOrThrow()).let { + assertThat(OsPath(OsType.WINDOWS, "C:\\home\\admin\\.kscript\\..\\..\\").getOrThrow()).let { it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) it.prop(OsPath::root).isEqualTo("C:\\") it.prop(OsPath::pathParts).isEqualTo(listOf("home")) } - assertThat(OsPath.of(OsType.WINDOWS, ".\\.\\.\\..\\..\\script").getOrThrow()).let { + assertThat(OsPath(OsType.WINDOWS, ".\\.\\.\\..\\..\\script").getOrThrow()).let { it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(listOf("..", "..", "script")) } - assertThat(OsPath.of(OsType.WINDOWS, "C:\\a\\b\\c\\..\\d\\script").getOrThrow()).let { + assertThat(OsPath(OsType.WINDOWS, "C:\\a\\b\\c\\..\\d\\script").getOrThrow()).let { it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) it.prop(OsPath::root).isEqualTo("C:\\") it.prop(OsPath::pathParts).isEqualTo(listOf("a", "b", "d", "script")) } - OsPath.of(OsType.WINDOWS, "C:\\.kscript\\..\\..\\").let { + OsPath(OsType.WINDOWS, "C:\\.kscript\\..\\..\\").let { assertThat(it.isFailure).isTrue() assertThat(it.exceptionOrNull()).isNotNull().isInstanceOf(IllegalArgumentException::class.java) .hasMessage("Path after normalization goes beyond root element: 'C:\\'") @@ -112,20 +112,20 @@ class WindowsOsPathTest { @Test fun `Test invalid Windows paths`() { - assertFailure { OsPath.of(OsType.WINDOWS, "C:\\adas?df").getOrThrow() } + assertFailure { OsPath(OsType.WINDOWS, "C:\\adas?df").getOrThrow() } .isInstanceOf(IllegalArgumentException::class.java) .hasMessage("Invalid character '?' in path 'C:\\adas?df'") - assertFailure { OsPath.of(OsType.WINDOWS, "home:\\vagrant").getOrThrow() } + assertFailure { OsPath(OsType.WINDOWS, "home:\\vagrant").getOrThrow() } .isInstanceOf(IllegalArgumentException::class.java) .hasMessage("Invalid character ':' in path 'home:\\vagrant'") } @Test fun `Test Windows stringPath`() { - assertThat(OsPath.of(OsType.WINDOWS, "C:\\home\\admin\\.kscript").path.getOrThrow()).isEqualTo("C:\\home\\admin\\.kscript") - assertThat(OsPath.of(OsType.WINDOWS, "c:\\a\\b\\c\\..\\d\\script").path.getOrThrow()).isEqualTo("c:\\a\\b\\d\\script") - assertThat(OsPath.of(OsType.WINDOWS, ".\\.\\.\\..\\..\\script").path.getOrThrow()).isEqualTo("..\\..\\script") - assertThat(OsPath.of(OsType.WINDOWS, "script\\file.txt").path.getOrThrow()).isEqualTo("script\\file.txt") + assertThat(OsPath(OsType.WINDOWS, "C:\\home\\admin\\.kscript").path.getOrThrow()).isEqualTo("C:\\home\\admin\\.kscript") + assertThat(OsPath(OsType.WINDOWS, "c:\\a\\b\\c\\..\\d\\script").path.getOrThrow()).isEqualTo("c:\\a\\b\\d\\script") + assertThat(OsPath(OsType.WINDOWS, ".\\.\\.\\..\\..\\script").path.getOrThrow()).isEqualTo("..\\..\\script") + assertThat(OsPath(OsType.WINDOWS, "script\\file.txt").path.getOrThrow()).isEqualTo("script\\file.txt") } } From 55e3487469007d564fdb4569ab5b1c707ca2290a Mon Sep 17 00:00:00 2001 From: Marcin Kuszczak <1508798+aartiPl@users.noreply.github.com> Date: Wed, 1 Nov 2023 12:26:00 +0100 Subject: [PATCH 41/50] Removed Result<> --- .../shell/integration/ShellExecutorTest.kt | 14 ++- .../shell/integration/tools/ShellTestBase.kt | 3 - .../shell/integration/tools/TestContext.kt | 20 ++- .../github/kscripting/os/instance/CygwinOs.kt | 4 +- .../kscripting/os/instance/FreeBsdOs.kt | 2 +- .../github/kscripting/os/instance/LinuxOs.kt | 2 +- .../io/github/kscripting/os/instance/MacOs.kt | 2 +- .../github/kscripting/os/instance/MsysOs.kt | 4 +- .../kscripting/os/instance/WindowsOs.kt | 2 +- .../io/github/kscripting/os/model/OsPath.kt | 33 ++--- .../github/kscripting/os/model/OsPathExt.kt | 92 ++++---------- .../kscripting/shell/process/ProcessRunner.kt | 4 +- .../kscripting/os/model/GenericOsPathTest.kt | 26 +--- .../kscripting/os/model/HostedOsPathTest.kt | 114 +++++++++++------- .../kscripting/os/model/PosixOsPathTest.kt | 73 ++++++----- .../kscripting/os/model/WindowsOsPathTest.kt | 67 +++++----- 16 files changed, 238 insertions(+), 224 deletions(-) diff --git a/src/itest/kotlin/io/github/kscripting/shell/integration/ShellExecutorTest.kt b/src/itest/kotlin/io/github/kscripting/shell/integration/ShellExecutorTest.kt index cf23e36..07f8da9 100644 --- a/src/itest/kotlin/io/github/kscripting/shell/integration/ShellExecutorTest.kt +++ b/src/itest/kotlin/io/github/kscripting/shell/integration/ShellExecutorTest.kt @@ -1,5 +1,7 @@ package io.github.kscripting.shell.integration +import io.github.kscripting.os.instance.WindowsOs +import io.github.kscripting.os.model.OsType import io.github.kscripting.os.model.readText import io.github.kscripting.os.model.resolve import io.github.kscripting.shell.integration.tools.ShellTestBase @@ -7,10 +9,20 @@ import io.github.kscripting.shell.integration.tools.TestContext import io.github.kscripting.shell.integration.tools.TestContext.execPath import io.github.kscripting.shell.integration.tools.TestContext.testPath import io.github.kscripting.shell.util.Sanitizer +import net.igsoft.typeutils.globalcontext.GlobalContext +import org.junit.jupiter.api.BeforeAll import org.junit.jupiter.api.Tag import org.junit.jupiter.api.Test +import org.junit.jupiter.api.TestInstance +@TestInstance(TestInstance.Lifecycle.PER_CLASS) class ShellExecutorTest : ShellTestBase { + + @BeforeAll + fun setup() { + + } + @Test @Tag("posix") @Tag("windows") @@ -27,7 +39,7 @@ class ShellExecutorTest : ShellTestBase { verify( "doEcho -f $path", 0, - path.readText().getOrThrow(), + path.readText(), "", inputSanitizer = Sanitizer.EMPTY_SANITIZER, outputSanitizer = Sanitizer.EMPTY_SANITIZER diff --git a/src/itest/kotlin/io/github/kscripting/shell/integration/tools/ShellTestBase.kt b/src/itest/kotlin/io/github/kscripting/shell/integration/tools/ShellTestBase.kt index b6bf57b..d1330bf 100644 --- a/src/itest/kotlin/io/github/kscripting/shell/integration/tools/ShellTestBase.kt +++ b/src/itest/kotlin/io/github/kscripting/shell/integration/tools/ShellTestBase.kt @@ -1,8 +1,5 @@ package io.github.kscripting.shell.integration.tools -import io.github.kscripting.shell.ShellExecutor -import io.github.kscripting.shell.integration.tools.TestContext.defaultInputSanitizer -import io.github.kscripting.shell.integration.tools.TestContext.defaultOutputSanitizer import io.github.kscripting.shell.model.ProcessResult import io.github.kscripting.shell.process.EnvAdjuster import io.github.kscripting.shell.util.Sanitizer diff --git a/src/itest/kotlin/io/github/kscripting/shell/integration/tools/TestContext.kt b/src/itest/kotlin/io/github/kscripting/shell/integration/tools/TestContext.kt index 9bf75a8..e797618 100644 --- a/src/itest/kotlin/io/github/kscripting/shell/integration/tools/TestContext.kt +++ b/src/itest/kotlin/io/github/kscripting/shell/integration/tools/TestContext.kt @@ -1,22 +1,30 @@ package io.github.kscripting.shell.integration.tools import io.github.kscripting.os.Os +import io.github.kscripting.os.instance.LinuxOs +import io.github.kscripting.os.instance.WindowsOs import io.github.kscripting.os.model.* import io.github.kscripting.shell.ShellExecutor import io.github.kscripting.shell.util.Sanitizer +import net.igsoft.typeutils.globalcontext.GlobalContext @Suppress("MemberVisibilityCanBePrivate") object TestContext { + init { + GlobalContext.registerOrReplace(OsType.WINDOWS, WindowsOs("/home/admin")) + } + val osType: OsType = OsType.find(System.getProperty("osType")) ?: OsType.native - val projectPath: OsPath = OsPath.of(OsType.native, System.getProperty("projectPath")).toHosted(osType).getOrThrow() - val execPath: OsPath = projectPath.resolve("build/shell_test/bin").getOrThrow() - val testPath: OsPath = projectPath.resolve("build/shell_test/tmp").getOrThrow() + val projectPath: OsPath = OsPath(OsType.native, System.getProperty("projectPath")).toHosted(osType) + val execPath: OsPath = projectPath.resolve("build/shell_test/bin") + val testPath: OsPath = projectPath.resolve("build/shell_test/tmp") val pathEnvVariableName = if (osType.isWindowsLike()) "Path" else "PATH" val pathEnvVariableValue: String = System.getenv()[pathEnvVariableName]!! val pathEnvVariableSeparator: String = if (osType.isWindowsLike() || osType.isPosixHostedOnWindows()) ";" else ":" - val pathEnvVariableCalculatedPath: String = "${execPath.toHosted(osType).path}$pathEnvVariableSeparator$pathEnvVariableValue" + val pathEnvVariableCalculatedPath: String = + "${execPath.toHosted(osType).path}$pathEnvVariableSeparator$pathEnvVariableValue" val nl: String = when { osType.isPosixHostedOnWindows() -> "\n" @@ -38,8 +46,8 @@ object TestContext { } fun copyFile(source: String, target: OsPath) { - val sourceFile = projectPath.resolve(source).toNativeFile().getOrThrow() - val targetFile = target.resolve(sourceFile.name).toNativeFile().getOrThrow() + val sourceFile = projectPath.resolve(source).toNativeFile() + val targetFile = target.resolve(sourceFile.name).toNativeFile() sourceFile.copyTo(targetFile, overwrite = true) if (target == execPath) { diff --git a/src/main/kotlin/io/github/kscripting/os/instance/CygwinOs.kt b/src/main/kotlin/io/github/kscripting/os/instance/CygwinOs.kt index 0f2f684..0e5c4f7 100644 --- a/src/main/kotlin/io/github/kscripting/os/instance/CygwinOs.kt +++ b/src/main/kotlin/io/github/kscripting/os/instance/CygwinOs.kt @@ -6,7 +6,7 @@ import io.github.kscripting.os.model.OsType class CygwinOs(userHome: String, nativeFileSystemRoot: String) : HostedOs { override val osTypePrefix: String = "cygwin" override val type: OsType = OsType.CYGWIN - override val userHome: OsPath = OsPath(type, userHome).getOrThrow() + override val userHome: OsPath = OsPath(type, userHome) override val nativeType: OsType = OsType.WINDOWS - override val nativeFileSystemRoot: OsPath = OsPath(nativeType, nativeFileSystemRoot).getOrThrow() + override val nativeFileSystemRoot: OsPath = OsPath(nativeType, nativeFileSystemRoot) } diff --git a/src/main/kotlin/io/github/kscripting/os/instance/FreeBsdOs.kt b/src/main/kotlin/io/github/kscripting/os/instance/FreeBsdOs.kt index 3f97491..2c883a6 100644 --- a/src/main/kotlin/io/github/kscripting/os/instance/FreeBsdOs.kt +++ b/src/main/kotlin/io/github/kscripting/os/instance/FreeBsdOs.kt @@ -8,5 +8,5 @@ class FreeBsdOs(userHome: String) : Os { override val osTypePrefix: String = "freebsd" override val type: OsType = OsType.FREEBSD override val pathSeparator: String get() = "/" - override val userHome: OsPath = OsPath(OsType.FREEBSD, userHome).getOrThrow() + override val userHome: OsPath = OsPath(OsType.FREEBSD, userHome) } diff --git a/src/main/kotlin/io/github/kscripting/os/instance/LinuxOs.kt b/src/main/kotlin/io/github/kscripting/os/instance/LinuxOs.kt index 7fb1df0..717176f 100644 --- a/src/main/kotlin/io/github/kscripting/os/instance/LinuxOs.kt +++ b/src/main/kotlin/io/github/kscripting/os/instance/LinuxOs.kt @@ -8,5 +8,5 @@ class LinuxOs(userHome: String) : Os { override val osTypePrefix: String = "linux" override val type: OsType = OsType.LINUX override val pathSeparator: String get() = "/" - override val userHome: OsPath = OsPath(OsType.LINUX, userHome).getOrThrow() + override val userHome: OsPath = OsPath(OsType.LINUX, userHome) } diff --git a/src/main/kotlin/io/github/kscripting/os/instance/MacOs.kt b/src/main/kotlin/io/github/kscripting/os/instance/MacOs.kt index 90ad66e..c4d88bf 100644 --- a/src/main/kotlin/io/github/kscripting/os/instance/MacOs.kt +++ b/src/main/kotlin/io/github/kscripting/os/instance/MacOs.kt @@ -8,5 +8,5 @@ class MacOs(userHome: String) : Os { override val osTypePrefix: String = "darwin" override val type: OsType = OsType.MACOS override val pathSeparator: String get() = "/" - override val userHome: OsPath = OsPath(OsType.MACOS, userHome).getOrThrow() + override val userHome: OsPath = OsPath(OsType.MACOS, userHome) } diff --git a/src/main/kotlin/io/github/kscripting/os/instance/MsysOs.kt b/src/main/kotlin/io/github/kscripting/os/instance/MsysOs.kt index ab97d8e..0333c23 100644 --- a/src/main/kotlin/io/github/kscripting/os/instance/MsysOs.kt +++ b/src/main/kotlin/io/github/kscripting/os/instance/MsysOs.kt @@ -6,7 +6,7 @@ import io.github.kscripting.os.model.OsType class MsysOs(userHome: String, nativeFileSystemRoot: String) : HostedOs { override val osTypePrefix: String = "msys" override val type: OsType = OsType.MSYS - override val userHome: OsPath = OsPath(type, userHome).getOrThrow() + override val userHome: OsPath = OsPath(type, userHome) override val nativeType: OsType = OsType.WINDOWS - override val nativeFileSystemRoot: OsPath = OsPath(nativeType, nativeFileSystemRoot).getOrThrow() + override val nativeFileSystemRoot: OsPath = OsPath(nativeType, nativeFileSystemRoot) } diff --git a/src/main/kotlin/io/github/kscripting/os/instance/WindowsOs.kt b/src/main/kotlin/io/github/kscripting/os/instance/WindowsOs.kt index 8e925ca..aa6045e 100644 --- a/src/main/kotlin/io/github/kscripting/os/instance/WindowsOs.kt +++ b/src/main/kotlin/io/github/kscripting/os/instance/WindowsOs.kt @@ -9,5 +9,5 @@ class WindowsOs(userHome: String) : Os { override val osTypePrefix: String = "windows" override val type: OsType = OsType.WINDOWS override val pathSeparator: String get() = "\\" - override val userHome: OsPath = OsPath(type, userHome).getOrThrow() + override val userHome: OsPath = OsPath(type, userHome) } diff --git a/src/main/kotlin/io/github/kscripting/os/model/OsPath.kt b/src/main/kotlin/io/github/kscripting/os/model/OsPath.kt index 9f720b6..1209bf1 100644 --- a/src/main/kotlin/io/github/kscripting/os/model/OsPath.kt +++ b/src/main/kotlin/io/github/kscripting/os/model/OsPath.kt @@ -10,12 +10,11 @@ sealed interface OsPathError { data class OsPath private constructor(val osType: OsType<*>, val root: String, val pathParts: List) { val isRelative: Boolean get() = root.isEmpty() && pathParts.isNotEmpty() val isAbsolute: Boolean get() = root.isNotEmpty() - val isEmpty: Boolean get() = root.isEmpty() && pathParts.isEmpty() val path: String get() = root + pathParts.joinToString(osType.value.pathSeparator) { it } val leaf: String get() = if (pathParts.isEmpty()) root else pathParts.last() - override fun toString(): String = if (isEmpty) "[$osType]" else "$path [$osType]" + override fun toString(): String = "$path [$osType]" companion object { //https://stackoverflow.com/questions/1976007/what-characters-are-forbidden-in-windows-and-linux-directory-names @@ -43,19 +42,24 @@ data class OsPath private constructor(val osType: OsType<*>, val root: String, v //1. It doesn't matter if there is '/' or '\' used as path separator - both are treated the same //2. Duplicated or trailing slashes '/' and backslashes '\' are just ignored - operator fun invoke(osType: OsType<*>, root: String, pathParts: List): Result { - return Result.success(OsPath(osType, root, pathParts)) + operator fun invoke(osType: OsType<*>, root: String, pathParts: List): OsPath { + //TODO: verify root - do not allow empty paths + if (root.isEmpty() && pathParts.isEmpty()) { + throw OsPathError.EmptyPath + } + + return OsPath(osType, root, pathParts) } - operator fun invoke(vararg pathParts: String): Result { + operator fun invoke(vararg pathParts: String): OsPath { return invoke(OsType.native, pathParts.toList()) } - operator fun invoke(osType: OsType<*>, vararg pathParts: String): Result { + operator fun invoke(osType: OsType<*>, vararg pathParts: String): OsPath { return invoke(osType, pathParts.toList()) } - operator fun invoke(osType: OsType<*>, pathParts: List): Result { + operator fun invoke(osType: OsType<*>, pathParts: List): OsPath { val path = pathParts.joinToString("/") //Detect root @@ -76,22 +80,19 @@ data class OsPath private constructor(val osType: OsType<*>, val root: String, v //Remove also empty path parts - there were duplicated or trailing slashes / backslashes in initial path val pathPartsResolved = path.drop(root.length).split('/', '\\').filter { it.isNotBlank() } - - val normalizedPath = normalize(root, pathPartsResolved).getOrElse { - return Result.failure(it) - } + val normalizedPath = normalize(root, pathPartsResolved) //Validate root element of path and find out if it is absolute or relative val forbiddenCharacter = path.substring(root.length).find { forbiddenCharacters.contains(it) } if (forbiddenCharacter != null) { - return Result.failure(IllegalArgumentException("Invalid character '$forbiddenCharacter' in path '$path'")) + throw IllegalArgumentException("Invalid character '$forbiddenCharacter' in path '$path'") } - return Result.success(OsPath(osType, root, normalizedPath)) + return OsPath(osType, root, normalizedPath) } - fun normalize(root: String, pathParts: List): Result> { + fun normalize(root: String, pathParts: List): List { //Relative: // ./../ --> ../ // ./a/../ --> ./ @@ -113,7 +114,7 @@ data class OsPath private constructor(val osType: OsType<*>, val root: String, v //Just skip . without adding it to newParts } else if (pathParts[index] == "..") { if (isAbsolute && newParts.size == 0) { - return Result.failure(IllegalArgumentException("Path after normalization goes beyond root element: '$root'")) + throw IllegalArgumentException("Path after normalization goes beyond root element: '$root'") } if (newParts.size > 0) { @@ -142,7 +143,7 @@ data class OsPath private constructor(val osType: OsType<*>, val root: String, v index += 1 } - return Result.success(newParts) + return newParts } } } diff --git a/src/main/kotlin/io/github/kscripting/os/model/OsPathExt.kt b/src/main/kotlin/io/github/kscripting/os/model/OsPathExt.kt index 84f855d..2f8bc82 100644 --- a/src/main/kotlin/io/github/kscripting/os/model/OsPathExt.kt +++ b/src/main/kotlin/io/github/kscripting/os/model/OsPathExt.kt @@ -15,10 +15,10 @@ fun Result

.flatMap(fn: (P) -> Result): Result = fold( onFailure = { Result.failure(it) } ) -fun OsPath.toNative(): Result { +fun OsPath.toNative(): OsPath { if (!osType.isPosixHostedOnWindows()) { //Everything besides Cygwin/Msys is native... - return Result.success(this) + return this } val hostedOs = osType.value as HostedOs @@ -67,17 +67,14 @@ fun OsPath.toNative(): Result { return OsPath(hostedOs.nativeType, newRoot, newParts) } -fun Result.toNative(): Result = flatMap { it.toNative() } - - fun List.startsWith(list: List): Boolean = (this.size >= list.size && this.subList(0, list.size) == list) fun OsPath.startsWith(osPath: OsPath): Boolean = root == osPath.root && pathParts.startsWith(osPath.pathParts) -fun OsPath.toHosted(targetOs: OsType<*>): Result = runCatching{ +fun OsPath.toHosted(targetOs: OsType<*>): OsPath { if (osType == targetOs) { //This is already targetOs... - return Result.success(this) + return this } if (!(targetOs.isPosixHostedOnWindows() && osType == ((targetOs.value) as HostedOs).nativeType)) { @@ -130,79 +127,51 @@ fun OsPath.toHosted(targetOs: OsType<*>): Result = runCatching{ return OsPath(targetOs, newRoot, newParts) } -fun Result.toHosted(targetOs: OsType<*>): Result = flatMap { it.toHosted(targetOs) } - - -val Result.path: Result get() = map { it.path } - // Conversion to OsPath -fun File.toOsPath(): Result = OsPath(OsType.native, absolutePath) +fun File.toOsPath(): OsPath = OsPath(OsType.native, absolutePath) -fun Path.toOsPath(): Result = OsPath(OsType.native, absolutePathString()) +fun Path.toOsPath(): OsPath = OsPath(OsType.native, absolutePathString()) -fun URI.toOsPath(): Result = - if (this.scheme == "file") File(this).toOsPath() else Result.failure(IllegalArgumentException("Invalid conversion from URL to OsPath")) +fun URI.toOsPath(): OsPath = + if (this.scheme == "file") File(this).toOsPath() else throw IllegalArgumentException("Invalid conversion from URL to OsPath") // Conversion from OsPath -fun OsPath.toNativePath(): Result = toNative().path.map { Paths.get(it) } +fun OsPath.toNativePath(): Path = Paths.get(toNative().path) -fun OsPath.toNativeFile(): Result = toNative().path.map { File(it) } -fun Result.toNativeFile(): Result = toNative().path.map { File(it) } +fun OsPath.toNativeFile(): File = File(toNative().path) -fun OsPath.toNativeUri(): Result = toNative().path.map { File(it).toURI() } +fun OsPath.toNativeUri(): URI = File(toNative().path).toURI() // OsPath operations -fun OsPath.exists(): Result = toNativePath().map { it.exists() } -val Result.exists: Result get() = flatMap { it.exists() } +fun OsPath.exists(): Boolean = toNativePath().exists() -fun OsPath.createDirectories(): Result = Result.runCatching { - return OsPath(nativeType, toNativePath().getOrThrow().createDirectories().pathString) -} +fun OsPath.createDirectories(): OsPath = OsPath(nativeType, toNativePath().createDirectories().pathString) -fun Result.createDirectories(): Result = flatMap { it.createDirectories() } +fun OsPath.copyTo(target: OsPath, overwrite: Boolean = false): OsPath = + OsPath(nativeType, toNativePath().copyTo(target.toNativePath(), overwrite).pathString) -fun OsPath.copyTo(target: OsPath, overwrite: Boolean = false): Result = Result.run { - return OsPath( - nativeType, - toNativePath().getOrThrow().copyTo(target.toNativePath().getOrThrow(), overwrite).pathString - ) -} +fun OsPath.writeText(text: CharSequence, charset: Charset = Charsets.UTF_8, vararg options: OpenOption): Unit = + toNativePath().writeText(text, charset, *options) -fun Result.copyTo(target: OsPath, overwrite: Boolean = false): Result = - flatMap { it.copyTo(target, overwrite) } +fun OsPath.readText(charset: Charset = Charsets.UTF_8): String = toNativePath().readText(charset) -fun OsPath.writeText(text: CharSequence, charset: Charset = Charsets.UTF_8, vararg options: OpenOption): Result = - runCatching { - toNativePath().getOrThrow().writeText(text, charset, *options) - } -fun OsPath.readText(charset: Charset = Charsets.UTF_8): Result = runCatching { - toNativePath().getOrThrow().readText(charset) -} +operator fun OsPath.div(osPath: OsPath): OsPath = resolve(osPath) -fun Result.readText(charset: Charset = Charsets.UTF_8): Result = flatMap { it.readText(charset) } +operator fun OsPath.div(path: String): OsPath = resolve(path) +fun OsPath.resolve(vararg pathParts: String): OsPath = resolve(OsPath(osType, *pathParts)) -operator fun OsPath.div(osPath: OsPath): Result = resolve(osPath) -operator fun Result.div(osPath: OsPath): Result = flatMap { it.div(osPath) } - -operator fun OsPath.div(path: String): Result = resolve(path) -operator fun Result.div(path: String): Result = flatMap { it.div(path) } - - -fun OsPath.resolve(vararg pathParts: String): Result = resolve(OsPath(osType, *pathParts).getOrThrow()) -fun Result.resolve(vararg pathParts: String): Result = flatMap { it.resolve(*pathParts) } - -fun OsPath.resolve(osPath: OsPath): Result { +fun OsPath.resolve(osPath: OsPath): OsPath { if (osType != osPath.osType) { - return Result.failure(IllegalArgumentException("Paths from different OS's: '${osType}' path can not be resolved with '${osPath.osType}' path")) + throw IllegalArgumentException("Paths from different OS's: '${osType}' path can not be resolved with '${osPath.osType}' path") } if (osPath.isAbsolute) { - return Result.failure(IllegalArgumentException("Can not resolve absolute or relative path '${path}' using absolute path '${osPath.path}'")) + throw IllegalArgumentException("Can not resolve absolute or relative path '${path}' using absolute path '${osPath.path}'") } val newPathParts = buildList { @@ -210,26 +179,15 @@ fun OsPath.resolve(osPath: OsPath): Result { addAll(osPath.pathParts) } - val normalizedPath = OsPath.normalize(root, newPathParts).getOrElse { return Result.failure(it) } + val normalizedPath = OsPath.normalize(root, newPathParts) return OsPath(osType, root, normalizedPath) } -fun Result.resolve(osPath: OsPath): Result = flatMap { it.resolve(osPath) } -fun Result.resolve(osPath: Result): Result { - val currentPath = this.getOrElse { return Result.failure(it) } - val newPath = osPath.getOrElse { return Result.failure(it) } - return currentPath.resolve(newPath) -} - - // OsPath accessors //val OsPath.rootOsPath // get() = OsPath.createOrThrow(osType, root) -//val OsPath.parent -// get() = toNativePath().parent - val OsPath.nativeType get() = if (osType.isPosixHostedOnWindows()) OsType.WINDOWS else osType diff --git a/src/main/kotlin/io/github/kscripting/shell/process/ProcessRunner.kt b/src/main/kotlin/io/github/kscripting/shell/process/ProcessRunner.kt index d9cdd22..977daa5 100644 --- a/src/main/kotlin/io/github/kscripting/shell/process/ProcessRunner.kt +++ b/src/main/kotlin/io/github/kscripting/shell/process/ProcessRunner.kt @@ -1,8 +1,8 @@ package io.github.kscripting.shell.process -import io.github.kscripting.shell.model.CommandTimeoutException import io.github.kscripting.os.model.OsPath import io.github.kscripting.os.model.toNativeFile +import io.github.kscripting.shell.model.CommandTimeoutException import io.github.kscripting.shell.util.Sanitizer import io.github.kscripting.shell.util.Sanitizer.Companion.EMPTY_SANITIZER import java.io.InputStream @@ -53,7 +53,7 @@ object ProcessRunner { try { // simplify with https://stackoverflow.com/questions/35421699/how-to-invoke-external-command-from-within-kotlin-code val process = ProcessBuilder(command) - .directory(workingDirectory?.toNativeFile()?.getOrNull()) + .directory(workingDirectory?.toNativeFile()) .redirectInput(if (inheritInput) ProcessBuilder.Redirect.INHERIT else ProcessBuilder.Redirect.PIPE) .redirectOutput(ProcessBuilder.Redirect.PIPE) .apply { diff --git a/src/test/kotlin/io/github/kscripting/os/model/GenericOsPathTest.kt b/src/test/kotlin/io/github/kscripting/os/model/GenericOsPathTest.kt index 44fb5b0..27ee70f 100644 --- a/src/test/kotlin/io/github/kscripting/os/model/GenericOsPathTest.kt +++ b/src/test/kotlin/io/github/kscripting/os/model/GenericOsPathTest.kt @@ -2,40 +2,26 @@ package io.github.kscripting.os.model import assertk.assertThat import assertk.assertions.isEqualTo -import assertk.assertions.isTrue import io.github.kscripting.os.instance.LinuxOs import net.igsoft.typeutils.globalcontext.GlobalContext -import org.junit.jupiter.api.BeforeAll import org.junit.jupiter.api.Test import org.junit.jupiter.api.TestInstance -import kotlin.Result.Companion.success @TestInstance(TestInstance.Lifecycle.PER_CLASS) class GenericOsPathTest { - private lateinit var emptyPath: OsPath - private lateinit var simplePath: OsPath + private var simplePath: OsPath - @BeforeAll - fun beforeAll() { + init { GlobalContext.registerOrReplace(OsType.LINUX, LinuxOs("/home/admin")) - emptyPath = OsPath(OsType.LINUX, "", emptyList()).getOrThrow() - simplePath = OsPath(OsType.LINUX, "", listOf("home", "admin")).getOrThrow() + simplePath = OsPath(OsType.LINUX, "", listOf("home", "admin")) } @Test fun `Test Empty paths`() { - assertThat(emptyPath.isEmpty).isTrue() - - assertThat(emptyPath.resolve(emptyPath)).isEqualTo(success(emptyPath)) - assertThat(emptyPath / emptyPath).isEqualTo(success(emptyPath)) - - assertThat(simplePath / emptyPath).isEqualTo(success(simplePath)) - assertThat(emptyPath / simplePath).isEqualTo(success(simplePath)) - - assertThat(emptyPath.toString()).isEqualTo("[LINUX]") assertThat(simplePath.toString()).isEqualTo("home/admin [LINUX]") - - assertThat(emptyPath.leaf).isEqualTo("") assertThat(simplePath.leaf).isEqualTo("admin") + + //println(simplePath.flatMap { it.resolve("mk", "km") }.flatMap { it.parent }) + } } diff --git a/src/test/kotlin/io/github/kscripting/os/model/HostedOsPathTest.kt b/src/test/kotlin/io/github/kscripting/os/model/HostedOsPathTest.kt index c89ed01..72d1c75 100644 --- a/src/test/kotlin/io/github/kscripting/os/model/HostedOsPathTest.kt +++ b/src/test/kotlin/io/github/kscripting/os/model/HostedOsPathTest.kt @@ -23,61 +23,93 @@ class HostedOsPathTest { @Test fun `Test Cygwin to Windows`() { - assertThat(OsPath(OsType.CYGWIN, "/cygdrive/c/home/admin/.kscript").toNative().path.getOrThrow()) - .isEqualTo("c:\\home\\admin\\.kscript") - - assertThat(OsPath(OsType.CYGWIN, "~/.kscript").toNative().path.getOrThrow()) - .isEqualTo("C:\\Programs\\Cygwin\\home\\admin\\.kscript") - - assertThat(OsPath(OsType.CYGWIN, "/usr/local/bin/sdk").toNative().path.getOrThrow()) - .isEqualTo("C:\\Programs\\Cygwin\\usr\\local\\bin\\sdk") - - assertThat(OsPath(OsType.CYGWIN, "../home/admin/.kscript").toNative().path.getOrThrow()) - .isEqualTo("..\\home\\admin\\.kscript") + assertThat( + OsPath( + OsType.CYGWIN, "/cygdrive/c/home/admin/.kscript" + ).toNative().path + ).isEqualTo("c:\\home\\admin\\.kscript") + + assertThat( + OsPath( + OsType.CYGWIN, "~/.kscript" + ).toNative().path + ).isEqualTo("C:\\Programs\\Cygwin\\home\\admin\\.kscript") + + assertThat( + OsPath( + OsType.CYGWIN, "/usr/local/bin/sdk" + ).toNative().path + ).isEqualTo("C:\\Programs\\Cygwin\\usr\\local\\bin\\sdk") + + assertThat( + OsPath(OsType.CYGWIN, "../home/admin/.kscript").toNative().path + ).isEqualTo("..\\home\\admin\\.kscript") } @Test fun `Test Windows to Cygwin`() { - assertThat(OsPath(OsType.WINDOWS, "C:\\home\\admin\\.kscript").toHosted(OsType.CYGWIN).path.getOrThrow()) - .isEqualTo("/cygdrive/c/home/admin/.kscript") - - assertThat(OsPath(OsType.WINDOWS, "..\\home\\admin\\.kscript").toHosted(OsType.CYGWIN).path.getOrThrow()) - .isEqualTo("../home/admin/.kscript") - - assertThat(OsPath(OsType.WINDOWS, "C:\\Programs\\Cygwin\\home\\admin\\.kscript").toHosted(OsType.CYGWIN).path.getOrThrow()) - .isEqualTo("~/.kscript") - - assertThat(OsPath(OsType.WINDOWS, "C:\\Programs\\Cygwin\\usr\\local\\sdk").toHosted(OsType.CYGWIN).path.getOrThrow()) - .isEqualTo("/usr/local/sdk") + assertThat( + OsPath(OsType.WINDOWS, "C:\\home\\admin\\.kscript").toHosted(OsType.CYGWIN).path + ).isEqualTo("/cygdrive/c/home/admin/.kscript") + + assertThat( + OsPath(OsType.WINDOWS, "..\\home\\admin\\.kscript").toHosted(OsType.CYGWIN).path + ).isEqualTo("../home/admin/.kscript") + + assertThat( + OsPath( + OsType.WINDOWS, "C:\\Programs\\Cygwin\\home\\admin\\.kscript" + ).toHosted(OsType.CYGWIN).path + ).isEqualTo("~/.kscript") + + assertThat( + OsPath( + OsType.WINDOWS, "C:\\Programs\\Cygwin\\usr\\local\\sdk" + ).toHosted(OsType.CYGWIN).path + ).isEqualTo("/usr/local/sdk") } @Test fun `Test MSys to Windows`() { - assertThat(OsPath(OsType.MSYS, "/c/home/admin/.kscript").toNative().path.getOrThrow()) - .isEqualTo("c:\\home\\admin\\.kscript") + assertThat( + OsPath(OsType.MSYS, "/c/home/admin/.kscript").toNative().path + ).isEqualTo("c:\\home\\admin\\.kscript") - assertThat(OsPath(OsType.MSYS, "~/.kscript").toNative().path.getOrThrow()) - .isEqualTo("C:\\Programs\\Msys\\home\\admin\\.kscript") + assertThat( + OsPath(OsType.MSYS, "~/.kscript").toNative().path + ).isEqualTo("C:\\Programs\\Msys\\home\\admin\\.kscript") - assertThat(OsPath(OsType.MSYS, "/usr/local/bin/sdk").toNative().path.getOrThrow()) - .isEqualTo("C:\\Programs\\Msys\\usr\\local\\bin\\sdk") + assertThat( + OsPath(OsType.MSYS, "/usr/local/bin/sdk").toNative().path + ).isEqualTo("C:\\Programs\\Msys\\usr\\local\\bin\\sdk") - assertThat(OsPath(OsType.MSYS, "../home/admin/.kscript").toNative().path.getOrThrow()) - .isEqualTo("..\\home\\admin\\.kscript") + assertThat( + OsPath(OsType.MSYS, "../home/admin/.kscript").toNative().path + ).isEqualTo("..\\home\\admin\\.kscript") } @Test fun `Test Windows to MSys`() { - assertThat(OsPath(OsType.WINDOWS, "C:\\home\\admin\\.kscript").toHosted(OsType.MSYS).path.getOrThrow()) - .isEqualTo("/c/home/admin/.kscript") - - assertThat(OsPath(OsType.WINDOWS, "..\\home\\admin\\.kscript").toHosted(OsType.MSYS).path.getOrThrow()) - .isEqualTo("../home/admin/.kscript") - - assertThat(OsPath(OsType.WINDOWS, "C:\\Programs\\Msys\\home\\admin\\.kscript").toHosted(OsType.MSYS).path.getOrThrow()) - .isEqualTo("~/.kscript") - - assertThat(OsPath(OsType.WINDOWS, "C:\\Programs\\Msys\\usr\\local\\sdk").toHosted(OsType.MSYS).path.getOrThrow()) - .isEqualTo("/usr/local/sdk") + assertThat( + OsPath( + OsType.WINDOWS, "C:\\home\\admin\\.kscript" + ).toHosted(OsType.MSYS).path + ).isEqualTo("/c/home/admin/.kscript") + + assertThat( + OsPath( + OsType.WINDOWS, "..\\home\\admin\\.kscript" + ).toHosted(OsType.MSYS).path + ).isEqualTo("../home/admin/.kscript") + + assertThat( + OsPath( + OsType.WINDOWS, "C:\\Programs\\Msys\\home\\admin\\.kscript" + ).toHosted(OsType.MSYS).path + ).isEqualTo("~/.kscript") + + assertThat( + OsPath(OsType.WINDOWS, "C:\\Programs\\Msys\\usr\\local\\sdk").toHosted(OsType.MSYS).path + ).isEqualTo("/usr/local/sdk") } } diff --git a/src/test/kotlin/io/github/kscripting/os/model/PosixOsPathTest.kt b/src/test/kotlin/io/github/kscripting/os/model/PosixOsPathTest.kt index 44db66c..58fe3d8 100644 --- a/src/test/kotlin/io/github/kscripting/os/model/PosixOsPathTest.kt +++ b/src/test/kotlin/io/github/kscripting/os/model/PosixOsPathTest.kt @@ -2,7 +2,10 @@ package io.github.kscripting.os.model import assertk.assertFailure import assertk.assertThat -import assertk.assertions.* +import assertk.assertions.hasMessage +import assertk.assertions.isEqualTo +import assertk.assertions.isInstanceOf +import assertk.assertions.prop import io.github.kscripting.os.instance.LinuxOs import net.igsoft.typeutils.globalcontext.GlobalContext import org.junit.jupiter.api.BeforeAll @@ -19,63 +22,63 @@ class PosixOsPathTest { @Test fun `Test Posix paths`() { - assertThat(OsPath(OsType.LINUX, "/").getOrThrow()).let { + assertThat(OsPath(OsType.LINUX, "/")).let { it.prop(OsPath::osType).isEqualTo(OsType.LINUX) it.prop(OsPath::root).isEqualTo("/") it.prop(OsPath::pathParts).isEqualTo(emptyList()) } - assertThat(OsPath(OsType.LINUX, "/home/admin/.kscript").getOrThrow()).let { + assertThat(OsPath(OsType.LINUX, "/home/admin/.kscript")).let { it.prop(OsPath::osType).isEqualTo(OsType.LINUX) it.prop(OsPath::root).isEqualTo("/") it.prop(OsPath::pathParts).isEqualTo(listOf("home", "admin", ".kscript")) } - assertThat(OsPath(OsType.LINUX, "./home/admin/.kscript").getOrThrow()).let { + assertThat(OsPath(OsType.LINUX, "./home/admin/.kscript")).let { it.prop(OsPath::osType).isEqualTo(OsType.LINUX) it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(listOf("home", "admin", ".kscript")) } - assertThat(OsPath(OsType.LINUX, "").getOrThrow()).let { + assertThat(OsPath(OsType.LINUX, "")).let { it.prop(OsPath::osType).isEqualTo(OsType.LINUX) it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(emptyList()) } - assertThat(OsPath(OsType.LINUX, "file.txt").getOrThrow()).let { + assertThat(OsPath(OsType.LINUX, "file.txt")).let { it.prop(OsPath::osType).isEqualTo(OsType.LINUX) it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(listOf("file.txt")) } - assertThat(OsPath(OsType.LINUX, ".").getOrThrow()).let { + assertThat(OsPath(OsType.LINUX, ".")).let { it.prop(OsPath::osType).isEqualTo(OsType.LINUX) it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(emptyList()) } - assertThat(OsPath(OsType.LINUX, "../home/admin/.kscript").getOrThrow()).let { + assertThat(OsPath(OsType.LINUX, "../home/admin/.kscript")).let { it.prop(OsPath::osType).isEqualTo(OsType.LINUX) it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(listOf("..", "home", "admin", ".kscript")) } - assertThat(OsPath(OsType.LINUX, "..").getOrThrow()).let { + assertThat(OsPath(OsType.LINUX, "..")).let { it.prop(OsPath::osType).isEqualTo(OsType.LINUX) it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(listOf("..")) } //Duplicated separators are accepted - assertThat(OsPath(OsType.LINUX, "..//home////admin/.kscript/").getOrThrow()).let { + assertThat(OsPath(OsType.LINUX, "..//home////admin/.kscript/")).let { it.prop(OsPath::osType).isEqualTo(OsType.LINUX) it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(listOf("..", "home", "admin", ".kscript")) } //Both types of separator are accepted - assertThat(OsPath(OsType.LINUX, "..//home\\admin\\.kscript/").getOrThrow()).let { + assertThat(OsPath(OsType.LINUX, "..//home\\admin\\.kscript/")).let { it.prop(OsPath::osType).isEqualTo(OsType.LINUX) it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(listOf("..", "home", "admin", ".kscript")) @@ -84,78 +87,82 @@ class PosixOsPathTest { @Test fun `Normalization of Posix paths`() { - assertThat(OsPath(OsType.LINUX, "/home/admin/.kscript/../../").getOrThrow()).let { + assertThat(OsPath(OsType.LINUX, "/home/admin/.kscript/../../")).let { it.prop(OsPath::osType).isEqualTo(OsType.LINUX) it.prop(OsPath::root).isEqualTo("/") it.prop(OsPath::pathParts).isEqualTo(listOf("home")) } - assertThat(OsPath(OsType.LINUX, "./././../../script").getOrThrow()).let { + assertThat(OsPath(OsType.LINUX, "./././../../script")).let { it.prop(OsPath::osType).isEqualTo(OsType.LINUX) it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(listOf("..", "..", "script")) } - assertThat(OsPath(OsType.LINUX, "/a/b/c/../d/script").getOrThrow()).let { + assertThat(OsPath(OsType.LINUX, "/a/b/c/../d/script")).let { it.prop(OsPath::osType).isEqualTo(OsType.LINUX) it.prop(OsPath::root).isEqualTo("/") it.prop(OsPath::pathParts).isEqualTo(listOf("a", "b", "d", "script")) } - OsPath(OsType.LINUX, "/.kscript/../../").let { - assertThat(it.isFailure).isTrue() - assertThat(it.exceptionOrNull()).isNotNull().isInstanceOf(IllegalArgumentException::class.java) - .hasMessage("Path after normalization goes beyond root element: '/'") - } + assertFailure { OsPath(OsType.LINUX, "/.kscript/../../") }.isInstanceOf(IllegalArgumentException::class) + .hasMessage("Path after normalization goes beyond root element: '/'") } @Test fun `Test invalid Posix paths`() { - assertFailure { OsPath(OsType.LINUX, "/ad*asdf").getOrThrow() } - .isInstanceOf(IllegalArgumentException::class.java).hasMessage("Invalid character '*' in path '/ad*asdf'") + assertFailure { + OsPath( + OsType.LINUX, + "/ad*asdf" + ) + }.isInstanceOf(IllegalArgumentException::class.java).hasMessage("Invalid character '*' in path '/ad*asdf'") } @Test fun `Test Posix stringPath`() { - assertThat(OsPath(OsType.LINUX, "/home/admin/.kscript").path.getOrThrow()).isEqualTo("/home/admin/.kscript") - assertThat(OsPath(OsType.LINUX, "/a/b/c/../d/script").path.getOrThrow()).isEqualTo("/a/b/d/script") - assertThat(OsPath(OsType.LINUX, "./././../../script").path.getOrThrow()).isEqualTo("../../script") - assertThat(OsPath(OsType.LINUX, "script/file.txt").path.getOrThrow()).isEqualTo("script/file.txt") + assertThat( + OsPath(OsType.LINUX, "/home/admin/.kscript").path + ).isEqualTo("/home/admin/.kscript") + assertThat(OsPath(OsType.LINUX, "/a/b/c/../d/script").path).isEqualTo("/a/b/d/script") + assertThat(OsPath(OsType.LINUX, "./././../../script").path).isEqualTo("../../script") + assertThat(OsPath(OsType.LINUX, "script/file.txt").path).isEqualTo("script/file.txt") } @Test fun `Test Posix resolve`() { assertThat( - OsPath(OsType.LINUX, "/").resolve(OsPath(OsType.LINUX, "./.kscript/")).path.getOrThrow() + OsPath(OsType.LINUX, "/").resolve(OsPath(OsType.LINUX, "./.kscript/")) + .path ).isEqualTo("/.kscript") assertThat( - OsPath(OsType.LINUX, "/home/admin/").resolve(OsPath(OsType.LINUX, "./.kscript/")).path.getOrThrow() + OsPath(OsType.LINUX, "/home/admin/").resolve(OsPath(OsType.LINUX, "./.kscript/")).path ).isEqualTo("/home/admin/.kscript") assertThat( - OsPath(OsType.LINUX, "./home/admin/").resolve(OsPath(OsType.LINUX, "./.kscript/")).path.getOrThrow() + OsPath(OsType.LINUX, "./home/admin/").resolve(OsPath(OsType.LINUX, "./.kscript/")).path ).isEqualTo("home/admin/.kscript") assertThat( - OsPath(OsType.LINUX, "../home/admin/").resolve(OsPath(OsType.LINUX, "./.kscript/")).path.getOrThrow() + OsPath(OsType.LINUX, "../home/admin/").resolve(OsPath(OsType.LINUX, "./.kscript/")).path ).isEqualTo("../home/admin/.kscript") assertThat( - OsPath(OsType.LINUX, "..").resolve(OsPath(OsType.LINUX, "./.kscript/")).path.getOrThrow() + OsPath(OsType.LINUX, "..").resolve(OsPath(OsType.LINUX, "./.kscript/")).path ).isEqualTo("../.kscript") assertThat( - OsPath(OsType.LINUX, ".").resolve(OsPath(OsType.LINUX, "./.kscript/")).path.getOrThrow() + OsPath(OsType.LINUX, ".").resolve(OsPath(OsType.LINUX, "./.kscript/")).path ).isEqualTo(".kscript") assertFailure { - OsPath(OsType.LINUX, "./home/admin").resolve(OsPath(OsType.WINDOWS, ".\\run")).getOrThrow() + OsPath(OsType.LINUX, "./home/admin").resolve(OsPath(OsType.WINDOWS, ".\\run")) }.isInstanceOf(IllegalArgumentException::class.java) .hasMessage("Paths from different OS's: 'LINUX' path can not be resolved with 'WINDOWS' path") assertFailure { - OsPath(OsType.LINUX, "./home/admin").resolve(OsPath(OsType.LINUX, "/run")).getOrThrow() + OsPath(OsType.LINUX, "./home/admin").resolve(OsPath(OsType.LINUX, "/run")) }.isInstanceOf(IllegalArgumentException::class.java) .hasMessage("Can not resolve absolute or relative path 'home/admin' using absolute path '/run'") } diff --git a/src/test/kotlin/io/github/kscripting/os/model/WindowsOsPathTest.kt b/src/test/kotlin/io/github/kscripting/os/model/WindowsOsPathTest.kt index f937d75..5418c73 100644 --- a/src/test/kotlin/io/github/kscripting/os/model/WindowsOsPathTest.kt +++ b/src/test/kotlin/io/github/kscripting/os/model/WindowsOsPathTest.kt @@ -2,9 +2,10 @@ package io.github.kscripting.os.model import assertk.assertFailure import assertk.assertThat -import assertk.assertions.* -import io.github.kscripting.os.instance.CygwinOs -import io.github.kscripting.os.instance.MsysOs +import assertk.assertions.hasMessage +import assertk.assertions.isEqualTo +import assertk.assertions.isInstanceOf +import assertk.assertions.prop import io.github.kscripting.os.instance.WindowsOs import net.igsoft.typeutils.globalcontext.GlobalContext import org.junit.jupiter.api.BeforeAll @@ -20,63 +21,63 @@ class WindowsOsPathTest { @Test fun `Test Windows paths`() { - assertThat(OsPath(OsType.WINDOWS, "C:\\").getOrThrow()).let { + assertThat(OsPath(OsType.WINDOWS, "C:\\")).let { it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) it.prop(OsPath::root).isEqualTo("C:\\") it.prop(OsPath::pathParts).isEqualTo(emptyList()) } - assertThat(OsPath(OsType.WINDOWS, "C:\\home\\admin\\.kscript").getOrThrow()).let { + assertThat(OsPath(OsType.WINDOWS, "C:\\home\\admin\\.kscript")).let { it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) it.prop(OsPath::root).isEqualTo("C:\\") it.prop(OsPath::pathParts).isEqualTo(listOf("home", "admin", ".kscript")) } - assertThat(OsPath(OsType.WINDOWS, "").getOrThrow()).let { + assertThat(OsPath(OsType.WINDOWS, "")).let { it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(emptyList()) } - assertThat(OsPath(OsType.WINDOWS, "file.txt").getOrThrow()).let { + assertThat(OsPath(OsType.WINDOWS, "file.txt")).let { it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(listOf("file.txt")) } - assertThat(OsPath(OsType.WINDOWS, ".").getOrThrow()).let { + assertThat(OsPath(OsType.WINDOWS, ".")).let { it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(emptyList()) } - assertThat(OsPath(OsType.WINDOWS, ".\\home\\admin\\.kscript").getOrThrow()).let { + assertThat(OsPath(OsType.WINDOWS, ".\\home\\admin\\.kscript")).let { it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(listOf("home", "admin", ".kscript")) } - assertThat(OsPath(OsType.WINDOWS, "..\\home\\admin\\.kscript").getOrThrow()).let { + assertThat(OsPath(OsType.WINDOWS, "..\\home\\admin\\.kscript")).let { it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(listOf("..", "home", "admin", ".kscript")) } - assertThat(OsPath(OsType.WINDOWS, "..").getOrThrow()).let { + assertThat(OsPath(OsType.WINDOWS, "..")).let { it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(listOf("..")) } //Duplicated separators are accepted - assertThat(OsPath(OsType.WINDOWS, "C:\\home\\\\\\\\admin\\.kscript\\").getOrThrow()).let { + assertThat(OsPath(OsType.WINDOWS, "C:\\home\\\\\\\\admin\\.kscript\\")).let { it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) it.prop(OsPath::root).isEqualTo("C:\\") it.prop(OsPath::pathParts).isEqualTo(listOf("home", "admin", ".kscript")) } //Both types of separator are accepted - assertThat(OsPath(OsType.WINDOWS, "C:/home\\admin/.kscript////").getOrThrow()).let { + assertThat(OsPath(OsType.WINDOWS, "C:/home\\admin/.kscript////")).let { it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) it.prop(OsPath::root).isEqualTo("C:\\") it.prop(OsPath::pathParts).isEqualTo(listOf("home", "admin", ".kscript")) @@ -85,47 +86,59 @@ class WindowsOsPathTest { @Test fun `Normalization of Windows paths`() { - assertThat(OsPath(OsType.WINDOWS, "C:\\home\\admin\\.kscript\\..\\..\\").getOrThrow()).let { + assertThat(OsPath(OsType.WINDOWS, "C:\\home\\admin\\.kscript\\..\\..\\")).let { it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) it.prop(OsPath::root).isEqualTo("C:\\") it.prop(OsPath::pathParts).isEqualTo(listOf("home")) } - assertThat(OsPath(OsType.WINDOWS, ".\\.\\.\\..\\..\\script").getOrThrow()).let { + assertThat(OsPath(OsType.WINDOWS, ".\\.\\.\\..\\..\\script")).let { it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(listOf("..", "..", "script")) } - assertThat(OsPath(OsType.WINDOWS, "C:\\a\\b\\c\\..\\d\\script").getOrThrow()).let { + assertThat(OsPath(OsType.WINDOWS, "C:\\a\\b\\c\\..\\d\\script")).let { it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) it.prop(OsPath::root).isEqualTo("C:\\") it.prop(OsPath::pathParts).isEqualTo(listOf("a", "b", "d", "script")) } - OsPath(OsType.WINDOWS, "C:\\.kscript\\..\\..\\").let { - assertThat(it.isFailure).isTrue() - assertThat(it.exceptionOrNull()).isNotNull().isInstanceOf(IllegalArgumentException::class.java) - .hasMessage("Path after normalization goes beyond root element: 'C:\\'") - } + assertFailure { + OsPath( + OsType.WINDOWS, + "C:\\.kscript\\..\\..\\" + ) + }.isInstanceOf(IllegalArgumentException::class.java) + .hasMessage("Path after normalization goes beyond root element: 'C:\\'") } @Test fun `Test invalid Windows paths`() { - assertFailure { OsPath(OsType.WINDOWS, "C:\\adas?df").getOrThrow() } + assertFailure { OsPath(OsType.WINDOWS, "C:\\adas?df") } .isInstanceOf(IllegalArgumentException::class.java) .hasMessage("Invalid character '?' in path 'C:\\adas?df'") - assertFailure { OsPath(OsType.WINDOWS, "home:\\vagrant").getOrThrow() } + assertFailure { OsPath(OsType.WINDOWS, "home:\\vagrant") } .isInstanceOf(IllegalArgumentException::class.java) .hasMessage("Invalid character ':' in path 'home:\\vagrant'") } @Test fun `Test Windows stringPath`() { - assertThat(OsPath(OsType.WINDOWS, "C:\\home\\admin\\.kscript").path.getOrThrow()).isEqualTo("C:\\home\\admin\\.kscript") - assertThat(OsPath(OsType.WINDOWS, "c:\\a\\b\\c\\..\\d\\script").path.getOrThrow()).isEqualTo("c:\\a\\b\\d\\script") - assertThat(OsPath(OsType.WINDOWS, ".\\.\\.\\..\\..\\script").path.getOrThrow()).isEqualTo("..\\..\\script") - assertThat(OsPath(OsType.WINDOWS, "script\\file.txt").path.getOrThrow()).isEqualTo("script\\file.txt") + assertThat( + OsPath( + OsType.WINDOWS, + "C:\\home\\admin\\.kscript" + ).path + ).isEqualTo("C:\\home\\admin\\.kscript") + assertThat( + OsPath( + OsType.WINDOWS, + "c:\\a\\b\\c\\..\\d\\script" + ).path + ).isEqualTo("c:\\a\\b\\d\\script") + assertThat(OsPath(OsType.WINDOWS, ".\\.\\.\\..\\..\\script").path).isEqualTo("..\\..\\script") + assertThat(OsPath(OsType.WINDOWS, "script\\file.txt").path).isEqualTo("script\\file.txt") } } From 34391e6839d312e39549c2e6b2f4738af88d5186 Mon Sep 17 00:00:00 2001 From: Marcin Kuszczak <1508798+aartiPl@users.noreply.github.com> Date: Wed, 1 Nov 2023 13:33:09 +0100 Subject: [PATCH 42/50] Update --- .../io/github/kscripting/os/model/OsPath.kt | 43 +++++++++++-------- .../github/kscripting/os/model/OsPathExt.kt | 3 +- .../kscripting/os/model/PosixOsPathTest.kt | 2 +- .../kscripting/os/model/WindowsOsPathTest.kt | 4 +- 4 files changed, 29 insertions(+), 23 deletions(-) diff --git a/src/main/kotlin/io/github/kscripting/os/model/OsPath.kt b/src/main/kotlin/io/github/kscripting/os/model/OsPath.kt index 1209bf1..57487cd 100644 --- a/src/main/kotlin/io/github/kscripting/os/model/OsPath.kt +++ b/src/main/kotlin/io/github/kscripting/os/model/OsPath.kt @@ -43,12 +43,13 @@ data class OsPath private constructor(val osType: OsType<*>, val root: String, v //2. Duplicated or trailing slashes '/' and backslashes '\' are just ignored operator fun invoke(osType: OsType<*>, root: String, pathParts: List): OsPath { - //TODO: verify root - do not allow empty paths - if (root.isEmpty() && pathParts.isEmpty()) { + val newRoot = root.trim() + + if (newRoot.isEmpty() && pathParts.isEmpty()) { throw OsPathError.EmptyPath } - return OsPath(osType, root, pathParts) + return normalize(validate(OsPath(osType, newRoot, pathParts))) } operator fun invoke(vararg pathParts: String): OsPath { @@ -80,19 +81,26 @@ data class OsPath private constructor(val osType: OsType<*>, val root: String, v //Remove also empty path parts - there were duplicated or trailing slashes / backslashes in initial path val pathPartsResolved = path.drop(root.length).split('/', '\\').filter { it.isNotBlank() } - val normalizedPath = normalize(root, pathPartsResolved) - //Validate root element of path and find out if it is absolute or relative - val forbiddenCharacter = path.substring(root.length).find { forbiddenCharacters.contains(it) } + return normalize(validate(OsPath(osType, root, pathPartsResolved))) + } - if (forbiddenCharacter != null) { - throw IllegalArgumentException("Invalid character '$forbiddenCharacter' in path '$path'") + fun validate(osPath: OsPath): OsPath { + osPath.pathParts.forEach { part-> + val invalidChar = part.find { forbiddenCharacters.contains(it) } + if (invalidChar != null) { + throw IllegalArgumentException("Invalid character '$invalidChar' in path part '$part'") + } } - return OsPath(osType, root, normalizedPath) + return osPath } - fun normalize(root: String, pathParts: List): List { + fun normalize(osPath: OsPath): OsPath { + return normalize(osPath.osType, osPath.root, osPath.pathParts) + } + + fun normalize(osPathOsType: OsType<*>, osPathRoot: String, osPathPathParts: List): OsPath { //Relative: // ./../ --> ../ // ./a/../ --> ./ @@ -104,17 +112,16 @@ data class OsPath private constructor(val osType: OsType<*>, val root: String, v // /../ --> invalid (above root) // /a/../ --> / - val isAbsolute: Boolean = root.isNotEmpty() - val newParts = mutableListOf() var index = 0 + val isAbsolute = osPathRoot.isNotEmpty() - while (index < pathParts.size) { - if (pathParts[index] == ".") { + while (index 0) { @@ -137,13 +144,13 @@ data class OsPath private constructor(val osType: OsType<*>, val root: String, v newParts.add("..") } } else { - newParts.add(pathParts[index]) + newParts.add(osPathPathParts[index]) } index += 1 } - return newParts + return OsPath(osPathOsType,osPathRoot, newParts) } } } diff --git a/src/main/kotlin/io/github/kscripting/os/model/OsPathExt.kt b/src/main/kotlin/io/github/kscripting/os/model/OsPathExt.kt index 2f8bc82..702532d 100644 --- a/src/main/kotlin/io/github/kscripting/os/model/OsPathExt.kt +++ b/src/main/kotlin/io/github/kscripting/os/model/OsPathExt.kt @@ -179,8 +179,7 @@ fun OsPath.resolve(osPath: OsPath): OsPath { addAll(osPath.pathParts) } - val normalizedPath = OsPath.normalize(root, newPathParts) - return OsPath(osType, root, normalizedPath) + return OsPath.normalize(osType, root, newPathParts) } // OsPath accessors diff --git a/src/test/kotlin/io/github/kscripting/os/model/PosixOsPathTest.kt b/src/test/kotlin/io/github/kscripting/os/model/PosixOsPathTest.kt index 58fe3d8..011b891 100644 --- a/src/test/kotlin/io/github/kscripting/os/model/PosixOsPathTest.kt +++ b/src/test/kotlin/io/github/kscripting/os/model/PosixOsPathTest.kt @@ -116,7 +116,7 @@ class PosixOsPathTest { OsType.LINUX, "/ad*asdf" ) - }.isInstanceOf(IllegalArgumentException::class.java).hasMessage("Invalid character '*' in path '/ad*asdf'") + }.isInstanceOf(IllegalArgumentException::class.java).hasMessage("Invalid character '*' in path part 'ad*asdf'") } @Test diff --git a/src/test/kotlin/io/github/kscripting/os/model/WindowsOsPathTest.kt b/src/test/kotlin/io/github/kscripting/os/model/WindowsOsPathTest.kt index 5418c73..f260cf6 100644 --- a/src/test/kotlin/io/github/kscripting/os/model/WindowsOsPathTest.kt +++ b/src/test/kotlin/io/github/kscripting/os/model/WindowsOsPathTest.kt @@ -117,11 +117,11 @@ class WindowsOsPathTest { fun `Test invalid Windows paths`() { assertFailure { OsPath(OsType.WINDOWS, "C:\\adas?df") } .isInstanceOf(IllegalArgumentException::class.java) - .hasMessage("Invalid character '?' in path 'C:\\adas?df'") + .hasMessage("Invalid character '?' in path part 'adas?df'") assertFailure { OsPath(OsType.WINDOWS, "home:\\vagrant") } .isInstanceOf(IllegalArgumentException::class.java) - .hasMessage("Invalid character ':' in path 'home:\\vagrant'") + .hasMessage("Invalid character ':' in path part 'home:'") } @Test From 0512f058bc9db8a405667e6e184ebd8d088bd4f4 Mon Sep 17 00:00:00 2001 From: Marcin Kuszczak <1508798+aartiPl@users.noreply.github.com> Date: Thu, 21 Dec 2023 23:07:49 +0100 Subject: [PATCH 43/50] Update --- .../shell/integration/ShellExecutorTest.kt | 3 - .../shell/integration/tools/TestContext.kt | 9 +- src/main/kotlin/io/github/kscripting/os/Os.kt | 5 ++ .../github/kscripting/os/instance/CygwinOs.kt | 9 +- .../kscripting/os/instance/FreeBsdOs.kt | 5 +- .../github/kscripting/os/instance/LinuxOs.kt | 5 +- .../io/github/kscripting/os/instance/MacOs.kt | 5 +- .../github/kscripting/os/instance/MsysOs.kt | 9 +- .../kscripting/os/instance/WindowsOs.kt | 3 +- .../kscripting/os/model/GlobalOsType.kt | 49 +++++++++++ .../io/github/kscripting/os/model/OsPath.kt | 32 +++----- .../github/kscripting/os/model/OsPathExt.kt | 26 +++--- .../io/github/kscripting/os/model/OsType.kt | 59 ++++--------- .../github/kscripting/shell/ShellExecutor.kt | 21 ++--- .../kscripting/os/model/GenericOsPathTest.kt | 4 +- .../kscripting/os/model/HostedOsPathTest.kt | 57 +++++++------ .../kscripting/os/model/PosixOsPathTest.kt | 82 +++++++++---------- .../kscripting/os/model/WindowsOsPathTest.kt | 68 +++++++-------- 18 files changed, 239 insertions(+), 212 deletions(-) create mode 100644 src/main/kotlin/io/github/kscripting/os/model/GlobalOsType.kt diff --git a/src/itest/kotlin/io/github/kscripting/shell/integration/ShellExecutorTest.kt b/src/itest/kotlin/io/github/kscripting/shell/integration/ShellExecutorTest.kt index 07f8da9..14efd76 100644 --- a/src/itest/kotlin/io/github/kscripting/shell/integration/ShellExecutorTest.kt +++ b/src/itest/kotlin/io/github/kscripting/shell/integration/ShellExecutorTest.kt @@ -1,7 +1,5 @@ package io.github.kscripting.shell.integration -import io.github.kscripting.os.instance.WindowsOs -import io.github.kscripting.os.model.OsType import io.github.kscripting.os.model.readText import io.github.kscripting.os.model.resolve import io.github.kscripting.shell.integration.tools.ShellTestBase @@ -9,7 +7,6 @@ import io.github.kscripting.shell.integration.tools.TestContext import io.github.kscripting.shell.integration.tools.TestContext.execPath import io.github.kscripting.shell.integration.tools.TestContext.testPath import io.github.kscripting.shell.util.Sanitizer -import net.igsoft.typeutils.globalcontext.GlobalContext import org.junit.jupiter.api.BeforeAll import org.junit.jupiter.api.Tag import org.junit.jupiter.api.Test diff --git a/src/itest/kotlin/io/github/kscripting/shell/integration/tools/TestContext.kt b/src/itest/kotlin/io/github/kscripting/shell/integration/tools/TestContext.kt index e797618..99b3606 100644 --- a/src/itest/kotlin/io/github/kscripting/shell/integration/tools/TestContext.kt +++ b/src/itest/kotlin/io/github/kscripting/shell/integration/tools/TestContext.kt @@ -1,7 +1,6 @@ package io.github.kscripting.shell.integration.tools import io.github.kscripting.os.Os -import io.github.kscripting.os.instance.LinuxOs import io.github.kscripting.os.instance.WindowsOs import io.github.kscripting.os.model.* import io.github.kscripting.shell.ShellExecutor @@ -11,12 +10,12 @@ import net.igsoft.typeutils.globalcontext.GlobalContext @Suppress("MemberVisibilityCanBePrivate") object TestContext { init { - GlobalContext.registerOrReplace(OsType.WINDOWS, WindowsOs("/home/admin")) + GlobalContext.registerOrReplace(GlobalOsType.WINDOWS, WindowsOs(GlobalOsType.WINDOWS, "/home/admin")) } - val osType: OsType = OsType.find(System.getProperty("osType")) ?: OsType.native + val osType: OsType = GlobalOsType.find(System.getProperty("osType")) ?: GlobalOsType.native - val projectPath: OsPath = OsPath(OsType.native, System.getProperty("projectPath")).toHosted(osType) + val projectPath: OsPath = OsPath(GlobalOsType.native, System.getProperty("projectPath")).toHosted(osType) val execPath: OsPath = projectPath.resolve("build/shell_test/bin") val testPath: OsPath = projectPath.resolve("build/shell_test/tmp") @@ -36,7 +35,7 @@ object TestContext { init { println("osType : $osType") - println("nativeType : ${OsType.native}") + println("nativeType : ${GlobalOsType.native}") println("projectDir : $projectPath") println("execDir : ${execPath.toHosted(osType)}") println("Kotlin version : ${ShellExecutor.evalAndGobble("kotlin -version", osType).stdout}") diff --git a/src/main/kotlin/io/github/kscripting/os/Os.kt b/src/main/kotlin/io/github/kscripting/os/Os.kt index 70fb3c2..a77a949 100644 --- a/src/main/kotlin/io/github/kscripting/os/Os.kt +++ b/src/main/kotlin/io/github/kscripting/os/Os.kt @@ -13,6 +13,11 @@ interface Os { // startsWith() covers all cases. val osTypePrefix: String val type: OsType + +// val environment: Map +// val systemProperties: Map +// val fileSystem: Fs + val userHome: OsPath val pathSeparator: String } diff --git a/src/main/kotlin/io/github/kscripting/os/instance/CygwinOs.kt b/src/main/kotlin/io/github/kscripting/os/instance/CygwinOs.kt index 0e5c4f7..65560c6 100644 --- a/src/main/kotlin/io/github/kscripting/os/instance/CygwinOs.kt +++ b/src/main/kotlin/io/github/kscripting/os/instance/CygwinOs.kt @@ -3,10 +3,13 @@ package io.github.kscripting.os.instance import io.github.kscripting.os.model.OsPath import io.github.kscripting.os.model.OsType -class CygwinOs(userHome: String, nativeFileSystemRoot: String) : HostedOs { +class CygwinOs( + override val type: OsType, + override val nativeType: OsType, + userHome: String, + nativeFileSystemRoot: String +) : HostedOs { override val osTypePrefix: String = "cygwin" - override val type: OsType = OsType.CYGWIN override val userHome: OsPath = OsPath(type, userHome) - override val nativeType: OsType = OsType.WINDOWS override val nativeFileSystemRoot: OsPath = OsPath(nativeType, nativeFileSystemRoot) } diff --git a/src/main/kotlin/io/github/kscripting/os/instance/FreeBsdOs.kt b/src/main/kotlin/io/github/kscripting/os/instance/FreeBsdOs.kt index 2c883a6..baa752e 100644 --- a/src/main/kotlin/io/github/kscripting/os/instance/FreeBsdOs.kt +++ b/src/main/kotlin/io/github/kscripting/os/instance/FreeBsdOs.kt @@ -4,9 +4,8 @@ import io.github.kscripting.os.Os import io.github.kscripting.os.model.OsPath import io.github.kscripting.os.model.OsType -class FreeBsdOs(userHome: String) : Os { +class FreeBsdOs(override val type: OsType, userHome: String) : Os { override val osTypePrefix: String = "freebsd" - override val type: OsType = OsType.FREEBSD override val pathSeparator: String get() = "/" - override val userHome: OsPath = OsPath(OsType.FREEBSD, userHome) + override val userHome: OsPath = OsPath(type, userHome) } diff --git a/src/main/kotlin/io/github/kscripting/os/instance/LinuxOs.kt b/src/main/kotlin/io/github/kscripting/os/instance/LinuxOs.kt index 717176f..9e272b6 100644 --- a/src/main/kotlin/io/github/kscripting/os/instance/LinuxOs.kt +++ b/src/main/kotlin/io/github/kscripting/os/instance/LinuxOs.kt @@ -4,9 +4,8 @@ import io.github.kscripting.os.Os import io.github.kscripting.os.model.OsPath import io.github.kscripting.os.model.OsType -class LinuxOs(userHome: String) : Os { +class LinuxOs(override val type: OsType, userHome: String) : Os { override val osTypePrefix: String = "linux" - override val type: OsType = OsType.LINUX override val pathSeparator: String get() = "/" - override val userHome: OsPath = OsPath(OsType.LINUX, userHome) + override val userHome: OsPath = OsPath(type, userHome) } diff --git a/src/main/kotlin/io/github/kscripting/os/instance/MacOs.kt b/src/main/kotlin/io/github/kscripting/os/instance/MacOs.kt index c4d88bf..ce250a1 100644 --- a/src/main/kotlin/io/github/kscripting/os/instance/MacOs.kt +++ b/src/main/kotlin/io/github/kscripting/os/instance/MacOs.kt @@ -4,9 +4,8 @@ import io.github.kscripting.os.Os import io.github.kscripting.os.model.OsPath import io.github.kscripting.os.model.OsType -class MacOs(userHome: String) : Os { +class MacOs(override val type: OsType, userHome: String) : Os { override val osTypePrefix: String = "darwin" - override val type: OsType = OsType.MACOS override val pathSeparator: String get() = "/" - override val userHome: OsPath = OsPath(OsType.MACOS, userHome) + override val userHome: OsPath = OsPath(type, userHome) } diff --git a/src/main/kotlin/io/github/kscripting/os/instance/MsysOs.kt b/src/main/kotlin/io/github/kscripting/os/instance/MsysOs.kt index 0333c23..9780548 100644 --- a/src/main/kotlin/io/github/kscripting/os/instance/MsysOs.kt +++ b/src/main/kotlin/io/github/kscripting/os/instance/MsysOs.kt @@ -3,10 +3,13 @@ package io.github.kscripting.os.instance import io.github.kscripting.os.model.OsPath import io.github.kscripting.os.model.OsType -class MsysOs(userHome: String, nativeFileSystemRoot: String) : HostedOs { +class MsysOs( + override val type: OsType, + override val nativeType: OsType, + userHome: String, + nativeFileSystemRoot: String +) : HostedOs { override val osTypePrefix: String = "msys" - override val type: OsType = OsType.MSYS override val userHome: OsPath = OsPath(type, userHome) - override val nativeType: OsType = OsType.WINDOWS override val nativeFileSystemRoot: OsPath = OsPath(nativeType, nativeFileSystemRoot) } diff --git a/src/main/kotlin/io/github/kscripting/os/instance/WindowsOs.kt b/src/main/kotlin/io/github/kscripting/os/instance/WindowsOs.kt index aa6045e..9f991e3 100644 --- a/src/main/kotlin/io/github/kscripting/os/instance/WindowsOs.kt +++ b/src/main/kotlin/io/github/kscripting/os/instance/WindowsOs.kt @@ -5,9 +5,8 @@ import io.github.kscripting.os.model.OsPath import io.github.kscripting.os.model.OsType -class WindowsOs(userHome: String) : Os { +class WindowsOs(override val type: OsType, userHome: String) : Os { override val osTypePrefix: String = "windows" - override val type: OsType = OsType.WINDOWS override val pathSeparator: String get() = "\\" override val userHome: OsPath = OsPath(type, userHome) } diff --git a/src/main/kotlin/io/github/kscripting/os/model/GlobalOsType.kt b/src/main/kotlin/io/github/kscripting/os/model/GlobalOsType.kt new file mode 100644 index 0000000..4998e0c --- /dev/null +++ b/src/main/kotlin/io/github/kscripting/os/model/GlobalOsType.kt @@ -0,0 +1,49 @@ +package io.github.kscripting.os.model + +import io.github.kscripting.os.Os +import io.github.kscripting.os.instance.* +import net.igsoft.typeutils.globalcontext.GlobalContext +import net.igsoft.typeutils.marker.AutoTypedMarker +import net.igsoft.typeutils.marker.DefaultTypedMarker +import net.igsoft.typeutils.marker.TypedMarker +import net.igsoft.typeutils.typedenum.TypedEnumCompanion +import org.apache.commons.lang3.SystemUtils + + +class GlobalOsType private constructor(private val marker: TypedMarker) : OsType, + DefaultTypedMarker(marker) { + override val os: T get() = GlobalContext.getValue(marker) + + override fun isPosixLike() = + (this == LINUX || this == MACOS || this == FREEBSD || this == CYGWIN || this == MSYS) + + override fun isPosixHostedOnWindows() = (this == CYGWIN || this == MSYS) + override fun isWindowsLike() = (this == WINDOWS) + + companion object : TypedEnumCompanion>() { + val LINUX: OsType = GlobalOsType(AutoTypedMarker.create()) + val WINDOWS: OsType = GlobalOsType(AutoTypedMarker.create()) + val CYGWIN: OsType = GlobalOsType(AutoTypedMarker.create()) + val MSYS: OsType = GlobalOsType(AutoTypedMarker.create()) + val MACOS: OsType = GlobalOsType(AutoTypedMarker.create()) + val FREEBSD: OsType = GlobalOsType(AutoTypedMarker.create()) + + val native: OsType = guessNativeType() + + fun findByOsTypeString(osTypeString: String): OsType? = + find { osTypeString.startsWith(it.os.osTypePrefix, true) } + + private fun guessNativeType(): OsType { + when { + SystemUtils.IS_OS_LINUX -> return LINUX + SystemUtils.IS_OS_MAC -> return MACOS + SystemUtils.IS_OS_WINDOWS -> return WINDOWS + SystemUtils.IS_OS_FREE_BSD -> return FREEBSD + } + + return LINUX + } + } + + override fun toString(): String = findName(this) +} diff --git a/src/main/kotlin/io/github/kscripting/os/model/OsPath.kt b/src/main/kotlin/io/github/kscripting/os/model/OsPath.kt index 57487cd..73721ce 100644 --- a/src/main/kotlin/io/github/kscripting/os/model/OsPath.kt +++ b/src/main/kotlin/io/github/kscripting/os/model/OsPath.kt @@ -11,7 +11,7 @@ data class OsPath private constructor(val osType: OsType<*>, val root: String, v val isRelative: Boolean get() = root.isEmpty() && pathParts.isNotEmpty() val isAbsolute: Boolean get() = root.isNotEmpty() - val path: String get() = root + pathParts.joinToString(osType.value.pathSeparator) { it } + val path: String get() = root + pathParts.joinToString(osType.os.pathSeparator) { it } val leaf: String get() = if (pathParts.isEmpty()) root else pathParts.last() override fun toString(): String = "$path [$osType]" @@ -37,10 +37,9 @@ data class OsPath private constructor(val osType: OsType<*>, val root: String, v private val windowsDriveRegex = "^([a-zA-Z]:(?=[\\\\/])|\\\\\\\\(?:[^*:<>?\\\\/|]+\\\\[^*:<>?\\\\/|]+|\\?\\\\(?:[a-zA-Z]:(?=\\\\)|(?:UNC\\\\)?[^*:<>?\\\\/|]+\\\\[^*:<>?\\\\/|]+)))".toRegex() - //Relaxed validation: //1. It doesn't matter if there is '/' or '\' used as path separator - both are treated the same - //2. Duplicated or trailing slashes '/' and backslashes '\' are just ignored + //2. Duplicated or trailing slashes '/' and backslashes '\' are ignored operator fun invoke(osType: OsType<*>, root: String, pathParts: List): OsPath { val newRoot = root.trim() @@ -53,7 +52,7 @@ data class OsPath private constructor(val osType: OsType<*>, val root: String, v } operator fun invoke(vararg pathParts: String): OsPath { - return invoke(OsType.native, pathParts.toList()) + return invoke(GlobalOsType.native, pathParts.toList()) } operator fun invoke(osType: OsType<*>, vararg pathParts: String): OsPath { @@ -76,9 +75,6 @@ data class OsPath private constructor(val osType: OsType<*>, val root: String, v else -> "" } - //TODO: https://learn.microsoft.com/pl-pl/dotnet/standard/io/file-path-formats - // https://regex101.com/r/aU4yZ7/1 - //Remove also empty path parts - there were duplicated or trailing slashes / backslashes in initial path val pathPartsResolved = path.drop(root.length).split('/', '\\').filter { it.isNotBlank() } @@ -86,6 +82,9 @@ data class OsPath private constructor(val osType: OsType<*>, val root: String, v } fun validate(osPath: OsPath): OsPath { + //TODO: https://learn.microsoft.com/pl-pl/dotnet/standard/io/file-path-formats + // https://regex101.com/r/aU4yZ7/1 + osPath.pathParts.forEach { part-> val invalidChar = part.find { forbiddenCharacters.contains(it) } if (invalidChar != null) { @@ -97,10 +96,6 @@ data class OsPath private constructor(val osType: OsType<*>, val root: String, v } fun normalize(osPath: OsPath): OsPath { - return normalize(osPath.osType, osPath.root, osPath.pathParts) - } - - fun normalize(osPathOsType: OsType<*>, osPathRoot: String, osPathPathParts: List): OsPath { //Relative: // ./../ --> ../ // ./a/../ --> ./ @@ -114,14 +109,13 @@ data class OsPath private constructor(val osType: OsType<*>, val root: String, v val newParts = mutableListOf() var index = 0 - val isAbsolute = osPathRoot.isNotEmpty() - while (index 0) { @@ -144,13 +138,13 @@ data class OsPath private constructor(val osType: OsType<*>, val root: String, v newParts.add("..") } } else { - newParts.add(osPathPathParts[index]) + newParts.add(osPath.pathParts[index]) } index += 1 } - return OsPath(osPathOsType,osPathRoot, newParts) + return OsPath(osPath.osType,osPath.root, newParts) } } } diff --git a/src/main/kotlin/io/github/kscripting/os/model/OsPathExt.kt b/src/main/kotlin/io/github/kscripting/os/model/OsPathExt.kt index 702532d..84f2500 100644 --- a/src/main/kotlin/io/github/kscripting/os/model/OsPathExt.kt +++ b/src/main/kotlin/io/github/kscripting/os/model/OsPathExt.kt @@ -9,26 +9,20 @@ import java.nio.file.Path import java.nio.file.Paths import kotlin.io.path.* -//Wrappers -fun Result

.flatMap(fn: (P) -> Result): Result = fold( - onSuccess = { fn(it) }, - onFailure = { Result.failure(it) } -) - fun OsPath.toNative(): OsPath { if (!osType.isPosixHostedOnWindows()) { //Everything besides Cygwin/Msys is native... return this } - val hostedOs = osType.value as HostedOs + val hostedOs = osType.os as HostedOs val newParts = mutableListOf() var newRoot = "" if (isAbsolute) { when (osType) { - OsType.CYGWIN -> { + GlobalOsType.CYGWIN -> { if (pathParts[0].equals("cygdrive", true)) { //Paths referring /cygdrive newRoot = pathParts[1] + ":\\" newParts.addAll(pathParts.subList(2, pathParts.size)) @@ -44,7 +38,7 @@ fun OsPath.toNative(): OsPath { } } - OsType.MSYS -> { + GlobalOsType.MSYS -> { if (pathParts[0].length == 1 && (pathParts[0][0].code in 65..90 || pathParts[0][0].code in 97..122)) { //Paths referring with drive letter at the beginning newRoot = pathParts[0] + ":\\" newParts.addAll(pathParts.subList(1, pathParts.size)) @@ -77,7 +71,7 @@ fun OsPath.toHosted(targetOs: OsType<*>): OsPath { return this } - if (!(targetOs.isPosixHostedOnWindows() && osType == ((targetOs.value) as HostedOs).nativeType)) { + if (!(targetOs.isPosixHostedOnWindows() && osType == ((targetOs.os) as HostedOs).nativeType)) { throw OsPathError.InvalidConversion("You can convert only paths to hosted OS-es") } @@ -85,7 +79,7 @@ fun OsPath.toHosted(targetOs: OsType<*>): OsPath { var newRoot = "" if (isAbsolute) { - val hostedOs = targetOs.value as HostedOs + val hostedOs = targetOs.os as HostedOs if (this.startsWith(hostedOs.nativeFileSystemRoot)) { if (pathParts.subList(hostedOs.nativeFileSystemRoot.pathParts.size, pathParts.size) @@ -111,7 +105,7 @@ fun OsPath.toHosted(targetOs: OsType<*>): OsPath { newRoot = "/" - if (targetOs.value.type == OsType.CYGWIN) { + if (targetOs.os.type == GlobalOsType.CYGWIN) { newParts.add("cygdrive") newParts.add(drive) } else { @@ -128,9 +122,9 @@ fun OsPath.toHosted(targetOs: OsType<*>): OsPath { } // Conversion to OsPath -fun File.toOsPath(): OsPath = OsPath(OsType.native, absolutePath) +fun File.toOsPath(): OsPath = OsPath(GlobalOsType.native, absolutePath) -fun Path.toOsPath(): OsPath = OsPath(OsType.native, absolutePathString()) +fun Path.toOsPath(): OsPath = OsPath(GlobalOsType.native, absolutePathString()) fun URI.toOsPath(): OsPath = if (this.scheme == "file") File(this).toOsPath() else throw IllegalArgumentException("Invalid conversion from URL to OsPath") @@ -179,7 +173,7 @@ fun OsPath.resolve(osPath: OsPath): OsPath { addAll(osPath.pathParts) } - return OsPath.normalize(osType, root, newPathParts) + return OsPath.normalize(OsPath(osType, root, newPathParts)) } // OsPath accessors @@ -188,7 +182,7 @@ fun OsPath.resolve(osPath: OsPath): OsPath { // get() = OsPath.createOrThrow(osType, root) val OsPath.nativeType - get() = if (osType.isPosixHostedOnWindows()) OsType.WINDOWS else osType + get() = if (osType.isPosixHostedOnWindows()) GlobalOsType.WINDOWS else osType val OsPath.extension get() = leaf.substringAfterLast('.', "") diff --git a/src/main/kotlin/io/github/kscripting/os/model/OsType.kt b/src/main/kotlin/io/github/kscripting/os/model/OsType.kt index 5563943..3c49392 100644 --- a/src/main/kotlin/io/github/kscripting/os/model/OsType.kt +++ b/src/main/kotlin/io/github/kscripting/os/model/OsType.kt @@ -2,47 +2,24 @@ package io.github.kscripting.os.model import io.github.kscripting.os.Os import io.github.kscripting.os.instance.* -import net.igsoft.typeutils.globalcontext.GlobalContext -import net.igsoft.typeutils.marker.AutoTypedMarker -import net.igsoft.typeutils.marker.DefaultTypedMarker import net.igsoft.typeutils.marker.TypedMarker -import net.igsoft.typeutils.typedenum.TypedEnumCompanion -import org.apache.commons.lang3.SystemUtils - -class OsType private constructor(private val marker: TypedMarker) : DefaultTypedMarker(marker) { - val value: T get() = GlobalContext.getValue(marker) - - fun isPosixLike() = - (this == LINUX || this == MACOS || this == FREEBSD || this == CYGWIN || this == MSYS) - - fun isPosixHostedOnWindows() = (this == CYGWIN || this == MSYS) - fun isWindowsLike() = (this == WINDOWS) - - companion object : TypedEnumCompanion>() { - val LINUX = OsType(AutoTypedMarker.create()) - val WINDOWS = OsType(AutoTypedMarker.create()) - val CYGWIN = OsType(AutoTypedMarker.create()) - val MSYS = OsType(AutoTypedMarker.create()) - val MACOS = OsType(AutoTypedMarker.create()) - val FREEBSD = OsType(AutoTypedMarker.create()) - - val native: OsType = guessNativeType() - - fun findByOsTypeString(osTypeString: String): OsType? = - find { osTypeString.startsWith(it.value.osTypePrefix, true) } - - private fun guessNativeType(): OsType { - when { - SystemUtils.IS_OS_LINUX -> return LINUX - SystemUtils.IS_OS_MAC -> return MACOS - SystemUtils.IS_OS_WINDOWS -> return WINDOWS - SystemUtils.IS_OS_FREE_BSD -> return FREEBSD - } - - return LINUX - } - } - - override fun toString(): String = findName(this) +//@Suppress("PropertyName") +//interface OsTypeCompanion { +// val LINUX: OsType +// val WINDOWS: OsType +// val CYGWIN: OsType +// val MSYS: OsType +// val MACOS: OsType +// val FREEBSD: OsType +// +// val native: OsType +//} + +interface OsType : TypedMarker { + val os: T + + fun isPosixLike(): Boolean + fun isPosixHostedOnWindows(): Boolean + fun isWindowsLike(): Boolean } diff --git a/src/main/kotlin/io/github/kscripting/shell/ShellExecutor.kt b/src/main/kotlin/io/github/kscripting/shell/ShellExecutor.kt index 4ad0de8..6f246b5 100644 --- a/src/main/kotlin/io/github/kscripting/shell/ShellExecutor.kt +++ b/src/main/kotlin/io/github/kscripting/shell/ShellExecutor.kt @@ -2,6 +2,7 @@ package io.github.kscripting.shell import io.github.kscripting.os.Os import io.github.kscripting.os.model.OsPath +import io.github.kscripting.os.model.GlobalOsType import io.github.kscripting.os.model.OsType import io.github.kscripting.shell.model.ProcessResult import io.github.kscripting.shell.model.ShellType @@ -22,17 +23,17 @@ object ShellExecutor { private val SPLIT_PATTERN = Pattern.compile("([^\"]\\S*|\".+?\")\\s*") private val UTF_8 = StandardCharsets.UTF_8.name() private val DEFAULT_SHELL_MAPPER: Map, ShellType> = mapOf( - OsType.WINDOWS to ShellType.CMD, - OsType.LINUX to ShellType.BASH, - OsType.FREEBSD to ShellType.BASH, - OsType.MACOS to ShellType.BASH, - OsType.CYGWIN to ShellType.BASH, - OsType.MSYS to ShellType.BASH, + GlobalOsType.WINDOWS to ShellType.CMD, + GlobalOsType.LINUX to ShellType.BASH, + GlobalOsType.FREEBSD to ShellType.BASH, + GlobalOsType.MACOS to ShellType.BASH, + GlobalOsType.CYGWIN to ShellType.BASH, + GlobalOsType.MSYS to ShellType.BASH, ) fun evalAndGobble( command: String, - osType: OsType = OsType.native, + globalOsType: OsType = GlobalOsType.native, workingDirectory: OsPath? = null, waitTimeMinutes: Int = 10, inheritInput: Boolean = false, @@ -53,7 +54,7 @@ object ShellExecutor { PrintStream(errStream, true, UTF_8).use { additionalErrPrinter -> result = eval( command, - osType, + globalOsType, workingDirectory, waitTimeMinutes, inheritInput, @@ -73,7 +74,7 @@ object ShellExecutor { fun eval( command: String, - osType: OsType = OsType.native, + globalOsType: OsType = GlobalOsType.native, workingDirectory: OsPath? = null, waitTimeMinutes: Int = 10, inheritInput: Boolean = false, @@ -87,7 +88,7 @@ object ShellExecutor { ): Int { val sanitizedCommand = inputSanitizer.sanitize(command) - val commandList = when (val shellType = shellMapper.getValue(osType)) { + val commandList = when (val shellType = shellMapper.getValue(globalOsType)) { //NOTE: usually command is an argument to shell (bash/cmd), so it should stay not split by whitespace as //a single string, but when there is no shell, we have to split the command ShellType.NONE -> { diff --git a/src/test/kotlin/io/github/kscripting/os/model/GenericOsPathTest.kt b/src/test/kotlin/io/github/kscripting/os/model/GenericOsPathTest.kt index 27ee70f..10b4370 100644 --- a/src/test/kotlin/io/github/kscripting/os/model/GenericOsPathTest.kt +++ b/src/test/kotlin/io/github/kscripting/os/model/GenericOsPathTest.kt @@ -12,8 +12,8 @@ class GenericOsPathTest { private var simplePath: OsPath init { - GlobalContext.registerOrReplace(OsType.LINUX, LinuxOs("/home/admin")) - simplePath = OsPath(OsType.LINUX, "", listOf("home", "admin")) + GlobalContext.registerOrReplace(GlobalOsType.LINUX, LinuxOs(GlobalOsType.LINUX, "/home/admin")) + simplePath = OsPath(GlobalOsType.LINUX, "", listOf("home", "admin")) } @Test diff --git a/src/test/kotlin/io/github/kscripting/os/model/HostedOsPathTest.kt b/src/test/kotlin/io/github/kscripting/os/model/HostedOsPathTest.kt index 72d1c75..45fd7b9 100644 --- a/src/test/kotlin/io/github/kscripting/os/model/HostedOsPathTest.kt +++ b/src/test/kotlin/io/github/kscripting/os/model/HostedOsPathTest.kt @@ -16,75 +16,84 @@ class HostedOsPathTest { @BeforeAll fun beforeAll() { //Installation cygwin/msys base path: cygpath -w /, cygpath ~ - GlobalContext.registerOrReplace(OsType.CYGWIN, CygwinOs("/home/admin", "C:\\Programs\\Cygwin\\")) - GlobalContext.registerOrReplace(OsType.MSYS, MsysOs("/home/admin", "C:\\Programs\\Msys\\")) - GlobalContext.registerOrReplace(OsType.WINDOWS, WindowsOs("C:\\Users\\Admin\\.kscript")) + GlobalContext.registerOrReplace( + GlobalOsType.CYGWIN, + CygwinOs(GlobalOsType.CYGWIN, GlobalOsType.WINDOWS, "/home/admin", "C:\\Programs\\Cygwin\\") + ) + GlobalContext.registerOrReplace( + GlobalOsType.MSYS, + MsysOs(GlobalOsType.MSYS, GlobalOsType.WINDOWS, "/home/admin", "C:\\Programs\\Msys\\") + ) + GlobalContext.registerOrReplace( + GlobalOsType.WINDOWS, + WindowsOs(GlobalOsType.WINDOWS, "C:\\Users\\Admin\\.kscript") + ) } @Test fun `Test Cygwin to Windows`() { assertThat( OsPath( - OsType.CYGWIN, "/cygdrive/c/home/admin/.kscript" + GlobalOsType.CYGWIN, "/cygdrive/c/home/admin/.kscript" ).toNative().path ).isEqualTo("c:\\home\\admin\\.kscript") assertThat( OsPath( - OsType.CYGWIN, "~/.kscript" + GlobalOsType.CYGWIN, "~/.kscript" ).toNative().path ).isEqualTo("C:\\Programs\\Cygwin\\home\\admin\\.kscript") assertThat( OsPath( - OsType.CYGWIN, "/usr/local/bin/sdk" + GlobalOsType.CYGWIN, "/usr/local/bin/sdk" ).toNative().path ).isEqualTo("C:\\Programs\\Cygwin\\usr\\local\\bin\\sdk") assertThat( - OsPath(OsType.CYGWIN, "../home/admin/.kscript").toNative().path + OsPath(GlobalOsType.CYGWIN, "../home/admin/.kscript").toNative().path ).isEqualTo("..\\home\\admin\\.kscript") } @Test fun `Test Windows to Cygwin`() { assertThat( - OsPath(OsType.WINDOWS, "C:\\home\\admin\\.kscript").toHosted(OsType.CYGWIN).path + OsPath(GlobalOsType.WINDOWS, "C:\\home\\admin\\.kscript").toHosted(GlobalOsType.CYGWIN).path ).isEqualTo("/cygdrive/c/home/admin/.kscript") assertThat( - OsPath(OsType.WINDOWS, "..\\home\\admin\\.kscript").toHosted(OsType.CYGWIN).path + OsPath(GlobalOsType.WINDOWS, "..\\home\\admin\\.kscript").toHosted(GlobalOsType.CYGWIN).path ).isEqualTo("../home/admin/.kscript") assertThat( OsPath( - OsType.WINDOWS, "C:\\Programs\\Cygwin\\home\\admin\\.kscript" - ).toHosted(OsType.CYGWIN).path + GlobalOsType.WINDOWS, "C:\\Programs\\Cygwin\\home\\admin\\.kscript" + ).toHosted(GlobalOsType.CYGWIN).path ).isEqualTo("~/.kscript") assertThat( OsPath( - OsType.WINDOWS, "C:\\Programs\\Cygwin\\usr\\local\\sdk" - ).toHosted(OsType.CYGWIN).path + GlobalOsType.WINDOWS, "C:\\Programs\\Cygwin\\usr\\local\\sdk" + ).toHosted(GlobalOsType.CYGWIN).path ).isEqualTo("/usr/local/sdk") } @Test fun `Test MSys to Windows`() { assertThat( - OsPath(OsType.MSYS, "/c/home/admin/.kscript").toNative().path + OsPath(GlobalOsType.MSYS, "/c/home/admin/.kscript").toNative().path ).isEqualTo("c:\\home\\admin\\.kscript") assertThat( - OsPath(OsType.MSYS, "~/.kscript").toNative().path + OsPath(GlobalOsType.MSYS, "~/.kscript").toNative().path ).isEqualTo("C:\\Programs\\Msys\\home\\admin\\.kscript") assertThat( - OsPath(OsType.MSYS, "/usr/local/bin/sdk").toNative().path + OsPath(GlobalOsType.MSYS, "/usr/local/bin/sdk").toNative().path ).isEqualTo("C:\\Programs\\Msys\\usr\\local\\bin\\sdk") assertThat( - OsPath(OsType.MSYS, "../home/admin/.kscript").toNative().path + OsPath(GlobalOsType.MSYS, "../home/admin/.kscript").toNative().path ).isEqualTo("..\\home\\admin\\.kscript") } @@ -92,24 +101,24 @@ class HostedOsPathTest { fun `Test Windows to MSys`() { assertThat( OsPath( - OsType.WINDOWS, "C:\\home\\admin\\.kscript" - ).toHosted(OsType.MSYS).path + GlobalOsType.WINDOWS, "C:\\home\\admin\\.kscript" + ).toHosted(GlobalOsType.MSYS).path ).isEqualTo("/c/home/admin/.kscript") assertThat( OsPath( - OsType.WINDOWS, "..\\home\\admin\\.kscript" - ).toHosted(OsType.MSYS).path + GlobalOsType.WINDOWS, "..\\home\\admin\\.kscript" + ).toHosted(GlobalOsType.MSYS).path ).isEqualTo("../home/admin/.kscript") assertThat( OsPath( - OsType.WINDOWS, "C:\\Programs\\Msys\\home\\admin\\.kscript" - ).toHosted(OsType.MSYS).path + GlobalOsType.WINDOWS, "C:\\Programs\\Msys\\home\\admin\\.kscript" + ).toHosted(GlobalOsType.MSYS).path ).isEqualTo("~/.kscript") assertThat( - OsPath(OsType.WINDOWS, "C:\\Programs\\Msys\\usr\\local\\sdk").toHosted(OsType.MSYS).path + OsPath(GlobalOsType.WINDOWS, "C:\\Programs\\Msys\\usr\\local\\sdk").toHosted(GlobalOsType.MSYS).path ).isEqualTo("/usr/local/sdk") } } diff --git a/src/test/kotlin/io/github/kscripting/os/model/PosixOsPathTest.kt b/src/test/kotlin/io/github/kscripting/os/model/PosixOsPathTest.kt index 011b891..dff996a 100644 --- a/src/test/kotlin/io/github/kscripting/os/model/PosixOsPathTest.kt +++ b/src/test/kotlin/io/github/kscripting/os/model/PosixOsPathTest.kt @@ -17,69 +17,69 @@ class PosixOsPathTest { @BeforeAll fun beforeAll() { - GlobalContext.registerOrReplace(OsType.LINUX, LinuxOs("userhome")) + GlobalContext.registerOrReplace(GlobalOsType.LINUX, LinuxOs(GlobalOsType.LINUX, "userhome")) } @Test fun `Test Posix paths`() { - assertThat(OsPath(OsType.LINUX, "/")).let { - it.prop(OsPath::osType).isEqualTo(OsType.LINUX) + assertThat(OsPath(GlobalOsType.LINUX, "/")).let { + it.prop(OsPath::osType).isEqualTo(GlobalOsType.LINUX) it.prop(OsPath::root).isEqualTo("/") it.prop(OsPath::pathParts).isEqualTo(emptyList()) } - assertThat(OsPath(OsType.LINUX, "/home/admin/.kscript")).let { - it.prop(OsPath::osType).isEqualTo(OsType.LINUX) + assertThat(OsPath(GlobalOsType.LINUX, "/home/admin/.kscript")).let { + it.prop(OsPath::osType).isEqualTo(GlobalOsType.LINUX) it.prop(OsPath::root).isEqualTo("/") it.prop(OsPath::pathParts).isEqualTo(listOf("home", "admin", ".kscript")) } - assertThat(OsPath(OsType.LINUX, "./home/admin/.kscript")).let { - it.prop(OsPath::osType).isEqualTo(OsType.LINUX) + assertThat(OsPath(GlobalOsType.LINUX, "./home/admin/.kscript")).let { + it.prop(OsPath::osType).isEqualTo(GlobalOsType.LINUX) it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(listOf("home", "admin", ".kscript")) } - assertThat(OsPath(OsType.LINUX, "")).let { - it.prop(OsPath::osType).isEqualTo(OsType.LINUX) + assertThat(OsPath(GlobalOsType.LINUX, "")).let { + it.prop(OsPath::osType).isEqualTo(GlobalOsType.LINUX) it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(emptyList()) } - assertThat(OsPath(OsType.LINUX, "file.txt")).let { - it.prop(OsPath::osType).isEqualTo(OsType.LINUX) + assertThat(OsPath(GlobalOsType.LINUX, "file.txt")).let { + it.prop(OsPath::osType).isEqualTo(GlobalOsType.LINUX) it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(listOf("file.txt")) } - assertThat(OsPath(OsType.LINUX, ".")).let { - it.prop(OsPath::osType).isEqualTo(OsType.LINUX) + assertThat(OsPath(GlobalOsType.LINUX, ".")).let { + it.prop(OsPath::osType).isEqualTo(GlobalOsType.LINUX) it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(emptyList()) } - assertThat(OsPath(OsType.LINUX, "../home/admin/.kscript")).let { - it.prop(OsPath::osType).isEqualTo(OsType.LINUX) + assertThat(OsPath(GlobalOsType.LINUX, "../home/admin/.kscript")).let { + it.prop(OsPath::osType).isEqualTo(GlobalOsType.LINUX) it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(listOf("..", "home", "admin", ".kscript")) } - assertThat(OsPath(OsType.LINUX, "..")).let { - it.prop(OsPath::osType).isEqualTo(OsType.LINUX) + assertThat(OsPath(GlobalOsType.LINUX, "..")).let { + it.prop(OsPath::osType).isEqualTo(GlobalOsType.LINUX) it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(listOf("..")) } //Duplicated separators are accepted - assertThat(OsPath(OsType.LINUX, "..//home////admin/.kscript/")).let { - it.prop(OsPath::osType).isEqualTo(OsType.LINUX) + assertThat(OsPath(GlobalOsType.LINUX, "..//home////admin/.kscript/")).let { + it.prop(OsPath::osType).isEqualTo(GlobalOsType.LINUX) it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(listOf("..", "home", "admin", ".kscript")) } //Both types of separator are accepted - assertThat(OsPath(OsType.LINUX, "..//home\\admin\\.kscript/")).let { - it.prop(OsPath::osType).isEqualTo(OsType.LINUX) + assertThat(OsPath(GlobalOsType.LINUX, "..//home\\admin\\.kscript/")).let { + it.prop(OsPath::osType).isEqualTo(GlobalOsType.LINUX) it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(listOf("..", "home", "admin", ".kscript")) } @@ -87,25 +87,25 @@ class PosixOsPathTest { @Test fun `Normalization of Posix paths`() { - assertThat(OsPath(OsType.LINUX, "/home/admin/.kscript/../../")).let { - it.prop(OsPath::osType).isEqualTo(OsType.LINUX) + assertThat(OsPath(GlobalOsType.LINUX, "/home/admin/.kscript/../../")).let { + it.prop(OsPath::osType).isEqualTo(GlobalOsType.LINUX) it.prop(OsPath::root).isEqualTo("/") it.prop(OsPath::pathParts).isEqualTo(listOf("home")) } - assertThat(OsPath(OsType.LINUX, "./././../../script")).let { - it.prop(OsPath::osType).isEqualTo(OsType.LINUX) + assertThat(OsPath(GlobalOsType.LINUX, "./././../../script")).let { + it.prop(OsPath::osType).isEqualTo(GlobalOsType.LINUX) it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(listOf("..", "..", "script")) } - assertThat(OsPath(OsType.LINUX, "/a/b/c/../d/script")).let { - it.prop(OsPath::osType).isEqualTo(OsType.LINUX) + assertThat(OsPath(GlobalOsType.LINUX, "/a/b/c/../d/script")).let { + it.prop(OsPath::osType).isEqualTo(GlobalOsType.LINUX) it.prop(OsPath::root).isEqualTo("/") it.prop(OsPath::pathParts).isEqualTo(listOf("a", "b", "d", "script")) } - assertFailure { OsPath(OsType.LINUX, "/.kscript/../../") }.isInstanceOf(IllegalArgumentException::class) + assertFailure { OsPath(GlobalOsType.LINUX, "/.kscript/../../") }.isInstanceOf(IllegalArgumentException::class) .hasMessage("Path after normalization goes beyond root element: '/'") } @@ -113,7 +113,7 @@ class PosixOsPathTest { fun `Test invalid Posix paths`() { assertFailure { OsPath( - OsType.LINUX, + GlobalOsType.LINUX, "/ad*asdf" ) }.isInstanceOf(IllegalArgumentException::class.java).hasMessage("Invalid character '*' in path part 'ad*asdf'") @@ -122,47 +122,47 @@ class PosixOsPathTest { @Test fun `Test Posix stringPath`() { assertThat( - OsPath(OsType.LINUX, "/home/admin/.kscript").path + OsPath(GlobalOsType.LINUX, "/home/admin/.kscript").path ).isEqualTo("/home/admin/.kscript") - assertThat(OsPath(OsType.LINUX, "/a/b/c/../d/script").path).isEqualTo("/a/b/d/script") - assertThat(OsPath(OsType.LINUX, "./././../../script").path).isEqualTo("../../script") - assertThat(OsPath(OsType.LINUX, "script/file.txt").path).isEqualTo("script/file.txt") + assertThat(OsPath(GlobalOsType.LINUX, "/a/b/c/../d/script").path).isEqualTo("/a/b/d/script") + assertThat(OsPath(GlobalOsType.LINUX, "./././../../script").path).isEqualTo("../../script") + assertThat(OsPath(GlobalOsType.LINUX, "script/file.txt").path).isEqualTo("script/file.txt") } @Test fun `Test Posix resolve`() { assertThat( - OsPath(OsType.LINUX, "/").resolve(OsPath(OsType.LINUX, "./.kscript/")) + OsPath(GlobalOsType.LINUX, "/").resolve(OsPath(GlobalOsType.LINUX, "./.kscript/")) .path ).isEqualTo("/.kscript") assertThat( - OsPath(OsType.LINUX, "/home/admin/").resolve(OsPath(OsType.LINUX, "./.kscript/")).path + OsPath(GlobalOsType.LINUX, "/home/admin/").resolve(OsPath(GlobalOsType.LINUX, "./.kscript/")).path ).isEqualTo("/home/admin/.kscript") assertThat( - OsPath(OsType.LINUX, "./home/admin/").resolve(OsPath(OsType.LINUX, "./.kscript/")).path + OsPath(GlobalOsType.LINUX, "./home/admin/").resolve(OsPath(GlobalOsType.LINUX, "./.kscript/")).path ).isEqualTo("home/admin/.kscript") assertThat( - OsPath(OsType.LINUX, "../home/admin/").resolve(OsPath(OsType.LINUX, "./.kscript/")).path + OsPath(GlobalOsType.LINUX, "../home/admin/").resolve(OsPath(GlobalOsType.LINUX, "./.kscript/")).path ).isEqualTo("../home/admin/.kscript") assertThat( - OsPath(OsType.LINUX, "..").resolve(OsPath(OsType.LINUX, "./.kscript/")).path + OsPath(GlobalOsType.LINUX, "..").resolve(OsPath(GlobalOsType.LINUX, "./.kscript/")).path ).isEqualTo("../.kscript") assertThat( - OsPath(OsType.LINUX, ".").resolve(OsPath(OsType.LINUX, "./.kscript/")).path + OsPath(GlobalOsType.LINUX, ".").resolve(OsPath(GlobalOsType.LINUX, "./.kscript/")).path ).isEqualTo(".kscript") assertFailure { - OsPath(OsType.LINUX, "./home/admin").resolve(OsPath(OsType.WINDOWS, ".\\run")) + OsPath(GlobalOsType.LINUX, "./home/admin").resolve(OsPath(GlobalOsType.WINDOWS, ".\\run")) }.isInstanceOf(IllegalArgumentException::class.java) .hasMessage("Paths from different OS's: 'LINUX' path can not be resolved with 'WINDOWS' path") assertFailure { - OsPath(OsType.LINUX, "./home/admin").resolve(OsPath(OsType.LINUX, "/run")) + OsPath(GlobalOsType.LINUX, "./home/admin").resolve(OsPath(GlobalOsType.LINUX, "/run")) }.isInstanceOf(IllegalArgumentException::class.java) .hasMessage("Can not resolve absolute or relative path 'home/admin' using absolute path '/run'") } diff --git a/src/test/kotlin/io/github/kscripting/os/model/WindowsOsPathTest.kt b/src/test/kotlin/io/github/kscripting/os/model/WindowsOsPathTest.kt index f260cf6..01c2496 100644 --- a/src/test/kotlin/io/github/kscripting/os/model/WindowsOsPathTest.kt +++ b/src/test/kotlin/io/github/kscripting/os/model/WindowsOsPathTest.kt @@ -16,69 +16,69 @@ import org.junit.jupiter.api.TestInstance class WindowsOsPathTest { @BeforeAll fun beforeAll() { - GlobalContext.registerOrReplace(OsType.WINDOWS, WindowsOs("C:\\Users\\Admin\\.kscript")) + GlobalContext.registerOrReplace(GlobalOsType.WINDOWS, WindowsOs(GlobalOsType.WINDOWS, "C:\\Users\\Admin\\.kscript")) } @Test fun `Test Windows paths`() { - assertThat(OsPath(OsType.WINDOWS, "C:\\")).let { - it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) + assertThat(OsPath(GlobalOsType.WINDOWS, "C:\\")).let { + it.prop(OsPath::osType).isEqualTo(GlobalOsType.WINDOWS) it.prop(OsPath::root).isEqualTo("C:\\") it.prop(OsPath::pathParts).isEqualTo(emptyList()) } - assertThat(OsPath(OsType.WINDOWS, "C:\\home\\admin\\.kscript")).let { - it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) + assertThat(OsPath(GlobalOsType.WINDOWS, "C:\\home\\admin\\.kscript")).let { + it.prop(OsPath::osType).isEqualTo(GlobalOsType.WINDOWS) it.prop(OsPath::root).isEqualTo("C:\\") it.prop(OsPath::pathParts).isEqualTo(listOf("home", "admin", ".kscript")) } - assertThat(OsPath(OsType.WINDOWS, "")).let { - it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) + assertThat(OsPath(GlobalOsType.WINDOWS, "")).let { + it.prop(OsPath::osType).isEqualTo(GlobalOsType.WINDOWS) it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(emptyList()) } - assertThat(OsPath(OsType.WINDOWS, "file.txt")).let { - it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) + assertThat(OsPath(GlobalOsType.WINDOWS, "file.txt")).let { + it.prop(OsPath::osType).isEqualTo(GlobalOsType.WINDOWS) it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(listOf("file.txt")) } - assertThat(OsPath(OsType.WINDOWS, ".")).let { - it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) + assertThat(OsPath(GlobalOsType.WINDOWS, ".")).let { + it.prop(OsPath::osType).isEqualTo(GlobalOsType.WINDOWS) it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(emptyList()) } - assertThat(OsPath(OsType.WINDOWS, ".\\home\\admin\\.kscript")).let { - it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) + assertThat(OsPath(GlobalOsType.WINDOWS, ".\\home\\admin\\.kscript")).let { + it.prop(OsPath::osType).isEqualTo(GlobalOsType.WINDOWS) it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(listOf("home", "admin", ".kscript")) } - assertThat(OsPath(OsType.WINDOWS, "..\\home\\admin\\.kscript")).let { - it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) + assertThat(OsPath(GlobalOsType.WINDOWS, "..\\home\\admin\\.kscript")).let { + it.prop(OsPath::osType).isEqualTo(GlobalOsType.WINDOWS) it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(listOf("..", "home", "admin", ".kscript")) } - assertThat(OsPath(OsType.WINDOWS, "..")).let { - it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) + assertThat(OsPath(GlobalOsType.WINDOWS, "..")).let { + it.prop(OsPath::osType).isEqualTo(GlobalOsType.WINDOWS) it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(listOf("..")) } //Duplicated separators are accepted - assertThat(OsPath(OsType.WINDOWS, "C:\\home\\\\\\\\admin\\.kscript\\")).let { - it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) + assertThat(OsPath(GlobalOsType.WINDOWS, "C:\\home\\\\\\\\admin\\.kscript\\")).let { + it.prop(OsPath::osType).isEqualTo(GlobalOsType.WINDOWS) it.prop(OsPath::root).isEqualTo("C:\\") it.prop(OsPath::pathParts).isEqualTo(listOf("home", "admin", ".kscript")) } //Both types of separator are accepted - assertThat(OsPath(OsType.WINDOWS, "C:/home\\admin/.kscript////")).let { - it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) + assertThat(OsPath(GlobalOsType.WINDOWS, "C:/home\\admin/.kscript////")).let { + it.prop(OsPath::osType).isEqualTo(GlobalOsType.WINDOWS) it.prop(OsPath::root).isEqualTo("C:\\") it.prop(OsPath::pathParts).isEqualTo(listOf("home", "admin", ".kscript")) } @@ -86,27 +86,27 @@ class WindowsOsPathTest { @Test fun `Normalization of Windows paths`() { - assertThat(OsPath(OsType.WINDOWS, "C:\\home\\admin\\.kscript\\..\\..\\")).let { - it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) + assertThat(OsPath(GlobalOsType.WINDOWS, "C:\\home\\admin\\.kscript\\..\\..\\")).let { + it.prop(OsPath::osType).isEqualTo(GlobalOsType.WINDOWS) it.prop(OsPath::root).isEqualTo("C:\\") it.prop(OsPath::pathParts).isEqualTo(listOf("home")) } - assertThat(OsPath(OsType.WINDOWS, ".\\.\\.\\..\\..\\script")).let { - it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) + assertThat(OsPath(GlobalOsType.WINDOWS, ".\\.\\.\\..\\..\\script")).let { + it.prop(OsPath::osType).isEqualTo(GlobalOsType.WINDOWS) it.prop(OsPath::root).isEqualTo("") it.prop(OsPath::pathParts).isEqualTo(listOf("..", "..", "script")) } - assertThat(OsPath(OsType.WINDOWS, "C:\\a\\b\\c\\..\\d\\script")).let { - it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) + assertThat(OsPath(GlobalOsType.WINDOWS, "C:\\a\\b\\c\\..\\d\\script")).let { + it.prop(OsPath::osType).isEqualTo(GlobalOsType.WINDOWS) it.prop(OsPath::root).isEqualTo("C:\\") it.prop(OsPath::pathParts).isEqualTo(listOf("a", "b", "d", "script")) } assertFailure { OsPath( - OsType.WINDOWS, + GlobalOsType.WINDOWS, "C:\\.kscript\\..\\..\\" ) }.isInstanceOf(IllegalArgumentException::class.java) @@ -115,11 +115,11 @@ class WindowsOsPathTest { @Test fun `Test invalid Windows paths`() { - assertFailure { OsPath(OsType.WINDOWS, "C:\\adas?df") } + assertFailure { OsPath(GlobalOsType.WINDOWS, "C:\\adas?df") } .isInstanceOf(IllegalArgumentException::class.java) .hasMessage("Invalid character '?' in path part 'adas?df'") - assertFailure { OsPath(OsType.WINDOWS, "home:\\vagrant") } + assertFailure { OsPath(GlobalOsType.WINDOWS, "home:\\vagrant") } .isInstanceOf(IllegalArgumentException::class.java) .hasMessage("Invalid character ':' in path part 'home:'") } @@ -128,17 +128,17 @@ class WindowsOsPathTest { fun `Test Windows stringPath`() { assertThat( OsPath( - OsType.WINDOWS, + GlobalOsType.WINDOWS, "C:\\home\\admin\\.kscript" ).path ).isEqualTo("C:\\home\\admin\\.kscript") assertThat( OsPath( - OsType.WINDOWS, + GlobalOsType.WINDOWS, "c:\\a\\b\\c\\..\\d\\script" ).path ).isEqualTo("c:\\a\\b\\d\\script") - assertThat(OsPath(OsType.WINDOWS, ".\\.\\.\\..\\..\\script").path).isEqualTo("..\\..\\script") - assertThat(OsPath(OsType.WINDOWS, "script\\file.txt").path).isEqualTo("script\\file.txt") + assertThat(OsPath(GlobalOsType.WINDOWS, ".\\.\\.\\..\\..\\script").path).isEqualTo("..\\..\\script") + assertThat(OsPath(GlobalOsType.WINDOWS, "script\\file.txt").path).isEqualTo("script\\file.txt") } } From c84e6039ffc4bb3d054984d5a067cf7f024e9466 Mon Sep 17 00:00:00 2001 From: Marcin Kuszczak <1508798+aartiPl@users.noreply.github.com> Date: Sat, 23 Dec 2023 20:07:11 +0100 Subject: [PATCH 44/50] Update --- build.gradle.kts | 36 ++-- .../shell/integration/ShellExecutorTest.kt | 3 - .../shell/integration/tools/TestContext.kt | 11 +- src/main/kotlin/io/github/kscripting/os/Os.kt | 16 +- .../io/github/kscripting/os/OsBuilder.kt | 26 +++ .../io/github/kscripting/os/OsTypeNew.kt | 11 ++ .../kotlin/io/github/kscripting/os/Vfs.kt | 104 ++++++++++++ .../github/kscripting/os/instance/CygwinOs.kt | 13 +- .../kscripting/os/instance/CygwinVfs.kt | 38 +++++ .../kscripting/os/instance/FreeBsdOs.kt | 8 +- .../kscripting/os/instance/FreeBsdVfs.kt | 10 ++ .../github/kscripting/os/instance/HostedOs.kt | 6 +- .../kscripting/os/instance/HostedVfs.kt | 8 + .../github/kscripting/os/instance/LinuxOs.kt | 8 +- .../github/kscripting/os/instance/LinuxVfs.kt | 10 ++ .../io/github/kscripting/os/instance/MacOs.kt | 8 +- .../github/kscripting/os/instance/MacOsVfs.kt | 10 ++ .../github/kscripting/os/instance/MsysOs.kt | 13 +- .../github/kscripting/os/instance/MsysVfs.kt | 36 ++++ .../github/kscripting/os/instance/PosixVfs.kt | 9 + .../kscripting/os/instance/WindowsOs.kt | 9 +- .../kscripting/os/instance/WindowsVfs.kt | 41 +++++ .../io/github/kscripting/os/model/OsPath.kt | 141 +--------------- .../github/kscripting/os/model/OsPathExt.kt | 159 +++++++----------- .../github/kscripting/shell/ShellExecutor.kt | 4 +- .../kscripting/shell/process/ProcessRunner.kt | 4 +- .../kscripting/os/model/GenericOsPathTest.kt | 10 +- .../kscripting/os/model/HostedOsPathTest.kt | 80 +++------ .../kscripting/os/model/PosixOsPathTest.kt | 149 ++++++++-------- .../kscripting/os/model/WindowsOsPathTest.kt | 141 +++++++--------- 30 files changed, 582 insertions(+), 540 deletions(-) create mode 100644 src/main/kotlin/io/github/kscripting/os/OsBuilder.kt create mode 100644 src/main/kotlin/io/github/kscripting/os/OsTypeNew.kt create mode 100644 src/main/kotlin/io/github/kscripting/os/Vfs.kt create mode 100644 src/main/kotlin/io/github/kscripting/os/instance/CygwinVfs.kt create mode 100644 src/main/kotlin/io/github/kscripting/os/instance/FreeBsdVfs.kt create mode 100644 src/main/kotlin/io/github/kscripting/os/instance/HostedVfs.kt create mode 100644 src/main/kotlin/io/github/kscripting/os/instance/LinuxVfs.kt create mode 100644 src/main/kotlin/io/github/kscripting/os/instance/MacOsVfs.kt create mode 100644 src/main/kotlin/io/github/kscripting/os/instance/MsysVfs.kt create mode 100644 src/main/kotlin/io/github/kscripting/os/instance/PosixVfs.kt create mode 100644 src/main/kotlin/io/github/kscripting/os/instance/WindowsVfs.kt diff --git a/build.gradle.kts b/build.gradle.kts index c5a7de2..fb8e9ea 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -21,6 +21,13 @@ kotlin { jvmToolchain(11) } +configurations.all { + resolutionStrategy.cacheDynamicVersionsFor(0, "seconds") + resolutionStrategy.cacheChangingModulesFor(0, "seconds") +} + +/* + sourceSets { create("itest") { kotlin.srcDir("$projectDir/src/itest/kotlin") @@ -35,10 +42,7 @@ configurations { get("itestImplementation").apply { extendsFrom(get("testImplementation")) } } -configurations.all { - resolutionStrategy.cacheDynamicVersionsFor(0, "seconds") - resolutionStrategy.cacheChangingModulesFor(0, "seconds") -} + tasks.create("itest") { val itags = System.getProperty("includeTags") ?: "" @@ -83,6 +87,21 @@ idea { } } +val testToolsJar by tasks.registering(Jar::class) { + //archiveFileName.set("eulenspiegel-testHelpers-$version.jar") + archiveClassifier.set("test") + include("io/github/kscripting/shell/integration/tools/*") + from(sourceSets["itest"].output) +} + + + +*/ +*/ + +//val publishArtifact = artifacts.add("archives", testToolsJar) +val publishArtifact = artifacts.add("archives", tasks.jar) //TODO: above line is correct - current should be removed + testlogger { showStandardStreams = true showFullStackTraces = false @@ -92,13 +111,6 @@ tasks.test { useJUnitPlatform() } -val testToolsJar by tasks.registering(Jar::class) { - //archiveFileName.set("eulenspiegel-testHelpers-$version.jar") - archiveClassifier.set("test") - include("io/github/kscripting/shell/integration/tools/*") - from(sourceSets["itest"].output) -} - val licencesSpec = Action { license { name.set("MIT License") @@ -120,8 +132,6 @@ val scmSpec = Action { url.set("https://github.com/kscripting/shell") } -val publishArtifact = artifacts.add("archives", testToolsJar) - publishing { publications { create("shell") { diff --git a/src/itest/kotlin/io/github/kscripting/shell/integration/ShellExecutorTest.kt b/src/itest/kotlin/io/github/kscripting/shell/integration/ShellExecutorTest.kt index 14efd76..26b9c29 100644 --- a/src/itest/kotlin/io/github/kscripting/shell/integration/ShellExecutorTest.kt +++ b/src/itest/kotlin/io/github/kscripting/shell/integration/ShellExecutorTest.kt @@ -1,7 +1,5 @@ package io.github.kscripting.shell.integration -import io.github.kscripting.os.model.readText -import io.github.kscripting.os.model.resolve import io.github.kscripting.shell.integration.tools.ShellTestBase import io.github.kscripting.shell.integration.tools.TestContext import io.github.kscripting.shell.integration.tools.TestContext.execPath @@ -32,7 +30,6 @@ class ShellExecutorTest : ShellTestBase { @Tag("windows") fun `Unicode characters output works`() { val path = testPath.resolve("unicodeOutput.txt") - println(path) verify( "doEcho -f $path", 0, diff --git a/src/itest/kotlin/io/github/kscripting/shell/integration/tools/TestContext.kt b/src/itest/kotlin/io/github/kscripting/shell/integration/tools/TestContext.kt index 99b3606..51427dd 100644 --- a/src/itest/kotlin/io/github/kscripting/shell/integration/tools/TestContext.kt +++ b/src/itest/kotlin/io/github/kscripting/shell/integration/tools/TestContext.kt @@ -1,6 +1,7 @@ package io.github.kscripting.shell.integration.tools import io.github.kscripting.os.Os +import io.github.kscripting.os.WindowsVfs import io.github.kscripting.os.instance.WindowsOs import io.github.kscripting.os.model.* import io.github.kscripting.shell.ShellExecutor @@ -9,15 +10,13 @@ import net.igsoft.typeutils.globalcontext.GlobalContext @Suppress("MemberVisibilityCanBePrivate") object TestContext { - init { - GlobalContext.registerOrReplace(GlobalOsType.WINDOWS, WindowsOs(GlobalOsType.WINDOWS, "/home/admin")) - } + val windowsVfs = WindowsVfs( "/home/admin") val osType: OsType = GlobalOsType.find(System.getProperty("osType")) ?: GlobalOsType.native - val projectPath: OsPath = OsPath(GlobalOsType.native, System.getProperty("projectPath")).toHosted(osType) - val execPath: OsPath = projectPath.resolve("build/shell_test/bin") - val testPath: OsPath = projectPath.resolve("build/shell_test/tmp") + val projectPath: OsPath<*> = OsPath(GlobalOsType.native, System.getProperty("projectPath")).toHosted(osType) + val execPath: OsPath<*> = projectPath.resolve("build/shell_test/bin") + val testPath: OsPath<*> = projectPath.resolve("build/shell_test/tmp") val pathEnvVariableName = if (osType.isWindowsLike()) "Path" else "PATH" val pathEnvVariableValue: String = System.getenv()[pathEnvVariableName]!! diff --git a/src/main/kotlin/io/github/kscripting/os/Os.kt b/src/main/kotlin/io/github/kscripting/os/Os.kt index a77a949..59e7c6d 100644 --- a/src/main/kotlin/io/github/kscripting/os/Os.kt +++ b/src/main/kotlin/io/github/kscripting/os/Os.kt @@ -1,23 +1,15 @@ package io.github.kscripting.os -import io.github.kscripting.os.model.OsPath -import io.github.kscripting.os.model.OsType -import net.igsoft.typeutils.marker.AutoTypedMarker - -val CURRENT_OS = AutoTypedMarker.create() - interface Os { //LINUX("linux"), MACOS("darwin"), WINDOWS("windows"), CYGWIN("cygwin"), MSYS("msys"), FREEBSD("freebsd"); // Exact comparison (it.osName.equals(name, true)) seems to be not feasible as there is also e.g. "darwin21" // "darwin19", "linux-musl" (for Docker Alpine), "linux-gnu" and maybe even other osTypes. But it seems that // startsWith() covers all cases. val osTypePrefix: String - val type: OsType -// val environment: Map -// val systemProperties: Map -// val fileSystem: Fs + val type: OsTypeNew - val userHome: OsPath - val pathSeparator: String + //val environment: Map + //val systemProperties: Map + val vfs: Vfs } diff --git a/src/main/kotlin/io/github/kscripting/os/OsBuilder.kt b/src/main/kotlin/io/github/kscripting/os/OsBuilder.kt new file mode 100644 index 0000000..3b73fda --- /dev/null +++ b/src/main/kotlin/io/github/kscripting/os/OsBuilder.kt @@ -0,0 +1,26 @@ +package io.github.kscripting.os + +import io.github.kscripting.os.model.GlobalOsType +import org.apache.commons.lang3.SystemUtils + +class OsBuilder { + + fun build(): Os { + TODO() + + } + + + val native: OsTypeNew = guessNativeType() + +// fun findByOsTypeString(osTypeString: String): OsTypeNew? = +// GlobalOsType.find { osTypeString.startsWith(it.os.osTypePrefix, true) } + + private fun guessNativeType(): OsTypeNew = when { + SystemUtils.IS_OS_MAC -> OsTypeNew.MACOS + SystemUtils.IS_OS_WINDOWS -> OsTypeNew.WINDOWS + SystemUtils.IS_OS_FREE_BSD -> OsTypeNew.FREEBSD + else -> OsTypeNew.LINUX + } + +} diff --git a/src/main/kotlin/io/github/kscripting/os/OsTypeNew.kt b/src/main/kotlin/io/github/kscripting/os/OsTypeNew.kt new file mode 100644 index 0000000..6bb128e --- /dev/null +++ b/src/main/kotlin/io/github/kscripting/os/OsTypeNew.kt @@ -0,0 +1,11 @@ +package io.github.kscripting.os + +enum class OsTypeNew { + LINUX, WINDOWS, CYGWIN, MSYS, MACOS, FREEBSD; + + fun isPosixLike() = + (this == LINUX || this == MACOS || this == FREEBSD || this == CYGWIN || this == MSYS) + + fun isPosixHostedOnWindows() = (this == CYGWIN || this == MSYS) + fun isWindowsLike() = (this == WINDOWS) +} \ No newline at end of file diff --git a/src/main/kotlin/io/github/kscripting/os/Vfs.kt b/src/main/kotlin/io/github/kscripting/os/Vfs.kt new file mode 100644 index 0000000..529c41d --- /dev/null +++ b/src/main/kotlin/io/github/kscripting/os/Vfs.kt @@ -0,0 +1,104 @@ +package io.github.kscripting.os + +import io.github.kscripting.os.model.OsPath + + +interface Vfs { + val type: OsTypeNew + val pathSeparator: String + val userHome: OsPath + + //Relaxed validation: + //1. It doesn't matter if there is '/' or '\' used as path separator - both are treated the same + //2. Duplicated or trailing slashes '/' and backslashes '\' are ignored + fun createOsPath(path: String): OsPath + fun createOsPath(root: String, pathParts: List): OsPath = + createOsPath(root.trim() + "/" + pathParts.joinToString("/")) + + fun createOsPath(pathParts: List): OsPath = + createOsPath(pathParts.joinToString("/")) + + fun createOsPath(vararg pathParts: String): OsPath = + createOsPath(pathParts.joinToString("/")) + + fun isValid(path: String): Boolean + + fun normalize(osPath: OsPath): OsPath { + //Relative: + // ./../ --> ../ + // ./a/../ --> ./ + // ./a/ --> ./a + // ../a --> ../a + // ../../a --> ../../a + + //Absolute: + // /../ --> invalid (above root) + // /a/../ --> / + + val newParts = mutableListOf() + var index = 0 + + while (index < osPath.pathParts.size) { + if (osPath.pathParts[index] == ".") { + //Just skip . without adding it to newParts + } else if (osPath.pathParts[index] == "..") { + if (osPath.isAbsolute && newParts.size == 0) { + throw IllegalArgumentException("Path after normalization goes beyond root element: '${osPath.root}'") + } + + if (newParts.size > 0) { + when (newParts.last()) { + "." -> { + //It's the first element - other dots should be already removed before + newParts.removeAt(newParts.size - 1) + newParts.add("..") + } + + ".." -> { + newParts.add("..") + } + + else -> { + newParts.removeAt(newParts.size - 1) + } + } + } else { + newParts.add("..") + } + } else { + newParts.add(osPath.pathParts[index]) + } + + index += 1 + } + + return OsPath(osPath.vfs, osPath.root, newParts) + } + + fun toNative(osPath: OsPath): OsPath = osPath + fun toHosted(osPath: OsPath): OsPath = osPath +} + +fun createPosixOsPath(vfs: T, path: String): OsPath { + require(vfs.isValid(path)) + + //Detect root + val root: String = when { + path.startsWith("~/") || path.startsWith("~\\") -> "~" + path.startsWith("/") -> "/" + else -> "" + } + + return createFinalPath(vfs, path, root) +} + +fun createFinalPath(vfs: T, path: String, root: String): OsPath { + //Remove also empty path parts - there were duplicated or trailing slashes / backslashes in initial path + val pathWithoutRoot = path.drop(root.length) + + require(vfs.isValid(pathWithoutRoot)) + + val pathPartsResolved = pathWithoutRoot.split('/', '\\').filter { it.isNotBlank() } + return vfs.normalize(OsPath(vfs, root, pathPartsResolved)) +} + diff --git a/src/main/kotlin/io/github/kscripting/os/instance/CygwinOs.kt b/src/main/kotlin/io/github/kscripting/os/instance/CygwinOs.kt index 65560c6..9718013 100644 --- a/src/main/kotlin/io/github/kscripting/os/instance/CygwinOs.kt +++ b/src/main/kotlin/io/github/kscripting/os/instance/CygwinOs.kt @@ -1,15 +1,8 @@ package io.github.kscripting.os.instance -import io.github.kscripting.os.model.OsPath -import io.github.kscripting.os.model.OsType +import io.github.kscripting.os.OsTypeNew -class CygwinOs( - override val type: OsType, - override val nativeType: OsType, - userHome: String, - nativeFileSystemRoot: String -) : HostedOs { +class CygwinOs(override val vfs: CygwinVfs, override val nativeOs: WindowsOs) : HostedOs { + override val type: OsTypeNew = OsTypeNew.CYGWIN override val osTypePrefix: String = "cygwin" - override val userHome: OsPath = OsPath(type, userHome) - override val nativeFileSystemRoot: OsPath = OsPath(nativeType, nativeFileSystemRoot) } diff --git a/src/main/kotlin/io/github/kscripting/os/instance/CygwinVfs.kt b/src/main/kotlin/io/github/kscripting/os/instance/CygwinVfs.kt new file mode 100644 index 0000000..fe46e5f --- /dev/null +++ b/src/main/kotlin/io/github/kscripting/os/instance/CygwinVfs.kt @@ -0,0 +1,38 @@ +package io.github.kscripting.os.instance + +import io.github.kscripting.os.OsTypeNew +import io.github.kscripting.os.Vfs +import io.github.kscripting.os.createPosixOsPath +import io.github.kscripting.os.model.OsPath + +class CygwinVfs(override val nativeFsRoot: OsPath, userHome: String) : HostedVfs, + PosixVfs(OsTypeNew.CYGWIN) { + + override fun toNative(osPath: OsPath): OsPath { + val newParts = mutableListOf() + var newRoot = "" + + if (osPath.isAbsolute) { + if (osPath.pathParts[0].equals("cygdrive", true)) { //Paths referring /cygdrive + newRoot = osPath.pathParts[1] + ":\\" + newParts.addAll(osPath.pathParts.subList(2, osPath.pathParts.size)) + } else if (osPath.root == "~") { //Paths starting with ~ + newRoot = this.nativeFsRoot.root + newParts.addAll(this.nativeFsRoot.pathParts) + newParts.addAll(this.userHome.pathParts) + newParts.addAll(osPath.pathParts) + } else { //Any other path like: /usr/bin + newRoot = this.nativeFsRoot.root + newParts.addAll(this.nativeFsRoot.pathParts) + newParts.addAll(osPath.pathParts) + } + } else { + newParts.addAll(osPath.pathParts) + } + + return OsPath(nativeFsRoot.vfs, newRoot, newParts) + } + + override val userHome: OsPath = createPosixOsPath(this, userHome) + override fun createOsPath(path: String): OsPath = createPosixOsPath(this, path) +} diff --git a/src/main/kotlin/io/github/kscripting/os/instance/FreeBsdOs.kt b/src/main/kotlin/io/github/kscripting/os/instance/FreeBsdOs.kt index baa752e..2b3bf46 100644 --- a/src/main/kotlin/io/github/kscripting/os/instance/FreeBsdOs.kt +++ b/src/main/kotlin/io/github/kscripting/os/instance/FreeBsdOs.kt @@ -1,11 +1,9 @@ package io.github.kscripting.os.instance import io.github.kscripting.os.Os -import io.github.kscripting.os.model.OsPath -import io.github.kscripting.os.model.OsType +import io.github.kscripting.os.OsTypeNew -class FreeBsdOs(override val type: OsType, userHome: String) : Os { +class FreeBsdOs(override val vfs: FreeBsdVfs) : Os { + override val type: OsTypeNew = OsTypeNew.FREEBSD override val osTypePrefix: String = "freebsd" - override val pathSeparator: String get() = "/" - override val userHome: OsPath = OsPath(type, userHome) } diff --git a/src/main/kotlin/io/github/kscripting/os/instance/FreeBsdVfs.kt b/src/main/kotlin/io/github/kscripting/os/instance/FreeBsdVfs.kt new file mode 100644 index 0000000..48cc6bb --- /dev/null +++ b/src/main/kotlin/io/github/kscripting/os/instance/FreeBsdVfs.kt @@ -0,0 +1,10 @@ +package io.github.kscripting.os.instance + +import io.github.kscripting.os.OsTypeNew +import io.github.kscripting.os.createPosixOsPath +import io.github.kscripting.os.model.OsPath + +class FreeBsdVfs(userHome: String) : PosixVfs(OsTypeNew.FREEBSD) { + override val userHome: OsPath = createOsPath(userHome) + override fun createOsPath(path: String): OsPath = createPosixOsPath(this, path) +} diff --git a/src/main/kotlin/io/github/kscripting/os/instance/HostedOs.kt b/src/main/kotlin/io/github/kscripting/os/instance/HostedOs.kt index e0cb5ef..cbb50f0 100644 --- a/src/main/kotlin/io/github/kscripting/os/instance/HostedOs.kt +++ b/src/main/kotlin/io/github/kscripting/os/instance/HostedOs.kt @@ -1,11 +1,7 @@ package io.github.kscripting.os.instance import io.github.kscripting.os.Os -import io.github.kscripting.os.model.OsPath -import io.github.kscripting.os.model.OsType interface HostedOs : Os { - val nativeType: OsType - val nativeFileSystemRoot: OsPath - override val pathSeparator: String get() = "/" + val nativeOs: Os } diff --git a/src/main/kotlin/io/github/kscripting/os/instance/HostedVfs.kt b/src/main/kotlin/io/github/kscripting/os/instance/HostedVfs.kt new file mode 100644 index 0000000..38e16e4 --- /dev/null +++ b/src/main/kotlin/io/github/kscripting/os/instance/HostedVfs.kt @@ -0,0 +1,8 @@ +package io.github.kscripting.os.instance + +import io.github.kscripting.os.Vfs +import io.github.kscripting.os.model.OsPath + +interface HostedVfs : Vfs { + val nativeFsRoot: OsPath +} diff --git a/src/main/kotlin/io/github/kscripting/os/instance/LinuxOs.kt b/src/main/kotlin/io/github/kscripting/os/instance/LinuxOs.kt index 9e272b6..0cee036 100644 --- a/src/main/kotlin/io/github/kscripting/os/instance/LinuxOs.kt +++ b/src/main/kotlin/io/github/kscripting/os/instance/LinuxOs.kt @@ -1,11 +1,9 @@ package io.github.kscripting.os.instance import io.github.kscripting.os.Os -import io.github.kscripting.os.model.OsPath -import io.github.kscripting.os.model.OsType +import io.github.kscripting.os.OsTypeNew -class LinuxOs(override val type: OsType, userHome: String) : Os { +class LinuxOs(override val vfs: LinuxVfs) : Os { + override val type: OsTypeNew = OsTypeNew.LINUX override val osTypePrefix: String = "linux" - override val pathSeparator: String get() = "/" - override val userHome: OsPath = OsPath(type, userHome) } diff --git a/src/main/kotlin/io/github/kscripting/os/instance/LinuxVfs.kt b/src/main/kotlin/io/github/kscripting/os/instance/LinuxVfs.kt new file mode 100644 index 0000000..a0fbf26 --- /dev/null +++ b/src/main/kotlin/io/github/kscripting/os/instance/LinuxVfs.kt @@ -0,0 +1,10 @@ +package io.github.kscripting.os.instance + +import io.github.kscripting.os.OsTypeNew +import io.github.kscripting.os.createPosixOsPath +import io.github.kscripting.os.model.OsPath + +class LinuxVfs(userHome: String) : PosixVfs(OsTypeNew.LINUX) { + override val userHome: OsPath = createOsPath(userHome) + override fun createOsPath(path: String): OsPath = createPosixOsPath(this, path) +} diff --git a/src/main/kotlin/io/github/kscripting/os/instance/MacOs.kt b/src/main/kotlin/io/github/kscripting/os/instance/MacOs.kt index ce250a1..6bb0f14 100644 --- a/src/main/kotlin/io/github/kscripting/os/instance/MacOs.kt +++ b/src/main/kotlin/io/github/kscripting/os/instance/MacOs.kt @@ -1,11 +1,9 @@ package io.github.kscripting.os.instance import io.github.kscripting.os.Os -import io.github.kscripting.os.model.OsPath -import io.github.kscripting.os.model.OsType +import io.github.kscripting.os.OsTypeNew -class MacOs(override val type: OsType, userHome: String) : Os { +class MacOs(override val vfs: MacOsVfs) : Os { + override val type: OsTypeNew = OsTypeNew.MACOS override val osTypePrefix: String = "darwin" - override val pathSeparator: String get() = "/" - override val userHome: OsPath = OsPath(type, userHome) } diff --git a/src/main/kotlin/io/github/kscripting/os/instance/MacOsVfs.kt b/src/main/kotlin/io/github/kscripting/os/instance/MacOsVfs.kt new file mode 100644 index 0000000..8bfd66a --- /dev/null +++ b/src/main/kotlin/io/github/kscripting/os/instance/MacOsVfs.kt @@ -0,0 +1,10 @@ +package io.github.kscripting.os.instance + +import io.github.kscripting.os.OsTypeNew +import io.github.kscripting.os.createPosixOsPath +import io.github.kscripting.os.model.OsPath + +class MacOsVfs(userHome: String) : PosixVfs(OsTypeNew.MACOS) { + override val userHome: OsPath = createOsPath(userHome) + override fun createOsPath(path: String): OsPath = createPosixOsPath(this, path) +} diff --git a/src/main/kotlin/io/github/kscripting/os/instance/MsysOs.kt b/src/main/kotlin/io/github/kscripting/os/instance/MsysOs.kt index 9780548..5f69184 100644 --- a/src/main/kotlin/io/github/kscripting/os/instance/MsysOs.kt +++ b/src/main/kotlin/io/github/kscripting/os/instance/MsysOs.kt @@ -1,15 +1,8 @@ package io.github.kscripting.os.instance -import io.github.kscripting.os.model.OsPath -import io.github.kscripting.os.model.OsType +import io.github.kscripting.os.OsTypeNew -class MsysOs( - override val type: OsType, - override val nativeType: OsType, - userHome: String, - nativeFileSystemRoot: String -) : HostedOs { +class MsysOs(override val nativeOs: WindowsOs, override val vfs: MsysVfs) : HostedOs { + override val type: OsTypeNew = OsTypeNew.MSYS override val osTypePrefix: String = "msys" - override val userHome: OsPath = OsPath(type, userHome) - override val nativeFileSystemRoot: OsPath = OsPath(nativeType, nativeFileSystemRoot) } diff --git a/src/main/kotlin/io/github/kscripting/os/instance/MsysVfs.kt b/src/main/kotlin/io/github/kscripting/os/instance/MsysVfs.kt new file mode 100644 index 0000000..f1386fe --- /dev/null +++ b/src/main/kotlin/io/github/kscripting/os/instance/MsysVfs.kt @@ -0,0 +1,36 @@ +package io.github.kscripting.os.instance + +import io.github.kscripting.os.OsTypeNew +import io.github.kscripting.os.Vfs +import io.github.kscripting.os.createPosixOsPath +import io.github.kscripting.os.model.OsPath + +class MsysVfs(override val nativeFsRoot: OsPath, userHome: String) : HostedVfs, PosixVfs(OsTypeNew.MSYS) { + override val userHome: OsPath = createOsPath(userHome) + override fun createOsPath(path: String): OsPath = createPosixOsPath(this, path) + + override fun toNative(osPath: OsPath): OsPath { + val newParts = mutableListOf() + var newRoot = "" + + if (osPath.isAbsolute) { + if (osPath.pathParts[0].length == 1 && (osPath.pathParts[0][0].code in 65..90 || osPath.pathParts[0][0].code in 97..122)) { //Paths referring with drive letter at the beginning + newRoot = osPath.pathParts[0] + ":\\" + newParts.addAll(osPath.pathParts.subList(1, osPath.pathParts.size)) + } else if (osPath.root == "~") { //Paths starting with ~ + newRoot = this.nativeFsRoot.root + newParts.addAll(this.nativeFsRoot.pathParts) + newParts.addAll(this.userHome.pathParts) + newParts.addAll(osPath.pathParts) + } else { //Any other path like: /usr/bin + newRoot = this.nativeFsRoot.root + newParts.addAll(this.nativeFsRoot.pathParts) + newParts.addAll(osPath.pathParts) + } + } else { + newParts.addAll(osPath.pathParts) + } + + return OsPath(nativeFsRoot.vfs, newRoot, newParts) + } +} diff --git a/src/main/kotlin/io/github/kscripting/os/instance/PosixVfs.kt b/src/main/kotlin/io/github/kscripting/os/instance/PosixVfs.kt new file mode 100644 index 0000000..3491b5c --- /dev/null +++ b/src/main/kotlin/io/github/kscripting/os/instance/PosixVfs.kt @@ -0,0 +1,9 @@ +package io.github.kscripting.os.instance + +import io.github.kscripting.os.OsTypeNew +import io.github.kscripting.os.Vfs + +abstract class PosixVfs(override val type: OsTypeNew) : Vfs { + override val pathSeparator: String = "/" + override fun isValid(path: String): Boolean = true +} \ No newline at end of file diff --git a/src/main/kotlin/io/github/kscripting/os/instance/WindowsOs.kt b/src/main/kotlin/io/github/kscripting/os/instance/WindowsOs.kt index 9f991e3..6abca86 100644 --- a/src/main/kotlin/io/github/kscripting/os/instance/WindowsOs.kt +++ b/src/main/kotlin/io/github/kscripting/os/instance/WindowsOs.kt @@ -1,12 +1,9 @@ package io.github.kscripting.os.instance import io.github.kscripting.os.Os -import io.github.kscripting.os.model.OsPath -import io.github.kscripting.os.model.OsType +import io.github.kscripting.os.OsTypeNew - -class WindowsOs(override val type: OsType, userHome: String) : Os { +class WindowsOs(override val vfs: WindowsVfs) : Os { + override val type: OsTypeNew = OsTypeNew.WINDOWS override val osTypePrefix: String = "windows" - override val pathSeparator: String get() = "\\" - override val userHome: OsPath = OsPath(type, userHome) } diff --git a/src/main/kotlin/io/github/kscripting/os/instance/WindowsVfs.kt b/src/main/kotlin/io/github/kscripting/os/instance/WindowsVfs.kt new file mode 100644 index 0000000..091e1cc --- /dev/null +++ b/src/main/kotlin/io/github/kscripting/os/instance/WindowsVfs.kt @@ -0,0 +1,41 @@ +package io.github.kscripting.os.instance + +import io.github.kscripting.os.OsTypeNew +import io.github.kscripting.os.Vfs +import io.github.kscripting.os.createFinalPath +import io.github.kscripting.os.model.OsPath + +class WindowsVfs(userHome: String) : Vfs { + override val type: OsTypeNew = OsTypeNew.WINDOWS + override val pathSeparator: String = "\\" + + //https://stackoverflow.com/questions/1976007/what-characters-are-forbidden-in-windows-and-linux-directory-names + //The rule here is more strict than necessary, but it is at least good practice to follow such a rule. + private val windowsDriveRegex = + "^([a-zA-Z]:(?=[\\\\/])|\\\\\\\\(?:[^*:<>?\\\\/|]+\\\\[^*:<>?\\\\/|]+|\\?\\\\(?:[a-zA-Z]:(?=\\\\)|(?:UNC\\\\)?[^*:<>?\\\\/|]+\\\\[^*:<>?\\\\/|]+)))".toRegex() + + + override val userHome: OsPath = createOsPath(userHome) + + override fun createOsPath(path: String): OsPath { + //Detect root + val root = when { + path.startsWith("~/") || path.startsWith("~\\") -> "~" + else -> { + val match = windowsDriveRegex.find(path) + val matchedRoot = match?.groupValues?.get(1) + if (matchedRoot != null) matchedRoot + "\\" else "" + } + } + + return createFinalPath(this, path, root) + } + + override fun isValid(path: String): Boolean { + return path.none { INVALID_CHARS.contains(it) } + } + + companion object { + private const val INVALID_CHARS = "<>:\"|?*" + } +} diff --git a/src/main/kotlin/io/github/kscripting/os/model/OsPath.kt b/src/main/kotlin/io/github/kscripting/os/model/OsPath.kt index 73721ce..cb85577 100644 --- a/src/main/kotlin/io/github/kscripting/os/model/OsPath.kt +++ b/src/main/kotlin/io/github/kscripting/os/model/OsPath.kt @@ -1,5 +1,8 @@ package io.github.kscripting.os.model +import io.github.kscripting.os.OsTypeNew +import io.github.kscripting.os.Vfs + sealed interface OsPathError { object EmptyPath : OsPathError, RuntimeException() data class InvalidConversion(val errorMessage: String) : OsPathError, RuntimeException(errorMessage) @@ -7,144 +10,14 @@ sealed interface OsPathError { //Path representation for different OSes @Suppress("MemberVisibilityCanBePrivate") -data class OsPath private constructor(val osType: OsType<*>, val root: String, val pathParts: List) { +//TODO: should be only instantiated from VFS, not in any place +data class OsPath(@Transient internal val vfs: T, val root: String, val pathParts: List) { + val osType: OsTypeNew = vfs.type val isRelative: Boolean get() = root.isEmpty() && pathParts.isNotEmpty() val isAbsolute: Boolean get() = root.isNotEmpty() - val path: String get() = root + pathParts.joinToString(osType.os.pathSeparator) { it } + val path: String get() = root + pathParts.joinToString(vfs.pathSeparator) { it } val leaf: String get() = if (pathParts.isEmpty()) root else pathParts.last() override fun toString(): String = "$path [$osType]" - - companion object { - //https://stackoverflow.com/questions/1976007/what-characters-are-forbidden-in-windows-and-linux-directory-names - //The rule here is more strict than necessary, but it is at least good practice to follow such a rule. - //TODO: should I remove validation all together? It allows e.g. for globbing with asterisks and question marks - // maybe separate function in FileSystem: validate? - private val forbiddenCharacters = buildSet { - add('<') - add('>') - add(':') - add('"') - add('|') - add('?') - add('*') - for (i in 0 until 32) { - add(i.toChar()) - } - } - - private val windowsDriveRegex = - "^([a-zA-Z]:(?=[\\\\/])|\\\\\\\\(?:[^*:<>?\\\\/|]+\\\\[^*:<>?\\\\/|]+|\\?\\\\(?:[a-zA-Z]:(?=\\\\)|(?:UNC\\\\)?[^*:<>?\\\\/|]+\\\\[^*:<>?\\\\/|]+)))".toRegex() - - //Relaxed validation: - //1. It doesn't matter if there is '/' or '\' used as path separator - both are treated the same - //2. Duplicated or trailing slashes '/' and backslashes '\' are ignored - - operator fun invoke(osType: OsType<*>, root: String, pathParts: List): OsPath { - val newRoot = root.trim() - - if (newRoot.isEmpty() && pathParts.isEmpty()) { - throw OsPathError.EmptyPath - } - - return normalize(validate(OsPath(osType, newRoot, pathParts))) - } - - operator fun invoke(vararg pathParts: String): OsPath { - return invoke(GlobalOsType.native, pathParts.toList()) - } - - operator fun invoke(osType: OsType<*>, vararg pathParts: String): OsPath { - return invoke(osType, pathParts.toList()) - } - - operator fun invoke(osType: OsType<*>, pathParts: List): OsPath { - val path = pathParts.joinToString("/") - - //Detect root - val root: String = when { - path.startsWith("~/") || path.startsWith("~\\") -> "~" - osType.isPosixLike() && path.startsWith("/") -> "/" - osType.isWindowsLike() -> { - val match = windowsDriveRegex.find(path) - val matchedRoot = match?.groupValues?.get(1) - if (matchedRoot != null) matchedRoot + "\\" else "" - } - - else -> "" - } - - //Remove also empty path parts - there were duplicated or trailing slashes / backslashes in initial path - val pathPartsResolved = path.drop(root.length).split('/', '\\').filter { it.isNotBlank() } - - return normalize(validate(OsPath(osType, root, pathPartsResolved))) - } - - fun validate(osPath: OsPath): OsPath { - //TODO: https://learn.microsoft.com/pl-pl/dotnet/standard/io/file-path-formats - // https://regex101.com/r/aU4yZ7/1 - - osPath.pathParts.forEach { part-> - val invalidChar = part.find { forbiddenCharacters.contains(it) } - if (invalidChar != null) { - throw IllegalArgumentException("Invalid character '$invalidChar' in path part '$part'") - } - } - - return osPath - } - - fun normalize(osPath: OsPath): OsPath { - //Relative: - // ./../ --> ../ - // ./a/../ --> ./ - // ./a/ --> ./a - // ../a --> ../a - // ../../a --> ../../a - - //Absolute: - // /../ --> invalid (above root) - // /a/../ --> / - - val newParts = mutableListOf() - var index = 0 - - while (index 0) { - when (newParts.last()) { - "." -> { - //It's the first element - other dots should be already removed before - newParts.removeAt(newParts.size - 1) - newParts.add("..") - } - - ".." -> { - newParts.add("..") - } - - else -> { - newParts.removeAt(newParts.size - 1) - } - } - } else { - newParts.add("..") - } - } else { - newParts.add(osPath.pathParts[index]) - } - - index += 1 - } - - return OsPath(osPath.osType,osPath.root, newParts) - } - } } diff --git a/src/main/kotlin/io/github/kscripting/os/model/OsPathExt.kt b/src/main/kotlin/io/github/kscripting/os/model/OsPathExt.kt index 84f2500..8c5dcc9 100644 --- a/src/main/kotlin/io/github/kscripting/os/model/OsPathExt.kt +++ b/src/main/kotlin/io/github/kscripting/os/model/OsPathExt.kt @@ -1,71 +1,33 @@ package io.github.kscripting.os.model +import io.github.kscripting.os.OsTypeNew +import io.github.kscripting.os.Vfs import io.github.kscripting.os.instance.HostedOs import java.io.File + +//import io.github.kscripting.os.Vfs +import io.github.kscripting.os.instance.HostedVfs import java.net.URI -import java.nio.charset.Charset -import java.nio.file.OpenOption import java.nio.file.Path import java.nio.file.Paths -import kotlin.io.path.* - -fun OsPath.toNative(): OsPath { - if (!osType.isPosixHostedOnWindows()) { - //Everything besides Cygwin/Msys is native... - return this - } - - val hostedOs = osType.os as HostedOs - - val newParts = mutableListOf() - var newRoot = "" - - if (isAbsolute) { - when (osType) { - GlobalOsType.CYGWIN -> { - if (pathParts[0].equals("cygdrive", true)) { //Paths referring /cygdrive - newRoot = pathParts[1] + ":\\" - newParts.addAll(pathParts.subList(2, pathParts.size)) - } else if (root == "~") { //Paths starting with ~ - newRoot = hostedOs.nativeFileSystemRoot.root - newParts.addAll(hostedOs.nativeFileSystemRoot.pathParts) - newParts.addAll(hostedOs.userHome.pathParts) - newParts.addAll(pathParts) - } else { //Any other path like: /usr/bin - newRoot = hostedOs.nativeFileSystemRoot.root - newParts.addAll(hostedOs.nativeFileSystemRoot.pathParts) - newParts.addAll(pathParts) - } - } - - GlobalOsType.MSYS -> { - if (pathParts[0].length == 1 && (pathParts[0][0].code in 65..90 || pathParts[0][0].code in 97..122)) { //Paths referring with drive letter at the beginning - newRoot = pathParts[0] + ":\\" - newParts.addAll(pathParts.subList(1, pathParts.size)) - } else if (root == "~") { //Paths starting with ~ - newRoot = hostedOs.nativeFileSystemRoot.root - newParts.addAll(hostedOs.nativeFileSystemRoot.pathParts) - newParts.addAll(hostedOs.userHome.pathParts) - newParts.addAll(pathParts) - } else { //Any other path like: /usr/bin - newRoot = hostedOs.nativeFileSystemRoot.root - newParts.addAll(hostedOs.nativeFileSystemRoot.pathParts) - newParts.addAll(pathParts) - } - } - } - } else { - newParts.addAll(pathParts) - } - - return OsPath(hostedOs.nativeType, newRoot, newParts) -} +//import java.io.File +//import java.net.URI +//import java.nio.charset.Charset +//import java.nio.file.OpenOption +//import java.nio.file.Path +//import java.nio.file.Paths +//import kotlin.io.path.* +// +fun OsPath.toNative(): OsPath<*> = vfs.toNative(this) + +/* fun List.startsWith(list: List): Boolean = (this.size >= list.size && this.subList(0, list.size) == list) -fun OsPath.startsWith(osPath: OsPath): Boolean = root == osPath.root && pathParts.startsWith(osPath.pathParts) +fun OsPath.startsWith(osPath: OsPath): Boolean = root == osPath.root && pathParts.startsWith(osPath.pathParts) -fun OsPath.toHosted(targetOs: OsType<*>): OsPath { + +fun OsPath.toHosted(targetOs: OsType<*>): OsPath { if (osType == targetOs) { //This is already targetOs... return this @@ -120,46 +82,45 @@ fun OsPath.toHosted(targetOs: OsType<*>): OsPath { return OsPath(targetOs, newRoot, newParts) } +*/ -// Conversion to OsPath -fun File.toOsPath(): OsPath = OsPath(GlobalOsType.native, absolutePath) - -fun Path.toOsPath(): OsPath = OsPath(GlobalOsType.native, absolutePathString()) - -fun URI.toOsPath(): OsPath = - if (this.scheme == "file") File(this).toOsPath() else throw IllegalArgumentException("Invalid conversion from URL to OsPath") - - -// Conversion from OsPath -fun OsPath.toNativePath(): Path = Paths.get(toNative().path) - -fun OsPath.toNativeFile(): File = File(toNative().path) +//// Conversion to OsPath +//fun File.toOsPath(): OsPath = OsPath(GlobalOsType.native, absolutePath) +// +//fun Path.toOsPath(): OsPath = OsPath(GlobalOsType.native, absolutePathString()) +// +//fun URI.toOsPath(): OsPath = +// if (this.scheme == "file") File(this).toOsPath() else throw IllegalArgumentException("Invalid conversion from URL to OsPath") -fun OsPath.toNativeUri(): URI = File(toNative().path).toURI() +//// Conversion from OsPath +fun OsPath<*>.toNativePath(): Path = Paths.get(toNative().path) +fun OsPath<*>.toNativeFile(): File = File(toNative().path) +fun OsPath<*>.toNativeUri(): URI = File(toNative().path).toURI() -// OsPath operations -fun OsPath.exists(): Boolean = toNativePath().exists() -fun OsPath.createDirectories(): OsPath = OsPath(nativeType, toNativePath().createDirectories().pathString) +//// OsPath operations +//fun OsPath.exists(): Boolean = toNativePath().exists() +// +//fun OsPath.createDirectories(): OsPath = OsPath(nativeType, toNativePath().createDirectories().pathString) +// +//fun OsPath.copyTo(target: OsPath, overwrite: Boolean = false): OsPath = +// OsPath(nativeType, toNativePath().copyTo(target.toNativePath(), overwrite).pathString) +// +// +//fun OsPath.writeText(text: CharSequence, charset: Charset = Charsets.UTF_8, vararg options: OpenOption): Unit = +// toNativePath().writeText(text, charset, *options) +// +//fun OsPath.readText(charset: Charset = Charsets.UTF_8): String = toNativePath().readText(charset) +// -fun OsPath.copyTo(target: OsPath, overwrite: Boolean = false): OsPath = - OsPath(nativeType, toNativePath().copyTo(target.toNativePath(), overwrite).pathString) +operator fun OsPath.div(osPath: OsPath): OsPath = resolve(osPath) +operator fun OsPath.div(path: String): OsPath = resolve(path) -fun OsPath.writeText(text: CharSequence, charset: Charset = Charsets.UTF_8, vararg options: OpenOption): Unit = - toNativePath().writeText(text, charset, *options) +fun OsPath.resolve(vararg pathParts: String): OsPath = resolve(vfs.createOsPath(pathParts.joinToString("/")) as OsPath) -fun OsPath.readText(charset: Charset = Charsets.UTF_8): String = toNativePath().readText(charset) - - -operator fun OsPath.div(osPath: OsPath): OsPath = resolve(osPath) - -operator fun OsPath.div(path: String): OsPath = resolve(path) - -fun OsPath.resolve(vararg pathParts: String): OsPath = resolve(OsPath(osType, *pathParts)) - -fun OsPath.resolve(osPath: OsPath): OsPath { +fun OsPath.resolve(osPath: OsPath): OsPath { if (osType != osPath.osType) { throw IllegalArgumentException("Paths from different OS's: '${osType}' path can not be resolved with '${osPath.osType}' path") } @@ -173,16 +134,16 @@ fun OsPath.resolve(osPath: OsPath): OsPath { addAll(osPath.pathParts) } - return OsPath.normalize(OsPath(osType, root, newPathParts)) + return vfs.normalize(vfs.createOsPath(root, newPathParts) as OsPath) } -// OsPath accessors - -//val OsPath.rootOsPath -// get() = OsPath.createOrThrow(osType, root) - -val OsPath.nativeType - get() = if (osType.isPosixHostedOnWindows()) GlobalOsType.WINDOWS else osType - -val OsPath.extension - get() = leaf.substringAfterLast('.', "") +//// OsPath accessors +// +////val OsPath.rootOsPath +//// get() = OsPath.createOrThrow(osType, root) +// +//val OsPath.nativeType +// get() = if (osType.isPosixHostedOnWindows()) GlobalOsType.WINDOWS else osType +// +//val OsPath.extension +// get() = leaf.substringAfterLast('.', "") diff --git a/src/main/kotlin/io/github/kscripting/shell/ShellExecutor.kt b/src/main/kotlin/io/github/kscripting/shell/ShellExecutor.kt index 6f246b5..b1f0634 100644 --- a/src/main/kotlin/io/github/kscripting/shell/ShellExecutor.kt +++ b/src/main/kotlin/io/github/kscripting/shell/ShellExecutor.kt @@ -34,7 +34,7 @@ object ShellExecutor { fun evalAndGobble( command: String, globalOsType: OsType = GlobalOsType.native, - workingDirectory: OsPath? = null, + workingDirectory: OsPath<*>? = null, waitTimeMinutes: Int = 10, inheritInput: Boolean = false, inputSanitizer: Sanitizer = EMPTY_SANITIZER, @@ -75,7 +75,7 @@ object ShellExecutor { fun eval( command: String, globalOsType: OsType = GlobalOsType.native, - workingDirectory: OsPath? = null, + workingDirectory: OsPath<*>? = null, waitTimeMinutes: Int = 10, inheritInput: Boolean = false, inputSanitizer: Sanitizer = EMPTY_SANITIZER, diff --git a/src/main/kotlin/io/github/kscripting/shell/process/ProcessRunner.kt b/src/main/kotlin/io/github/kscripting/shell/process/ProcessRunner.kt index 977daa5..c16f0e3 100644 --- a/src/main/kotlin/io/github/kscripting/shell/process/ProcessRunner.kt +++ b/src/main/kotlin/io/github/kscripting/shell/process/ProcessRunner.kt @@ -17,7 +17,7 @@ object ProcessRunner { fun runProcess( vararg command: String, - workingDirectory: OsPath? = null, + workingDirectory: OsPath<*>? = null, envAdjuster: EnvAdjuster = {}, waitTimeMinutes: Int = 10, inheritInput: Boolean = false, @@ -41,7 +41,7 @@ object ProcessRunner { fun runProcess( command: List, - workingDirectory: OsPath? = null, + workingDirectory: OsPath<*>? = null, envAdjuster: EnvAdjuster = {}, waitTimeMinutes: Int = 10, inheritInput: Boolean = false, diff --git a/src/test/kotlin/io/github/kscripting/os/model/GenericOsPathTest.kt b/src/test/kotlin/io/github/kscripting/os/model/GenericOsPathTest.kt index 10b4370..020d926 100644 --- a/src/test/kotlin/io/github/kscripting/os/model/GenericOsPathTest.kt +++ b/src/test/kotlin/io/github/kscripting/os/model/GenericOsPathTest.kt @@ -2,19 +2,13 @@ package io.github.kscripting.os.model import assertk.assertThat import assertk.assertions.isEqualTo -import io.github.kscripting.os.instance.LinuxOs -import net.igsoft.typeutils.globalcontext.GlobalContext +import io.github.kscripting.os.instance.LinuxVfs import org.junit.jupiter.api.Test import org.junit.jupiter.api.TestInstance @TestInstance(TestInstance.Lifecycle.PER_CLASS) class GenericOsPathTest { - private var simplePath: OsPath - - init { - GlobalContext.registerOrReplace(GlobalOsType.LINUX, LinuxOs(GlobalOsType.LINUX, "/home/admin")) - simplePath = OsPath(GlobalOsType.LINUX, "", listOf("home", "admin")) - } + private var simplePath = OsPath(LinuxVfs("/home/admin"), "", listOf("home", "admin")) @Test fun `Test Empty paths`() { diff --git a/src/test/kotlin/io/github/kscripting/os/model/HostedOsPathTest.kt b/src/test/kotlin/io/github/kscripting/os/model/HostedOsPathTest.kt index 45fd7b9..40ba193 100644 --- a/src/test/kotlin/io/github/kscripting/os/model/HostedOsPathTest.kt +++ b/src/test/kotlin/io/github/kscripting/os/model/HostedOsPathTest.kt @@ -2,123 +2,93 @@ package io.github.kscripting.os.model import assertk.assertThat import assertk.assertions.isEqualTo -import io.github.kscripting.os.instance.CygwinOs -import io.github.kscripting.os.instance.MsysOs -import io.github.kscripting.os.instance.WindowsOs -import net.igsoft.typeutils.globalcontext.GlobalContext -import org.junit.jupiter.api.BeforeAll +import io.github.kscripting.os.instance.CygwinVfs +import io.github.kscripting.os.instance.MsysVfs +import io.github.kscripting.os.instance.WindowsVfs import org.junit.jupiter.api.Test import org.junit.jupiter.api.TestInstance @TestInstance(TestInstance.Lifecycle.PER_CLASS) class HostedOsPathTest { - - @BeforeAll - fun beforeAll() { - //Installation cygwin/msys base path: cygpath -w /, cygpath ~ - GlobalContext.registerOrReplace( - GlobalOsType.CYGWIN, - CygwinOs(GlobalOsType.CYGWIN, GlobalOsType.WINDOWS, "/home/admin", "C:\\Programs\\Cygwin\\") - ) - GlobalContext.registerOrReplace( - GlobalOsType.MSYS, - MsysOs(GlobalOsType.MSYS, GlobalOsType.WINDOWS, "/home/admin", "C:\\Programs\\Msys\\") - ) - GlobalContext.registerOrReplace( - GlobalOsType.WINDOWS, - WindowsOs(GlobalOsType.WINDOWS, "C:\\Users\\Admin\\.kscript") - ) - } + private val windowsVfs = WindowsVfs("C:\\Users\\Admin\\.kscript") + private val cygwinVfs = CygwinVfs(windowsVfs.createOsPath("C:\\Programs\\Cygwin\\"), "/home/admin") + private val msysVfs = MsysVfs(windowsVfs.createOsPath("C:\\Programs\\Msys\\"), "/home/admin") @Test fun `Test Cygwin to Windows`() { assertThat( - OsPath( - GlobalOsType.CYGWIN, "/cygdrive/c/home/admin/.kscript" - ).toNative().path + cygwinVfs.createOsPath("/cygdrive/c/home/admin/.kscript").toNative().path ).isEqualTo("c:\\home\\admin\\.kscript") assertThat( - OsPath( - GlobalOsType.CYGWIN, "~/.kscript" - ).toNative().path + cygwinVfs.createOsPath("~/.kscript").toNative().path ).isEqualTo("C:\\Programs\\Cygwin\\home\\admin\\.kscript") assertThat( - OsPath( - GlobalOsType.CYGWIN, "/usr/local/bin/sdk" - ).toNative().path + cygwinVfs.createOsPath("/usr/local/bin/sdk").toNative().path ).isEqualTo("C:\\Programs\\Cygwin\\usr\\local\\bin\\sdk") assertThat( - OsPath(GlobalOsType.CYGWIN, "../home/admin/.kscript").toNative().path + cygwinVfs.createOsPath("../home/admin/.kscript").toNative().path ).isEqualTo("..\\home\\admin\\.kscript") } - +/* @Test fun `Test Windows to Cygwin`() { assertThat( - OsPath(GlobalOsType.WINDOWS, "C:\\home\\admin\\.kscript").toHosted(GlobalOsType.CYGWIN).path + OsPath(windowsVfs, "C:\\home\\admin\\.kscript").toHosted(cygwinVfs).path ).isEqualTo("/cygdrive/c/home/admin/.kscript") assertThat( - OsPath(GlobalOsType.WINDOWS, "..\\home\\admin\\.kscript").toHosted(GlobalOsType.CYGWIN).path + OsPath(windowsVfs, "..\\home\\admin\\.kscript").toHosted(cygwinVfs).path ).isEqualTo("../home/admin/.kscript") assertThat( - OsPath( - GlobalOsType.WINDOWS, "C:\\Programs\\Cygwin\\home\\admin\\.kscript" - ).toHosted(GlobalOsType.CYGWIN).path + OsPath(windowsVfs, "C:\\Programs\\Cygwin\\home\\admin\\.kscript").toHosted(cygwinVfs).path ).isEqualTo("~/.kscript") assertThat( - OsPath( - GlobalOsType.WINDOWS, "C:\\Programs\\Cygwin\\usr\\local\\sdk" - ).toHosted(GlobalOsType.CYGWIN).path + OsPath(windowsVfs, "C:\\Programs\\Cygwin\\usr\\local\\sdk").toHosted(cygwinVfs).path ).isEqualTo("/usr/local/sdk") } @Test fun `Test MSys to Windows`() { assertThat( - OsPath(GlobalOsType.MSYS, "/c/home/admin/.kscript").toNative().path + msysVfs.createOsPath("/c/home/admin/.kscript").toNative().path ).isEqualTo("c:\\home\\admin\\.kscript") assertThat( - OsPath(GlobalOsType.MSYS, "~/.kscript").toNative().path + msysVfs.createOsPath("~/.kscript").toNative().path ).isEqualTo("C:\\Programs\\Msys\\home\\admin\\.kscript") assertThat( - OsPath(GlobalOsType.MSYS, "/usr/local/bin/sdk").toNative().path + msysVfs.createOsPath("/usr/local/bin/sdk").toNative().path ).isEqualTo("C:\\Programs\\Msys\\usr\\local\\bin\\sdk") assertThat( - OsPath(GlobalOsType.MSYS, "../home/admin/.kscript").toNative().path + msysVfs.createOsPath("../home/admin/.kscript").toNative().path ).isEqualTo("..\\home\\admin\\.kscript") } @Test fun `Test Windows to MSys`() { assertThat( - OsPath( - GlobalOsType.WINDOWS, "C:\\home\\admin\\.kscript" - ).toHosted(GlobalOsType.MSYS).path + OsPath(windowsVfs, "C:\\home\\admin\\.kscript").toHosted(msysVfs).path ).isEqualTo("/c/home/admin/.kscript") assertThat( - OsPath( - GlobalOsType.WINDOWS, "..\\home\\admin\\.kscript" - ).toHosted(GlobalOsType.MSYS).path + OsPath(windowsVfs, "..\\home\\admin\\.kscript").toHosted(msysVfs).path ).isEqualTo("../home/admin/.kscript") assertThat( - OsPath( - GlobalOsType.WINDOWS, "C:\\Programs\\Msys\\home\\admin\\.kscript" - ).toHosted(GlobalOsType.MSYS).path + OsPath(windowsVfs, "C:\\Programs\\Msys\\home\\admin\\.kscript").toHosted(msysVfs).path ).isEqualTo("~/.kscript") assertThat( - OsPath(GlobalOsType.WINDOWS, "C:\\Programs\\Msys\\usr\\local\\sdk").toHosted(GlobalOsType.MSYS).path + OsPath(windowsVfs, "C:\\Programs\\Msys\\usr\\local\\sdk").toHosted(msysVfs).path ).isEqualTo("/usr/local/sdk") } + + */ } diff --git a/src/test/kotlin/io/github/kscripting/os/model/PosixOsPathTest.kt b/src/test/kotlin/io/github/kscripting/os/model/PosixOsPathTest.kt index dff996a..7d36405 100644 --- a/src/test/kotlin/io/github/kscripting/os/model/PosixOsPathTest.kt +++ b/src/test/kotlin/io/github/kscripting/os/model/PosixOsPathTest.kt @@ -6,163 +6,150 @@ import assertk.assertions.hasMessage import assertk.assertions.isEqualTo import assertk.assertions.isInstanceOf import assertk.assertions.prop -import io.github.kscripting.os.instance.LinuxOs -import net.igsoft.typeutils.globalcontext.GlobalContext -import org.junit.jupiter.api.BeforeAll +import io.github.kscripting.os.OsTypeNew +import io.github.kscripting.os.instance.LinuxVfs import org.junit.jupiter.api.Test import org.junit.jupiter.api.TestInstance @TestInstance(TestInstance.Lifecycle.PER_CLASS) class PosixOsPathTest { - - @BeforeAll - fun beforeAll() { - GlobalContext.registerOrReplace(GlobalOsType.LINUX, LinuxOs(GlobalOsType.LINUX, "userhome")) - } + private val linuxVfs = LinuxVfs("userhome") @Test fun `Test Posix paths`() { - assertThat(OsPath(GlobalOsType.LINUX, "/")).let { - it.prop(OsPath::osType).isEqualTo(GlobalOsType.LINUX) - it.prop(OsPath::root).isEqualTo("/") - it.prop(OsPath::pathParts).isEqualTo(emptyList()) + assertThat(linuxVfs.createOsPath("/")).let { + it.prop(OsPath<*>::osType).isEqualTo(OsTypeNew.LINUX) + it.prop(OsPath<*>::root).isEqualTo("/") + it.prop(OsPath<*>::pathParts).isEqualTo(emptyList()) } - assertThat(OsPath(GlobalOsType.LINUX, "/home/admin/.kscript")).let { - it.prop(OsPath::osType).isEqualTo(GlobalOsType.LINUX) - it.prop(OsPath::root).isEqualTo("/") - it.prop(OsPath::pathParts).isEqualTo(listOf("home", "admin", ".kscript")) + assertThat(linuxVfs.createOsPath("/home/admin/.kscript")).let { + it.prop(OsPath<*>::osType).isEqualTo(OsTypeNew.LINUX) + it.prop(OsPath<*>::root).isEqualTo("/") + it.prop(OsPath<*>::pathParts).isEqualTo(listOf("home", "admin", ".kscript")) } - assertThat(OsPath(GlobalOsType.LINUX, "./home/admin/.kscript")).let { - it.prop(OsPath::osType).isEqualTo(GlobalOsType.LINUX) - it.prop(OsPath::root).isEqualTo("") - it.prop(OsPath::pathParts).isEqualTo(listOf("home", "admin", ".kscript")) + assertThat(linuxVfs.createOsPath("./home/admin/.kscript")).let { + it.prop(OsPath<*>::osType).isEqualTo(OsTypeNew.LINUX) + it.prop(OsPath<*>::root).isEqualTo("") + it.prop(OsPath<*>::pathParts).isEqualTo(listOf("home", "admin", ".kscript")) } - assertThat(OsPath(GlobalOsType.LINUX, "")).let { - it.prop(OsPath::osType).isEqualTo(GlobalOsType.LINUX) - it.prop(OsPath::root).isEqualTo("") - it.prop(OsPath::pathParts).isEqualTo(emptyList()) + assertThat(linuxVfs.createOsPath("")).let { + it.prop(OsPath<*>::osType).isEqualTo(OsTypeNew.LINUX) + it.prop(OsPath<*>::root).isEqualTo("") + it.prop(OsPath<*>::pathParts).isEqualTo(emptyList()) } - assertThat(OsPath(GlobalOsType.LINUX, "file.txt")).let { - it.prop(OsPath::osType).isEqualTo(GlobalOsType.LINUX) - it.prop(OsPath::root).isEqualTo("") - it.prop(OsPath::pathParts).isEqualTo(listOf("file.txt")) + assertThat(linuxVfs.createOsPath("file.txt")).let { + it.prop(OsPath<*>::osType).isEqualTo(OsTypeNew.LINUX) + it.prop(OsPath<*>::root).isEqualTo("") + it.prop(OsPath<*>::pathParts).isEqualTo(listOf("file.txt")) } - assertThat(OsPath(GlobalOsType.LINUX, ".")).let { - it.prop(OsPath::osType).isEqualTo(GlobalOsType.LINUX) - it.prop(OsPath::root).isEqualTo("") - it.prop(OsPath::pathParts).isEqualTo(emptyList()) + assertThat(linuxVfs.createOsPath(".")).let { + it.prop(OsPath<*>::osType).isEqualTo(OsTypeNew.LINUX) + it.prop(OsPath<*>::root).isEqualTo("") + it.prop(OsPath<*>::pathParts).isEqualTo(emptyList()) } - assertThat(OsPath(GlobalOsType.LINUX, "../home/admin/.kscript")).let { - it.prop(OsPath::osType).isEqualTo(GlobalOsType.LINUX) - it.prop(OsPath::root).isEqualTo("") - it.prop(OsPath::pathParts).isEqualTo(listOf("..", "home", "admin", ".kscript")) + assertThat(linuxVfs.createOsPath("../home/admin/.kscript")).let { + it.prop(OsPath<*>::osType).isEqualTo(OsTypeNew.LINUX) + it.prop(OsPath<*>::root).isEqualTo("") + it.prop(OsPath<*>::pathParts).isEqualTo(listOf("..", "home", "admin", ".kscript")) } - assertThat(OsPath(GlobalOsType.LINUX, "..")).let { - it.prop(OsPath::osType).isEqualTo(GlobalOsType.LINUX) - it.prop(OsPath::root).isEqualTo("") - it.prop(OsPath::pathParts).isEqualTo(listOf("..")) + assertThat(linuxVfs.createOsPath("..")).let { + it.prop(OsPath<*>::osType).isEqualTo(OsTypeNew.LINUX) + it.prop(OsPath<*>::root).isEqualTo("") + it.prop(OsPath<*>::pathParts).isEqualTo(listOf("..")) } //Duplicated separators are accepted - assertThat(OsPath(GlobalOsType.LINUX, "..//home////admin/.kscript/")).let { - it.prop(OsPath::osType).isEqualTo(GlobalOsType.LINUX) - it.prop(OsPath::root).isEqualTo("") - it.prop(OsPath::pathParts).isEqualTo(listOf("..", "home", "admin", ".kscript")) + assertThat(linuxVfs.createOsPath("..//home////admin/.kscript/")).let { + it.prop(OsPath<*>::osType).isEqualTo(OsTypeNew.LINUX) + it.prop(OsPath<*>::root).isEqualTo("") + it.prop(OsPath<*>::pathParts).isEqualTo(listOf("..", "home", "admin", ".kscript")) } //Both types of separator are accepted - assertThat(OsPath(GlobalOsType.LINUX, "..//home\\admin\\.kscript/")).let { - it.prop(OsPath::osType).isEqualTo(GlobalOsType.LINUX) - it.prop(OsPath::root).isEqualTo("") - it.prop(OsPath::pathParts).isEqualTo(listOf("..", "home", "admin", ".kscript")) + assertThat(linuxVfs.createOsPath("..//home\\admin\\.kscript/")).let { + it.prop(OsPath<*>::osType).isEqualTo(OsTypeNew.LINUX) + it.prop(OsPath<*>::root).isEqualTo("") + it.prop(OsPath<*>::pathParts).isEqualTo(listOf("..", "home", "admin", ".kscript")) } } @Test fun `Normalization of Posix paths`() { - assertThat(OsPath(GlobalOsType.LINUX, "/home/admin/.kscript/../../")).let { - it.prop(OsPath::osType).isEqualTo(GlobalOsType.LINUX) - it.prop(OsPath::root).isEqualTo("/") - it.prop(OsPath::pathParts).isEqualTo(listOf("home")) + assertThat(linuxVfs.createOsPath("/home/admin/.kscript/../../")).let { + it.prop(OsPath<*>::osType).isEqualTo(OsTypeNew.LINUX) + it.prop(OsPath<*>::root).isEqualTo("/") + it.prop(OsPath<*>::pathParts).isEqualTo(listOf("home")) } - assertThat(OsPath(GlobalOsType.LINUX, "./././../../script")).let { - it.prop(OsPath::osType).isEqualTo(GlobalOsType.LINUX) - it.prop(OsPath::root).isEqualTo("") - it.prop(OsPath::pathParts).isEqualTo(listOf("..", "..", "script")) + assertThat(linuxVfs.createOsPath("./././../../script")).let { + it.prop(OsPath<*>::osType).isEqualTo(OsTypeNew.LINUX) + it.prop(OsPath<*>::root).isEqualTo("") + it.prop(OsPath<*>::pathParts).isEqualTo(listOf("..", "..", "script")) } - assertThat(OsPath(GlobalOsType.LINUX, "/a/b/c/../d/script")).let { - it.prop(OsPath::osType).isEqualTo(GlobalOsType.LINUX) - it.prop(OsPath::root).isEqualTo("/") - it.prop(OsPath::pathParts).isEqualTo(listOf("a", "b", "d", "script")) + assertThat(linuxVfs.createOsPath("/a/b/c/../d/script")).let { + it.prop(OsPath<*>::osType).isEqualTo(OsTypeNew.LINUX) + it.prop(OsPath<*>::root).isEqualTo("/") + it.prop(OsPath<*>::pathParts).isEqualTo(listOf("a", "b", "d", "script")) } - assertFailure { OsPath(GlobalOsType.LINUX, "/.kscript/../../") }.isInstanceOf(IllegalArgumentException::class) + assertFailure { linuxVfs.createOsPath("/.kscript/../../") }.isInstanceOf(IllegalArgumentException::class) .hasMessage("Path after normalization goes beyond root element: '/'") } @Test fun `Test invalid Posix paths`() { assertFailure { - OsPath( - GlobalOsType.LINUX, - "/ad*asdf" - ) + linuxVfs.createOsPath("/ad*asdf") }.isInstanceOf(IllegalArgumentException::class.java).hasMessage("Invalid character '*' in path part 'ad*asdf'") } @Test fun `Test Posix stringPath`() { assertThat( - OsPath(GlobalOsType.LINUX, "/home/admin/.kscript").path + linuxVfs.createOsPath("/home/admin/.kscript").path ).isEqualTo("/home/admin/.kscript") - assertThat(OsPath(GlobalOsType.LINUX, "/a/b/c/../d/script").path).isEqualTo("/a/b/d/script") - assertThat(OsPath(GlobalOsType.LINUX, "./././../../script").path).isEqualTo("../../script") - assertThat(OsPath(GlobalOsType.LINUX, "script/file.txt").path).isEqualTo("script/file.txt") + assertThat(linuxVfs.createOsPath("/a/b/c/../d/script").path).isEqualTo("/a/b/d/script") + assertThat(linuxVfs.createOsPath("./././../../script").path).isEqualTo("../../script") + assertThat(linuxVfs.createOsPath("script/file.txt").path).isEqualTo("script/file.txt") } @Test fun `Test Posix resolve`() { assertThat( - OsPath(GlobalOsType.LINUX, "/").resolve(OsPath(GlobalOsType.LINUX, "./.kscript/")) + linuxVfs.createOsPath("/").resolve(linuxVfs.createOsPath("./.kscript/")) .path ).isEqualTo("/.kscript") assertThat( - OsPath(GlobalOsType.LINUX, "/home/admin/").resolve(OsPath(GlobalOsType.LINUX, "./.kscript/")).path + linuxVfs.createOsPath("/home/admin/").resolve(linuxVfs.createOsPath("./.kscript/")).path ).isEqualTo("/home/admin/.kscript") assertThat( - OsPath(GlobalOsType.LINUX, "./home/admin/").resolve(OsPath(GlobalOsType.LINUX, "./.kscript/")).path + linuxVfs.createOsPath("./home/admin/").resolve(linuxVfs.createOsPath("./.kscript/")).path ).isEqualTo("home/admin/.kscript") assertThat( - OsPath(GlobalOsType.LINUX, "../home/admin/").resolve(OsPath(GlobalOsType.LINUX, "./.kscript/")).path + linuxVfs.createOsPath("../home/admin/").resolve(linuxVfs.createOsPath("./.kscript/")).path ).isEqualTo("../home/admin/.kscript") assertThat( - OsPath(GlobalOsType.LINUX, "..").resolve(OsPath(GlobalOsType.LINUX, "./.kscript/")).path + linuxVfs.createOsPath("..").resolve(linuxVfs.createOsPath("./.kscript/")).path ).isEqualTo("../.kscript") assertThat( - OsPath(GlobalOsType.LINUX, ".").resolve(OsPath(GlobalOsType.LINUX, "./.kscript/")).path + linuxVfs.createOsPath(".").resolve(linuxVfs.createOsPath("./.kscript/")).path ).isEqualTo(".kscript") assertFailure { - OsPath(GlobalOsType.LINUX, "./home/admin").resolve(OsPath(GlobalOsType.WINDOWS, ".\\run")) - }.isInstanceOf(IllegalArgumentException::class.java) - .hasMessage("Paths from different OS's: 'LINUX' path can not be resolved with 'WINDOWS' path") - - assertFailure { - OsPath(GlobalOsType.LINUX, "./home/admin").resolve(OsPath(GlobalOsType.LINUX, "/run")) + linuxVfs.createOsPath("./home/admin").resolve(linuxVfs.createOsPath("/run")) }.isInstanceOf(IllegalArgumentException::class.java) .hasMessage("Can not resolve absolute or relative path 'home/admin' using absolute path '/run'") } diff --git a/src/test/kotlin/io/github/kscripting/os/model/WindowsOsPathTest.kt b/src/test/kotlin/io/github/kscripting/os/model/WindowsOsPathTest.kt index 01c2496..5f74c0f 100644 --- a/src/test/kotlin/io/github/kscripting/os/model/WindowsOsPathTest.kt +++ b/src/test/kotlin/io/github/kscripting/os/model/WindowsOsPathTest.kt @@ -6,139 +6,122 @@ import assertk.assertions.hasMessage import assertk.assertions.isEqualTo import assertk.assertions.isInstanceOf import assertk.assertions.prop -import io.github.kscripting.os.instance.WindowsOs -import net.igsoft.typeutils.globalcontext.GlobalContext -import org.junit.jupiter.api.BeforeAll +import io.github.kscripting.os.OsTypeNew +import io.github.kscripting.os.instance.WindowsVfs import org.junit.jupiter.api.Test import org.junit.jupiter.api.TestInstance @TestInstance(TestInstance.Lifecycle.PER_CLASS) class WindowsOsPathTest { - @BeforeAll - fun beforeAll() { - GlobalContext.registerOrReplace(GlobalOsType.WINDOWS, WindowsOs(GlobalOsType.WINDOWS, "C:\\Users\\Admin\\.kscript")) - } + private val windowsVfs = WindowsVfs("C:\\Users\\Admin\\.kscript") @Test fun `Test Windows paths`() { - assertThat(OsPath(GlobalOsType.WINDOWS, "C:\\")).let { - it.prop(OsPath::osType).isEqualTo(GlobalOsType.WINDOWS) - it.prop(OsPath::root).isEqualTo("C:\\") - it.prop(OsPath::pathParts).isEqualTo(emptyList()) + assertThat(windowsVfs.createOsPath("C:\\")).let { + it.prop(OsPath<*>::osType).isEqualTo(OsTypeNew.WINDOWS) + it.prop(OsPath<*>::root).isEqualTo("C:\\") + it.prop(OsPath<*>::pathParts).isEqualTo(emptyList()) } - assertThat(OsPath(GlobalOsType.WINDOWS, "C:\\home\\admin\\.kscript")).let { - it.prop(OsPath::osType).isEqualTo(GlobalOsType.WINDOWS) - it.prop(OsPath::root).isEqualTo("C:\\") - it.prop(OsPath::pathParts).isEqualTo(listOf("home", "admin", ".kscript")) + assertThat(windowsVfs.createOsPath("C:\\home\\admin\\.kscript")).let { + it.prop(OsPath<*>::osType).isEqualTo(OsTypeNew.WINDOWS) + it.prop(OsPath<*>::root).isEqualTo("C:\\") + it.prop(OsPath<*>::pathParts).isEqualTo(listOf("home", "admin", ".kscript")) } - assertThat(OsPath(GlobalOsType.WINDOWS, "")).let { - it.prop(OsPath::osType).isEqualTo(GlobalOsType.WINDOWS) - it.prop(OsPath::root).isEqualTo("") - it.prop(OsPath::pathParts).isEqualTo(emptyList()) + assertThat(windowsVfs.createOsPath("")).let { + it.prop(OsPath<*>::osType).isEqualTo(OsTypeNew.WINDOWS) + it.prop(OsPath<*>::root).isEqualTo("") + it.prop(OsPath<*>::pathParts).isEqualTo(emptyList()) } - assertThat(OsPath(GlobalOsType.WINDOWS, "file.txt")).let { - it.prop(OsPath::osType).isEqualTo(GlobalOsType.WINDOWS) - it.prop(OsPath::root).isEqualTo("") - it.prop(OsPath::pathParts).isEqualTo(listOf("file.txt")) + assertThat(windowsVfs.createOsPath("file.txt")).let { + it.prop(OsPath<*>::osType).isEqualTo(OsTypeNew.WINDOWS) + it.prop(OsPath<*>::root).isEqualTo("") + it.prop(OsPath<*>::pathParts).isEqualTo(listOf("file.txt")) } - assertThat(OsPath(GlobalOsType.WINDOWS, ".")).let { - it.prop(OsPath::osType).isEqualTo(GlobalOsType.WINDOWS) - it.prop(OsPath::root).isEqualTo("") - it.prop(OsPath::pathParts).isEqualTo(emptyList()) + assertThat(windowsVfs.createOsPath(".")).let { + it.prop(OsPath<*>::osType).isEqualTo(OsTypeNew.WINDOWS) + it.prop(OsPath<*>::root).isEqualTo("") + it.prop(OsPath<*>::pathParts).isEqualTo(emptyList()) } - assertThat(OsPath(GlobalOsType.WINDOWS, ".\\home\\admin\\.kscript")).let { - it.prop(OsPath::osType).isEqualTo(GlobalOsType.WINDOWS) - it.prop(OsPath::root).isEqualTo("") - it.prop(OsPath::pathParts).isEqualTo(listOf("home", "admin", ".kscript")) + assertThat(windowsVfs.createOsPath(".\\home\\admin\\.kscript")).let { + it.prop(OsPath<*>::osType).isEqualTo(OsTypeNew.WINDOWS) + it.prop(OsPath<*>::root).isEqualTo("") + it.prop(OsPath<*>::pathParts).isEqualTo(listOf("home", "admin", ".kscript")) } - assertThat(OsPath(GlobalOsType.WINDOWS, "..\\home\\admin\\.kscript")).let { - it.prop(OsPath::osType).isEqualTo(GlobalOsType.WINDOWS) - it.prop(OsPath::root).isEqualTo("") - it.prop(OsPath::pathParts).isEqualTo(listOf("..", "home", "admin", ".kscript")) + assertThat(windowsVfs.createOsPath("..\\home\\admin\\.kscript")).let { + it.prop(OsPath<*>::osType).isEqualTo(OsTypeNew.WINDOWS) + it.prop(OsPath<*>::root).isEqualTo("") + it.prop(OsPath<*>::pathParts).isEqualTo(listOf("..", "home", "admin", ".kscript")) } - assertThat(OsPath(GlobalOsType.WINDOWS, "..")).let { - it.prop(OsPath::osType).isEqualTo(GlobalOsType.WINDOWS) - it.prop(OsPath::root).isEqualTo("") - it.prop(OsPath::pathParts).isEqualTo(listOf("..")) + assertThat(windowsVfs.createOsPath("..")).let { + it.prop(OsPath<*>::osType).isEqualTo(OsTypeNew.WINDOWS) + it.prop(OsPath<*>::root).isEqualTo("") + it.prop(OsPath<*>::pathParts).isEqualTo(listOf("..")) } //Duplicated separators are accepted - assertThat(OsPath(GlobalOsType.WINDOWS, "C:\\home\\\\\\\\admin\\.kscript\\")).let { - it.prop(OsPath::osType).isEqualTo(GlobalOsType.WINDOWS) - it.prop(OsPath::root).isEqualTo("C:\\") - it.prop(OsPath::pathParts).isEqualTo(listOf("home", "admin", ".kscript")) + assertThat(windowsVfs.createOsPath("C:\\home\\\\\\\\admin\\.kscript\\")).let { + it.prop(OsPath<*>::osType).isEqualTo(OsTypeNew.WINDOWS) + it.prop(OsPath<*>::root).isEqualTo("C:\\") + it.prop(OsPath<*>::pathParts).isEqualTo(listOf("home", "admin", ".kscript")) } //Both types of separator are accepted - assertThat(OsPath(GlobalOsType.WINDOWS, "C:/home\\admin/.kscript////")).let { - it.prop(OsPath::osType).isEqualTo(GlobalOsType.WINDOWS) - it.prop(OsPath::root).isEqualTo("C:\\") - it.prop(OsPath::pathParts).isEqualTo(listOf("home", "admin", ".kscript")) + assertThat(windowsVfs.createOsPath("C:/home\\admin/.kscript////")).let { + it.prop(OsPath<*>::osType).isEqualTo(OsTypeNew.WINDOWS) + it.prop(OsPath<*>::root).isEqualTo("C:\\") + it.prop(OsPath<*>::pathParts).isEqualTo(listOf("home", "admin", ".kscript")) } } @Test fun `Normalization of Windows paths`() { - assertThat(OsPath(GlobalOsType.WINDOWS, "C:\\home\\admin\\.kscript\\..\\..\\")).let { - it.prop(OsPath::osType).isEqualTo(GlobalOsType.WINDOWS) - it.prop(OsPath::root).isEqualTo("C:\\") - it.prop(OsPath::pathParts).isEqualTo(listOf("home")) + assertThat(windowsVfs.createOsPath("C:\\home\\admin\\.kscript\\..\\..\\")).let { + it.prop(OsPath<*>::osType).isEqualTo(OsTypeNew.WINDOWS) + it.prop(OsPath<*>::root).isEqualTo("C:\\") + it.prop(OsPath<*>::pathParts).isEqualTo(listOf("home")) } - assertThat(OsPath(GlobalOsType.WINDOWS, ".\\.\\.\\..\\..\\script")).let { - it.prop(OsPath::osType).isEqualTo(GlobalOsType.WINDOWS) - it.prop(OsPath::root).isEqualTo("") - it.prop(OsPath::pathParts).isEqualTo(listOf("..", "..", "script")) + assertThat(windowsVfs.createOsPath(".\\.\\.\\..\\..\\script")).let { + it.prop(OsPath<*>::osType).isEqualTo(OsTypeNew.WINDOWS) + it.prop(OsPath<*>::root).isEqualTo("") + it.prop(OsPath<*>::pathParts).isEqualTo(listOf("..", "..", "script")) } - assertThat(OsPath(GlobalOsType.WINDOWS, "C:\\a\\b\\c\\..\\d\\script")).let { - it.prop(OsPath::osType).isEqualTo(GlobalOsType.WINDOWS) - it.prop(OsPath::root).isEqualTo("C:\\") - it.prop(OsPath::pathParts).isEqualTo(listOf("a", "b", "d", "script")) + assertThat(windowsVfs.createOsPath("C:\\a\\b\\c\\..\\d\\script")).let { + it.prop(OsPath<*>::osType).isEqualTo(OsTypeNew.WINDOWS) + it.prop(OsPath<*>::root).isEqualTo("C:\\") + it.prop(OsPath<*>::pathParts).isEqualTo(listOf("a", "b", "d", "script")) } assertFailure { - OsPath( - GlobalOsType.WINDOWS, - "C:\\.kscript\\..\\..\\" - ) + windowsVfs.createOsPath("C:\\.kscript\\..\\..\\") }.isInstanceOf(IllegalArgumentException::class.java) .hasMessage("Path after normalization goes beyond root element: 'C:\\'") } @Test fun `Test invalid Windows paths`() { - assertFailure { OsPath(GlobalOsType.WINDOWS, "C:\\adas?df") } + assertFailure { windowsVfs.createOsPath("C:\\adas?df") } .isInstanceOf(IllegalArgumentException::class.java) .hasMessage("Invalid character '?' in path part 'adas?df'") - assertFailure { OsPath(GlobalOsType.WINDOWS, "home:\\vagrant") } + assertFailure { windowsVfs.createOsPath("home:\\vagrant") } .isInstanceOf(IllegalArgumentException::class.java) .hasMessage("Invalid character ':' in path part 'home:'") } @Test fun `Test Windows stringPath`() { - assertThat( - OsPath( - GlobalOsType.WINDOWS, - "C:\\home\\admin\\.kscript" - ).path - ).isEqualTo("C:\\home\\admin\\.kscript") - assertThat( - OsPath( - GlobalOsType.WINDOWS, - "c:\\a\\b\\c\\..\\d\\script" - ).path - ).isEqualTo("c:\\a\\b\\d\\script") - assertThat(OsPath(GlobalOsType.WINDOWS, ".\\.\\.\\..\\..\\script").path).isEqualTo("..\\..\\script") - assertThat(OsPath(GlobalOsType.WINDOWS, "script\\file.txt").path).isEqualTo("script\\file.txt") + assertThat(windowsVfs.createOsPath("C:\\home\\admin\\.kscript").path).isEqualTo("C:\\home\\admin\\.kscript") + assertThat(windowsVfs.createOsPath("c:\\a\\b\\c\\..\\d\\script").path).isEqualTo("c:\\a\\b\\d\\script") + assertThat(windowsVfs.createOsPath(".\\.\\.\\..\\..\\script").path).isEqualTo("..\\..\\script") + assertThat(windowsVfs.createOsPath("script\\file.txt").path).isEqualTo("script\\file.txt") } } From cd3774b3c82107b09fe5b00be08de215784cedff Mon Sep 17 00:00:00 2001 From: Marcin Kuszczak <1508798+aartiPl@users.noreply.github.com> Date: Sat, 23 Dec 2023 20:46:05 +0100 Subject: [PATCH 45/50] Update --- src/main/kotlin/io/github/kscripting/os/Vfs.kt | 3 --- src/main/kotlin/io/github/kscripting/os/instance/HostedVfs.kt | 3 +++ src/main/kotlin/io/github/kscripting/os/model/OsPathExt.kt | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/kotlin/io/github/kscripting/os/Vfs.kt b/src/main/kotlin/io/github/kscripting/os/Vfs.kt index 529c41d..ac702d9 100644 --- a/src/main/kotlin/io/github/kscripting/os/Vfs.kt +++ b/src/main/kotlin/io/github/kscripting/os/Vfs.kt @@ -74,9 +74,6 @@ interface Vfs { return OsPath(osPath.vfs, osPath.root, newParts) } - - fun toNative(osPath: OsPath): OsPath = osPath - fun toHosted(osPath: OsPath): OsPath = osPath } fun createPosixOsPath(vfs: T, path: String): OsPath { diff --git a/src/main/kotlin/io/github/kscripting/os/instance/HostedVfs.kt b/src/main/kotlin/io/github/kscripting/os/instance/HostedVfs.kt index 38e16e4..08a15be 100644 --- a/src/main/kotlin/io/github/kscripting/os/instance/HostedVfs.kt +++ b/src/main/kotlin/io/github/kscripting/os/instance/HostedVfs.kt @@ -5,4 +5,7 @@ import io.github.kscripting.os.model.OsPath interface HostedVfs : Vfs { val nativeFsRoot: OsPath + + fun toNative(osPath: OsPath): OsPath = osPath + fun toHosted(osPath: OsPath): OsPath = osPath } diff --git a/src/main/kotlin/io/github/kscripting/os/model/OsPathExt.kt b/src/main/kotlin/io/github/kscripting/os/model/OsPathExt.kt index 8c5dcc9..a33a469 100644 --- a/src/main/kotlin/io/github/kscripting/os/model/OsPathExt.kt +++ b/src/main/kotlin/io/github/kscripting/os/model/OsPathExt.kt @@ -19,7 +19,7 @@ import java.nio.file.Paths //import java.nio.file.Paths //import kotlin.io.path.* // -fun OsPath.toNative(): OsPath<*> = vfs.toNative(this) +fun OsPath.toNative(): OsPath<*> = (vfs as? HostedVfs)?.toNative(this) ?: this /* fun List.startsWith(list: List): Boolean = (this.size >= list.size && this.subList(0, list.size) == list) From 0760e076c1ea59c0055d29d3655bbbb5a87fb38d Mon Sep 17 00:00:00 2001 From: Marcin Kuszczak <1508798+aartiPl@users.noreply.github.com> Date: Sat, 23 Dec 2023 22:04:05 +0100 Subject: [PATCH 46/50] Update --- .../kotlin/io/github/kscripting/os/Vfs.kt | 23 ------ .../kscripting/os/instance/CygwinVfs.kt | 11 ++- .../kscripting/os/instance/FreeBsdVfs.kt | 2 +- .../kscripting/os/instance/HostedVfs.kt | 4 +- .../github/kscripting/os/instance/LinuxVfs.kt | 2 +- .../github/kscripting/os/instance/MacOsVfs.kt | 2 +- .../github/kscripting/os/instance/MsysVfs.kt | 12 ++- .../kscripting/os/instance/WindowsVfs.kt | 2 +- .../github/kscripting/os/model/OsPathExt.kt | 59 +------------- .../io/github/kscripting/os/util/VfsUtil.kt | 78 +++++++++++++++++++ .../kscripting/os/model/HostedOsPathTest.kt | 20 +++-- 11 files changed, 112 insertions(+), 103 deletions(-) create mode 100644 src/main/kotlin/io/github/kscripting/os/util/VfsUtil.kt diff --git a/src/main/kotlin/io/github/kscripting/os/Vfs.kt b/src/main/kotlin/io/github/kscripting/os/Vfs.kt index ac702d9..b5a4a46 100644 --- a/src/main/kotlin/io/github/kscripting/os/Vfs.kt +++ b/src/main/kotlin/io/github/kscripting/os/Vfs.kt @@ -76,26 +76,3 @@ interface Vfs { } } -fun createPosixOsPath(vfs: T, path: String): OsPath { - require(vfs.isValid(path)) - - //Detect root - val root: String = when { - path.startsWith("~/") || path.startsWith("~\\") -> "~" - path.startsWith("/") -> "/" - else -> "" - } - - return createFinalPath(vfs, path, root) -} - -fun createFinalPath(vfs: T, path: String, root: String): OsPath { - //Remove also empty path parts - there were duplicated or trailing slashes / backslashes in initial path - val pathWithoutRoot = path.drop(root.length) - - require(vfs.isValid(pathWithoutRoot)) - - val pathPartsResolved = pathWithoutRoot.split('/', '\\').filter { it.isNotBlank() } - return vfs.normalize(OsPath(vfs, root, pathPartsResolved)) -} - diff --git a/src/main/kotlin/io/github/kscripting/os/instance/CygwinVfs.kt b/src/main/kotlin/io/github/kscripting/os/instance/CygwinVfs.kt index fe46e5f..7c0ca4d 100644 --- a/src/main/kotlin/io/github/kscripting/os/instance/CygwinVfs.kt +++ b/src/main/kotlin/io/github/kscripting/os/instance/CygwinVfs.kt @@ -2,13 +2,15 @@ package io.github.kscripting.os.instance import io.github.kscripting.os.OsTypeNew import io.github.kscripting.os.Vfs -import io.github.kscripting.os.createPosixOsPath +import io.github.kscripting.os.util.createPosixOsPath import io.github.kscripting.os.model.OsPath +import io.github.kscripting.os.util.toHostedConverter class CygwinVfs(override val nativeFsRoot: OsPath, userHome: String) : HostedVfs, PosixVfs(OsTypeNew.CYGWIN) { - override fun toNative(osPath: OsPath): OsPath { + override fun toNative(providedOsPath: OsPath): OsPath { + val osPath = providedOsPath as OsPath val newParts = mutableListOf() var newRoot = "" @@ -33,6 +35,11 @@ class CygwinVfs(override val nativeFsRoot: OsPath, userHome: String) return OsPath(nativeFsRoot.vfs, newRoot, newParts) } + override fun toHosted(osPath: OsPath): OsPath { + return toHostedConverter(this, osPath as OsPath) + } + override val userHome: OsPath = createPosixOsPath(this, userHome) override fun createOsPath(path: String): OsPath = createPosixOsPath(this, path) } + diff --git a/src/main/kotlin/io/github/kscripting/os/instance/FreeBsdVfs.kt b/src/main/kotlin/io/github/kscripting/os/instance/FreeBsdVfs.kt index 48cc6bb..498b9c2 100644 --- a/src/main/kotlin/io/github/kscripting/os/instance/FreeBsdVfs.kt +++ b/src/main/kotlin/io/github/kscripting/os/instance/FreeBsdVfs.kt @@ -1,7 +1,7 @@ package io.github.kscripting.os.instance import io.github.kscripting.os.OsTypeNew -import io.github.kscripting.os.createPosixOsPath +import io.github.kscripting.os.util.createPosixOsPath import io.github.kscripting.os.model.OsPath class FreeBsdVfs(userHome: String) : PosixVfs(OsTypeNew.FREEBSD) { diff --git a/src/main/kotlin/io/github/kscripting/os/instance/HostedVfs.kt b/src/main/kotlin/io/github/kscripting/os/instance/HostedVfs.kt index 08a15be..7de333c 100644 --- a/src/main/kotlin/io/github/kscripting/os/instance/HostedVfs.kt +++ b/src/main/kotlin/io/github/kscripting/os/instance/HostedVfs.kt @@ -6,6 +6,6 @@ import io.github.kscripting.os.model.OsPath interface HostedVfs : Vfs { val nativeFsRoot: OsPath - fun toNative(osPath: OsPath): OsPath = osPath - fun toHosted(osPath: OsPath): OsPath = osPath + fun toNative(osPath: OsPath): OsPath + fun toHosted(osPath: OsPath): OsPath } diff --git a/src/main/kotlin/io/github/kscripting/os/instance/LinuxVfs.kt b/src/main/kotlin/io/github/kscripting/os/instance/LinuxVfs.kt index a0fbf26..ed4ef28 100644 --- a/src/main/kotlin/io/github/kscripting/os/instance/LinuxVfs.kt +++ b/src/main/kotlin/io/github/kscripting/os/instance/LinuxVfs.kt @@ -1,7 +1,7 @@ package io.github.kscripting.os.instance import io.github.kscripting.os.OsTypeNew -import io.github.kscripting.os.createPosixOsPath +import io.github.kscripting.os.util.createPosixOsPath import io.github.kscripting.os.model.OsPath class LinuxVfs(userHome: String) : PosixVfs(OsTypeNew.LINUX) { diff --git a/src/main/kotlin/io/github/kscripting/os/instance/MacOsVfs.kt b/src/main/kotlin/io/github/kscripting/os/instance/MacOsVfs.kt index 8bfd66a..cd5a722 100644 --- a/src/main/kotlin/io/github/kscripting/os/instance/MacOsVfs.kt +++ b/src/main/kotlin/io/github/kscripting/os/instance/MacOsVfs.kt @@ -1,7 +1,7 @@ package io.github.kscripting.os.instance import io.github.kscripting.os.OsTypeNew -import io.github.kscripting.os.createPosixOsPath +import io.github.kscripting.os.util.createPosixOsPath import io.github.kscripting.os.model.OsPath class MacOsVfs(userHome: String) : PosixVfs(OsTypeNew.MACOS) { diff --git a/src/main/kotlin/io/github/kscripting/os/instance/MsysVfs.kt b/src/main/kotlin/io/github/kscripting/os/instance/MsysVfs.kt index f1386fe..ab75e5b 100644 --- a/src/main/kotlin/io/github/kscripting/os/instance/MsysVfs.kt +++ b/src/main/kotlin/io/github/kscripting/os/instance/MsysVfs.kt @@ -2,14 +2,16 @@ package io.github.kscripting.os.instance import io.github.kscripting.os.OsTypeNew import io.github.kscripting.os.Vfs -import io.github.kscripting.os.createPosixOsPath +import io.github.kscripting.os.util.createPosixOsPath import io.github.kscripting.os.model.OsPath +import io.github.kscripting.os.util.toHostedConverter -class MsysVfs(override val nativeFsRoot: OsPath, userHome: String) : HostedVfs, PosixVfs(OsTypeNew.MSYS) { +class MsysVfs(override val nativeFsRoot: OsPath, userHome: String) : HostedVfs, PosixVfs(OsTypeNew.MSYS) { override val userHome: OsPath = createOsPath(userHome) override fun createOsPath(path: String): OsPath = createPosixOsPath(this, path) - override fun toNative(osPath: OsPath): OsPath { + override fun toNative(providedOsPath: OsPath): OsPath { + val osPath = providedOsPath as OsPath val newParts = mutableListOf() var newRoot = "" @@ -33,4 +35,8 @@ class MsysVfs(override val nativeFsRoot: OsPath, userHome: String) : Ho return OsPath(nativeFsRoot.vfs, newRoot, newParts) } + + override fun toHosted(osPath: OsPath): OsPath { + return toHostedConverter(this, osPath as OsPath) + } } diff --git a/src/main/kotlin/io/github/kscripting/os/instance/WindowsVfs.kt b/src/main/kotlin/io/github/kscripting/os/instance/WindowsVfs.kt index 091e1cc..34ef1b8 100644 --- a/src/main/kotlin/io/github/kscripting/os/instance/WindowsVfs.kt +++ b/src/main/kotlin/io/github/kscripting/os/instance/WindowsVfs.kt @@ -2,7 +2,7 @@ package io.github.kscripting.os.instance import io.github.kscripting.os.OsTypeNew import io.github.kscripting.os.Vfs -import io.github.kscripting.os.createFinalPath +import io.github.kscripting.os.util.createFinalPath import io.github.kscripting.os.model.OsPath class WindowsVfs(userHome: String) : Vfs { diff --git a/src/main/kotlin/io/github/kscripting/os/model/OsPathExt.kt b/src/main/kotlin/io/github/kscripting/os/model/OsPathExt.kt index a33a469..02045c5 100644 --- a/src/main/kotlin/io/github/kscripting/os/model/OsPathExt.kt +++ b/src/main/kotlin/io/github/kscripting/os/model/OsPathExt.kt @@ -21,69 +21,12 @@ import java.nio.file.Paths // fun OsPath.toNative(): OsPath<*> = (vfs as? HostedVfs)?.toNative(this) ?: this -/* + fun List.startsWith(list: List): Boolean = (this.size >= list.size && this.subList(0, list.size) == list) fun OsPath.startsWith(osPath: OsPath): Boolean = root == osPath.root && pathParts.startsWith(osPath.pathParts) -fun OsPath.toHosted(targetOs: OsType<*>): OsPath { - if (osType == targetOs) { - //This is already targetOs... - return this - } - - if (!(targetOs.isPosixHostedOnWindows() && osType == ((targetOs.os) as HostedOs).nativeType)) { - throw OsPathError.InvalidConversion("You can convert only paths to hosted OS-es") - } - - val newParts = mutableListOf() - var newRoot = "" - - if (isAbsolute) { - val hostedOs = targetOs.os as HostedOs - - if (this.startsWith(hostedOs.nativeFileSystemRoot)) { - if (pathParts.subList(hostedOs.nativeFileSystemRoot.pathParts.size, pathParts.size) - .startsWith(hostedOs.userHome.pathParts) - ) { - //It is user home: ~ - newRoot = "~/" - newParts.addAll( - pathParts.subList( - hostedOs.nativeFileSystemRoot.pathParts.size + hostedOs.userHome.pathParts.size, - pathParts.size - ) - ) - } else { - //It is hostedOs root: / - newRoot = "/" - newParts.addAll(pathParts.subList(hostedOs.nativeFileSystemRoot.pathParts.size, pathParts.size)) - } - } else { - //Otherwise: - //root is like 'C:\' - val drive = root.dropLast(2).lowercase() - - newRoot = "/" - - if (targetOs.os.type == GlobalOsType.CYGWIN) { - newParts.add("cygdrive") - newParts.add(drive) - } else { - newParts.add(drive) - } - - newParts.addAll(pathParts) - } - } else { - newParts.addAll(pathParts) - } - - return OsPath(targetOs, newRoot, newParts) -} -*/ - //// Conversion to OsPath //fun File.toOsPath(): OsPath = OsPath(GlobalOsType.native, absolutePath) // diff --git a/src/main/kotlin/io/github/kscripting/os/util/VfsUtil.kt b/src/main/kotlin/io/github/kscripting/os/util/VfsUtil.kt new file mode 100644 index 0000000..3bce675 --- /dev/null +++ b/src/main/kotlin/io/github/kscripting/os/util/VfsUtil.kt @@ -0,0 +1,78 @@ +package io.github.kscripting.os.util + +import io.github.kscripting.os.Vfs +import io.github.kscripting.os.instance.CygwinVfs +import io.github.kscripting.os.instance.HostedVfs +import io.github.kscripting.os.instance.WindowsVfs +import io.github.kscripting.os.model.OsPath +import io.github.kscripting.os.model.startsWith + + +fun createPosixOsPath(vfs: T, path: String): OsPath { + require(vfs.isValid(path)) + + //Detect root + val root: String = when { + path.startsWith("~/") || path.startsWith("~\\") -> "~" + path.startsWith("/") -> "/" + else -> "" + } + + return createFinalPath(vfs, path, root) +} + +fun createFinalPath(vfs: T, path: String, root: String): OsPath { + //Remove also empty path parts - there were duplicated or trailing slashes / backslashes in initial path + val pathWithoutRoot = path.drop(root.length) + + require(vfs.isValid(pathWithoutRoot)) + + val pathPartsResolved = pathWithoutRoot.split('/', '\\').filter { it.isNotBlank() } + return vfs.normalize(OsPath(vfs, root, pathPartsResolved)) +} + +fun toHostedConverter(vfs: T, osPath: OsPath): OsPath { + val newParts = mutableListOf() + var newRoot = "" + + if (osPath.isAbsolute) { + val nativeFsRoot = vfs.nativeFsRoot as OsPath + + if (osPath.startsWith(nativeFsRoot)) { + if (osPath.pathParts.subList(nativeFsRoot.pathParts.size, osPath.pathParts.size) + .startsWith(vfs.userHome.pathParts) + ) { + //It is user home: ~ + newRoot = "~/" + newParts.addAll( + osPath.pathParts.subList( + nativeFsRoot.pathParts.size + vfs.userHome.pathParts.size, + osPath.pathParts.size + ) + ) + } else { + //It is hostedOs root: / + newRoot = "/" + newParts.addAll(osPath.pathParts.subList(nativeFsRoot.pathParts.size, osPath.pathParts.size)) + } + } else { + //Otherwise: + //root is like 'C:\' + val drive = osPath.root.dropLast(2).lowercase() + + newRoot = "/" + + if (vfs is CygwinVfs) { + newParts.add("cygdrive") + } + + newParts.add(drive) + + newParts.addAll(osPath.pathParts) + } + } else { + newParts.addAll(osPath.pathParts) + } + + return OsPath(vfs, newRoot, newParts) +} \ No newline at end of file diff --git a/src/test/kotlin/io/github/kscripting/os/model/HostedOsPathTest.kt b/src/test/kotlin/io/github/kscripting/os/model/HostedOsPathTest.kt index 40ba193..8782ec2 100644 --- a/src/test/kotlin/io/github/kscripting/os/model/HostedOsPathTest.kt +++ b/src/test/kotlin/io/github/kscripting/os/model/HostedOsPathTest.kt @@ -32,23 +32,23 @@ class HostedOsPathTest { cygwinVfs.createOsPath("../home/admin/.kscript").toNative().path ).isEqualTo("..\\home\\admin\\.kscript") } -/* + @Test fun `Test Windows to Cygwin`() { assertThat( - OsPath(windowsVfs, "C:\\home\\admin\\.kscript").toHosted(cygwinVfs).path + cygwinVfs.toHosted(windowsVfs.createOsPath("C:\\home\\admin\\.kscript")).path ).isEqualTo("/cygdrive/c/home/admin/.kscript") assertThat( - OsPath(windowsVfs, "..\\home\\admin\\.kscript").toHosted(cygwinVfs).path + cygwinVfs.toHosted(windowsVfs.createOsPath("..\\home\\admin\\.kscript")).path ).isEqualTo("../home/admin/.kscript") assertThat( - OsPath(windowsVfs, "C:\\Programs\\Cygwin\\home\\admin\\.kscript").toHosted(cygwinVfs).path + cygwinVfs.toHosted(windowsVfs.createOsPath("C:\\Programs\\Cygwin\\home\\admin\\.kscript")).path ).isEqualTo("~/.kscript") assertThat( - OsPath(windowsVfs, "C:\\Programs\\Cygwin\\usr\\local\\sdk").toHosted(cygwinVfs).path + cygwinVfs.toHosted(windowsVfs.createOsPath("C:\\Programs\\Cygwin\\usr\\local\\sdk")).path ).isEqualTo("/usr/local/sdk") } @@ -74,21 +74,19 @@ class HostedOsPathTest { @Test fun `Test Windows to MSys`() { assertThat( - OsPath(windowsVfs, "C:\\home\\admin\\.kscript").toHosted(msysVfs).path + msysVfs.toHosted(windowsVfs.createOsPath("C:\\home\\admin\\.kscript")).path ).isEqualTo("/c/home/admin/.kscript") assertThat( - OsPath(windowsVfs, "..\\home\\admin\\.kscript").toHosted(msysVfs).path + msysVfs.toHosted(windowsVfs.createOsPath("..\\home\\admin\\.kscript")).path ).isEqualTo("../home/admin/.kscript") assertThat( - OsPath(windowsVfs, "C:\\Programs\\Msys\\home\\admin\\.kscript").toHosted(msysVfs).path + msysVfs.toHosted(windowsVfs.createOsPath("C:\\Programs\\Msys\\home\\admin\\.kscript")).path ).isEqualTo("~/.kscript") assertThat( - OsPath(windowsVfs, "C:\\Programs\\Msys\\usr\\local\\sdk").toHosted(msysVfs).path + msysVfs.toHosted(windowsVfs.createOsPath("C:\\Programs\\Msys\\usr\\local\\sdk")).path ).isEqualTo("/usr/local/sdk") } - - */ } From b9c0866ded1343d9eb1f447666779679adeaf9c4 Mon Sep 17 00:00:00 2001 From: Marcin Kuszczak <1508798+aartiPl@users.noreply.github.com> Date: Tue, 26 Dec 2023 12:15:52 +0100 Subject: [PATCH 47/50] Update --- src/main/kotlin/io/github/kscripting/os/Os.kt | 2 +- .../io/github/kscripting/os/OsBuilder.kt | 13 ++++---- .../kscripting/os/{OsTypeNew.kt => OsType.kt} | 4 +-- .../kotlin/io/github/kscripting/os/Vfs.kt | 2 +- .../github/kscripting/os/instance/CygwinOs.kt | 4 +-- .../kscripting/os/instance/CygwinVfs.kt | 4 +-- .../kscripting/os/instance/FreeBsdOs.kt | 4 +-- .../kscripting/os/instance/FreeBsdVfs.kt | 4 +-- .../github/kscripting/os/instance/LinuxOs.kt | 4 +-- .../github/kscripting/os/instance/LinuxVfs.kt | 4 +-- .../io/github/kscripting/os/instance/MacOs.kt | 4 +-- .../github/kscripting/os/instance/MacOsVfs.kt | 4 +-- .../github/kscripting/os/instance/MsysOs.kt | 4 +-- .../github/kscripting/os/instance/MsysVfs.kt | 4 +-- .../github/kscripting/os/instance/PosixVfs.kt | 4 +-- .../kscripting/os/instance/WindowsOs.kt | 4 +-- .../kscripting/os/instance/WindowsVfs.kt | 4 +-- .../kscripting/os/model/GlobalOsType.kt | 31 +++++++++---------- .../io/github/kscripting/os/model/OsPath.kt | 4 +-- .../github/kscripting/os/model/OsPathExt.kt | 4 +-- .../io/github/kscripting/os/model/OsType.kt | 25 --------------- .../github/kscripting/shell/ShellExecutor.kt | 30 +++++++++--------- .../kscripting/os/model/PosixOsPathTest.kt | 28 ++++++++--------- .../kscripting/os/model/WindowsOsPathTest.kt | 28 ++++++++--------- 24 files changed, 96 insertions(+), 127 deletions(-) rename src/main/kotlin/io/github/kscripting/os/{OsTypeNew.kt => OsType.kt} (92%) delete mode 100644 src/main/kotlin/io/github/kscripting/os/model/OsType.kt diff --git a/src/main/kotlin/io/github/kscripting/os/Os.kt b/src/main/kotlin/io/github/kscripting/os/Os.kt index 59e7c6d..00410b8 100644 --- a/src/main/kotlin/io/github/kscripting/os/Os.kt +++ b/src/main/kotlin/io/github/kscripting/os/Os.kt @@ -7,7 +7,7 @@ interface Os { // startsWith() covers all cases. val osTypePrefix: String - val type: OsTypeNew + val type: OsType //val environment: Map //val systemProperties: Map diff --git a/src/main/kotlin/io/github/kscripting/os/OsBuilder.kt b/src/main/kotlin/io/github/kscripting/os/OsBuilder.kt index 3b73fda..adc4ec1 100644 --- a/src/main/kotlin/io/github/kscripting/os/OsBuilder.kt +++ b/src/main/kotlin/io/github/kscripting/os/OsBuilder.kt @@ -1,6 +1,5 @@ package io.github.kscripting.os -import io.github.kscripting.os.model.GlobalOsType import org.apache.commons.lang3.SystemUtils class OsBuilder { @@ -11,16 +10,16 @@ class OsBuilder { } - val native: OsTypeNew = guessNativeType() + val native: OsType = guessNativeType() // fun findByOsTypeString(osTypeString: String): OsTypeNew? = // GlobalOsType.find { osTypeString.startsWith(it.os.osTypePrefix, true) } - private fun guessNativeType(): OsTypeNew = when { - SystemUtils.IS_OS_MAC -> OsTypeNew.MACOS - SystemUtils.IS_OS_WINDOWS -> OsTypeNew.WINDOWS - SystemUtils.IS_OS_FREE_BSD -> OsTypeNew.FREEBSD - else -> OsTypeNew.LINUX + private fun guessNativeType(): OsType = when { + SystemUtils.IS_OS_MAC -> OsType.MACOS + SystemUtils.IS_OS_WINDOWS -> OsType.WINDOWS + SystemUtils.IS_OS_FREE_BSD -> OsType.FREEBSD + else -> OsType.LINUX } } diff --git a/src/main/kotlin/io/github/kscripting/os/OsTypeNew.kt b/src/main/kotlin/io/github/kscripting/os/OsType.kt similarity index 92% rename from src/main/kotlin/io/github/kscripting/os/OsTypeNew.kt rename to src/main/kotlin/io/github/kscripting/os/OsType.kt index 6bb128e..ea7fd96 100644 --- a/src/main/kotlin/io/github/kscripting/os/OsTypeNew.kt +++ b/src/main/kotlin/io/github/kscripting/os/OsType.kt @@ -1,6 +1,6 @@ package io.github.kscripting.os -enum class OsTypeNew { +enum class OsType { LINUX, WINDOWS, CYGWIN, MSYS, MACOS, FREEBSD; fun isPosixLike() = @@ -8,4 +8,4 @@ enum class OsTypeNew { fun isPosixHostedOnWindows() = (this == CYGWIN || this == MSYS) fun isWindowsLike() = (this == WINDOWS) -} \ No newline at end of file +} diff --git a/src/main/kotlin/io/github/kscripting/os/Vfs.kt b/src/main/kotlin/io/github/kscripting/os/Vfs.kt index b5a4a46..0b6fad0 100644 --- a/src/main/kotlin/io/github/kscripting/os/Vfs.kt +++ b/src/main/kotlin/io/github/kscripting/os/Vfs.kt @@ -4,7 +4,7 @@ import io.github.kscripting.os.model.OsPath interface Vfs { - val type: OsTypeNew + val type: OsType val pathSeparator: String val userHome: OsPath diff --git a/src/main/kotlin/io/github/kscripting/os/instance/CygwinOs.kt b/src/main/kotlin/io/github/kscripting/os/instance/CygwinOs.kt index 9718013..74902c8 100644 --- a/src/main/kotlin/io/github/kscripting/os/instance/CygwinOs.kt +++ b/src/main/kotlin/io/github/kscripting/os/instance/CygwinOs.kt @@ -1,8 +1,8 @@ package io.github.kscripting.os.instance -import io.github.kscripting.os.OsTypeNew +import io.github.kscripting.os.OsType class CygwinOs(override val vfs: CygwinVfs, override val nativeOs: WindowsOs) : HostedOs { - override val type: OsTypeNew = OsTypeNew.CYGWIN + override val type: OsType = OsType.CYGWIN override val osTypePrefix: String = "cygwin" } diff --git a/src/main/kotlin/io/github/kscripting/os/instance/CygwinVfs.kt b/src/main/kotlin/io/github/kscripting/os/instance/CygwinVfs.kt index 7c0ca4d..7f5e306 100644 --- a/src/main/kotlin/io/github/kscripting/os/instance/CygwinVfs.kt +++ b/src/main/kotlin/io/github/kscripting/os/instance/CygwinVfs.kt @@ -1,13 +1,13 @@ package io.github.kscripting.os.instance -import io.github.kscripting.os.OsTypeNew +import io.github.kscripting.os.OsType import io.github.kscripting.os.Vfs import io.github.kscripting.os.util.createPosixOsPath import io.github.kscripting.os.model.OsPath import io.github.kscripting.os.util.toHostedConverter class CygwinVfs(override val nativeFsRoot: OsPath, userHome: String) : HostedVfs, - PosixVfs(OsTypeNew.CYGWIN) { + PosixVfs(OsType.CYGWIN) { override fun toNative(providedOsPath: OsPath): OsPath { val osPath = providedOsPath as OsPath diff --git a/src/main/kotlin/io/github/kscripting/os/instance/FreeBsdOs.kt b/src/main/kotlin/io/github/kscripting/os/instance/FreeBsdOs.kt index 2b3bf46..21ebbe9 100644 --- a/src/main/kotlin/io/github/kscripting/os/instance/FreeBsdOs.kt +++ b/src/main/kotlin/io/github/kscripting/os/instance/FreeBsdOs.kt @@ -1,9 +1,9 @@ package io.github.kscripting.os.instance import io.github.kscripting.os.Os -import io.github.kscripting.os.OsTypeNew +import io.github.kscripting.os.OsType class FreeBsdOs(override val vfs: FreeBsdVfs) : Os { - override val type: OsTypeNew = OsTypeNew.FREEBSD + override val type: OsType = OsType.FREEBSD override val osTypePrefix: String = "freebsd" } diff --git a/src/main/kotlin/io/github/kscripting/os/instance/FreeBsdVfs.kt b/src/main/kotlin/io/github/kscripting/os/instance/FreeBsdVfs.kt index 498b9c2..d4ddb24 100644 --- a/src/main/kotlin/io/github/kscripting/os/instance/FreeBsdVfs.kt +++ b/src/main/kotlin/io/github/kscripting/os/instance/FreeBsdVfs.kt @@ -1,10 +1,10 @@ package io.github.kscripting.os.instance -import io.github.kscripting.os.OsTypeNew +import io.github.kscripting.os.OsType import io.github.kscripting.os.util.createPosixOsPath import io.github.kscripting.os.model.OsPath -class FreeBsdVfs(userHome: String) : PosixVfs(OsTypeNew.FREEBSD) { +class FreeBsdVfs(userHome: String) : PosixVfs(OsType.FREEBSD) { override val userHome: OsPath = createOsPath(userHome) override fun createOsPath(path: String): OsPath = createPosixOsPath(this, path) } diff --git a/src/main/kotlin/io/github/kscripting/os/instance/LinuxOs.kt b/src/main/kotlin/io/github/kscripting/os/instance/LinuxOs.kt index 0cee036..249efde 100644 --- a/src/main/kotlin/io/github/kscripting/os/instance/LinuxOs.kt +++ b/src/main/kotlin/io/github/kscripting/os/instance/LinuxOs.kt @@ -1,9 +1,9 @@ package io.github.kscripting.os.instance import io.github.kscripting.os.Os -import io.github.kscripting.os.OsTypeNew +import io.github.kscripting.os.OsType class LinuxOs(override val vfs: LinuxVfs) : Os { - override val type: OsTypeNew = OsTypeNew.LINUX + override val type: OsType = OsType.LINUX override val osTypePrefix: String = "linux" } diff --git a/src/main/kotlin/io/github/kscripting/os/instance/LinuxVfs.kt b/src/main/kotlin/io/github/kscripting/os/instance/LinuxVfs.kt index ed4ef28..d82bfdd 100644 --- a/src/main/kotlin/io/github/kscripting/os/instance/LinuxVfs.kt +++ b/src/main/kotlin/io/github/kscripting/os/instance/LinuxVfs.kt @@ -1,10 +1,10 @@ package io.github.kscripting.os.instance -import io.github.kscripting.os.OsTypeNew +import io.github.kscripting.os.OsType import io.github.kscripting.os.util.createPosixOsPath import io.github.kscripting.os.model.OsPath -class LinuxVfs(userHome: String) : PosixVfs(OsTypeNew.LINUX) { +class LinuxVfs(userHome: String) : PosixVfs(OsType.LINUX) { override val userHome: OsPath = createOsPath(userHome) override fun createOsPath(path: String): OsPath = createPosixOsPath(this, path) } diff --git a/src/main/kotlin/io/github/kscripting/os/instance/MacOs.kt b/src/main/kotlin/io/github/kscripting/os/instance/MacOs.kt index 6bb0f14..a6f3cf7 100644 --- a/src/main/kotlin/io/github/kscripting/os/instance/MacOs.kt +++ b/src/main/kotlin/io/github/kscripting/os/instance/MacOs.kt @@ -1,9 +1,9 @@ package io.github.kscripting.os.instance import io.github.kscripting.os.Os -import io.github.kscripting.os.OsTypeNew +import io.github.kscripting.os.OsType class MacOs(override val vfs: MacOsVfs) : Os { - override val type: OsTypeNew = OsTypeNew.MACOS + override val type: OsType = OsType.MACOS override val osTypePrefix: String = "darwin" } diff --git a/src/main/kotlin/io/github/kscripting/os/instance/MacOsVfs.kt b/src/main/kotlin/io/github/kscripting/os/instance/MacOsVfs.kt index cd5a722..fb5160a 100644 --- a/src/main/kotlin/io/github/kscripting/os/instance/MacOsVfs.kt +++ b/src/main/kotlin/io/github/kscripting/os/instance/MacOsVfs.kt @@ -1,10 +1,10 @@ package io.github.kscripting.os.instance -import io.github.kscripting.os.OsTypeNew +import io.github.kscripting.os.OsType import io.github.kscripting.os.util.createPosixOsPath import io.github.kscripting.os.model.OsPath -class MacOsVfs(userHome: String) : PosixVfs(OsTypeNew.MACOS) { +class MacOsVfs(userHome: String) : PosixVfs(OsType.MACOS) { override val userHome: OsPath = createOsPath(userHome) override fun createOsPath(path: String): OsPath = createPosixOsPath(this, path) } diff --git a/src/main/kotlin/io/github/kscripting/os/instance/MsysOs.kt b/src/main/kotlin/io/github/kscripting/os/instance/MsysOs.kt index 5f69184..724f8b4 100644 --- a/src/main/kotlin/io/github/kscripting/os/instance/MsysOs.kt +++ b/src/main/kotlin/io/github/kscripting/os/instance/MsysOs.kt @@ -1,8 +1,8 @@ package io.github.kscripting.os.instance -import io.github.kscripting.os.OsTypeNew +import io.github.kscripting.os.OsType class MsysOs(override val nativeOs: WindowsOs, override val vfs: MsysVfs) : HostedOs { - override val type: OsTypeNew = OsTypeNew.MSYS + override val type: OsType = OsType.MSYS override val osTypePrefix: String = "msys" } diff --git a/src/main/kotlin/io/github/kscripting/os/instance/MsysVfs.kt b/src/main/kotlin/io/github/kscripting/os/instance/MsysVfs.kt index ab75e5b..0d14cba 100644 --- a/src/main/kotlin/io/github/kscripting/os/instance/MsysVfs.kt +++ b/src/main/kotlin/io/github/kscripting/os/instance/MsysVfs.kt @@ -1,12 +1,12 @@ package io.github.kscripting.os.instance -import io.github.kscripting.os.OsTypeNew +import io.github.kscripting.os.OsType import io.github.kscripting.os.Vfs import io.github.kscripting.os.util.createPosixOsPath import io.github.kscripting.os.model.OsPath import io.github.kscripting.os.util.toHostedConverter -class MsysVfs(override val nativeFsRoot: OsPath, userHome: String) : HostedVfs, PosixVfs(OsTypeNew.MSYS) { +class MsysVfs(override val nativeFsRoot: OsPath, userHome: String) : HostedVfs, PosixVfs(OsType.MSYS) { override val userHome: OsPath = createOsPath(userHome) override fun createOsPath(path: String): OsPath = createPosixOsPath(this, path) diff --git a/src/main/kotlin/io/github/kscripting/os/instance/PosixVfs.kt b/src/main/kotlin/io/github/kscripting/os/instance/PosixVfs.kt index 3491b5c..7420513 100644 --- a/src/main/kotlin/io/github/kscripting/os/instance/PosixVfs.kt +++ b/src/main/kotlin/io/github/kscripting/os/instance/PosixVfs.kt @@ -1,9 +1,9 @@ package io.github.kscripting.os.instance -import io.github.kscripting.os.OsTypeNew +import io.github.kscripting.os.OsType import io.github.kscripting.os.Vfs -abstract class PosixVfs(override val type: OsTypeNew) : Vfs { +abstract class PosixVfs(override val type: OsType) : Vfs { override val pathSeparator: String = "/" override fun isValid(path: String): Boolean = true } \ No newline at end of file diff --git a/src/main/kotlin/io/github/kscripting/os/instance/WindowsOs.kt b/src/main/kotlin/io/github/kscripting/os/instance/WindowsOs.kt index 6abca86..9610f43 100644 --- a/src/main/kotlin/io/github/kscripting/os/instance/WindowsOs.kt +++ b/src/main/kotlin/io/github/kscripting/os/instance/WindowsOs.kt @@ -1,9 +1,9 @@ package io.github.kscripting.os.instance import io.github.kscripting.os.Os -import io.github.kscripting.os.OsTypeNew +import io.github.kscripting.os.OsType class WindowsOs(override val vfs: WindowsVfs) : Os { - override val type: OsTypeNew = OsTypeNew.WINDOWS + override val type: OsType = OsType.WINDOWS override val osTypePrefix: String = "windows" } diff --git a/src/main/kotlin/io/github/kscripting/os/instance/WindowsVfs.kt b/src/main/kotlin/io/github/kscripting/os/instance/WindowsVfs.kt index 34ef1b8..46f31c5 100644 --- a/src/main/kotlin/io/github/kscripting/os/instance/WindowsVfs.kt +++ b/src/main/kotlin/io/github/kscripting/os/instance/WindowsVfs.kt @@ -1,12 +1,12 @@ package io.github.kscripting.os.instance -import io.github.kscripting.os.OsTypeNew +import io.github.kscripting.os.OsType import io.github.kscripting.os.Vfs import io.github.kscripting.os.util.createFinalPath import io.github.kscripting.os.model.OsPath class WindowsVfs(userHome: String) : Vfs { - override val type: OsTypeNew = OsTypeNew.WINDOWS + override val type: OsType = OsType.WINDOWS override val pathSeparator: String = "\\" //https://stackoverflow.com/questions/1976007/what-characters-are-forbidden-in-windows-and-linux-directory-names diff --git a/src/main/kotlin/io/github/kscripting/os/model/GlobalOsType.kt b/src/main/kotlin/io/github/kscripting/os/model/GlobalOsType.kt index 4998e0c..d25cdad 100644 --- a/src/main/kotlin/io/github/kscripting/os/model/GlobalOsType.kt +++ b/src/main/kotlin/io/github/kscripting/os/model/GlobalOsType.kt @@ -10,30 +10,29 @@ import net.igsoft.typeutils.typedenum.TypedEnumCompanion import org.apache.commons.lang3.SystemUtils -class GlobalOsType private constructor(private val marker: TypedMarker) : OsType, - DefaultTypedMarker(marker) { - override val os: T get() = GlobalContext.getValue(marker) +class GlobalOsType private constructor(private val marker: TypedMarker) : DefaultTypedMarker(marker) { + val os: T get() = GlobalContext.getValue(marker) - override fun isPosixLike() = + fun isPosixLike() = (this == LINUX || this == MACOS || this == FREEBSD || this == CYGWIN || this == MSYS) - override fun isPosixHostedOnWindows() = (this == CYGWIN || this == MSYS) - override fun isWindowsLike() = (this == WINDOWS) + fun isPosixHostedOnWindows() = (this == CYGWIN || this == MSYS) + fun isWindowsLike() = (this == WINDOWS) - companion object : TypedEnumCompanion>() { - val LINUX: OsType = GlobalOsType(AutoTypedMarker.create()) - val WINDOWS: OsType = GlobalOsType(AutoTypedMarker.create()) - val CYGWIN: OsType = GlobalOsType(AutoTypedMarker.create()) - val MSYS: OsType = GlobalOsType(AutoTypedMarker.create()) - val MACOS: OsType = GlobalOsType(AutoTypedMarker.create()) - val FREEBSD: OsType = GlobalOsType(AutoTypedMarker.create()) + companion object : TypedEnumCompanion>() { + val LINUX: GlobalOsType = GlobalOsType(AutoTypedMarker.create()) + val WINDOWS: GlobalOsType = GlobalOsType(AutoTypedMarker.create()) + val CYGWIN: GlobalOsType = GlobalOsType(AutoTypedMarker.create()) + val MSYS: GlobalOsType = GlobalOsType(AutoTypedMarker.create()) + val MACOS: GlobalOsType = GlobalOsType(AutoTypedMarker.create()) + val FREEBSD: GlobalOsType = GlobalOsType(AutoTypedMarker.create()) - val native: OsType = guessNativeType() + val native: GlobalOsType = guessNativeType() - fun findByOsTypeString(osTypeString: String): OsType? = + fun findByOsTypeString(osTypeString: String): GlobalOsType? = find { osTypeString.startsWith(it.os.osTypePrefix, true) } - private fun guessNativeType(): OsType { + private fun guessNativeType(): GlobalOsType { when { SystemUtils.IS_OS_LINUX -> return LINUX SystemUtils.IS_OS_MAC -> return MACOS diff --git a/src/main/kotlin/io/github/kscripting/os/model/OsPath.kt b/src/main/kotlin/io/github/kscripting/os/model/OsPath.kt index cb85577..35c98df 100644 --- a/src/main/kotlin/io/github/kscripting/os/model/OsPath.kt +++ b/src/main/kotlin/io/github/kscripting/os/model/OsPath.kt @@ -1,6 +1,6 @@ package io.github.kscripting.os.model -import io.github.kscripting.os.OsTypeNew +import io.github.kscripting.os.OsType import io.github.kscripting.os.Vfs sealed interface OsPathError { @@ -12,7 +12,7 @@ sealed interface OsPathError { @Suppress("MemberVisibilityCanBePrivate") //TODO: should be only instantiated from VFS, not in any place data class OsPath(@Transient internal val vfs: T, val root: String, val pathParts: List) { - val osType: OsTypeNew = vfs.type + val osType: OsType = vfs.type val isRelative: Boolean get() = root.isEmpty() && pathParts.isNotEmpty() val isAbsolute: Boolean get() = root.isNotEmpty() diff --git a/src/main/kotlin/io/github/kscripting/os/model/OsPathExt.kt b/src/main/kotlin/io/github/kscripting/os/model/OsPathExt.kt index 02045c5..95b31c0 100644 --- a/src/main/kotlin/io/github/kscripting/os/model/OsPathExt.kt +++ b/src/main/kotlin/io/github/kscripting/os/model/OsPathExt.kt @@ -1,8 +1,6 @@ package io.github.kscripting.os.model -import io.github.kscripting.os.OsTypeNew import io.github.kscripting.os.Vfs -import io.github.kscripting.os.instance.HostedOs import java.io.File //import io.github.kscripting.os.Vfs @@ -61,7 +59,7 @@ operator fun OsPath.div(osPath: OsPath): OsPath = resolve(osP operator fun OsPath.div(path: String): OsPath = resolve(path) -fun OsPath.resolve(vararg pathParts: String): OsPath = resolve(vfs.createOsPath(pathParts.joinToString("/")) as OsPath) +fun OsPath.resolve(vararg pathParts: String): OsPath = resolve(vfs.createOsPath(*pathParts) as OsPath) fun OsPath.resolve(osPath: OsPath): OsPath { if (osType != osPath.osType) { diff --git a/src/main/kotlin/io/github/kscripting/os/model/OsType.kt b/src/main/kotlin/io/github/kscripting/os/model/OsType.kt deleted file mode 100644 index 3c49392..0000000 --- a/src/main/kotlin/io/github/kscripting/os/model/OsType.kt +++ /dev/null @@ -1,25 +0,0 @@ -package io.github.kscripting.os.model - -import io.github.kscripting.os.Os -import io.github.kscripting.os.instance.* -import net.igsoft.typeutils.marker.TypedMarker - -//@Suppress("PropertyName") -//interface OsTypeCompanion { -// val LINUX: OsType -// val WINDOWS: OsType -// val CYGWIN: OsType -// val MSYS: OsType -// val MACOS: OsType -// val FREEBSD: OsType -// -// val native: OsType -//} - -interface OsType : TypedMarker { - val os: T - - fun isPosixLike(): Boolean - fun isPosixHostedOnWindows(): Boolean - fun isWindowsLike(): Boolean -} diff --git a/src/main/kotlin/io/github/kscripting/shell/ShellExecutor.kt b/src/main/kotlin/io/github/kscripting/shell/ShellExecutor.kt index b1f0634..bab10d7 100644 --- a/src/main/kotlin/io/github/kscripting/shell/ShellExecutor.kt +++ b/src/main/kotlin/io/github/kscripting/shell/ShellExecutor.kt @@ -1,9 +1,7 @@ package io.github.kscripting.shell -import io.github.kscripting.os.Os +import io.github.kscripting.os.OsType import io.github.kscripting.os.model.OsPath -import io.github.kscripting.os.model.GlobalOsType -import io.github.kscripting.os.model.OsType import io.github.kscripting.shell.model.ProcessResult import io.github.kscripting.shell.model.ShellType import io.github.kscripting.shell.process.EnvAdjuster @@ -22,18 +20,18 @@ import java.util.regex.Pattern object ShellExecutor { private val SPLIT_PATTERN = Pattern.compile("([^\"]\\S*|\".+?\")\\s*") private val UTF_8 = StandardCharsets.UTF_8.name() - private val DEFAULT_SHELL_MAPPER: Map, ShellType> = mapOf( - GlobalOsType.WINDOWS to ShellType.CMD, - GlobalOsType.LINUX to ShellType.BASH, - GlobalOsType.FREEBSD to ShellType.BASH, - GlobalOsType.MACOS to ShellType.BASH, - GlobalOsType.CYGWIN to ShellType.BASH, - GlobalOsType.MSYS to ShellType.BASH, + private val DEFAULT_SHELL_MAPPER: Map = mapOf( + OsType.WINDOWS to ShellType.CMD, + OsType.LINUX to ShellType.BASH, + OsType.FREEBSD to ShellType.BASH, + OsType.MACOS to ShellType.BASH, + OsType.CYGWIN to ShellType.BASH, + OsType.MSYS to ShellType.BASH, ) fun evalAndGobble( command: String, - globalOsType: OsType = GlobalOsType.native, + osType: OsType, workingDirectory: OsPath<*>? = null, waitTimeMinutes: Int = 10, inheritInput: Boolean = false, @@ -42,7 +40,7 @@ object ShellExecutor { outPrinter: List = emptyList(), errPrinter: List = emptyList(), inputStream: InputStream? = null, - shellMapper: Map, ShellType> = DEFAULT_SHELL_MAPPER, + shellMapper: Map = DEFAULT_SHELL_MAPPER, envAdjuster: EnvAdjuster = {} ): ProcessResult { val outStream = ByteArrayOutputStream(1024) @@ -54,7 +52,7 @@ object ShellExecutor { PrintStream(errStream, true, UTF_8).use { additionalErrPrinter -> result = eval( command, - globalOsType, + osType, workingDirectory, waitTimeMinutes, inheritInput, @@ -74,7 +72,7 @@ object ShellExecutor { fun eval( command: String, - globalOsType: OsType = GlobalOsType.native, + osType: OsType, workingDirectory: OsPath<*>? = null, waitTimeMinutes: Int = 10, inheritInput: Boolean = false, @@ -83,12 +81,12 @@ object ShellExecutor { outPrinter: List = DEFAULT_OUT_PRINTERS, errPrinter: List = DEFAULT_ERR_PRINTERS, inputStream: InputStream? = null, - shellMapper: Map, ShellType> = DEFAULT_SHELL_MAPPER, + shellMapper: Map = DEFAULT_SHELL_MAPPER, envAdjuster: EnvAdjuster = {} ): Int { val sanitizedCommand = inputSanitizer.sanitize(command) - val commandList = when (val shellType = shellMapper.getValue(globalOsType)) { + val commandList = when (val shellType = shellMapper.getValue(osType)) { //NOTE: usually command is an argument to shell (bash/cmd), so it should stay not split by whitespace as //a single string, but when there is no shell, we have to split the command ShellType.NONE -> { diff --git a/src/test/kotlin/io/github/kscripting/os/model/PosixOsPathTest.kt b/src/test/kotlin/io/github/kscripting/os/model/PosixOsPathTest.kt index 7d36405..6c86041 100644 --- a/src/test/kotlin/io/github/kscripting/os/model/PosixOsPathTest.kt +++ b/src/test/kotlin/io/github/kscripting/os/model/PosixOsPathTest.kt @@ -6,7 +6,7 @@ import assertk.assertions.hasMessage import assertk.assertions.isEqualTo import assertk.assertions.isInstanceOf import assertk.assertions.prop -import io.github.kscripting.os.OsTypeNew +import io.github.kscripting.os.OsType import io.github.kscripting.os.instance.LinuxVfs import org.junit.jupiter.api.Test import org.junit.jupiter.api.TestInstance @@ -18,63 +18,63 @@ class PosixOsPathTest { @Test fun `Test Posix paths`() { assertThat(linuxVfs.createOsPath("/")).let { - it.prop(OsPath<*>::osType).isEqualTo(OsTypeNew.LINUX) + it.prop(OsPath<*>::osType).isEqualTo(OsType.LINUX) it.prop(OsPath<*>::root).isEqualTo("/") it.prop(OsPath<*>::pathParts).isEqualTo(emptyList()) } assertThat(linuxVfs.createOsPath("/home/admin/.kscript")).let { - it.prop(OsPath<*>::osType).isEqualTo(OsTypeNew.LINUX) + it.prop(OsPath<*>::osType).isEqualTo(OsType.LINUX) it.prop(OsPath<*>::root).isEqualTo("/") it.prop(OsPath<*>::pathParts).isEqualTo(listOf("home", "admin", ".kscript")) } assertThat(linuxVfs.createOsPath("./home/admin/.kscript")).let { - it.prop(OsPath<*>::osType).isEqualTo(OsTypeNew.LINUX) + it.prop(OsPath<*>::osType).isEqualTo(OsType.LINUX) it.prop(OsPath<*>::root).isEqualTo("") it.prop(OsPath<*>::pathParts).isEqualTo(listOf("home", "admin", ".kscript")) } assertThat(linuxVfs.createOsPath("")).let { - it.prop(OsPath<*>::osType).isEqualTo(OsTypeNew.LINUX) + it.prop(OsPath<*>::osType).isEqualTo(OsType.LINUX) it.prop(OsPath<*>::root).isEqualTo("") it.prop(OsPath<*>::pathParts).isEqualTo(emptyList()) } assertThat(linuxVfs.createOsPath("file.txt")).let { - it.prop(OsPath<*>::osType).isEqualTo(OsTypeNew.LINUX) + it.prop(OsPath<*>::osType).isEqualTo(OsType.LINUX) it.prop(OsPath<*>::root).isEqualTo("") it.prop(OsPath<*>::pathParts).isEqualTo(listOf("file.txt")) } assertThat(linuxVfs.createOsPath(".")).let { - it.prop(OsPath<*>::osType).isEqualTo(OsTypeNew.LINUX) + it.prop(OsPath<*>::osType).isEqualTo(OsType.LINUX) it.prop(OsPath<*>::root).isEqualTo("") it.prop(OsPath<*>::pathParts).isEqualTo(emptyList()) } assertThat(linuxVfs.createOsPath("../home/admin/.kscript")).let { - it.prop(OsPath<*>::osType).isEqualTo(OsTypeNew.LINUX) + it.prop(OsPath<*>::osType).isEqualTo(OsType.LINUX) it.prop(OsPath<*>::root).isEqualTo("") it.prop(OsPath<*>::pathParts).isEqualTo(listOf("..", "home", "admin", ".kscript")) } assertThat(linuxVfs.createOsPath("..")).let { - it.prop(OsPath<*>::osType).isEqualTo(OsTypeNew.LINUX) + it.prop(OsPath<*>::osType).isEqualTo(OsType.LINUX) it.prop(OsPath<*>::root).isEqualTo("") it.prop(OsPath<*>::pathParts).isEqualTo(listOf("..")) } //Duplicated separators are accepted assertThat(linuxVfs.createOsPath("..//home////admin/.kscript/")).let { - it.prop(OsPath<*>::osType).isEqualTo(OsTypeNew.LINUX) + it.prop(OsPath<*>::osType).isEqualTo(OsType.LINUX) it.prop(OsPath<*>::root).isEqualTo("") it.prop(OsPath<*>::pathParts).isEqualTo(listOf("..", "home", "admin", ".kscript")) } //Both types of separator are accepted assertThat(linuxVfs.createOsPath("..//home\\admin\\.kscript/")).let { - it.prop(OsPath<*>::osType).isEqualTo(OsTypeNew.LINUX) + it.prop(OsPath<*>::osType).isEqualTo(OsType.LINUX) it.prop(OsPath<*>::root).isEqualTo("") it.prop(OsPath<*>::pathParts).isEqualTo(listOf("..", "home", "admin", ".kscript")) } @@ -83,19 +83,19 @@ class PosixOsPathTest { @Test fun `Normalization of Posix paths`() { assertThat(linuxVfs.createOsPath("/home/admin/.kscript/../../")).let { - it.prop(OsPath<*>::osType).isEqualTo(OsTypeNew.LINUX) + it.prop(OsPath<*>::osType).isEqualTo(OsType.LINUX) it.prop(OsPath<*>::root).isEqualTo("/") it.prop(OsPath<*>::pathParts).isEqualTo(listOf("home")) } assertThat(linuxVfs.createOsPath("./././../../script")).let { - it.prop(OsPath<*>::osType).isEqualTo(OsTypeNew.LINUX) + it.prop(OsPath<*>::osType).isEqualTo(OsType.LINUX) it.prop(OsPath<*>::root).isEqualTo("") it.prop(OsPath<*>::pathParts).isEqualTo(listOf("..", "..", "script")) } assertThat(linuxVfs.createOsPath("/a/b/c/../d/script")).let { - it.prop(OsPath<*>::osType).isEqualTo(OsTypeNew.LINUX) + it.prop(OsPath<*>::osType).isEqualTo(OsType.LINUX) it.prop(OsPath<*>::root).isEqualTo("/") it.prop(OsPath<*>::pathParts).isEqualTo(listOf("a", "b", "d", "script")) } diff --git a/src/test/kotlin/io/github/kscripting/os/model/WindowsOsPathTest.kt b/src/test/kotlin/io/github/kscripting/os/model/WindowsOsPathTest.kt index 5f74c0f..19185f1 100644 --- a/src/test/kotlin/io/github/kscripting/os/model/WindowsOsPathTest.kt +++ b/src/test/kotlin/io/github/kscripting/os/model/WindowsOsPathTest.kt @@ -6,7 +6,7 @@ import assertk.assertions.hasMessage import assertk.assertions.isEqualTo import assertk.assertions.isInstanceOf import assertk.assertions.prop -import io.github.kscripting.os.OsTypeNew +import io.github.kscripting.os.OsType import io.github.kscripting.os.instance.WindowsVfs import org.junit.jupiter.api.Test import org.junit.jupiter.api.TestInstance @@ -18,63 +18,63 @@ class WindowsOsPathTest { @Test fun `Test Windows paths`() { assertThat(windowsVfs.createOsPath("C:\\")).let { - it.prop(OsPath<*>::osType).isEqualTo(OsTypeNew.WINDOWS) + it.prop(OsPath<*>::osType).isEqualTo(OsType.WINDOWS) it.prop(OsPath<*>::root).isEqualTo("C:\\") it.prop(OsPath<*>::pathParts).isEqualTo(emptyList()) } assertThat(windowsVfs.createOsPath("C:\\home\\admin\\.kscript")).let { - it.prop(OsPath<*>::osType).isEqualTo(OsTypeNew.WINDOWS) + it.prop(OsPath<*>::osType).isEqualTo(OsType.WINDOWS) it.prop(OsPath<*>::root).isEqualTo("C:\\") it.prop(OsPath<*>::pathParts).isEqualTo(listOf("home", "admin", ".kscript")) } assertThat(windowsVfs.createOsPath("")).let { - it.prop(OsPath<*>::osType).isEqualTo(OsTypeNew.WINDOWS) + it.prop(OsPath<*>::osType).isEqualTo(OsType.WINDOWS) it.prop(OsPath<*>::root).isEqualTo("") it.prop(OsPath<*>::pathParts).isEqualTo(emptyList()) } assertThat(windowsVfs.createOsPath("file.txt")).let { - it.prop(OsPath<*>::osType).isEqualTo(OsTypeNew.WINDOWS) + it.prop(OsPath<*>::osType).isEqualTo(OsType.WINDOWS) it.prop(OsPath<*>::root).isEqualTo("") it.prop(OsPath<*>::pathParts).isEqualTo(listOf("file.txt")) } assertThat(windowsVfs.createOsPath(".")).let { - it.prop(OsPath<*>::osType).isEqualTo(OsTypeNew.WINDOWS) + it.prop(OsPath<*>::osType).isEqualTo(OsType.WINDOWS) it.prop(OsPath<*>::root).isEqualTo("") it.prop(OsPath<*>::pathParts).isEqualTo(emptyList()) } assertThat(windowsVfs.createOsPath(".\\home\\admin\\.kscript")).let { - it.prop(OsPath<*>::osType).isEqualTo(OsTypeNew.WINDOWS) + it.prop(OsPath<*>::osType).isEqualTo(OsType.WINDOWS) it.prop(OsPath<*>::root).isEqualTo("") it.prop(OsPath<*>::pathParts).isEqualTo(listOf("home", "admin", ".kscript")) } assertThat(windowsVfs.createOsPath("..\\home\\admin\\.kscript")).let { - it.prop(OsPath<*>::osType).isEqualTo(OsTypeNew.WINDOWS) + it.prop(OsPath<*>::osType).isEqualTo(OsType.WINDOWS) it.prop(OsPath<*>::root).isEqualTo("") it.prop(OsPath<*>::pathParts).isEqualTo(listOf("..", "home", "admin", ".kscript")) } assertThat(windowsVfs.createOsPath("..")).let { - it.prop(OsPath<*>::osType).isEqualTo(OsTypeNew.WINDOWS) + it.prop(OsPath<*>::osType).isEqualTo(OsType.WINDOWS) it.prop(OsPath<*>::root).isEqualTo("") it.prop(OsPath<*>::pathParts).isEqualTo(listOf("..")) } //Duplicated separators are accepted assertThat(windowsVfs.createOsPath("C:\\home\\\\\\\\admin\\.kscript\\")).let { - it.prop(OsPath<*>::osType).isEqualTo(OsTypeNew.WINDOWS) + it.prop(OsPath<*>::osType).isEqualTo(OsType.WINDOWS) it.prop(OsPath<*>::root).isEqualTo("C:\\") it.prop(OsPath<*>::pathParts).isEqualTo(listOf("home", "admin", ".kscript")) } //Both types of separator are accepted assertThat(windowsVfs.createOsPath("C:/home\\admin/.kscript////")).let { - it.prop(OsPath<*>::osType).isEqualTo(OsTypeNew.WINDOWS) + it.prop(OsPath<*>::osType).isEqualTo(OsType.WINDOWS) it.prop(OsPath<*>::root).isEqualTo("C:\\") it.prop(OsPath<*>::pathParts).isEqualTo(listOf("home", "admin", ".kscript")) } @@ -83,19 +83,19 @@ class WindowsOsPathTest { @Test fun `Normalization of Windows paths`() { assertThat(windowsVfs.createOsPath("C:\\home\\admin\\.kscript\\..\\..\\")).let { - it.prop(OsPath<*>::osType).isEqualTo(OsTypeNew.WINDOWS) + it.prop(OsPath<*>::osType).isEqualTo(OsType.WINDOWS) it.prop(OsPath<*>::root).isEqualTo("C:\\") it.prop(OsPath<*>::pathParts).isEqualTo(listOf("home")) } assertThat(windowsVfs.createOsPath(".\\.\\.\\..\\..\\script")).let { - it.prop(OsPath<*>::osType).isEqualTo(OsTypeNew.WINDOWS) + it.prop(OsPath<*>::osType).isEqualTo(OsType.WINDOWS) it.prop(OsPath<*>::root).isEqualTo("") it.prop(OsPath<*>::pathParts).isEqualTo(listOf("..", "..", "script")) } assertThat(windowsVfs.createOsPath("C:\\a\\b\\c\\..\\d\\script")).let { - it.prop(OsPath<*>::osType).isEqualTo(OsTypeNew.WINDOWS) + it.prop(OsPath<*>::osType).isEqualTo(OsType.WINDOWS) it.prop(OsPath<*>::root).isEqualTo("C:\\") it.prop(OsPath<*>::pathParts).isEqualTo(listOf("a", "b", "d", "script")) } From 0415f3129ce8585b5f461ad19778032b379aa7e7 Mon Sep 17 00:00:00 2001 From: Marcin Kuszczak <1508798+aartiPl@users.noreply.github.com> Date: Tue, 26 Dec 2023 15:34:19 +0100 Subject: [PATCH 48/50] Update --- src/main/kotlin/io/github/kscripting/os/Os.kt | 4 + .../io/github/kscripting/os/OsProvider.kt | 80 +++++++++++++++++++ .../kotlin/io/github/kscripting/os/Vfs.kt | 52 ------------ .../github/kscripting/os/instance/CygwinOs.kt | 2 + .../kscripting/os/instance/FreeBsdOs.kt | 4 + .../github/kscripting/os/instance/LinuxOs.kt | 8 ++ .../io/github/kscripting/os/instance/MacOs.kt | 4 + .../github/kscripting/os/instance/MsysOs.kt | 2 + .../kscripting/os/instance/WindowsOs.kt | 4 + .../github/kscripting/os/model/OsPathExt.kt | 3 +- .../io/github/kscripting/os/util/VfsUtil.kt | 58 +++++++++++++- 11 files changed, 166 insertions(+), 55 deletions(-) create mode 100644 src/main/kotlin/io/github/kscripting/os/OsProvider.kt diff --git a/src/main/kotlin/io/github/kscripting/os/Os.kt b/src/main/kotlin/io/github/kscripting/os/Os.kt index 00410b8..456b494 100644 --- a/src/main/kotlin/io/github/kscripting/os/Os.kt +++ b/src/main/kotlin/io/github/kscripting/os/Os.kt @@ -1,10 +1,14 @@ package io.github.kscripting.os +import net.igsoft.typeutils.marker.DefaultTypedMarker +import net.igsoft.typeutils.marker.NamedAutoTypedMarker + interface Os { //LINUX("linux"), MACOS("darwin"), WINDOWS("windows"), CYGWIN("cygwin"), MSYS("msys"), FREEBSD("freebsd"); // Exact comparison (it.osName.equals(name, true)) seems to be not feasible as there is also e.g. "darwin21" // "darwin19", "linux-musl" (for Docker Alpine), "linux-gnu" and maybe even other osTypes. But it seems that // startsWith() covers all cases. + val marker: NamedAutoTypedMarker val osTypePrefix: String val type: OsType diff --git a/src/main/kotlin/io/github/kscripting/os/OsProvider.kt b/src/main/kotlin/io/github/kscripting/os/OsProvider.kt new file mode 100644 index 0000000..e75e0e7 --- /dev/null +++ b/src/main/kotlin/io/github/kscripting/os/OsProvider.kt @@ -0,0 +1,80 @@ +package io.github.kscripting.os + +import io.github.kscripting.os.instance.LinuxOs +import net.igsoft.typeutils.marker.NamedAutoTypedMarker + +interface OsProvider { + val native: Os + fun provide(marker: NamedAutoTypedMarker = native.marker): Os + + companion object { + private val defaultOsProvider = DefaultOsProvider() + private val providerStack = java.util.ArrayDeque() + + fun push(osProvider: OsProvider) { + synchronized(providerStack) { + providerStack.push(osProvider) + } + } + + fun poll() { + synchronized(providerStack) { + providerStack.poll() + } + } + + + val current: OsProvider get() { + synchronized(providerStack) { + return providerStack.peek() ?: defaultOsProvider + } + } + } +} + +@Synchronized +fun osContext(block: (OsProvider) -> Unit) { + block(OsProvider.current) +} + +@Synchronized +fun osContext(osProvider: OsProvider, block: (OsProvider) -> Unit) { + OsProvider.push(osProvider) + block(osProvider) + OsProvider.poll() +} + + +class DefaultOsProvider : OsProvider { + override val native: Os = TODO() + + override fun provide(marker: NamedAutoTypedMarker): Os { + TODO() + } +} + + +class MockProvider : OsProvider { + override val native: Os + get() = TODO("Not yet implemented") + + override fun provide(marker: NamedAutoTypedMarker): Os { + TODO("Not yet implemented") + } +} + +fun main() { + osContext { + val os = it.provide(LinuxOs.marker) + } + + osContext(MockProvider()) { + val os = it.provide(LinuxOs.marker) + + osContext { + val subOs = it.provide(LinuxOs.marker) + } + } + + OsProvider.current.provide(LinuxOs.marker) +} diff --git a/src/main/kotlin/io/github/kscripting/os/Vfs.kt b/src/main/kotlin/io/github/kscripting/os/Vfs.kt index 0b6fad0..14f9749 100644 --- a/src/main/kotlin/io/github/kscripting/os/Vfs.kt +++ b/src/main/kotlin/io/github/kscripting/os/Vfs.kt @@ -22,57 +22,5 @@ interface Vfs { createOsPath(pathParts.joinToString("/")) fun isValid(path: String): Boolean - - fun normalize(osPath: OsPath): OsPath { - //Relative: - // ./../ --> ../ - // ./a/../ --> ./ - // ./a/ --> ./a - // ../a --> ../a - // ../../a --> ../../a - - //Absolute: - // /../ --> invalid (above root) - // /a/../ --> / - - val newParts = mutableListOf() - var index = 0 - - while (index < osPath.pathParts.size) { - if (osPath.pathParts[index] == ".") { - //Just skip . without adding it to newParts - } else if (osPath.pathParts[index] == "..") { - if (osPath.isAbsolute && newParts.size == 0) { - throw IllegalArgumentException("Path after normalization goes beyond root element: '${osPath.root}'") - } - - if (newParts.size > 0) { - when (newParts.last()) { - "." -> { - //It's the first element - other dots should be already removed before - newParts.removeAt(newParts.size - 1) - newParts.add("..") - } - - ".." -> { - newParts.add("..") - } - - else -> { - newParts.removeAt(newParts.size - 1) - } - } - } else { - newParts.add("..") - } - } else { - newParts.add(osPath.pathParts[index]) - } - - index += 1 - } - - return OsPath(osPath.vfs, osPath.root, newParts) - } } diff --git a/src/main/kotlin/io/github/kscripting/os/instance/CygwinOs.kt b/src/main/kotlin/io/github/kscripting/os/instance/CygwinOs.kt index 74902c8..32dffa7 100644 --- a/src/main/kotlin/io/github/kscripting/os/instance/CygwinOs.kt +++ b/src/main/kotlin/io/github/kscripting/os/instance/CygwinOs.kt @@ -1,8 +1,10 @@ package io.github.kscripting.os.instance import io.github.kscripting.os.OsType +import net.igsoft.typeutils.marker.NamedAutoTypedMarker class CygwinOs(override val vfs: CygwinVfs, override val nativeOs: WindowsOs) : HostedOs { + override val marker: NamedAutoTypedMarker = NamedAutoTypedMarker.create("Cygwin") override val type: OsType = OsType.CYGWIN override val osTypePrefix: String = "cygwin" } diff --git a/src/main/kotlin/io/github/kscripting/os/instance/FreeBsdOs.kt b/src/main/kotlin/io/github/kscripting/os/instance/FreeBsdOs.kt index 21ebbe9..4c532c7 100644 --- a/src/main/kotlin/io/github/kscripting/os/instance/FreeBsdOs.kt +++ b/src/main/kotlin/io/github/kscripting/os/instance/FreeBsdOs.kt @@ -2,8 +2,12 @@ package io.github.kscripting.os.instance import io.github.kscripting.os.Os import io.github.kscripting.os.OsType +import net.igsoft.typeutils.marker.AutoTypedMarker +import net.igsoft.typeutils.marker.DefaultTypedMarker +import net.igsoft.typeutils.marker.NamedAutoTypedMarker class FreeBsdOs(override val vfs: FreeBsdVfs) : Os { + override val marker: NamedAutoTypedMarker = NamedAutoTypedMarker.create("FreeBsd") override val type: OsType = OsType.FREEBSD override val osTypePrefix: String = "freebsd" } diff --git a/src/main/kotlin/io/github/kscripting/os/instance/LinuxOs.kt b/src/main/kotlin/io/github/kscripting/os/instance/LinuxOs.kt index 249efde..241ba0a 100644 --- a/src/main/kotlin/io/github/kscripting/os/instance/LinuxOs.kt +++ b/src/main/kotlin/io/github/kscripting/os/instance/LinuxOs.kt @@ -2,8 +2,16 @@ package io.github.kscripting.os.instance import io.github.kscripting.os.Os import io.github.kscripting.os.OsType +import net.igsoft.typeutils.marker.AutoTypedMarker +import net.igsoft.typeutils.marker.DefaultTypedMarker +import net.igsoft.typeutils.marker.NamedAutoTypedMarker class LinuxOs(override val vfs: LinuxVfs) : Os { + override val marker: NamedAutoTypedMarker = NamedAutoTypedMarker.create("Linux") override val type: OsType = OsType.LINUX override val osTypePrefix: String = "linux" + + companion object { + val marker: NamedAutoTypedMarker = NamedAutoTypedMarker.create("Linux") + } } diff --git a/src/main/kotlin/io/github/kscripting/os/instance/MacOs.kt b/src/main/kotlin/io/github/kscripting/os/instance/MacOs.kt index a6f3cf7..b310229 100644 --- a/src/main/kotlin/io/github/kscripting/os/instance/MacOs.kt +++ b/src/main/kotlin/io/github/kscripting/os/instance/MacOs.kt @@ -2,8 +2,12 @@ package io.github.kscripting.os.instance import io.github.kscripting.os.Os import io.github.kscripting.os.OsType +import net.igsoft.typeutils.marker.AutoTypedMarker +import net.igsoft.typeutils.marker.DefaultTypedMarker +import net.igsoft.typeutils.marker.NamedAutoTypedMarker class MacOs(override val vfs: MacOsVfs) : Os { + override val marker: NamedAutoTypedMarker = NamedAutoTypedMarker.create("Mac") override val type: OsType = OsType.MACOS override val osTypePrefix: String = "darwin" } diff --git a/src/main/kotlin/io/github/kscripting/os/instance/MsysOs.kt b/src/main/kotlin/io/github/kscripting/os/instance/MsysOs.kt index 724f8b4..31e654f 100644 --- a/src/main/kotlin/io/github/kscripting/os/instance/MsysOs.kt +++ b/src/main/kotlin/io/github/kscripting/os/instance/MsysOs.kt @@ -1,8 +1,10 @@ package io.github.kscripting.os.instance import io.github.kscripting.os.OsType +import net.igsoft.typeutils.marker.NamedAutoTypedMarker class MsysOs(override val nativeOs: WindowsOs, override val vfs: MsysVfs) : HostedOs { + override val marker: NamedAutoTypedMarker = NamedAutoTypedMarker.create("Msys") override val type: OsType = OsType.MSYS override val osTypePrefix: String = "msys" } diff --git a/src/main/kotlin/io/github/kscripting/os/instance/WindowsOs.kt b/src/main/kotlin/io/github/kscripting/os/instance/WindowsOs.kt index 9610f43..ad3e0c2 100644 --- a/src/main/kotlin/io/github/kscripting/os/instance/WindowsOs.kt +++ b/src/main/kotlin/io/github/kscripting/os/instance/WindowsOs.kt @@ -2,8 +2,12 @@ package io.github.kscripting.os.instance import io.github.kscripting.os.Os import io.github.kscripting.os.OsType +import net.igsoft.typeutils.marker.AutoTypedMarker +import net.igsoft.typeutils.marker.DefaultTypedMarker +import net.igsoft.typeutils.marker.NamedAutoTypedMarker class WindowsOs(override val vfs: WindowsVfs) : Os { + override val marker: NamedAutoTypedMarker = NamedAutoTypedMarker.create("Windows") override val type: OsType = OsType.WINDOWS override val osTypePrefix: String = "windows" } diff --git a/src/main/kotlin/io/github/kscripting/os/model/OsPathExt.kt b/src/main/kotlin/io/github/kscripting/os/model/OsPathExt.kt index 95b31c0..1410055 100644 --- a/src/main/kotlin/io/github/kscripting/os/model/OsPathExt.kt +++ b/src/main/kotlin/io/github/kscripting/os/model/OsPathExt.kt @@ -5,6 +5,7 @@ import java.io.File //import io.github.kscripting.os.Vfs import io.github.kscripting.os.instance.HostedVfs +import io.github.kscripting.os.util.normalize import java.net.URI import java.nio.file.Path import java.nio.file.Paths @@ -75,7 +76,7 @@ fun OsPath.resolve(osPath: OsPath): OsPath { addAll(osPath.pathParts) } - return vfs.normalize(vfs.createOsPath(root, newPathParts) as OsPath) + return OsPath(vfs, root, normalize(root, newPathParts)) } //// OsPath accessors diff --git a/src/main/kotlin/io/github/kscripting/os/util/VfsUtil.kt b/src/main/kotlin/io/github/kscripting/os/util/VfsUtil.kt index 3bce675..9c74a1a 100644 --- a/src/main/kotlin/io/github/kscripting/os/util/VfsUtil.kt +++ b/src/main/kotlin/io/github/kscripting/os/util/VfsUtil.kt @@ -28,7 +28,7 @@ fun createFinalPath(vfs: T, path: String, root: String): OsPath { require(vfs.isValid(pathWithoutRoot)) val pathPartsResolved = pathWithoutRoot.split('/', '\\').filter { it.isNotBlank() } - return vfs.normalize(OsPath(vfs, root, pathPartsResolved)) + return OsPath(vfs, root, normalize(root, pathPartsResolved)) } fun toHostedConverter(vfs: T, osPath: OsPath): OsPath { @@ -62,6 +62,7 @@ fun toHostedConverter(vfs: T, osPath: OsPath): OsPath newRoot = "/" + //TODO: not generic!! if (vfs is CygwinVfs) { newParts.add("cygdrive") } @@ -75,4 +76,57 @@ fun toHostedConverter(vfs: T, osPath: OsPath): OsPath } return OsPath(vfs, newRoot, newParts) -} \ No newline at end of file +} + +fun normalize(root: String, pathParts: List): List { + //Relative: + // ./../ --> ../ + // ./a/../ --> ./ + // ./a/ --> ./a + // ../a --> ../a + // ../../a --> ../../a + + //Absolute: + // /../ --> invalid (above root) + // /a/../ --> / + + val isAbsolute = root.isNotEmpty() + val newParts = mutableListOf() + var index = 0 + + while (index < pathParts.size) { + if (pathParts[index] == ".") { + //Just skip . without adding it to newParts + } else if (pathParts[index] == "..") { + if (isAbsolute && newParts.size == 0) { + throw IllegalArgumentException("Path after normalization goes beyond root element: '${root}'") + } + + if (newParts.size > 0) { + when (newParts.last()) { + "." -> { + //It's the first element - other dots should be already removed before + newParts.removeAt(newParts.size - 1) + newParts.add("..") + } + + ".." -> { + newParts.add("..") + } + + else -> { + newParts.removeAt(newParts.size - 1) + } + } + } else { + newParts.add("..") + } + } else { + newParts.add(pathParts[index]) + } + + index += 1 + } + + return newParts +} From 24aba6163ffb1ae97c69c5227f7350e337d71433 Mon Sep 17 00:00:00 2001 From: Marcin Kuszczak <1508798+aartiPl@users.noreply.github.com> Date: Tue, 26 Dec 2023 15:50:13 +0100 Subject: [PATCH 49/50] Update --- .../kotlin/io/github/kscripting/os/Vfs.kt | 11 ++- .../kscripting/os/instance/CygwinVfs.kt | 14 ++-- .../kscripting/os/instance/FreeBsdVfs.kt | 4 +- .../kscripting/os/instance/HostedVfs.kt | 6 +- .../github/kscripting/os/instance/LinuxVfs.kt | 4 +- .../github/kscripting/os/instance/MacOsVfs.kt | 4 +- .../github/kscripting/os/instance/MsysVfs.kt | 14 ++-- .../kscripting/os/instance/WindowsVfs.kt | 4 +- .../io/github/kscripting/os/model/OsPath.kt | 2 +- .../github/kscripting/os/model/OsPathExt.kt | 18 ++--- .../io/github/kscripting/os/util/VfsUtil.kt | 8 +- .../github/kscripting/shell/ShellExecutor.kt | 4 +- .../kscripting/shell/process/ProcessRunner.kt | 4 +- .../kscripting/os/model/PosixOsPathTest.kt | 78 +++++++++---------- .../kscripting/os/model/WindowsOsPathTest.kt | 78 +++++++++---------- 15 files changed, 126 insertions(+), 127 deletions(-) diff --git a/src/main/kotlin/io/github/kscripting/os/Vfs.kt b/src/main/kotlin/io/github/kscripting/os/Vfs.kt index 14f9749..bd3020b 100644 --- a/src/main/kotlin/io/github/kscripting/os/Vfs.kt +++ b/src/main/kotlin/io/github/kscripting/os/Vfs.kt @@ -6,21 +6,20 @@ import io.github.kscripting.os.model.OsPath interface Vfs { val type: OsType val pathSeparator: String - val userHome: OsPath + val userHome: OsPath //Relaxed validation: //1. It doesn't matter if there is '/' or '\' used as path separator - both are treated the same //2. Duplicated or trailing slashes '/' and backslashes '\' are ignored - fun createOsPath(path: String): OsPath - fun createOsPath(root: String, pathParts: List): OsPath = + fun createOsPath(path: String): OsPath + fun createOsPath(root: String, pathParts: List): OsPath = createOsPath(root.trim() + "/" + pathParts.joinToString("/")) - fun createOsPath(pathParts: List): OsPath = + fun createOsPath(pathParts: List): OsPath = createOsPath(pathParts.joinToString("/")) - fun createOsPath(vararg pathParts: String): OsPath = + fun createOsPath(vararg pathParts: String): OsPath = createOsPath(pathParts.joinToString("/")) fun isValid(path: String): Boolean } - diff --git a/src/main/kotlin/io/github/kscripting/os/instance/CygwinVfs.kt b/src/main/kotlin/io/github/kscripting/os/instance/CygwinVfs.kt index 7f5e306..dca532e 100644 --- a/src/main/kotlin/io/github/kscripting/os/instance/CygwinVfs.kt +++ b/src/main/kotlin/io/github/kscripting/os/instance/CygwinVfs.kt @@ -6,11 +6,11 @@ import io.github.kscripting.os.util.createPosixOsPath import io.github.kscripting.os.model.OsPath import io.github.kscripting.os.util.toHostedConverter -class CygwinVfs(override val nativeFsRoot: OsPath, userHome: String) : HostedVfs, +class CygwinVfs(override val nativeFsRoot: OsPath, userHome: String) : HostedVfs, PosixVfs(OsType.CYGWIN) { - override fun toNative(providedOsPath: OsPath): OsPath { - val osPath = providedOsPath as OsPath + override fun toNative(providedOsPath: OsPath): OsPath { + val osPath = providedOsPath val newParts = mutableListOf() var newRoot = "" @@ -35,11 +35,11 @@ class CygwinVfs(override val nativeFsRoot: OsPath, userHome: String) return OsPath(nativeFsRoot.vfs, newRoot, newParts) } - override fun toHosted(osPath: OsPath): OsPath { - return toHostedConverter(this, osPath as OsPath) + override fun toHosted(osPath: OsPath): OsPath { + return toHostedConverter(this, osPath) } - override val userHome: OsPath = createPosixOsPath(this, userHome) - override fun createOsPath(path: String): OsPath = createPosixOsPath(this, path) + override val userHome: OsPath = createPosixOsPath(this, userHome) + override fun createOsPath(path: String): OsPath = createPosixOsPath(this, path) } diff --git a/src/main/kotlin/io/github/kscripting/os/instance/FreeBsdVfs.kt b/src/main/kotlin/io/github/kscripting/os/instance/FreeBsdVfs.kt index d4ddb24..402adc8 100644 --- a/src/main/kotlin/io/github/kscripting/os/instance/FreeBsdVfs.kt +++ b/src/main/kotlin/io/github/kscripting/os/instance/FreeBsdVfs.kt @@ -5,6 +5,6 @@ import io.github.kscripting.os.util.createPosixOsPath import io.github.kscripting.os.model.OsPath class FreeBsdVfs(userHome: String) : PosixVfs(OsType.FREEBSD) { - override val userHome: OsPath = createOsPath(userHome) - override fun createOsPath(path: String): OsPath = createPosixOsPath(this, path) + override val userHome: OsPath = createOsPath(userHome) + override fun createOsPath(path: String): OsPath = createPosixOsPath(this, path) } diff --git a/src/main/kotlin/io/github/kscripting/os/instance/HostedVfs.kt b/src/main/kotlin/io/github/kscripting/os/instance/HostedVfs.kt index 7de333c..a6d52dd 100644 --- a/src/main/kotlin/io/github/kscripting/os/instance/HostedVfs.kt +++ b/src/main/kotlin/io/github/kscripting/os/instance/HostedVfs.kt @@ -4,8 +4,8 @@ import io.github.kscripting.os.Vfs import io.github.kscripting.os.model.OsPath interface HostedVfs : Vfs { - val nativeFsRoot: OsPath + val nativeFsRoot: OsPath - fun toNative(osPath: OsPath): OsPath - fun toHosted(osPath: OsPath): OsPath + fun toNative(osPath: OsPath): OsPath + fun toHosted(osPath: OsPath): OsPath } diff --git a/src/main/kotlin/io/github/kscripting/os/instance/LinuxVfs.kt b/src/main/kotlin/io/github/kscripting/os/instance/LinuxVfs.kt index d82bfdd..8d9b5e1 100644 --- a/src/main/kotlin/io/github/kscripting/os/instance/LinuxVfs.kt +++ b/src/main/kotlin/io/github/kscripting/os/instance/LinuxVfs.kt @@ -5,6 +5,6 @@ import io.github.kscripting.os.util.createPosixOsPath import io.github.kscripting.os.model.OsPath class LinuxVfs(userHome: String) : PosixVfs(OsType.LINUX) { - override val userHome: OsPath = createOsPath(userHome) - override fun createOsPath(path: String): OsPath = createPosixOsPath(this, path) + override val userHome: OsPath = createOsPath(userHome) + override fun createOsPath(path: String): OsPath = createPosixOsPath(this, path) } diff --git a/src/main/kotlin/io/github/kscripting/os/instance/MacOsVfs.kt b/src/main/kotlin/io/github/kscripting/os/instance/MacOsVfs.kt index fb5160a..724db38 100644 --- a/src/main/kotlin/io/github/kscripting/os/instance/MacOsVfs.kt +++ b/src/main/kotlin/io/github/kscripting/os/instance/MacOsVfs.kt @@ -5,6 +5,6 @@ import io.github.kscripting.os.util.createPosixOsPath import io.github.kscripting.os.model.OsPath class MacOsVfs(userHome: String) : PosixVfs(OsType.MACOS) { - override val userHome: OsPath = createOsPath(userHome) - override fun createOsPath(path: String): OsPath = createPosixOsPath(this, path) + override val userHome: OsPath = createOsPath(userHome) + override fun createOsPath(path: String): OsPath = createPosixOsPath(this, path) } diff --git a/src/main/kotlin/io/github/kscripting/os/instance/MsysVfs.kt b/src/main/kotlin/io/github/kscripting/os/instance/MsysVfs.kt index 0d14cba..3394521 100644 --- a/src/main/kotlin/io/github/kscripting/os/instance/MsysVfs.kt +++ b/src/main/kotlin/io/github/kscripting/os/instance/MsysVfs.kt @@ -6,12 +6,12 @@ import io.github.kscripting.os.util.createPosixOsPath import io.github.kscripting.os.model.OsPath import io.github.kscripting.os.util.toHostedConverter -class MsysVfs(override val nativeFsRoot: OsPath, userHome: String) : HostedVfs, PosixVfs(OsType.MSYS) { - override val userHome: OsPath = createOsPath(userHome) - override fun createOsPath(path: String): OsPath = createPosixOsPath(this, path) +class MsysVfs(override val nativeFsRoot: OsPath, userHome: String) : HostedVfs, PosixVfs(OsType.MSYS) { + override val userHome: OsPath = createOsPath(userHome) + override fun createOsPath(path: String): OsPath = createPosixOsPath(this, path) - override fun toNative(providedOsPath: OsPath): OsPath { - val osPath = providedOsPath as OsPath + override fun toNative(providedOsPath: OsPath): OsPath { + val osPath = providedOsPath val newParts = mutableListOf() var newRoot = "" @@ -36,7 +36,7 @@ class MsysVfs(override val nativeFsRoot: OsPath, userHome: String) : return OsPath(nativeFsRoot.vfs, newRoot, newParts) } - override fun toHosted(osPath: OsPath): OsPath { - return toHostedConverter(this, osPath as OsPath) + override fun toHosted(osPath: OsPath): OsPath { + return toHostedConverter(this, osPath) } } diff --git a/src/main/kotlin/io/github/kscripting/os/instance/WindowsVfs.kt b/src/main/kotlin/io/github/kscripting/os/instance/WindowsVfs.kt index 46f31c5..17dc6b9 100644 --- a/src/main/kotlin/io/github/kscripting/os/instance/WindowsVfs.kt +++ b/src/main/kotlin/io/github/kscripting/os/instance/WindowsVfs.kt @@ -15,9 +15,9 @@ class WindowsVfs(userHome: String) : Vfs { "^([a-zA-Z]:(?=[\\\\/])|\\\\\\\\(?:[^*:<>?\\\\/|]+\\\\[^*:<>?\\\\/|]+|\\?\\\\(?:[a-zA-Z]:(?=\\\\)|(?:UNC\\\\)?[^*:<>?\\\\/|]+\\\\[^*:<>?\\\\/|]+)))".toRegex() - override val userHome: OsPath = createOsPath(userHome) + override val userHome: OsPath = createOsPath(userHome) - override fun createOsPath(path: String): OsPath { + override fun createOsPath(path: String): OsPath { //Detect root val root = when { path.startsWith("~/") || path.startsWith("~\\") -> "~" diff --git a/src/main/kotlin/io/github/kscripting/os/model/OsPath.kt b/src/main/kotlin/io/github/kscripting/os/model/OsPath.kt index 35c98df..6cb769f 100644 --- a/src/main/kotlin/io/github/kscripting/os/model/OsPath.kt +++ b/src/main/kotlin/io/github/kscripting/os/model/OsPath.kt @@ -11,7 +11,7 @@ sealed interface OsPathError { //Path representation for different OSes @Suppress("MemberVisibilityCanBePrivate") //TODO: should be only instantiated from VFS, not in any place -data class OsPath(@Transient internal val vfs: T, val root: String, val pathParts: List) { +data class OsPath(@Transient internal val vfs: Vfs, val root: String, val pathParts: List) { val osType: OsType = vfs.type val isRelative: Boolean get() = root.isEmpty() && pathParts.isNotEmpty() val isAbsolute: Boolean get() = root.isNotEmpty() diff --git a/src/main/kotlin/io/github/kscripting/os/model/OsPathExt.kt b/src/main/kotlin/io/github/kscripting/os/model/OsPathExt.kt index 1410055..d81e73c 100644 --- a/src/main/kotlin/io/github/kscripting/os/model/OsPathExt.kt +++ b/src/main/kotlin/io/github/kscripting/os/model/OsPathExt.kt @@ -18,12 +18,12 @@ import java.nio.file.Paths //import java.nio.file.Paths //import kotlin.io.path.* // -fun OsPath.toNative(): OsPath<*> = (vfs as? HostedVfs)?.toNative(this) ?: this +fun OsPath.toNative(): OsPath = (vfs as? HostedVfs)?.toNative(this) ?: this fun List.startsWith(list: List): Boolean = (this.size >= list.size && this.subList(0, list.size) == list) -fun OsPath.startsWith(osPath: OsPath): Boolean = root == osPath.root && pathParts.startsWith(osPath.pathParts) +fun OsPath.startsWith(osPath: OsPath): Boolean = root == osPath.root && pathParts.startsWith(osPath.pathParts) //// Conversion to OsPath @@ -36,9 +36,9 @@ fun OsPath.startsWith(osPath: OsPath): Boolean = root == osPath. //// Conversion from OsPath -fun OsPath<*>.toNativePath(): Path = Paths.get(toNative().path) -fun OsPath<*>.toNativeFile(): File = File(toNative().path) -fun OsPath<*>.toNativeUri(): URI = File(toNative().path).toURI() +fun OsPath.toNativePath(): Path = Paths.get(toNative().path) +fun OsPath.toNativeFile(): File = File(toNative().path) +fun OsPath.toNativeUri(): URI = File(toNative().path).toURI() //// OsPath operations @@ -56,13 +56,13 @@ fun OsPath<*>.toNativeUri(): URI = File(toNative().path).toURI() //fun OsPath.readText(charset: Charset = Charsets.UTF_8): String = toNativePath().readText(charset) // -operator fun OsPath.div(osPath: OsPath): OsPath = resolve(osPath) +operator fun OsPath.div(osPath: OsPath): OsPath = resolve(osPath) -operator fun OsPath.div(path: String): OsPath = resolve(path) +operator fun OsPath.div(path: String): OsPath = resolve(path) -fun OsPath.resolve(vararg pathParts: String): OsPath = resolve(vfs.createOsPath(*pathParts) as OsPath) +fun OsPath.resolve(vararg pathParts: String): OsPath = resolve(vfs.createOsPath(*pathParts)) -fun OsPath.resolve(osPath: OsPath): OsPath { +fun OsPath.resolve(osPath: OsPath): OsPath { if (osType != osPath.osType) { throw IllegalArgumentException("Paths from different OS's: '${osType}' path can not be resolved with '${osPath.osType}' path") } diff --git a/src/main/kotlin/io/github/kscripting/os/util/VfsUtil.kt b/src/main/kotlin/io/github/kscripting/os/util/VfsUtil.kt index 9c74a1a..6c1596b 100644 --- a/src/main/kotlin/io/github/kscripting/os/util/VfsUtil.kt +++ b/src/main/kotlin/io/github/kscripting/os/util/VfsUtil.kt @@ -8,7 +8,7 @@ import io.github.kscripting.os.model.OsPath import io.github.kscripting.os.model.startsWith -fun createPosixOsPath(vfs: T, path: String): OsPath { +fun createPosixOsPath(vfs: Vfs, path: String): OsPath { require(vfs.isValid(path)) //Detect root @@ -21,7 +21,7 @@ fun createPosixOsPath(vfs: T, path: String): OsPath { return createFinalPath(vfs, path, root) } -fun createFinalPath(vfs: T, path: String, root: String): OsPath { +fun createFinalPath(vfs: Vfs, path: String, root: String): OsPath { //Remove also empty path parts - there were duplicated or trailing slashes / backslashes in initial path val pathWithoutRoot = path.drop(root.length) @@ -31,12 +31,12 @@ fun createFinalPath(vfs: T, path: String, root: String): OsPath { return OsPath(vfs, root, normalize(root, pathPartsResolved)) } -fun toHostedConverter(vfs: T, osPath: OsPath): OsPath { +fun toHostedConverter(vfs: T, osPath: OsPath): OsPath { val newParts = mutableListOf() var newRoot = "" if (osPath.isAbsolute) { - val nativeFsRoot = vfs.nativeFsRoot as OsPath + val nativeFsRoot = vfs.nativeFsRoot if (osPath.startsWith(nativeFsRoot)) { if (osPath.pathParts.subList(nativeFsRoot.pathParts.size, osPath.pathParts.size) diff --git a/src/main/kotlin/io/github/kscripting/shell/ShellExecutor.kt b/src/main/kotlin/io/github/kscripting/shell/ShellExecutor.kt index bab10d7..00ca2ff 100644 --- a/src/main/kotlin/io/github/kscripting/shell/ShellExecutor.kt +++ b/src/main/kotlin/io/github/kscripting/shell/ShellExecutor.kt @@ -32,7 +32,7 @@ object ShellExecutor { fun evalAndGobble( command: String, osType: OsType, - workingDirectory: OsPath<*>? = null, + workingDirectory: OsPath? = null, waitTimeMinutes: Int = 10, inheritInput: Boolean = false, inputSanitizer: Sanitizer = EMPTY_SANITIZER, @@ -73,7 +73,7 @@ object ShellExecutor { fun eval( command: String, osType: OsType, - workingDirectory: OsPath<*>? = null, + workingDirectory: OsPath? = null, waitTimeMinutes: Int = 10, inheritInput: Boolean = false, inputSanitizer: Sanitizer = EMPTY_SANITIZER, diff --git a/src/main/kotlin/io/github/kscripting/shell/process/ProcessRunner.kt b/src/main/kotlin/io/github/kscripting/shell/process/ProcessRunner.kt index c16f0e3..977daa5 100644 --- a/src/main/kotlin/io/github/kscripting/shell/process/ProcessRunner.kt +++ b/src/main/kotlin/io/github/kscripting/shell/process/ProcessRunner.kt @@ -17,7 +17,7 @@ object ProcessRunner { fun runProcess( vararg command: String, - workingDirectory: OsPath<*>? = null, + workingDirectory: OsPath? = null, envAdjuster: EnvAdjuster = {}, waitTimeMinutes: Int = 10, inheritInput: Boolean = false, @@ -41,7 +41,7 @@ object ProcessRunner { fun runProcess( command: List, - workingDirectory: OsPath<*>? = null, + workingDirectory: OsPath? = null, envAdjuster: EnvAdjuster = {}, waitTimeMinutes: Int = 10, inheritInput: Boolean = false, diff --git a/src/test/kotlin/io/github/kscripting/os/model/PosixOsPathTest.kt b/src/test/kotlin/io/github/kscripting/os/model/PosixOsPathTest.kt index 6c86041..b1e7e25 100644 --- a/src/test/kotlin/io/github/kscripting/os/model/PosixOsPathTest.kt +++ b/src/test/kotlin/io/github/kscripting/os/model/PosixOsPathTest.kt @@ -18,86 +18,86 @@ class PosixOsPathTest { @Test fun `Test Posix paths`() { assertThat(linuxVfs.createOsPath("/")).let { - it.prop(OsPath<*>::osType).isEqualTo(OsType.LINUX) - it.prop(OsPath<*>::root).isEqualTo("/") - it.prop(OsPath<*>::pathParts).isEqualTo(emptyList()) + it.prop(OsPath::osType).isEqualTo(OsType.LINUX) + it.prop(OsPath::root).isEqualTo("/") + it.prop(OsPath::pathParts).isEqualTo(emptyList()) } assertThat(linuxVfs.createOsPath("/home/admin/.kscript")).let { - it.prop(OsPath<*>::osType).isEqualTo(OsType.LINUX) - it.prop(OsPath<*>::root).isEqualTo("/") - it.prop(OsPath<*>::pathParts).isEqualTo(listOf("home", "admin", ".kscript")) + it.prop(OsPath::osType).isEqualTo(OsType.LINUX) + it.prop(OsPath::root).isEqualTo("/") + it.prop(OsPath::pathParts).isEqualTo(listOf("home", "admin", ".kscript")) } assertThat(linuxVfs.createOsPath("./home/admin/.kscript")).let { - it.prop(OsPath<*>::osType).isEqualTo(OsType.LINUX) - it.prop(OsPath<*>::root).isEqualTo("") - it.prop(OsPath<*>::pathParts).isEqualTo(listOf("home", "admin", ".kscript")) + it.prop(OsPath::osType).isEqualTo(OsType.LINUX) + it.prop(OsPath::root).isEqualTo("") + it.prop(OsPath::pathParts).isEqualTo(listOf("home", "admin", ".kscript")) } assertThat(linuxVfs.createOsPath("")).let { - it.prop(OsPath<*>::osType).isEqualTo(OsType.LINUX) - it.prop(OsPath<*>::root).isEqualTo("") - it.prop(OsPath<*>::pathParts).isEqualTo(emptyList()) + it.prop(OsPath::osType).isEqualTo(OsType.LINUX) + it.prop(OsPath::root).isEqualTo("") + it.prop(OsPath::pathParts).isEqualTo(emptyList()) } assertThat(linuxVfs.createOsPath("file.txt")).let { - it.prop(OsPath<*>::osType).isEqualTo(OsType.LINUX) - it.prop(OsPath<*>::root).isEqualTo("") - it.prop(OsPath<*>::pathParts).isEqualTo(listOf("file.txt")) + it.prop(OsPath::osType).isEqualTo(OsType.LINUX) + it.prop(OsPath::root).isEqualTo("") + it.prop(OsPath::pathParts).isEqualTo(listOf("file.txt")) } assertThat(linuxVfs.createOsPath(".")).let { - it.prop(OsPath<*>::osType).isEqualTo(OsType.LINUX) - it.prop(OsPath<*>::root).isEqualTo("") - it.prop(OsPath<*>::pathParts).isEqualTo(emptyList()) + it.prop(OsPath::osType).isEqualTo(OsType.LINUX) + it.prop(OsPath::root).isEqualTo("") + it.prop(OsPath::pathParts).isEqualTo(emptyList()) } assertThat(linuxVfs.createOsPath("../home/admin/.kscript")).let { - it.prop(OsPath<*>::osType).isEqualTo(OsType.LINUX) - it.prop(OsPath<*>::root).isEqualTo("") - it.prop(OsPath<*>::pathParts).isEqualTo(listOf("..", "home", "admin", ".kscript")) + it.prop(OsPath::osType).isEqualTo(OsType.LINUX) + it.prop(OsPath::root).isEqualTo("") + it.prop(OsPath::pathParts).isEqualTo(listOf("..", "home", "admin", ".kscript")) } assertThat(linuxVfs.createOsPath("..")).let { - it.prop(OsPath<*>::osType).isEqualTo(OsType.LINUX) - it.prop(OsPath<*>::root).isEqualTo("") - it.prop(OsPath<*>::pathParts).isEqualTo(listOf("..")) + it.prop(OsPath::osType).isEqualTo(OsType.LINUX) + it.prop(OsPath::root).isEqualTo("") + it.prop(OsPath::pathParts).isEqualTo(listOf("..")) } //Duplicated separators are accepted assertThat(linuxVfs.createOsPath("..//home////admin/.kscript/")).let { - it.prop(OsPath<*>::osType).isEqualTo(OsType.LINUX) - it.prop(OsPath<*>::root).isEqualTo("") - it.prop(OsPath<*>::pathParts).isEqualTo(listOf("..", "home", "admin", ".kscript")) + it.prop(OsPath::osType).isEqualTo(OsType.LINUX) + it.prop(OsPath::root).isEqualTo("") + it.prop(OsPath::pathParts).isEqualTo(listOf("..", "home", "admin", ".kscript")) } //Both types of separator are accepted assertThat(linuxVfs.createOsPath("..//home\\admin\\.kscript/")).let { - it.prop(OsPath<*>::osType).isEqualTo(OsType.LINUX) - it.prop(OsPath<*>::root).isEqualTo("") - it.prop(OsPath<*>::pathParts).isEqualTo(listOf("..", "home", "admin", ".kscript")) + it.prop(OsPath::osType).isEqualTo(OsType.LINUX) + it.prop(OsPath::root).isEqualTo("") + it.prop(OsPath::pathParts).isEqualTo(listOf("..", "home", "admin", ".kscript")) } } @Test fun `Normalization of Posix paths`() { assertThat(linuxVfs.createOsPath("/home/admin/.kscript/../../")).let { - it.prop(OsPath<*>::osType).isEqualTo(OsType.LINUX) - it.prop(OsPath<*>::root).isEqualTo("/") - it.prop(OsPath<*>::pathParts).isEqualTo(listOf("home")) + it.prop(OsPath::osType).isEqualTo(OsType.LINUX) + it.prop(OsPath::root).isEqualTo("/") + it.prop(OsPath::pathParts).isEqualTo(listOf("home")) } assertThat(linuxVfs.createOsPath("./././../../script")).let { - it.prop(OsPath<*>::osType).isEqualTo(OsType.LINUX) - it.prop(OsPath<*>::root).isEqualTo("") - it.prop(OsPath<*>::pathParts).isEqualTo(listOf("..", "..", "script")) + it.prop(OsPath::osType).isEqualTo(OsType.LINUX) + it.prop(OsPath::root).isEqualTo("") + it.prop(OsPath::pathParts).isEqualTo(listOf("..", "..", "script")) } assertThat(linuxVfs.createOsPath("/a/b/c/../d/script")).let { - it.prop(OsPath<*>::osType).isEqualTo(OsType.LINUX) - it.prop(OsPath<*>::root).isEqualTo("/") - it.prop(OsPath<*>::pathParts).isEqualTo(listOf("a", "b", "d", "script")) + it.prop(OsPath::osType).isEqualTo(OsType.LINUX) + it.prop(OsPath::root).isEqualTo("/") + it.prop(OsPath::pathParts).isEqualTo(listOf("a", "b", "d", "script")) } assertFailure { linuxVfs.createOsPath("/.kscript/../../") }.isInstanceOf(IllegalArgumentException::class) diff --git a/src/test/kotlin/io/github/kscripting/os/model/WindowsOsPathTest.kt b/src/test/kotlin/io/github/kscripting/os/model/WindowsOsPathTest.kt index 19185f1..ff6046f 100644 --- a/src/test/kotlin/io/github/kscripting/os/model/WindowsOsPathTest.kt +++ b/src/test/kotlin/io/github/kscripting/os/model/WindowsOsPathTest.kt @@ -18,86 +18,86 @@ class WindowsOsPathTest { @Test fun `Test Windows paths`() { assertThat(windowsVfs.createOsPath("C:\\")).let { - it.prop(OsPath<*>::osType).isEqualTo(OsType.WINDOWS) - it.prop(OsPath<*>::root).isEqualTo("C:\\") - it.prop(OsPath<*>::pathParts).isEqualTo(emptyList()) + it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) + it.prop(OsPath::root).isEqualTo("C:\\") + it.prop(OsPath::pathParts).isEqualTo(emptyList()) } assertThat(windowsVfs.createOsPath("C:\\home\\admin\\.kscript")).let { - it.prop(OsPath<*>::osType).isEqualTo(OsType.WINDOWS) - it.prop(OsPath<*>::root).isEqualTo("C:\\") - it.prop(OsPath<*>::pathParts).isEqualTo(listOf("home", "admin", ".kscript")) + it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) + it.prop(OsPath::root).isEqualTo("C:\\") + it.prop(OsPath::pathParts).isEqualTo(listOf("home", "admin", ".kscript")) } assertThat(windowsVfs.createOsPath("")).let { - it.prop(OsPath<*>::osType).isEqualTo(OsType.WINDOWS) - it.prop(OsPath<*>::root).isEqualTo("") - it.prop(OsPath<*>::pathParts).isEqualTo(emptyList()) + it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) + it.prop(OsPath::root).isEqualTo("") + it.prop(OsPath::pathParts).isEqualTo(emptyList()) } assertThat(windowsVfs.createOsPath("file.txt")).let { - it.prop(OsPath<*>::osType).isEqualTo(OsType.WINDOWS) - it.prop(OsPath<*>::root).isEqualTo("") - it.prop(OsPath<*>::pathParts).isEqualTo(listOf("file.txt")) + it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) + it.prop(OsPath::root).isEqualTo("") + it.prop(OsPath::pathParts).isEqualTo(listOf("file.txt")) } assertThat(windowsVfs.createOsPath(".")).let { - it.prop(OsPath<*>::osType).isEqualTo(OsType.WINDOWS) - it.prop(OsPath<*>::root).isEqualTo("") - it.prop(OsPath<*>::pathParts).isEqualTo(emptyList()) + it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) + it.prop(OsPath::root).isEqualTo("") + it.prop(OsPath::pathParts).isEqualTo(emptyList()) } assertThat(windowsVfs.createOsPath(".\\home\\admin\\.kscript")).let { - it.prop(OsPath<*>::osType).isEqualTo(OsType.WINDOWS) - it.prop(OsPath<*>::root).isEqualTo("") - it.prop(OsPath<*>::pathParts).isEqualTo(listOf("home", "admin", ".kscript")) + it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) + it.prop(OsPath::root).isEqualTo("") + it.prop(OsPath::pathParts).isEqualTo(listOf("home", "admin", ".kscript")) } assertThat(windowsVfs.createOsPath("..\\home\\admin\\.kscript")).let { - it.prop(OsPath<*>::osType).isEqualTo(OsType.WINDOWS) - it.prop(OsPath<*>::root).isEqualTo("") - it.prop(OsPath<*>::pathParts).isEqualTo(listOf("..", "home", "admin", ".kscript")) + it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) + it.prop(OsPath::root).isEqualTo("") + it.prop(OsPath::pathParts).isEqualTo(listOf("..", "home", "admin", ".kscript")) } assertThat(windowsVfs.createOsPath("..")).let { - it.prop(OsPath<*>::osType).isEqualTo(OsType.WINDOWS) - it.prop(OsPath<*>::root).isEqualTo("") - it.prop(OsPath<*>::pathParts).isEqualTo(listOf("..")) + it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) + it.prop(OsPath::root).isEqualTo("") + it.prop(OsPath::pathParts).isEqualTo(listOf("..")) } //Duplicated separators are accepted assertThat(windowsVfs.createOsPath("C:\\home\\\\\\\\admin\\.kscript\\")).let { - it.prop(OsPath<*>::osType).isEqualTo(OsType.WINDOWS) - it.prop(OsPath<*>::root).isEqualTo("C:\\") - it.prop(OsPath<*>::pathParts).isEqualTo(listOf("home", "admin", ".kscript")) + it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) + it.prop(OsPath::root).isEqualTo("C:\\") + it.prop(OsPath::pathParts).isEqualTo(listOf("home", "admin", ".kscript")) } //Both types of separator are accepted assertThat(windowsVfs.createOsPath("C:/home\\admin/.kscript////")).let { - it.prop(OsPath<*>::osType).isEqualTo(OsType.WINDOWS) - it.prop(OsPath<*>::root).isEqualTo("C:\\") - it.prop(OsPath<*>::pathParts).isEqualTo(listOf("home", "admin", ".kscript")) + it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) + it.prop(OsPath::root).isEqualTo("C:\\") + it.prop(OsPath::pathParts).isEqualTo(listOf("home", "admin", ".kscript")) } } @Test fun `Normalization of Windows paths`() { assertThat(windowsVfs.createOsPath("C:\\home\\admin\\.kscript\\..\\..\\")).let { - it.prop(OsPath<*>::osType).isEqualTo(OsType.WINDOWS) - it.prop(OsPath<*>::root).isEqualTo("C:\\") - it.prop(OsPath<*>::pathParts).isEqualTo(listOf("home")) + it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) + it.prop(OsPath::root).isEqualTo("C:\\") + it.prop(OsPath::pathParts).isEqualTo(listOf("home")) } assertThat(windowsVfs.createOsPath(".\\.\\.\\..\\..\\script")).let { - it.prop(OsPath<*>::osType).isEqualTo(OsType.WINDOWS) - it.prop(OsPath<*>::root).isEqualTo("") - it.prop(OsPath<*>::pathParts).isEqualTo(listOf("..", "..", "script")) + it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) + it.prop(OsPath::root).isEqualTo("") + it.prop(OsPath::pathParts).isEqualTo(listOf("..", "..", "script")) } assertThat(windowsVfs.createOsPath("C:\\a\\b\\c\\..\\d\\script")).let { - it.prop(OsPath<*>::osType).isEqualTo(OsType.WINDOWS) - it.prop(OsPath<*>::root).isEqualTo("C:\\") - it.prop(OsPath<*>::pathParts).isEqualTo(listOf("a", "b", "d", "script")) + it.prop(OsPath::osType).isEqualTo(OsType.WINDOWS) + it.prop(OsPath::root).isEqualTo("C:\\") + it.prop(OsPath::pathParts).isEqualTo(listOf("a", "b", "d", "script")) } assertFailure { From 6c9b70185169e086a2e52384bcd207fe487917e7 Mon Sep 17 00:00:00 2001 From: Marcin Kuszczak Date: Tue, 17 Jun 2025 18:51:13 +0200 Subject: [PATCH 50/50] * Updated build.gradle.kts to the latest Kotlin * Updated build.gradle.kts libraries * Updated build.gradle.kts JVM version * Changed function isValid to provide more information about the problem * Fixed tests --- build.gradle.kts | 29 ++++++++++--------- src/main/kotlin/io/github/kscripting/os/Os.kt | 5 ++-- .../io/github/kscripting/os/OsProvider.kt | 8 ++--- .../kotlin/io/github/kscripting/os/Vfs.kt | 2 +- .../github/kscripting/os/instance/CygwinOs.kt | 4 +-- .../kscripting/os/instance/FreeBsdOs.kt | 4 +-- .../github/kscripting/os/instance/LinuxOs.kt | 6 ++-- .../io/github/kscripting/os/instance/MacOs.kt | 4 +-- .../github/kscripting/os/instance/MsysOs.kt | 4 +-- .../github/kscripting/os/instance/PosixVfs.kt | 17 +++++++++-- .../kscripting/os/instance/WindowsOs.kt | 4 +-- .../kscripting/os/instance/WindowsVfs.kt | 14 +++++++-- .../io/github/kscripting/os/util/VfsUtil.kt | 8 ++--- .../kscripting/os/model/PosixOsPathTest.kt | 4 +-- .../kscripting/os/model/WindowsOsPathTest.kt | 4 +-- 15 files changed, 65 insertions(+), 52 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index fb8e9ea..37e65ec 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,9 +1,9 @@ -val kotlinVersion: String = "1.8.22" +val kotlinVersion: String = "2.1.21" plugins { - kotlin("jvm") version "1.8.22" - id("com.adarshr.test-logger") version "3.2.0" + kotlin("jvm") version "2.1.21" + id("com.adarshr.test-logger") version "4.0.0" `maven-publish` signing idea @@ -18,7 +18,7 @@ group = "io.github.kscripting" version = "0.6.0-SNAPSHOT" kotlin { - jvmToolchain(11) + jvmToolchain(17) } configurations.all { @@ -170,20 +170,23 @@ signing { } dependencies { - api("net.igsoft:typeutils:0.7.0-SNAPSHOT") - - implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlinVersion") implementation("org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4") + + api("net.igsoft:typeutils:0.7.0-SNAPSHOT") + implementation("org.apache.commons:commons-lang3:3.12.0") implementation("org.slf4j:slf4j-nop:2.0.5") - testImplementation("org.junit.platform:junit-platform-suite-engine:1.9.0") - testImplementation("org.junit.platform:junit-platform-suite-api:1.9.0") - testImplementation("org.junit.platform:junit-platform-suite-commons:1.9.0") - testImplementation("org.junit.jupiter:junit-jupiter-engine:5.9.0") - testImplementation("org.junit.jupiter:junit-jupiter-params:5.9.0") - testImplementation("com.willowtreeapps.assertk:assertk-jvm:0.27.0") + val junitPlatformVersion = "1.13.1" + val junitEngineVersion = "5.13.1" + testRuntimeOnly("org.junit.platform:junit-platform-launcher") + testImplementation("org.junit.platform:junit-platform-suite-engine:${junitPlatformVersion}") + testImplementation("org.junit.platform:junit-platform-suite-api:${junitPlatformVersion}") + testImplementation("org.junit.platform:junit-platform-suite-commons:${junitPlatformVersion}") + testImplementation("org.junit.jupiter:junit-jupiter:$junitEngineVersion") + testImplementation("org.junit.jupiter:junit-jupiter-params:$junitEngineVersion") + testImplementation("com.willowtreeapps.assertk:assertk:0.28.1") testImplementation("io.mockk:mockk:1.13.2") testImplementation(kotlin("script-runtime")) diff --git a/src/main/kotlin/io/github/kscripting/os/Os.kt b/src/main/kotlin/io/github/kscripting/os/Os.kt index 456b494..c6d167d 100644 --- a/src/main/kotlin/io/github/kscripting/os/Os.kt +++ b/src/main/kotlin/io/github/kscripting/os/Os.kt @@ -1,14 +1,13 @@ package io.github.kscripting.os -import net.igsoft.typeutils.marker.DefaultTypedMarker -import net.igsoft.typeutils.marker.NamedAutoTypedMarker +import net.igsoft.typeutils.marker.AutoTypedMarker interface Os { //LINUX("linux"), MACOS("darwin"), WINDOWS("windows"), CYGWIN("cygwin"), MSYS("msys"), FREEBSD("freebsd"); // Exact comparison (it.osName.equals(name, true)) seems to be not feasible as there is also e.g. "darwin21" // "darwin19", "linux-musl" (for Docker Alpine), "linux-gnu" and maybe even other osTypes. But it seems that // startsWith() covers all cases. - val marker: NamedAutoTypedMarker + val marker: AutoTypedMarker val osTypePrefix: String val type: OsType diff --git a/src/main/kotlin/io/github/kscripting/os/OsProvider.kt b/src/main/kotlin/io/github/kscripting/os/OsProvider.kt index e75e0e7..d6d4904 100644 --- a/src/main/kotlin/io/github/kscripting/os/OsProvider.kt +++ b/src/main/kotlin/io/github/kscripting/os/OsProvider.kt @@ -1,11 +1,11 @@ package io.github.kscripting.os import io.github.kscripting.os.instance.LinuxOs -import net.igsoft.typeutils.marker.NamedAutoTypedMarker +import net.igsoft.typeutils.marker.AutoTypedMarker interface OsProvider { val native: Os - fun provide(marker: NamedAutoTypedMarker = native.marker): Os + fun provide(marker: AutoTypedMarker = native.marker): Os companion object { private val defaultOsProvider = DefaultOsProvider() @@ -48,7 +48,7 @@ fun osContext(osProvider: OsProvider, block: (OsProvider) -> Unit) { class DefaultOsProvider : OsProvider { override val native: Os = TODO() - override fun provide(marker: NamedAutoTypedMarker): Os { + override fun provide(marker: AutoTypedMarker): Os { TODO() } } @@ -58,7 +58,7 @@ class MockProvider : OsProvider { override val native: Os get() = TODO("Not yet implemented") - override fun provide(marker: NamedAutoTypedMarker): Os { + override fun provide(marker: AutoTypedMarker): Os { TODO("Not yet implemented") } } diff --git a/src/main/kotlin/io/github/kscripting/os/Vfs.kt b/src/main/kotlin/io/github/kscripting/os/Vfs.kt index bd3020b..be6a3b8 100644 --- a/src/main/kotlin/io/github/kscripting/os/Vfs.kt +++ b/src/main/kotlin/io/github/kscripting/os/Vfs.kt @@ -21,5 +21,5 @@ interface Vfs { fun createOsPath(vararg pathParts: String): OsPath = createOsPath(pathParts.joinToString("/")) - fun isValid(path: String): Boolean + fun isValid(path: String): Result } diff --git a/src/main/kotlin/io/github/kscripting/os/instance/CygwinOs.kt b/src/main/kotlin/io/github/kscripting/os/instance/CygwinOs.kt index 32dffa7..f01cba5 100644 --- a/src/main/kotlin/io/github/kscripting/os/instance/CygwinOs.kt +++ b/src/main/kotlin/io/github/kscripting/os/instance/CygwinOs.kt @@ -1,10 +1,10 @@ package io.github.kscripting.os.instance import io.github.kscripting.os.OsType -import net.igsoft.typeutils.marker.NamedAutoTypedMarker +import net.igsoft.typeutils.marker.AutoTypedMarker class CygwinOs(override val vfs: CygwinVfs, override val nativeOs: WindowsOs) : HostedOs { - override val marker: NamedAutoTypedMarker = NamedAutoTypedMarker.create("Cygwin") + override val marker: AutoTypedMarker = AutoTypedMarker.create("Cygwin") override val type: OsType = OsType.CYGWIN override val osTypePrefix: String = "cygwin" } diff --git a/src/main/kotlin/io/github/kscripting/os/instance/FreeBsdOs.kt b/src/main/kotlin/io/github/kscripting/os/instance/FreeBsdOs.kt index 4c532c7..f8cb485 100644 --- a/src/main/kotlin/io/github/kscripting/os/instance/FreeBsdOs.kt +++ b/src/main/kotlin/io/github/kscripting/os/instance/FreeBsdOs.kt @@ -3,11 +3,9 @@ package io.github.kscripting.os.instance import io.github.kscripting.os.Os import io.github.kscripting.os.OsType import net.igsoft.typeutils.marker.AutoTypedMarker -import net.igsoft.typeutils.marker.DefaultTypedMarker -import net.igsoft.typeutils.marker.NamedAutoTypedMarker class FreeBsdOs(override val vfs: FreeBsdVfs) : Os { - override val marker: NamedAutoTypedMarker = NamedAutoTypedMarker.create("FreeBsd") + override val marker: AutoTypedMarker = AutoTypedMarker.create("FreeBsd") override val type: OsType = OsType.FREEBSD override val osTypePrefix: String = "freebsd" } diff --git a/src/main/kotlin/io/github/kscripting/os/instance/LinuxOs.kt b/src/main/kotlin/io/github/kscripting/os/instance/LinuxOs.kt index 241ba0a..f6b6dc2 100644 --- a/src/main/kotlin/io/github/kscripting/os/instance/LinuxOs.kt +++ b/src/main/kotlin/io/github/kscripting/os/instance/LinuxOs.kt @@ -3,15 +3,13 @@ package io.github.kscripting.os.instance import io.github.kscripting.os.Os import io.github.kscripting.os.OsType import net.igsoft.typeutils.marker.AutoTypedMarker -import net.igsoft.typeutils.marker.DefaultTypedMarker -import net.igsoft.typeutils.marker.NamedAutoTypedMarker class LinuxOs(override val vfs: LinuxVfs) : Os { - override val marker: NamedAutoTypedMarker = NamedAutoTypedMarker.create("Linux") + override val marker: AutoTypedMarker = AutoTypedMarker.create("Linux") override val type: OsType = OsType.LINUX override val osTypePrefix: String = "linux" companion object { - val marker: NamedAutoTypedMarker = NamedAutoTypedMarker.create("Linux") + val marker: AutoTypedMarker = AutoTypedMarker.create("Linux") } } diff --git a/src/main/kotlin/io/github/kscripting/os/instance/MacOs.kt b/src/main/kotlin/io/github/kscripting/os/instance/MacOs.kt index b310229..f106f5b 100644 --- a/src/main/kotlin/io/github/kscripting/os/instance/MacOs.kt +++ b/src/main/kotlin/io/github/kscripting/os/instance/MacOs.kt @@ -3,11 +3,9 @@ package io.github.kscripting.os.instance import io.github.kscripting.os.Os import io.github.kscripting.os.OsType import net.igsoft.typeutils.marker.AutoTypedMarker -import net.igsoft.typeutils.marker.DefaultTypedMarker -import net.igsoft.typeutils.marker.NamedAutoTypedMarker class MacOs(override val vfs: MacOsVfs) : Os { - override val marker: NamedAutoTypedMarker = NamedAutoTypedMarker.create("Mac") + override val marker: AutoTypedMarker = AutoTypedMarker.create("Mac") override val type: OsType = OsType.MACOS override val osTypePrefix: String = "darwin" } diff --git a/src/main/kotlin/io/github/kscripting/os/instance/MsysOs.kt b/src/main/kotlin/io/github/kscripting/os/instance/MsysOs.kt index 31e654f..dcc71ca 100644 --- a/src/main/kotlin/io/github/kscripting/os/instance/MsysOs.kt +++ b/src/main/kotlin/io/github/kscripting/os/instance/MsysOs.kt @@ -1,10 +1,10 @@ package io.github.kscripting.os.instance import io.github.kscripting.os.OsType -import net.igsoft.typeutils.marker.NamedAutoTypedMarker +import net.igsoft.typeutils.marker.AutoTypedMarker class MsysOs(override val nativeOs: WindowsOs, override val vfs: MsysVfs) : HostedOs { - override val marker: NamedAutoTypedMarker = NamedAutoTypedMarker.create("Msys") + override val marker: AutoTypedMarker = AutoTypedMarker.create("Msys") override val type: OsType = OsType.MSYS override val osTypePrefix: String = "msys" } diff --git a/src/main/kotlin/io/github/kscripting/os/instance/PosixVfs.kt b/src/main/kotlin/io/github/kscripting/os/instance/PosixVfs.kt index 7420513..1ed0f66 100644 --- a/src/main/kotlin/io/github/kscripting/os/instance/PosixVfs.kt +++ b/src/main/kotlin/io/github/kscripting/os/instance/PosixVfs.kt @@ -5,5 +5,18 @@ import io.github.kscripting.os.Vfs abstract class PosixVfs(override val type: OsType) : Vfs { override val pathSeparator: String = "/" - override fun isValid(path: String): Boolean = true -} \ No newline at end of file + override fun isValid(path: String): Result { + // NOTE: https://stackoverflow.com/a/1311070/5321061 + for (char in path.withIndex()) { + if (char.value in INVALID_CHARS) { + return Result.failure(IllegalArgumentException("Invalid character '${char.value}' in path '$path'")) + } + } + + return Result.success(Unit) + } + + companion object { + private const val INVALID_CHARS = "\u0000" + } +} diff --git a/src/main/kotlin/io/github/kscripting/os/instance/WindowsOs.kt b/src/main/kotlin/io/github/kscripting/os/instance/WindowsOs.kt index ad3e0c2..1c311ce 100644 --- a/src/main/kotlin/io/github/kscripting/os/instance/WindowsOs.kt +++ b/src/main/kotlin/io/github/kscripting/os/instance/WindowsOs.kt @@ -3,11 +3,9 @@ package io.github.kscripting.os.instance import io.github.kscripting.os.Os import io.github.kscripting.os.OsType import net.igsoft.typeutils.marker.AutoTypedMarker -import net.igsoft.typeutils.marker.DefaultTypedMarker -import net.igsoft.typeutils.marker.NamedAutoTypedMarker class WindowsOs(override val vfs: WindowsVfs) : Os { - override val marker: NamedAutoTypedMarker = NamedAutoTypedMarker.create("Windows") + override val marker: AutoTypedMarker = AutoTypedMarker.create("Windows") override val type: OsType = OsType.WINDOWS override val osTypePrefix: String = "windows" } diff --git a/src/main/kotlin/io/github/kscripting/os/instance/WindowsVfs.kt b/src/main/kotlin/io/github/kscripting/os/instance/WindowsVfs.kt index 17dc6b9..d12cd8c 100644 --- a/src/main/kotlin/io/github/kscripting/os/instance/WindowsVfs.kt +++ b/src/main/kotlin/io/github/kscripting/os/instance/WindowsVfs.kt @@ -18,6 +18,8 @@ class WindowsVfs(userHome: String) : Vfs { override val userHome: OsPath = createOsPath(userHome) override fun createOsPath(path: String): OsPath { + isValid(path).getOrThrow() + //Detect root val root = when { path.startsWith("~/") || path.startsWith("~\\") -> "~" @@ -31,8 +33,16 @@ class WindowsVfs(userHome: String) : Vfs { return createFinalPath(this, path, root) } - override fun isValid(path: String): Boolean { - return path.none { INVALID_CHARS.contains(it) } + override fun isValid(path: String): Result { + val isAbsolute = path.length >= 2 && path[1] == ':' && path[0] != '\\' + + for(char in path.withIndex()) { + if (!(isAbsolute && char.index == 1) && char.value in INVALID_CHARS) { + return Result.failure(IllegalArgumentException("Invalid character '${char.value}' in path '$path'")) + } + } + + return Result.success(Unit) } companion object { diff --git a/src/main/kotlin/io/github/kscripting/os/util/VfsUtil.kt b/src/main/kotlin/io/github/kscripting/os/util/VfsUtil.kt index 6c1596b..e691d7b 100644 --- a/src/main/kotlin/io/github/kscripting/os/util/VfsUtil.kt +++ b/src/main/kotlin/io/github/kscripting/os/util/VfsUtil.kt @@ -3,13 +3,12 @@ package io.github.kscripting.os.util import io.github.kscripting.os.Vfs import io.github.kscripting.os.instance.CygwinVfs import io.github.kscripting.os.instance.HostedVfs -import io.github.kscripting.os.instance.WindowsVfs import io.github.kscripting.os.model.OsPath import io.github.kscripting.os.model.startsWith fun createPosixOsPath(vfs: Vfs, path: String): OsPath { - require(vfs.isValid(path)) + vfs.isValid(path).getOrThrow() //Detect root val root: String = when { @@ -24,14 +23,11 @@ fun createPosixOsPath(vfs: Vfs, path: String): OsPath { fun createFinalPath(vfs: Vfs, path: String, root: String): OsPath { //Remove also empty path parts - there were duplicated or trailing slashes / backslashes in initial path val pathWithoutRoot = path.drop(root.length) - - require(vfs.isValid(pathWithoutRoot)) - val pathPartsResolved = pathWithoutRoot.split('/', '\\').filter { it.isNotBlank() } return OsPath(vfs, root, normalize(root, pathPartsResolved)) } -fun toHostedConverter(vfs: T, osPath: OsPath): OsPath { +fun toHostedConverter(vfs: T, osPath: OsPath): OsPath { val newParts = mutableListOf() var newRoot = "" diff --git a/src/test/kotlin/io/github/kscripting/os/model/PosixOsPathTest.kt b/src/test/kotlin/io/github/kscripting/os/model/PosixOsPathTest.kt index b1e7e25..556a229 100644 --- a/src/test/kotlin/io/github/kscripting/os/model/PosixOsPathTest.kt +++ b/src/test/kotlin/io/github/kscripting/os/model/PosixOsPathTest.kt @@ -107,8 +107,8 @@ class PosixOsPathTest { @Test fun `Test invalid Posix paths`() { assertFailure { - linuxVfs.createOsPath("/ad*asdf") - }.isInstanceOf(IllegalArgumentException::class.java).hasMessage("Invalid character '*' in path part 'ad*asdf'") + linuxVfs.createOsPath("/ad\u0000asdf") + }.isInstanceOf(IllegalArgumentException::class.java).hasMessage("Invalid character '\u0000' in path '/ad\u0000asdf'") } @Test diff --git a/src/test/kotlin/io/github/kscripting/os/model/WindowsOsPathTest.kt b/src/test/kotlin/io/github/kscripting/os/model/WindowsOsPathTest.kt index ff6046f..3d87c18 100644 --- a/src/test/kotlin/io/github/kscripting/os/model/WindowsOsPathTest.kt +++ b/src/test/kotlin/io/github/kscripting/os/model/WindowsOsPathTest.kt @@ -110,11 +110,11 @@ class WindowsOsPathTest { fun `Test invalid Windows paths`() { assertFailure { windowsVfs.createOsPath("C:\\adas?df") } .isInstanceOf(IllegalArgumentException::class.java) - .hasMessage("Invalid character '?' in path part 'adas?df'") + .hasMessage("Invalid character '?' in path 'C:\\adas?df'") assertFailure { windowsVfs.createOsPath("home:\\vagrant") } .isInstanceOf(IllegalArgumentException::class.java) - .hasMessage("Invalid character ':' in path part 'home:'") + .hasMessage("Invalid character ':' in path 'home:\\vagrant'") } @Test