Skip to content

Commit

Permalink
Finalize CLI generator before the beta-1 release
Browse files Browse the repository at this point in the history
  • Loading branch information
Ololoshechkin committed Jul 28, 2022
1 parent e6a62cf commit df4b71b
Show file tree
Hide file tree
Showing 9 changed files with 61 additions and 27 deletions.
24 changes: 11 additions & 13 deletions src/nativeMain/kotlin/CliGeneratorMain.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,40 +7,38 @@ 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)
}
}

class RunProject(client: HttpClient) : KtorCommand(
"start", description = PropertiesBundle.message("run.command.description"), client = client
) {
private val args: List<String> 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)
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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.*
Expand All @@ -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 =
Expand Down
20 changes: 17 additions & 3 deletions src/nativeMain/kotlin/io/ktor/generator/bundle/PropertiesBundle.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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<String, String> = 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...",
Expand All @@ -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()
Expand All @@ -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)))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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()) {
Expand Down Expand Up @@ -130,8 +142,9 @@ class KtorInstaller(private val service: KtorGeneratorWeb) {
PropertiesBundle.writeSuccessMessage("project.generated", projectName)
}

fun runKtorProject(path: String, args: List<String>) {
installJdkIfAbsent()
fun runKtorProject(path: String, args: List<String> = emptyList()) {
if (!installJdkIfAbsent()) return

val ktorJavaHome = getRcProperty(JAVA_HOME)!!

val currentDir = Directory.current()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.*
Expand All @@ -16,6 +17,13 @@ suspend fun HttpClient.fetchZipContent(
request<HttpStatement> {
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()
2 changes: 2 additions & 0 deletions src/nativeMain/kotlin/io/ktor/generator/cli/utils/Common.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ internal fun pwd(): String = memScoped {
val pathBuffer: CArrayPointer<ByteVar> = allocArray<ByteVar>(pathBufferSize)
getCwd(pathBuffer, pathBufferSize) ?: throw Exception("Failed to locate working dir")

return@memScoped pathBuffer.toKString()
return@memScoped pathBuffer.toKString().replace(" ", "\\ ")
}

interface FsUnit {
Expand Down
2 changes: 1 addition & 1 deletion src/nativeTest/kotlin/io/ktor/generator/CommonTest.kt
Original file line number Diff line number Diff line change
@@ -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.*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
Expand Down

0 comments on commit df4b71b

Please sign in to comment.