Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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: 68 additions & 169 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 Down Expand Up @@ -29,75 +27,83 @@ data class Command(
val cmdAsMap: Map<String, String>,
val env: Env,
val depends: List<String>,
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>()

/**
* 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>,
) {
var cmd = ""
var cmdAsMap = emptyMap<String, String>()
var env: Env = emptyMap()

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()
when (val value = obj.value) {
is YAMLMapping -> {
value.keyValues.forEach {
kv ->
when (kv.keyText) {
"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)
}
}
}
}
else -> defaultConfig()
}
}

private fun defaultConfig(): Config {
return Config(
"",
emptyList(),
emptyMap(),
emptyMap(),
"",
"",
emptyList(),
emptySet(),
return Command(
name,
cmd,
cmdAsMap,
env,
depends,
obj,
)
}

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

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 @@ -119,155 +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,
cmd,
cmdAsMap,
env,
depends,
)
}

@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,
)
}
}
}
Loading
Loading