Skip to content

Commit

Permalink
Refactor Config,kt
Browse files Browse the repository at this point in the history
* Move all parse* methods to ConfigParser
* Rewrite all tests for ConfigParser
* Remove config test resources
  • Loading branch information
kindermax committed Mar 8, 2025
1 parent bd0017d commit d7438c6
Show file tree
Hide file tree
Showing 8 changed files with 242 additions and 294 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ File type recognition for `lets.yaml` and `lets.*.yaml` configs
- **Completion**
- [x] Complete command `options` with code snippet
- [x] Complete commands in `depends` with code snippet
- [ ] Complete commands in `depends` from mixins
- [x] Complete commands in `depends` from mixins
- [ ] Complete env mode in `env` with code snippet
- [x] Complete `LETS*` environment variables in cmd scripts
- [ ] Complete environment variables for checksum
Expand All @@ -26,7 +26,7 @@ File type recognition for `lets.yaml` and `lets.*.yaml` configs
- [x] Navigate to definitions of `mixins` remote files (as http links)
- [x] Navigate to definitions of commands in `depends`
- [x] Navigate to definitions of commands in `depends` from mixins
- [ ] Navigate to definitions of commands in `ref`
- [x] Navigate to definitions of commands in `ref`
- [ ] Navigate to files in `checksum`
- **Highlighting**
- [x] Highlighting for shell script in `cmd`
Expand Down
237 changes: 49 additions & 188 deletions src/main/kotlin/com/github/kindermax/intellijlets/Config.kt
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package com.github.kindermax.intellijlets

import com.intellij.psi.PsiFile
import org.jetbrains.yaml.psi.YAMLDocument
import org.jetbrains.yaml.psi.YAMLKeyValue
import org.jetbrains.yaml.psi.YAMLMapping
import org.jetbrains.yaml.psi.YAMLScalar
Expand All @@ -23,32 +21,26 @@ sealed class Mixin {

typealias Mixins = List<Mixin>

//data class Command(
// val name: String,
// val cmd: String,
// val cmdAsMap: Map<String, String>,
// val env: Env,
// val depends: List<String>,
//)

// Store original YAMLKeyValue for later use, maybe to lazily parse it
data class Command(
val name: String,
val cmd: String,
val cmdAsMap: Map<String, String>,
val env: Env,
val depends: List<String>,
val yamlKeyValue: YAMLKeyValue,
val yaml: YAMLKeyValue,
)

open class ConfigException(message: String) : Exception(message)

class ConfigParseException(message: String) : ConfigException(message)
class CommandParseException(message: String) : ConfigException(message)


class ConfigParser {
companion object {
// @Suppress("NestedBlockDepth")
fun parseCommand(obj: YAMLKeyValue): Command {
val name = obj.keyText
var depends = emptyList<String>()

var cmd = ""
var cmdAsMap = emptyMap<String, String>()
var env: Env = emptyMap()

when (val value = obj.value) {
is YAMLMapping -> {
value.keyValues.forEach {
Expand All @@ -57,13 +49,32 @@ class ConfigParser {
"depends" -> {
depends = parseDepends(kv)
}
"cmd" -> {
when (val cmdValue = kv.value) {
is YAMLMapping -> {
cmdAsMap = cmdValue.keyValues.associate {
cmdEntry ->
cmdEntry.keyText to cmdEntry.valueText
}
}
else -> {
cmd = parseCmd(kv)
}
}
}
"env" -> {
env = parseEnv(kv)
}
}
}
}
}

return Command(
name,
cmd,
cmdAsMap,
env,
depends,
obj,
)
Expand All @@ -74,71 +85,25 @@ class ConfigParser {
is YAMLSequence -> value.items.mapNotNull { it.value?.text }
else -> emptyList()
}
}
}
}
/**
* Representation of current lets.yaml.
* Note that since we parse config during completion, the config itself may be broken at that moment,
* so we should parse gracefully.
*/
@Suppress("LongParameterList")
class Config(
val shell: String,
val commands: List<Command>,
val commandsMap: Map<String, Command>,
val env: Env,
val before: String,
val init: String,
val mixins: Mixins,
// Keywords that are used in the config
val keywordsInConfig: Set<String>,
) {

companion object Parser {
// TODO parse mixins
fun parseFromPSI(file: PsiFile): Config {
return when (val child = file.firstChild) {
is YAMLDocument -> {
when (val value = child.topLevelValue) {
is YAMLMapping -> parseConfigFromMapping(value)
else -> defaultConfig()
}
}
else -> defaultConfig()
}
}

private fun defaultConfig(): Config {
return Config(
"",
emptyList(),
emptyMap(),
emptyMap(),
"",
"",
emptyList(),
emptySet(),
)
}
}

private fun parseEnv(keyValue: YAMLKeyValue): Env {
fun parseEnv(keyValue: YAMLKeyValue): Env {
val value = keyValue.value as? YAMLMapping ?: return emptyMap()

return value.keyValues.associate { kv ->
kv.keyText to parseEnvValue(kv)
}
}

private fun parseEnvValue(kv: YAMLKeyValue): EnvValue {
fun parseEnvValue(kv: YAMLKeyValue): EnvValue {
return when (val envValue = kv.value) {
is YAMLScalar -> EnvValue.StringValue(envValue.textValue)
is YAMLMapping -> parseMappingEnvValue(envValue)
else -> EnvValue.StringValue("")
}
}

private fun parseMappingEnvValue(value: YAMLMapping): EnvValue {
fun parseMappingEnvValue(value: YAMLMapping): EnvValue {
value.keyValues.forEach { kv ->
when (kv.keyText) {
"sh" -> return EnvValue.ShMode(kv.valueText)
Expand All @@ -160,152 +125,48 @@ class Config(
return EnvValue.StringValue("")
}

private fun parseShell(keyValue: YAMLKeyValue): String {
fun parseShell(keyValue: YAMLKeyValue): String {
return keyValue.valueText
}

private fun parseCmd(keyValue: YAMLKeyValue): String {
fun parseCmd(keyValue: YAMLKeyValue): String {
return when (val value = keyValue.value) {
is YAMLScalar -> value.text
is YAMLSequence -> value.items.mapNotNull { it.value?.text }.joinToString(" ")
else -> ""
}
}

private fun parseDepends(keyValue: YAMLKeyValue): List<String> {
return when (val value = keyValue.value) {
is YAMLSequence -> value.items.mapNotNull { it.value?.text }
else -> emptyList()
}
}

private fun parseBefore(keyValue: YAMLKeyValue): String {
fun parseBefore(keyValue: YAMLKeyValue): String {
return when (val value = keyValue.value) {
is YAMLScalar -> value.textValue
else -> ""
}
}

private fun parseInit(keyValue: YAMLKeyValue): String {
fun parseInit(keyValue: YAMLKeyValue): String {
return when (val value = keyValue.value) {
is YAMLScalar -> value.textValue
else -> ""
}
}

@Suppress("NestedBlockDepth")
private fun parseCommand(keyValue: YAMLKeyValue): Command {
val name = keyValue.keyText
var cmd = ""
var cmdAsMap = emptyMap<String, String>()
var env: Env = emptyMap()
var depends = emptyList<String>()

when (val value = keyValue.value) {
is YAMLMapping -> {
value.keyValues.forEach {
kv ->
when (kv.keyText) {
"cmd" -> {
when (val cmdValue = kv.value) {
is YAMLMapping -> {
cmdAsMap = cmdValue.keyValues.associate {
cmdEntry ->
cmdEntry.keyText to cmdEntry.valueText
}
}
else -> {
cmd = parseCmd(kv)
}
}
}
"env" -> {
env = parseEnv(kv)
}
"depends" -> {
depends = parseDepends(kv)
}
}
}
}
}

return Command(
name,
depends,
keyValue,
)
}

@Suppress("NestedBlockDepth")
private fun parseConfigFromMapping(mapping: YAMLMapping): Config {
var shell = ""
val mixins = mutableListOf<Mixin>()
val commands = mutableListOf<Command>()
val commandsMap = mutableMapOf<String, Command>()
var env: Env = emptyMap()
var before = ""
var init = ""
val keywordsInConfig = mutableSetOf<String>()

mapping.keyValues.forEach {
kv ->
when (kv.keyText) {
"shell" -> {
shell = parseShell(kv)
}
"mixins" -> {
when (val value = kv.value) {
is YAMLSequence -> {
mixins.addAll(
value.items.mapNotNull { it.value }
.map { when (it) {
is YAMLScalar -> Mixin.Local(it.textValue)
is YAMLMapping -> {
val url = it.getKeyValueByKey("url")?.valueText ?: ""
val version = it.getKeyValueByKey("version")?.valueText ?: ""
Mixin.Remote(url, version)
}
else -> Mixin.Local("")
} }
)
}
}
}
"env" -> {
env = parseEnv(kv)
}
"before" -> {
before = parseBefore(kv)
}
"init" -> {
init = parseInit(kv)
}
"commands" -> {
when (val value = kv.value) {
fun parseMixins(keyValue: YAMLKeyValue): Mixins {
return when (val value = keyValue.value) {
is YAMLSequence -> {
value.items.mapNotNull { it.value }
.map { when (it) {
is YAMLScalar -> Mixin.Local(it.textValue)
is YAMLMapping -> {
value.keyValues.forEach { rawCommand ->
val command = parseCommand(rawCommand)
commands.add(command)
commandsMap[command.name] = command
}
val url = it.getKeyValueByKey("url")?.valueText ?: ""
val version = it.getKeyValueByKey("version")?.valueText ?: ""
Mixin.Remote(url, version)
}
}
}
else -> Mixin.Local("")
} }
}
keywordsInConfig.add(kv.keyText)
else -> emptyList()
}

return Config(
shell,
commands,
commandsMap,
env,
before,
init,
mixins,
keywordsInConfig,
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ object LetsCompletionHelper {
)
}

// TODO: refactor this methods, find more idiomatic way to check the level
fun isDependsLevel(parameters: CompletionParameters): Boolean {
val yamlKeyValueParents = parameters.position.parentsOfType<YAMLKeyValue>(false).toList()

Expand Down
Loading

0 comments on commit d7438c6

Please sign in to comment.