Skip to content

Commit

Permalink
Split initialize and configure into two separate sub-commands
Browse files Browse the repository at this point in the history
  • Loading branch information
martinvisser committed Sep 11, 2023
1 parent 9f5fe34 commit 7d16e62
Show file tree
Hide file tree
Showing 16 changed files with 284 additions and 314 deletions.
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
package io.rabobank.ret.context

import io.rabobank.ret.configuration.version.VersionProperties
import jakarta.enterprise.context.ApplicationScoped

@ApplicationScoped
class ExecutionContext(private val versionProperties: VersionProperties = VersionProperties()) {
class ExecutionContext {

private val gitContext = GitContext.create()

fun repositoryName() = gitContext?.repositoryName()
fun branchName() = gitContext?.branchName()
fun version() = versionProperties.getAppVersion()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package io.rabobank.ret.plugins

import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.kotlin.readValue
import io.rabobank.ret.plugin.Plugin
import io.rabobank.ret.plugin.PluginDefinition
import io.rabobank.ret.util.OsUtils
import jakarta.enterprise.context.ApplicationScoped
import jakarta.enterprise.inject.Produces
import java.nio.file.Path
import kotlin.io.path.ExperimentalPathApi
import kotlin.io.path.walk

@ApplicationScoped
class PluginConfiguration {

companion object {
const val PLUGIN_EXTENSION = "plugin"
}

@OptIn(ExperimentalPathApi::class)
@Produces
@ApplicationScoped
fun plugins(osUtils: OsUtils, objectMapper: ObjectMapper): List<Plugin> =
osUtils.getRetPluginsDirectory().let { pluginPath ->
pluginPath.walk()
.map(Path::toFile)
.filter { it.extension == PLUGIN_EXTENSION }
.map { objectMapper.readValue<PluginDefinition>(it.readText()) }
.map { Plugin(it, pluginPath.resolve(it.dylibFile())) }
.toList()
}

private fun PluginDefinition.dylibFile() = libName.takeIf { it.endsWith(".dylib") } ?: "$libName.dylib"
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,9 @@ import io.mockk.mockk
import io.mockk.verify
import io.rabobank.ret.RetConsole
import io.rabobank.ret.autocompletion.zsh.ZshAutocompletionGenerator
import io.rabobank.ret.configuration.Answer
import io.rabobank.ret.configuration.Config
import io.rabobank.ret.configuration.ConfigurationProperty
import io.rabobank.ret.configuration.Question
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import picocli.CommandLine.Model.CommandSpec
import java.nio.file.Path
import java.util.Properties

internal class ConfigureCommandTest {

Expand Down Expand Up @@ -54,28 +48,4 @@ internal class ConfigureCommandTest {
command.printZshAutocompletionScript()
verify { retConsole.out("mocked zsh autocompletion file") }
}

class TestConfig : Config {
private val configProps = listOf(
ConfigurationProperty("project", "Enter your Rabobank project"),
ConfigurationProperty("organisation", "Enter your Rabobank organisation"),
)
private val properties = Properties()

override fun get(key: String) = properties[key] as String?

override fun set(key: String, value: String) {
properties[key] = value
}

override fun configure(function: (ConfigurationProperty) -> Unit) {
configProps.forEach(function)
}

override fun prompt(function: (Question) -> Answer): List<Answer> {
TODO("Not yet implemented")
}

override fun configFile(): Path = Path.of("test-configuration")
}
}
2 changes: 1 addition & 1 deletion ret-core/src/main/kotlin/io/rabobank/ret/RetConsole.kt
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ class RetConsole(parseResult: ParseResult) {
*/
fun prompt(message: String, currentValue: String?): String {
val messageWithDefault = if (currentValue.isNullOrEmpty()) message else "$message [$currentValue]"
out("$messageWithDefault:")
out(messageWithDefault)
return readln()
}
}
17 changes: 3 additions & 14 deletions ret-core/src/main/kotlin/io/rabobank/ret/configuration/Config.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,9 @@ package io.rabobank.ret.configuration
import java.nio.file.Path

interface Config {
operator fun get(key: String): String?
operator fun set(key: String, value: String)
operator fun get(key: String): Any?
operator fun set(key: String, value: Any?)
fun configure(function: (ConfigurationProperty) -> Unit)
fun configFile(): Path
fun prompt(function: (Question) -> Answer): List<Answer>
fun pluginConfigDirectory(): Path
}

data class Question(
val key: String,
val prompt: String,
val required: Boolean = false,
)

data class Answer(
val key: String,
val answer: String,
)
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ package io.rabobank.ret.configuration
* Upon initializing the plugin, the user will be prompted for all provided configuration properties,
* and inputs are saved in the RET configuration file.
*/
interface Configurable {
fun properties(): List<ConfigurationProperty> = emptyList()

fun prompts(): List<Question> = emptyList()
fun interface Configurable {
fun properties(): List<ConfigurationProperty>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package io.rabobank.ret.configuration

import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.kotlin.readValue
import io.rabobank.ret.util.OsUtils
import jakarta.enterprise.context.Dependent
import jakarta.inject.Inject
import org.eclipse.microprofile.config.inject.ConfigProperty

/**
* Extend from this base plugin class to help out with loading plugin specific configuration.
* This class also implements [Configurable] with a default implementation of [properties] with an empty list.
*
* @see Configurable
*/
@Dependent
open class ConfigurablePlugin : Configurable {
@ConfigProperty(name = "plugin.name", defaultValue = "ret")
lateinit var pluginName: String

@Inject
lateinit var osUtils: OsUtils

@Inject
lateinit var objectMapper: ObjectMapper

val config by lazy {
runCatching { objectMapper.readValue<Map<String, String>>(osUtils.getPluginConfig(pluginName).toFile()) }
.getOrDefault(emptyMap())
}

override fun properties() = emptyList<ConfigurationProperty>()
}
44 changes: 33 additions & 11 deletions ret-core/src/main/kotlin/io/rabobank/ret/configuration/RetConfig.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
package io.rabobank.ret.configuration

import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.kotlin.convertValue
import com.fasterxml.jackson.module.kotlin.readValue
import io.rabobank.ret.RetConsole
import io.rabobank.ret.util.OsUtils
import jakarta.enterprise.context.ApplicationScoped
import jakarta.enterprise.inject.Instance
Expand All @@ -16,35 +20,54 @@ private const val RET_VERSION = "ret_config_version"
*/
@ApplicationScoped
class RetConfig(
osUtils: OsUtils,
private val osUtils: OsUtils,
private val retConsole: RetConsole,
private val objectMapper: ObjectMapper,
private val configurables: Instance<Configurable>,
@ConfigProperty(name = "quarkus.application.version") private val retVersion: String,
) : Config {
private val configFile = Path.of(osUtils.getHomeDirectory(), ".ret", "ret.config").toFile()
private val properties = Properties()
private val oldConfigFile = osUtils.getRetHomeDirectory().resolve("ret.config").toFile()
private val oldConfigFileBackup = osUtils.getRetHomeDirectory().resolve("ret.config.bak").toFile()
private val configFile = osUtils.getRetHomeDirectory().resolve("ret.json").toFile()
private var properties = mutableMapOf<String, Any?>()

init {
loadExistingProperties()
}

private fun loadExistingProperties() {
if (oldConfigFile.exists() && !configFile.exists()) {
migrateConfig()
}

if (configFile.exists()) {
properties.load(configFile.inputStream())
properties = objectMapper.readValue<MutableMap<String, Any?>>(configFile)
}
}

private fun migrateConfig() {
val oldProperties = Properties()
.apply { load(oldConfigFile.inputStream()) }
properties = objectMapper.convertValue<MutableMap<String, Any?>>(oldProperties)

if (!oldConfigFile.renameTo(oldConfigFileBackup)) {
retConsole.errorOut("Unable to rename $oldConfigFile to $oldConfigFileBackup")
}
save()
}

/**
* Get the [key] property from the user configurations.
*
* @return The configured value for [key], or null if not configured.
*/
override operator fun get(key: String) = properties[key] as String?
override operator fun get(key: String) = properties[key]

/**
* Set the [value] to property [key] in the user configurations.
* This is automatically called when initializing a plugin, so you normally do not call this yourself.
*/
override operator fun set(key: String, value: String) {
override operator fun set(key: String, value: Any?) {
properties[key] = value
}

Expand All @@ -57,13 +80,12 @@ class RetConfig(
save()
}

override fun prompt(function: (Question) -> Answer): List<Answer> =
configurables.flatMap(Configurable::prompts).map(function)

private fun save() {
fun save() {
properties[RET_VERSION] = retVersion
properties.store(configFile.outputStream(), "")
objectMapper.writeValue(configFile, properties)
}

override fun configFile(): Path = Path.of(configFile.toURI())

override fun pluginConfigDirectory(): Path = osUtils.getRetPluginsDirectory()
}
2 changes: 0 additions & 2 deletions ret-core/src/main/resources/application.properties
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
ret.version=@project.version@
quarkus.log.level=WARN
quarkus.log.category."io.rabobank".level=INFO
quarkus.log.file.enable=true
Expand All @@ -10,4 +9,3 @@ quarkus.log.console.format=${quarkus.log.file.format}
#dev profile
%dev.quarkus.log.console.enable=true
%dev.quarkus.log.file.enable=false
%dev.quarkus.log.category."io.rabobank".level=debug
36 changes: 22 additions & 14 deletions ret-plugin/src/main/java/io/rabobank/ret/RetPlugin.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,44 +2,52 @@

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.quarkus.logging.Log;
import io.quarkus.picocli.runtime.PicocliRunner;
import io.quarkus.runtime.Quarkus;
import io.rabobank.ret.jni.JClass;
import io.rabobank.ret.jni.JNIEnvironment;
import io.rabobank.ret.jni.JNINativeInterface;
import io.rabobank.ret.jni.JString;
import org.graalvm.nativeimage.IsolateThread;
import org.graalvm.nativeimage.c.function.CEntryPoint;
import org.graalvm.nativeimage.c.type.CCharPointer;
import org.graalvm.nativeimage.c.type.CTypeConversion;

@SuppressWarnings("unused")
public final class RetPlugin {
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();

private RetPlugin() {

}

@CEntryPoint(name = "Java_io_rabobank_ret_plugins_RetPlugin_invoke")
public static void invoke(JNIEnvironment jniEnv, JClass clazz, @CEntryPoint.IsolateThreadContext long isolateId, JString dataCharStr) {
JNINativeInterface fn = jniEnv.getFunctions();
CCharPointer charptr = fn.getGetStringUTFChars().call(jniEnv, dataCharStr, (byte) 0);
final String resultString = CTypeConversion.toJavaString(charptr);
public static void invoke(final JNIEnvironment jniEnv,
final JClass clazz,
@CEntryPoint.IsolateThreadContext final long isolateId,
final JString dataCharStr) {
final var fn = jniEnv.getFunctions();
final var charptr = fn.getGetStringUTFChars().call(jniEnv, dataCharStr, (byte) 0);
final var resultString = CTypeConversion.toJavaString(charptr);

try {
RetContext retContext = new ObjectMapper().readValue(resultString, RetContext.class);
var retContext = OBJECT_MAPPER.readValue(resultString, RetContext.class);
PluginConfiguration.RET_CONTEXT_INSTANCE = retContext;
Quarkus.run(PicocliRunner.class, retContext.getCommand().toArray(new String[0]));
} catch (JsonProcessingException e) {
e.printStackTrace();
Log.error(e.getMessage(), e);
}
}

@CEntryPoint(name = "Java_io_rabobank_ret_plugins_RetPlugin_initialize")
public static void initialize(JNIEnvironment jniEnv, JClass clazz, @CEntryPoint.IsolateThreadContext long isolateId, JString dataCharStr) {
JNINativeInterface fn = jniEnv.getFunctions();
CCharPointer charptr = fn.getGetStringUTFChars().call(jniEnv, dataCharStr, (byte) 0);
final String resultString = CTypeConversion.toJavaString(charptr);

Quarkus.run(PicocliRunner.class, "initialize", resultString);
public static void initialize(final JNIEnvironment jniEnv,
final JClass clazz,
@CEntryPoint.IsolateThreadContext final long isolateId,
final JString dataCharStr) {
final var fn = jniEnv.getFunctions();
final var charptr = fn.getGetStringUTFChars().call(jniEnv, dataCharStr, (byte) 0);
final var args = CTypeConversion.toJavaString(charptr);

Quarkus.run(PicocliRunner.class, "initialize", args);
}

@CEntryPoint(name = "Java_io_rabobank_ret_plugins_RetPlugin_createIsolate", builtin = CEntryPoint.Builtin.CREATE_ISOLATE)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package io.rabobank.ret.commands

import io.quarkus.runtime.annotations.RegisterForReflection
import io.rabobank.ret.RetContext
import io.rabobank.ret.configuration.RetConfig
import picocli.CommandLine.Command
import picocli.CommandLine.Model.CommandSpec
import picocli.CommandLine.Spec
import java.io.File

@Command(
name = "project",
hidden = true,
)
@RegisterForReflection(targets = [RetContext::class])
class ConfigureProjectCommand(
private val retConfig: RetConfig,
) : Runnable {
@Spec
lateinit var commandSpec: CommandSpec

override fun run() {
val workingDir = File("").absoluteFile

if (retConfig["projects"] == null) {
retConfig["projects"] = listOf(Project(workingDir.name, workingDir.absolutePath))
} else {
@Suppress("UNCHECKED_CAST") val projectsMap = retConfig["projects"] as MutableList<Project>
projectsMap += Project(workingDir.name, workingDir.absolutePath)
}

retConfig.save()
}
}

data class Project(
val name: String,
val path: String,
)
Loading

0 comments on commit 7d16e62

Please sign in to comment.