From df4b71b1c5c3879d33b41537af568bcabeb316f9 Mon Sep 17 00:00:00 2001 From: Vadim Briliantov Date: Thu, 28 Jul 2022 03:25:35 +0200 Subject: [PATCH] Finalize CLI generator before the beta-1 release --- src/nativeMain/kotlin/CliGeneratorMain.kt | 24 ++++++++---------- .../io/ktor/generator/api/KtorWebGenerator.kt | 3 ++- .../ktor/generator/bundle/PropertiesBundle.kt | 20 ++++++++++++--- .../generator/cli/installer/KtorInstaller.kt | 25 ++++++++++++++----- .../ktor/generator/cli/utils/ClientUtils.kt | 8 ++++++ .../io/ktor/generator/cli/utils/Common.kt | 2 ++ .../generator/cli/utils/FileSystemUtils.kt | 2 +- .../kotlin/io/ktor/generator/CommonTest.kt | 2 +- .../generator/bundle/BundleParsingTest.kt | 2 -- 9 files changed, 61 insertions(+), 27 deletions(-) diff --git a/src/nativeMain/kotlin/CliGeneratorMain.kt b/src/nativeMain/kotlin/CliGeneratorMain.kt index 107e589..32d1b52 100644 --- a/src/nativeMain/kotlin/CliGeneratorMain.kt +++ b/src/nativeMain/kotlin/CliGeneratorMain.kt @@ -7,27 +7,29 @@ import io.ktor.generator.bundle.* import io.ktor.generator.cli.installer.* import kotlinx.cli.* -const val DEFAULT_KTOR_URL = "https://ktor-plugin.europe-north1-gke.intellij.net" +private fun executeCatching(action: () -> Unit) { + try { + action() + } catch (e: Exception) { + PropertiesBundle.writeMessage("error.happened", e.message ?: e.stackTraceToString()) + } +} @OptIn(ExperimentalCli::class) abstract class KtorCommand( name: String, description: String, client: HttpClient ) : Subcommand(name, description) { - private val host: String by option( - ArgType.String, fullName = "host", description = PropertiesBundle.message("ktor.backend.url.description") - ).default(DEFAULT_KTOR_URL) - protected val projectName: String by argument( ArgType.String, description = PropertiesBundle.message("project.name.description") ) - protected val ktorInstaller: KtorInstaller by lazy { KtorInstaller(KtorGeneratorWebImpl(client, host)) } + protected val ktorInstaller: KtorInstaller by lazy { KtorInstaller(KtorGeneratorWebImpl(client)) } } class GenerateProject(client: HttpClient) : KtorCommand( "generate", description = PropertiesBundle.message("generate.command.description"), client = client ) { - override fun execute() { + override fun execute() = executeCatching { ktorInstaller.downloadKtorProject(projectName) } } @@ -35,12 +37,8 @@ class GenerateProject(client: HttpClient) : KtorCommand( class RunProject(client: HttpClient) : KtorCommand( "start", description = PropertiesBundle.message("run.command.description"), client = client ) { - private val args: List by argument( - ArgType.String, fullName = "args", description = PropertiesBundle.message("run.arguments.description") - ).optional().vararg() - - override fun execute() { - ktorInstaller.runKtorProject(projectName, args) + override fun execute() = executeCatching { + ktorInstaller.runKtorProject(projectName) } } diff --git a/src/nativeMain/kotlin/io/ktor/generator/api/KtorWebGenerator.kt b/src/nativeMain/kotlin/io/ktor/generator/api/KtorWebGenerator.kt index f1ac568..69a9457 100644 --- a/src/nativeMain/kotlin/io/ktor/generator/api/KtorWebGenerator.kt +++ b/src/nativeMain/kotlin/io/ktor/generator/api/KtorWebGenerator.kt @@ -1,5 +1,6 @@ package io.ktor.generator.api +import io.ktor.generator.cli.utils.DEFAULT_KTOR_URL import io.ktor.client.* import io.ktor.client.request.* import io.ktor.generator.cli.installer.* @@ -24,7 +25,7 @@ interface KtorGeneratorWeb { suspend fun genProjectSettings(): ProjectSettingsTemplate } -class KtorGeneratorWebImpl(val client: HttpClient, private val ktorBackendHost: String) : KtorGeneratorWeb { +class KtorGeneratorWebImpl(val client: HttpClient, private val ktorBackendHost: String = DEFAULT_KTOR_URL) : KtorGeneratorWeb { override suspend fun downloadJdkArchive(): ByteArray = client.fetchZipContent(jdkDownloadUrl) override suspend fun generateKtorProject(configuration: SelectedProjectConfiguration): ByteArray = diff --git a/src/nativeMain/kotlin/io/ktor/generator/bundle/PropertiesBundle.kt b/src/nativeMain/kotlin/io/ktor/generator/bundle/PropertiesBundle.kt index d2b3dbe..1250c34 100644 --- a/src/nativeMain/kotlin/io/ktor/generator/bundle/PropertiesBundle.kt +++ b/src/nativeMain/kotlin/io/ktor/generator/bundle/PropertiesBundle.kt @@ -5,15 +5,17 @@ import com.github.ajalt.mordant.rendering.TextColors.red import com.github.ajalt.mordant.terminal.Terminal import kotlin.test.assertNotNull +private const val JDK_LICENSE_LINK = "https://www.oracle.com/a/tech/docs/jdk11-lium.pdf" + +const val ANSWER_YES = "Y" + // TODO: figure out how to read properties from resources and deploy with project in Kotlin/Native object PropertiesBundle { private val propToValue: Map = mapOf( "program.name" to "Ktor CLI generator", - "ktor.backend.url.description" to "Ktor generator backend url. It is recommended to leave this property as default", "generate.command.description" to "Generate new ktor project", "run.command.description" to "Run existing ktor project", "project.name.description" to "Name of the ktor project", - "run.arguments.description" to "Arguments that will be passed to your project", "unable.to.run.command" to "Unable to run: {0}", "error.running.command" to "Error running process: {0}", "jdk.11.not.found" to "JDK 11 not found.\nJdk download path: {0}\nDownloading JDK 11 from server, please wait...", @@ -23,7 +25,14 @@ object PropertiesBundle { "project.downloaded" to "Project \"{0}\" was downloaded. Running gradle setup...", "project.generated" to "Project \"{0}\" was successfully generated.\nYou can execute `ktor start {0}` to start it", "project.not.exists" to "Project {0} does not exist", - "project.not.have.gradlew" to "Invalid project. Project \"{0}\" does not have gradlew file" + "project.not.have.gradlew" to "Invalid project. Project \"{0}\" does not have gradlew file", + "download.jdk.legal.message" to "JDK not found\n" + + "JDK is a software licensed by Oracle, Inc. under the terms available at $JDK_LICENSE_LINK.\n" + + "JDK download path: {0}.\n" + + "By typing \"$ANSWER_YES\" you agree with these terms and completion of installation [Y/n]: ", + "jdk.legal.rejected" to "JDK is required to proceed. JDK not found. Quitting...", + "jdk.setup.failed" to "Failed to setup JDK", + "error.happened" to "Error happened: {0}" ) private val argumentRegex = "\\{\\d+}".toRegex() @@ -48,6 +57,11 @@ object PropertiesBundle { } fun writeMessage(property: String, vararg args: String) = println(message(property, *args)) + + fun askQuestion(property: String, vararg args: String): Boolean { + print(message(property, *args)) + return readLine() == ANSWER_YES + } fun writeErrorMessage(property: String, vararg args: String) { println() Terminal().println(red(message(property, *args))) diff --git a/src/nativeMain/kotlin/io/ktor/generator/cli/installer/KtorInstaller.kt b/src/nativeMain/kotlin/io/ktor/generator/cli/installer/KtorInstaller.kt index fdd3139..01abb43 100644 --- a/src/nativeMain/kotlin/io/ktor/generator/cli/installer/KtorInstaller.kt +++ b/src/nativeMain/kotlin/io/ktor/generator/cli/installer/KtorInstaller.kt @@ -54,15 +54,20 @@ class KtorInstaller(private val service: KtorGeneratorWeb) { private fun jdkIsInstalled(): Boolean = getRcProperty(JAVA_HOME) != null || customJdkIsInstalled() || hasJavaHome11() - private fun installJdkIfAbsent() { + private fun installJdkIfAbsent(): Boolean { initKtorRootIfAbsent() if (jdkIsInstalled()) { if (getRcProperty(JAVA_HOME) == null) { addRcProperty(JAVA_HOME, findCustomJdk()?.path ?: getJavaHome()!!) } - return + return true + } + if (!PropertiesBundle.askQuestion("download.jdk.legal.message", jdkDownloadUrl)) { + PropertiesBundle.writeMessage("jdk.legal.rejected") + return false } + val jdkArchiveFile = Directory.home().createFileIfNeeded(jdkArchiveName) PropertiesBundle.writeMessage("jdk.11.not.found", jdkDownloadUrl) @@ -76,12 +81,19 @@ class KtorInstaller(private val service: KtorGeneratorWeb) { unpackJdk(archive = jdkArchiveFile, outputDir = jdkDir) jdkArchiveFile.delete() - val newJdkPath = findCustomJdk()?.path ?: throw Exception("Failed to setup JDK") + val newJdkPath = findCustomJdk()?.path + if (newJdkPath == null) { + PropertiesBundle.writeMessage("jdk.setup.failed") + return false + } + addRcProperty(JAVA_HOME, newJdkPath) + + return true } fun downloadKtorProject(projectName: String) { - installJdkIfAbsent() + if (!installJdkIfAbsent()) return val currentDir = Directory.current() if (currentDir.subdir(projectName).exists()) { @@ -130,8 +142,9 @@ class KtorInstaller(private val service: KtorGeneratorWeb) { PropertiesBundle.writeSuccessMessage("project.generated", projectName) } - fun runKtorProject(path: String, args: List) { - installJdkIfAbsent() + fun runKtorProject(path: String, args: List = emptyList()) { + if (!installJdkIfAbsent()) return + val ktorJavaHome = getRcProperty(JAVA_HOME)!! val currentDir = Directory.current() diff --git a/src/nativeMain/kotlin/io/ktor/generator/cli/utils/ClientUtils.kt b/src/nativeMain/kotlin/io/ktor/generator/cli/utils/ClientUtils.kt index 76dd054..acadc5e 100644 --- a/src/nativeMain/kotlin/io/ktor/generator/cli/utils/ClientUtils.kt +++ b/src/nativeMain/kotlin/io/ktor/generator/cli/utils/ClientUtils.kt @@ -2,6 +2,7 @@ package io.ktor.generator.cli.utils import io.ktor.client.* import io.ktor.client.call.* +import io.ktor.client.features.* import io.ktor.client.request.* import io.ktor.client.statement.* import io.ktor.http.* @@ -16,6 +17,13 @@ suspend fun HttpClient.fetchZipContent( request { url.takeFrom(urlString) method = httpMethod + onDownload { bytesSentTotal, contentLength -> + val progress = 100 * bytesSentTotal / contentLength + if (progress != (100 * (bytesSentTotal - 1024) / contentLength)) print("\rProgress: $progress %") + if (progress == 100L) { + println() + } + } apply(block) } .receive() \ No newline at end of file diff --git a/src/nativeMain/kotlin/io/ktor/generator/cli/utils/Common.kt b/src/nativeMain/kotlin/io/ktor/generator/cli/utils/Common.kt index 1cf6b5d..0c0e5fb 100644 --- a/src/nativeMain/kotlin/io/ktor/generator/cli/utils/Common.kt +++ b/src/nativeMain/kotlin/io/ktor/generator/cli/utils/Common.kt @@ -5,6 +5,8 @@ import kotlinx.cinterop.toKString import kotlinx.cinterop.usePinned import platform.posix.PATH_MAX +const val DEFAULT_KTOR_URL = "https://ktor-plugin.europe-north1-gke.intellij.net" + expect val RESOURCES_PATH: String fun getResourcePath(path: String): String { diff --git a/src/nativeMain/kotlin/io/ktor/generator/cli/utils/FileSystemUtils.kt b/src/nativeMain/kotlin/io/ktor/generator/cli/utils/FileSystemUtils.kt index 76355b5..4093760 100644 --- a/src/nativeMain/kotlin/io/ktor/generator/cli/utils/FileSystemUtils.kt +++ b/src/nativeMain/kotlin/io/ktor/generator/cli/utils/FileSystemUtils.kt @@ -34,7 +34,7 @@ internal fun pwd(): String = memScoped { val pathBuffer: CArrayPointer = allocArray(pathBufferSize) getCwd(pathBuffer, pathBufferSize) ?: throw Exception("Failed to locate working dir") - return@memScoped pathBuffer.toKString() + return@memScoped pathBuffer.toKString().replace(" ", "\\ ") } interface FsUnit { diff --git a/src/nativeTest/kotlin/io/ktor/generator/CommonTest.kt b/src/nativeTest/kotlin/io/ktor/generator/CommonTest.kt index ac462da..9b8f20a 100644 --- a/src/nativeTest/kotlin/io/ktor/generator/CommonTest.kt +++ b/src/nativeTest/kotlin/io/ktor/generator/CommonTest.kt @@ -1,6 +1,6 @@ package io.ktor.generator -import DEFAULT_KTOR_URL +import io.ktor.generator.cli.utils.DEFAULT_KTOR_URL import createHttpClient import io.ktor.generator.api.* import io.ktor.generator.cli.installer.* diff --git a/src/nativeTest/kotlin/io/ktor/generator/bundle/BundleParsingTest.kt b/src/nativeTest/kotlin/io/ktor/generator/bundle/BundleParsingTest.kt index b043ebb..8ad5cc4 100644 --- a/src/nativeTest/kotlin/io/ktor/generator/bundle/BundleParsingTest.kt +++ b/src/nativeTest/kotlin/io/ktor/generator/bundle/BundleParsingTest.kt @@ -60,11 +60,9 @@ class BundleParsingTest { @Test fun testSimpleMessagesNoError() { assertNoErrorInBundle("program.name") - assertNoErrorInBundle("ktor.backend.url.description") assertNoErrorInBundle("generate.command.description") assertNoErrorInBundle("run.command.description") assertNoErrorInBundle("project.name.description") - assertNoErrorInBundle("run.arguments.description") assertNoErrorInBundle("jdk.installed.success") assertNoErrorInBundle("generating.project") }