Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add comment support. #48

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
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
36 changes: 35 additions & 1 deletion konf-core/src/main/kotlin/com/uchuhimo/konf/BaseConfig.kt
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import com.fasterxml.jackson.databind.module.SimpleModule
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.uchuhimo.konf.source.Source
import com.uchuhimo.konf.source.asTree
import com.uchuhimo.konf.source.base.EmptyMapSource
import com.uchuhimo.konf.source.deserializer.DurationDeserializer
import com.uchuhimo.konf.source.deserializer.EmptyStringToCollectionDeserializerModifier
Expand Down Expand Up @@ -164,6 +165,29 @@ open class BaseConfig(
}
}

override fun toTree(): TreeNode {
return ContainerNode(mutableMapOf()).apply {
lock.read {
itemWithNames.forEach { (item, name) ->
val value = try {
getOrNull(item, errorWhenNotFound = true).toCompatibleValue(mapper)
} catch (_: UnsetValueException) {
return@forEach
}
set(name, value.asTree(item.description))
}
// Add spec descriptions
specs.forEach { spec ->
val path = spec.prefix.toPath()
val node = tree.getOrNull(path)
if (node != null && node.comments.isNotEmpty()) {
getOrNull(path)?.comments = node.comments
}
}
}
}
}

@Suppress("UNCHECKED_CAST")
override fun <T> get(item: Item<T>): T = getOrNull(item, errorWhenNotFound = true) as T

Expand Down Expand Up @@ -574,6 +598,13 @@ open class BaseConfig(
throw RepeatedItemException(name)
}
}
val description = spec.description
if (description.isNotEmpty()) {
val node = this.tree.getOrNull(spec.prefix.toPath())
if (node != null && node.comments.isEmpty()) {
node.comments = description
}
}
spec.innerSpecs.forEach { innerSpec ->
addSpec(innerSpec.withPrefix(spec.prefix))
}
Expand Down Expand Up @@ -611,7 +642,10 @@ open class BaseConfig(
return "Config(items=${toMap()})"
}

class ItemNode(override var value: ValueState, val item: Item<*>) : ValueNode
class ItemNode(override var value: ValueState, val item: Item<*>) : ValueNode {

override var comments = this.item.description
}

data class Value<T>(var value: T)

Expand Down
18 changes: 12 additions & 6 deletions konf-core/src/main/kotlin/com/uchuhimo/konf/Config.kt
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,15 @@ interface Config : ItemContainer {
*/
fun toMap(): Map<String, Any>

/**
* Convert this config to a tree node.
*
* @return a tree node
*/
fun toTree(): TreeNode {
return toMap().kvToTree()
}

/**
* Enables the specified feature and returns this config.
*
Expand Down Expand Up @@ -477,11 +486,8 @@ open class LazyConfigProperty<T>(
}
}

/**
* Convert the config to a tree node.
*
* @return a tree node
*/
@Suppress("EXTENSION_SHADOWED_BY_MEMBER")
@Deprecated(message = "Use method in Config.", replaceWith = ReplaceWith("toTree()"))
fun Config.toTree(): TreeNode {
return toMap().kvToTree()
return toTree()
}
3 changes: 2 additions & 1 deletion konf-core/src/main/kotlin/com/uchuhimo/konf/ConfigSpec.kt
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ import com.fasterxml.jackson.module.kotlin.isKotlinClass
open class ConfigSpec @JvmOverloads constructor(
prefix: String? = null,
items: Set<Item<*>> = mutableSetOf(),
innerSpecs: Set<Spec> = mutableSetOf()
innerSpecs: Set<Spec> = mutableSetOf(),
override val description: String = ""
) : Spec {
final override val prefix: String = prefix ?: {
if (javaClass == ConfigSpec::class.java || javaClass.isAnonymousClass) {
Expand Down
9 changes: 8 additions & 1 deletion konf-core/src/main/kotlin/com/uchuhimo/konf/Feature.kt
Original file line number Diff line number Diff line change
Expand Up @@ -45,5 +45,12 @@ enum class Feature(val enabledByDefault: Boolean) {
*
* Feature is enabled by default.
*/
SUBSTITUTE_SOURCE_BEFORE_LOADED(true)
SUBSTITUTE_SOURCE_BEFORE_LOADED(true),
/**
* Feature that writes descriptions assigned to [Item]s as comments
* above the written configuration value.
*
* Feature is disabled by default.
*/
WRITE_DESCRIPTIONS_AS_COMMENTS(false)
}
20 changes: 19 additions & 1 deletion konf-core/src/main/kotlin/com/uchuhimo/konf/Spec.kt
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,12 @@ interface Spec {
*/
val prefix: String

/**
* The description of the spec.
*/
val description: String
get() = ""

/**
* Qualify item name with prefix of this config spec.
*
Expand Down Expand Up @@ -162,10 +168,22 @@ interface Spec {
return if (newPrefix.isEmpty()) {
this
} else {
ConfigSpec((newPrefix + prefix).name, items, innerSpecs)
ConfigSpec((newPrefix + prefix).name, items, innerSpecs, description)
}
}

/**
* Returns config spec with the specified description.
*
* @param description description
* @return config spec with the specified description
*/
fun withDescription(description: String): Spec {
if (this.description == description)
return this
return ConfigSpec(prefix, items, innerSpecs, description)
}

companion object {
/**
* A dummy implementation for [Spec].
Expand Down
18 changes: 13 additions & 5 deletions konf-core/src/main/kotlin/com/uchuhimo/konf/TreeNode.kt
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ interface TreeNode {
*/
val children: MutableMap<String, TreeNode>

/**
* The comments assigned to this tree node.
*/
var comments: String

/**
* Associate path with specified node.
*
Expand Down Expand Up @@ -317,16 +322,18 @@ interface ListNode : LeafNode {
/**
* Tree node that contains children nodes.
*/
open class ContainerNode(
open class ContainerNode @JvmOverloads constructor(
override val children: MutableMap<String, TreeNode>,
override var isPlaceHolder: Boolean = false
override var isPlaceHolder: Boolean = false,
override var comments: String = ""
) : MapNode {

override fun withMap(map: Map<String, TreeNode>): MapNode {
val isPlaceHolder = map.isEmpty() && this.isPlaceHolder
if (map is MutableMap<String, TreeNode>) {
return ContainerNode(map, isPlaceHolder)
return if (map is MutableMap<String, TreeNode>) {
ContainerNode(map, isPlaceHolder, comments)
} else {
return ContainerNode(map.toMutableMap(), isPlaceHolder)
ContainerNode(map.toMutableMap(), isPlaceHolder, comments)
}
}

Expand All @@ -341,4 +348,5 @@ open class ContainerNode(
*/
object EmptyNode : LeafNode {
override val children: MutableMap<String, TreeNode> = emptyMutableMap
override var comments: String = ""
}
15 changes: 8 additions & 7 deletions konf-core/src/main/kotlin/com/uchuhimo/konf/source/Source.kt
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@ import com.uchuhimo.konf.annotation.JavaApi
import com.uchuhimo.konf.source.base.ListStringNode
import com.uchuhimo.konf.source.base.toHierarchical
import com.uchuhimo.konf.toPath
import com.uchuhimo.konf.toTree
import com.uchuhimo.konf.toValue
import java.lang.reflect.InvocationTargetException
import java.math.BigDecimal
Expand Down Expand Up @@ -973,21 +972,23 @@ private fun implOf(clazz: Class<*>): Class<*> =
else -> clazz
}

fun Any.asTree(): TreeNode =
fun Any.asTree(): TreeNode = asTree("")

fun Any.asTree(comment: String = ""): TreeNode =
when (this) {
is TreeNode -> this
is Source -> this.tree
is List<*> ->
@Suppress("UNCHECKED_CAST")
(ListSourceNode((this as List<Any>).map { it.asTree() }))
(ListSourceNode((this as List<Any>).map { it.asTree() }, comments = comment))
is Map<*, *> -> {
when {
this.size == 0 -> ContainerNode(mutableMapOf())
this.iterator().next().key is String -> {
@Suppress("UNCHECKED_CAST")
(ContainerNode((this as Map<String, Any>).mapValues { (_, value) ->
value.asTree()
}.toMutableMap()))
}.toMutableMap(), comments = comment))
}
this.iterator().next().key!!::class in listOf(
Char::class,
Expand All @@ -1000,12 +1001,12 @@ fun Any.asTree(): TreeNode =
@Suppress("UNCHECKED_CAST")
(ContainerNode((this as Map<Any, Any>).map { (key, value) ->
key.toString() to value.asTree()
}.toMap().toMutableMap()))
}.toMap().toMutableMap(), comments = comment))
}
else -> ValueSourceNode(this)
else -> ValueSourceNode(this, comments = comment)
}
}
else -> ValueSourceNode(this)
else -> ValueSourceNode(this, comments = comment)
}

fun Any.asSource(type: String = "", info: SourceInfo = SourceInfo()): Source =
Expand Down
16 changes: 10 additions & 6 deletions konf-core/src/main/kotlin/com/uchuhimo/konf/source/SourceNode.kt
Original file line number Diff line number Diff line change
Expand Up @@ -30,29 +30,33 @@ interface SubstitutableNode : ValueNode {
val originalValue: Any?
}

class ValueSourceNode(
class ValueSourceNode @JvmOverloads constructor(
override val value: Any,
override val substituted: Boolean = false,
override val originalValue: Any? = null
override val originalValue: Any? = null,
override var comments: String = ""
) : SubstitutableNode {

override fun substitute(value: String): TreeNode {
return ValueSourceNode(value, true, originalValue ?: this.value)
return ValueSourceNode(value, true, originalValue ?: this.value, this.comments)
}
}

object NullSourceNode : NullNode {
override val children: MutableMap<String, TreeNode> = emptyMutableMap
override var comments: String = ""
}

open class ListSourceNode(
open class ListSourceNode @JvmOverloads constructor(
override val list: List<TreeNode>,
override var isPlaceHolder: Boolean = false
override var isPlaceHolder: Boolean = false,
override var comments: String = ""
) : ListNode, MapNode {
override val children: MutableMap<String, TreeNode>
get() = Collections.unmodifiableMap(
list.withIndex().associate { (key, value) -> key.toString() to value })

override fun withList(list: List<TreeNode>): ListNode {
return ListSourceNode(list)
return ListSourceNode(list, comments = this.comments)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ object EmptyStringNode : SubstitutableNode, ListNode {
override val list: List<TreeNode> = listOf()
override val originalValue: Any? = null
override val substituted: Boolean = false
override var comments: String = ""
override fun substitute(value: String): TreeNode {
check(value.isEmpty())
return this
Expand All @@ -71,7 +72,8 @@ object EmptyStringNode : SubstitutableNode, ListNode {
class SingleStringListNode(
override val value: String,
override val substituted: Boolean = false,
override val originalValue: Any? = null
override val originalValue: Any? = null,
override var comments: String = ""
) : SubstitutableNode, ListNode {
override val children: MutableMap<String, TreeNode> = Collections.unmodifiableMap(
mutableMapOf("0" to value.asTree()))
Expand All @@ -83,8 +85,9 @@ class SingleStringListNode(
class ListStringNode(
override val value: String,
override val substituted: Boolean = false,
override val originalValue: Any? = null
) : ListSourceNode(value.split(',').map { ValueSourceNode(it) }), SubstitutableNode {
override val originalValue: Any? = null,
override var comments: String = ""
) : ListSourceNode(value.split(',').map { ValueSourceNode(it) }, comments = comments), SubstitutableNode {
override fun substitute(value: String): TreeNode =
value.promoteToList(true, originalValue ?: this.value)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import com.uchuhimo.konf.TreeNode
import com.uchuhimo.konf.ValueNode
import com.uchuhimo.konf.notEmptyOr
import com.uchuhimo.konf.source.SourceInfo
import com.uchuhimo.konf.toTree

/**
* Source from a hierarchical map.
Expand Down Expand Up @@ -53,10 +52,10 @@ fun Config.toHierarchicalMap(): Map<String, Any> {
fun TreeNode.toHierarchical(): Any = withoutPlaceHolder().toHierarchicalInternal()

private fun TreeNode.toHierarchicalInternal(): Any {
when (this) {
is ValueNode -> return value
is ListNode -> return list.map { it.toHierarchicalInternal() }
else -> return children.mapValues { (_, child) -> child.toHierarchicalInternal() }
return when (this) {
is ValueNode -> value
is ListNode -> list.map { it.toHierarchicalInternal() }
else -> children.mapValues { (_, child) -> child.toHierarchicalInternal() }
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,13 @@
package com.uchuhimo.konf.source.hocon

import com.typesafe.config.ConfigRenderOptions
import com.typesafe.config.ConfigValue
import com.typesafe.config.ConfigValueFactory
import com.uchuhimo.konf.Config
import com.uchuhimo.konf.Feature
import com.uchuhimo.konf.ListNode
import com.uchuhimo.konf.TreeNode
import com.uchuhimo.konf.ValueNode
import com.uchuhimo.konf.source.Writer
import com.uchuhimo.konf.source.base.toHierarchicalMap
import java.io.OutputStream
Expand All @@ -27,6 +32,7 @@ import java.io.OutputStream
* Writer for HOCON source.
*/
class HoconWriter(val config: Config) : Writer {

private val renderOpts = ConfigRenderOptions.defaults()
.setOriginComments(false)
.setComments(false)
Expand All @@ -42,9 +48,26 @@ class HoconWriter(val config: Config) : Writer {
}
}

private fun TreeNode.toConfigValue(): ConfigValue {
val value = when (this) {
is ValueNode -> ConfigValueFactory.fromAnyRef(value)
is ListNode -> ConfigValueFactory.fromIterable(list.map { it.toConfigValue() })
else -> ConfigValueFactory.fromMap(children.mapValues { (_, value) -> value.toConfigValue() })
}
val comments = comments
if (comments != null) {
return value.withOrigin(value.origin().withComments(comments.split("\n")))
}
return value
}

override fun toText(): String {
return ConfigValueFactory.fromMap(config.toHierarchicalMap()).render(renderOpts)
.replace("\n", System.lineSeparator())
val output = if (config.isEnabled(Feature.WRITE_DESCRIPTIONS_AS_COMMENTS)) {
config.toTree().toConfigValue().render(renderOpts.setComments(true))
} else {
ConfigValueFactory.fromMap(config.toHierarchicalMap()).render(renderOpts)
}
return output.replace("\n", System.lineSeparator())
}
}

Expand Down
Loading