Split ItemDescriptor to builder and read-only descriptor
This commit is contained in:
parent
1a983665f8
commit
2352f1cff1
@ -5,6 +5,7 @@
|
|||||||
- LogManager plugin
|
- LogManager plugin
|
||||||
- dataforge-context API dependency on SLF4j
|
- dataforge-context API dependency on SLF4j
|
||||||
- Context `withEnv` and `fetch` methods to manipulate plugins without changing plugins after creation.
|
- Context `withEnv` and `fetch` methods to manipulate plugins without changing plugins after creation.
|
||||||
|
- Split `ItemDescriptor` into builder and read-only part
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- Kotlin-logging moved from common to JVM and JS. Replaced by console for native.
|
- Kotlin-logging moved from common to JVM and JS. Replaced by console for native.
|
||||||
|
@ -4,7 +4,7 @@ plugins {
|
|||||||
|
|
||||||
allprojects {
|
allprojects {
|
||||||
group = "space.kscience"
|
group = "space.kscience"
|
||||||
version = "0.4.0-dev-7"
|
version = "0.4.0-dev-8"
|
||||||
}
|
}
|
||||||
|
|
||||||
subprojects {
|
subprojects {
|
||||||
@ -22,5 +22,8 @@ ksciencePublish {
|
|||||||
}
|
}
|
||||||
|
|
||||||
apiValidation {
|
apiValidation {
|
||||||
|
if(project.version.toString().contains("dev")) {
|
||||||
|
validationDisabled = true
|
||||||
|
}
|
||||||
nonPublicMarkers.add("space.kscience.dataforge.misc.DFExperimental")
|
nonPublicMarkers.add("space.kscience.dataforge.misc.DFExperimental")
|
||||||
}
|
}
|
@ -8,6 +8,7 @@ import space.kscience.dataforge.meta.transformations.nullableItemToObject
|
|||||||
import space.kscience.dataforge.meta.transformations.nullableObjectToMetaItem
|
import space.kscience.dataforge.meta.transformations.nullableObjectToMetaItem
|
||||||
import space.kscience.dataforge.misc.DFExperimental
|
import space.kscience.dataforge.misc.DFExperimental
|
||||||
import space.kscience.dataforge.names.Name
|
import space.kscience.dataforge.names.Name
|
||||||
|
import space.kscience.dataforge.names.startsWith
|
||||||
|
|
||||||
@DFExperimental
|
@DFExperimental
|
||||||
public class ConfigProperty<T : Any>(
|
public class ConfigProperty<T : Any>(
|
||||||
@ -24,7 +25,7 @@ public class ConfigProperty<T : Any>(
|
|||||||
|
|
||||||
override fun onChange(owner: Any?, callback: (T?) -> Unit) {
|
override fun onChange(owner: Any?, callback: (T?) -> Unit) {
|
||||||
config.onChange(owner) { name, oldItem, newItem ->
|
config.onChange(owner) { name, oldItem, newItem ->
|
||||||
if (name == this.name && oldItem != newItem) callback(converter.nullableItemToObject(newItem))
|
if (name.startsWith(this.name) && oldItem != newItem) callback(converter.nullableItemToObject(newItem))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@ package space.kscience.dataforge.properties
|
|||||||
|
|
||||||
import space.kscience.dataforge.meta.ItemPropertyProvider
|
import space.kscience.dataforge.meta.ItemPropertyProvider
|
||||||
import space.kscience.dataforge.misc.DFExperimental
|
import space.kscience.dataforge.misc.DFExperimental
|
||||||
|
import space.kscience.dataforge.names.startsWith
|
||||||
import space.kscience.dataforge.names.toName
|
import space.kscience.dataforge.names.toName
|
||||||
import kotlin.reflect.KMutableProperty1
|
import kotlin.reflect.KMutableProperty1
|
||||||
|
|
||||||
@ -16,7 +17,7 @@ public fun <P : ItemPropertyProvider, T : Any> P.property(property: KMutableProp
|
|||||||
|
|
||||||
override fun onChange(owner: Any?, callback: (T?) -> Unit) {
|
override fun onChange(owner: Any?, callback: (T?) -> Unit) {
|
||||||
this@property.onChange(this) { name, oldItem, newItem ->
|
this@property.onChange(this) { name, oldItem, newItem ->
|
||||||
if (name == property.name.toName() && oldItem != newItem) {
|
if (name.startsWith(property.name.toName()) && oldItem != newItem) {
|
||||||
callback(property.get(this@property))
|
callback(property.get(this@property))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -98,6 +98,12 @@ public fun Meta.toConfig(): Config = Config().also { builder ->
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a copy of this config, optionally applying the given [block].
|
||||||
|
* The listeners of the original Config are not retained.
|
||||||
|
*/
|
||||||
|
public inline fun Config.copy(block: Config.() -> Unit = {}): Config = toConfig().apply(block)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return this [Meta] as [Config] if it is [Config] and create a new copy otherwise
|
* Return this [Meta] as [Config] if it is [Config] and create a new copy otherwise
|
||||||
*/
|
*/
|
||||||
|
@ -2,6 +2,7 @@ package space.kscience.dataforge.meta
|
|||||||
|
|
||||||
import space.kscience.dataforge.misc.DFExperimental
|
import space.kscience.dataforge.misc.DFExperimental
|
||||||
import space.kscience.dataforge.names.Name
|
import space.kscience.dataforge.names.Name
|
||||||
|
import space.kscience.dataforge.names.startsWith
|
||||||
import space.kscience.dataforge.names.toName
|
import space.kscience.dataforge.names.toName
|
||||||
import kotlin.reflect.KProperty1
|
import kotlin.reflect.KProperty1
|
||||||
|
|
||||||
@ -34,7 +35,7 @@ public fun <O : ObservableItemProvider, T> O.useProperty(
|
|||||||
//Pass initial value.
|
//Pass initial value.
|
||||||
callBack(property.get(this))
|
callBack(property.get(this))
|
||||||
onChange(owner) { name, oldItem, newItem ->
|
onChange(owner) { name, oldItem, newItem ->
|
||||||
if (name == property.name.toName() && oldItem != newItem) {
|
if (name.startsWith(property.name.toName()) && oldItem != newItem) {
|
||||||
callBack(property.get(this))
|
callBack(property.get(this))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,28 +3,26 @@ package space.kscience.dataforge.meta.descriptors
|
|||||||
import space.kscience.dataforge.meta.*
|
import space.kscience.dataforge.meta.*
|
||||||
import space.kscience.dataforge.misc.DFBuilder
|
import space.kscience.dataforge.misc.DFBuilder
|
||||||
import space.kscience.dataforge.names.*
|
import space.kscience.dataforge.names.*
|
||||||
import space.kscience.dataforge.values.*
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A common parent for [ValueDescriptor] and [NodeDescriptor]. Describes a single [TypedMetaItem] or a group of same-name-siblings.
|
* A common parent for [ValueDescriptor] and [NodeDescriptor]. Describes a single [TypedMetaItem] or a group of same-name-siblings.
|
||||||
*/
|
*/
|
||||||
@DFBuilder
|
public sealed interface ItemDescriptor: MetaRepr {
|
||||||
public sealed class ItemDescriptor(final override val config: Config) : Configurable {
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* True if same name siblings with this name are allowed
|
* True if same name siblings with this name are allowed
|
||||||
*/
|
*/
|
||||||
public var multiple: Boolean by config.boolean(false)
|
public val multiple: Boolean
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The item description text
|
* The item description text
|
||||||
*/
|
*/
|
||||||
public var info: String? by config.string()
|
public val info: String?
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* True if the item is required
|
* True if the item is required
|
||||||
*/
|
*/
|
||||||
public abstract var required: Boolean
|
public val required: Boolean
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -32,14 +30,56 @@ public sealed class ItemDescriptor(final override val config: Config) : Configur
|
|||||||
*
|
*
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
public var attributes: Config? by config.node()
|
public val attributes: Meta?
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An index field by which this node is identified in case of same name siblings construct
|
* An index field by which this node is identified in case of same name siblings construct
|
||||||
*/
|
*/
|
||||||
public var indexKey: String by config.string(DEFAULT_INDEX_KEY)
|
public val indexKey: String
|
||||||
|
|
||||||
public abstract fun copy(): ItemDescriptor
|
public companion object {
|
||||||
|
public const val DEFAULT_INDEX_KEY: String = "@index"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The builder for [ItemDescriptor]
|
||||||
|
*/
|
||||||
|
@DFBuilder
|
||||||
|
public sealed class ItemDescriptorBuilder(final override val config: Config) : Configurable, ItemDescriptor {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* True if same name siblings with this name are allowed
|
||||||
|
*/
|
||||||
|
override var multiple: Boolean by config.boolean(false)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The item description text
|
||||||
|
*/
|
||||||
|
override var info: String? by config.string()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* True if the item is required
|
||||||
|
*/
|
||||||
|
abstract override var required: Boolean
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Additional attributes of an item. For example validation and widget parameters
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
override var attributes: Config? by config.node()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An index field by which this node is identified in case of same name siblings construct
|
||||||
|
*/
|
||||||
|
override var indexKey: String by config.string(DEFAULT_INDEX_KEY)
|
||||||
|
|
||||||
|
public abstract fun build(): ItemDescriptor
|
||||||
|
|
||||||
|
override fun toMeta(): Meta = config
|
||||||
|
|
||||||
public companion object {
|
public companion object {
|
||||||
public const val DEFAULT_INDEX_KEY: String = "@index"
|
public const val DEFAULT_INDEX_KEY: String = "@index"
|
||||||
@ -49,7 +89,7 @@ public sealed class ItemDescriptor(final override val config: Config) : Configur
|
|||||||
/**
|
/**
|
||||||
* Configure attributes of the descriptor, creating an attributes node if needed.
|
* Configure attributes of the descriptor, creating an attributes node if needed.
|
||||||
*/
|
*/
|
||||||
public inline fun ItemDescriptor.attributes(block: Config.() -> Unit) {
|
public inline fun ItemDescriptorBuilder.attributes(block: Config.() -> Unit) {
|
||||||
(attributes ?: Config().also { this.attributes = it }).apply(block)
|
(attributes ?: Config().also { this.attributes = it }).apply(block)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -66,140 +106,6 @@ public fun ItemDescriptor.validateItem(item: MetaItem?): Boolean {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Descriptor for meta node. Could contain additional information for viewing
|
|
||||||
* and editing.
|
|
||||||
*
|
|
||||||
* @author Alexander Nozik
|
|
||||||
*/
|
|
||||||
@DFBuilder
|
|
||||||
public class NodeDescriptor(config: Config = Config()) : ItemDescriptor(config) {
|
|
||||||
init {
|
|
||||||
config[IS_NODE_KEY] = true
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* True if the node is required
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
override var required: Boolean by config.boolean { default == null }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The default for this node. Null if there is no default.
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public var default: Config? by config.node()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The map of children item descriptors (both nodes and values)
|
|
||||||
*/
|
|
||||||
public val items: Map<String, ItemDescriptor>
|
|
||||||
get() = config.getIndexed(ITEM_KEY).entries.associate { (name, item) ->
|
|
||||||
if (name == null) error("Child item index should not be null")
|
|
||||||
val node = item.node ?: error("Node descriptor must be a node")
|
|
||||||
if (node[IS_NODE_KEY].boolean == true) {
|
|
||||||
name to NodeDescriptor(node as Config)
|
|
||||||
} else {
|
|
||||||
name to ValueDescriptor(node as Config)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The map of children node descriptors
|
|
||||||
*/
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
|
||||||
public val nodes: Map<String, NodeDescriptor>
|
|
||||||
get() = config.getIndexed(ITEM_KEY).entries.filter {
|
|
||||||
it.value.node[IS_NODE_KEY].boolean == true
|
|
||||||
}.associate { (name, item) ->
|
|
||||||
if (name == null) error("Child node index should not be null")
|
|
||||||
val node = item.node ?: error("Node descriptor must be a node")
|
|
||||||
name to NodeDescriptor(node as Config)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The list of children value descriptors
|
|
||||||
*/
|
|
||||||
public val values: Map<String, ValueDescriptor>
|
|
||||||
get() = config.getIndexed(ITEM_KEY).entries.filter {
|
|
||||||
it.value.node[IS_NODE_KEY].boolean != true
|
|
||||||
}.associate { (name, item) ->
|
|
||||||
if (name == null) error("Child value index should not be null")
|
|
||||||
val node = item.node ?: error("Node descriptor must be a node")
|
|
||||||
name to ValueDescriptor(node as Config)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun buildNode(name: Name): NodeDescriptor {
|
|
||||||
return when (name.length) {
|
|
||||||
0 -> this
|
|
||||||
1 -> {
|
|
||||||
val token = NameToken(ITEM_KEY.toString(), name.toString())
|
|
||||||
val config: Config = config[token].node ?: Config().also {
|
|
||||||
it[IS_NODE_KEY] = true
|
|
||||||
config[token] = it
|
|
||||||
}
|
|
||||||
NodeDescriptor(config)
|
|
||||||
}
|
|
||||||
else -> buildNode(name.firstOrNull()?.asName()!!).buildNode(name.cutFirst())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Define a child item descriptor for this node
|
|
||||||
*/
|
|
||||||
private fun newItem(key: String, descriptor: ItemDescriptor) {
|
|
||||||
if (items.keys.contains(key)) error("The key $key already exists in descriptor")
|
|
||||||
val token = ITEM_KEY.withIndex(key)
|
|
||||||
config[token] = descriptor.config
|
|
||||||
}
|
|
||||||
|
|
||||||
public fun item(name: Name, descriptor: ItemDescriptor) {
|
|
||||||
buildNode(name.cutLast()).newItem(name.lastOrNull().toString(), descriptor)
|
|
||||||
}
|
|
||||||
|
|
||||||
public fun item(name: String, descriptor: ItemDescriptor) {
|
|
||||||
item(name.toName(), descriptor)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create and configure a child node descriptor
|
|
||||||
*/
|
|
||||||
public fun node(name: Name, block: NodeDescriptor.() -> Unit) {
|
|
||||||
item(name, NodeDescriptor().apply(block))
|
|
||||||
}
|
|
||||||
|
|
||||||
public fun node(name: String, block: NodeDescriptor.() -> Unit) {
|
|
||||||
node(name.toName(), block)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create and configure child value descriptor
|
|
||||||
*/
|
|
||||||
public fun value(name: Name, block: ValueDescriptor.() -> Unit) {
|
|
||||||
require(name.length >= 1) { "Name length for value descriptor must be non-empty" }
|
|
||||||
item(name, ValueDescriptor().apply(block))
|
|
||||||
}
|
|
||||||
|
|
||||||
public fun value(name: String, block: ValueDescriptor.() -> Unit) {
|
|
||||||
value(name.toName(), block)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun copy(): NodeDescriptor = NodeDescriptor(config.toConfig())
|
|
||||||
|
|
||||||
public companion object {
|
|
||||||
|
|
||||||
internal val ITEM_KEY: Name = "item".asName()
|
|
||||||
internal val IS_NODE_KEY: Name = "@isNode".asName()
|
|
||||||
|
|
||||||
//TODO infer descriptor from spec
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public inline fun NodeDescriptor(block: NodeDescriptor.() -> Unit): NodeDescriptor =
|
|
||||||
NodeDescriptor().apply(block)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a descriptor item associated with given name or null if item for given name not provided
|
* Get a descriptor item associated with given name or null if item for given name not provided
|
||||||
*/
|
*/
|
||||||
@ -213,93 +119,3 @@ public operator fun ItemDescriptor.get(name: Name): ItemDescriptor? {
|
|||||||
|
|
||||||
public operator fun ItemDescriptor.get(name: String): ItemDescriptor? = get(name.toName())
|
public operator fun ItemDescriptor.get(name: String): ItemDescriptor? = get(name.toName())
|
||||||
|
|
||||||
/**
|
|
||||||
* A descriptor for meta value
|
|
||||||
*
|
|
||||||
* Descriptor can have non-atomic path. It is resolved when descriptor is added to the node
|
|
||||||
*
|
|
||||||
* @author Alexander Nozik
|
|
||||||
*/
|
|
||||||
@DFBuilder
|
|
||||||
public class ValueDescriptor(config: Config = Config()) : ItemDescriptor(config) {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* True if the value is required
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
override var required: Boolean by config.boolean { default == null }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The default for this value. Null if there is no default.
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public var default: Value? by config.value()
|
|
||||||
|
|
||||||
public fun default(v: Any) {
|
|
||||||
this.default = Value.of(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A list of allowed ValueTypes. Empty if any value type allowed
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public var type: List<ValueType>? by config.listValue { ValueType.valueOf(it.string) }
|
|
||||||
|
|
||||||
public fun type(vararg t: ValueType) {
|
|
||||||
this.type = listOf(*t)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if given value is allowed for here. The type should be allowed and
|
|
||||||
* if it is value should be within allowed values
|
|
||||||
*
|
|
||||||
* @param value
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public fun isAllowedValue(value: Value): Boolean {
|
|
||||||
return (type?.let { it.contains(ValueType.STRING) || it.contains(value.type) } ?: true)
|
|
||||||
&& (allowedValues.isEmpty() || allowedValues.contains(value))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A list of allowed values with descriptions. If empty than any value is
|
|
||||||
* allowed.
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public var allowedValues: List<Value> by config.item().convert(
|
|
||||||
reader = {
|
|
||||||
val value = it.value
|
|
||||||
when {
|
|
||||||
value?.list != null -> value.list
|
|
||||||
type?.let { type -> type.size == 1 && type[0] === ValueType.BOOLEAN } ?: false -> listOf(True, False)
|
|
||||||
else -> emptyList()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
writer = {
|
|
||||||
MetaItemValue(it.asValue())
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Allow given list of value and forbid others
|
|
||||||
*/
|
|
||||||
public fun allow(vararg v: Any) {
|
|
||||||
this.allowedValues = v.map { Value.of(it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun copy(): ValueDescriptor = ValueDescriptor(config.toConfig())
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Merge two node descriptors into one using first one as primary
|
|
||||||
*/
|
|
||||||
public operator fun NodeDescriptor.plus(other: NodeDescriptor): NodeDescriptor {
|
|
||||||
return NodeDescriptor().apply {
|
|
||||||
config.update(other.config)
|
|
||||||
config.update(this@plus.config)
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,191 @@
|
|||||||
|
package space.kscience.dataforge.meta.descriptors
|
||||||
|
|
||||||
|
import space.kscience.dataforge.meta.*
|
||||||
|
import space.kscience.dataforge.misc.DFBuilder
|
||||||
|
import space.kscience.dataforge.names.*
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Descriptor for meta node. Could contain additional information for viewing
|
||||||
|
* and editing.
|
||||||
|
*
|
||||||
|
* @author Alexander Nozik
|
||||||
|
*/
|
||||||
|
@DFBuilder
|
||||||
|
public sealed interface NodeDescriptor: ItemDescriptor {
|
||||||
|
/**
|
||||||
|
* True if the node is required
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
override val required: Boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default for this node. Null if there is no default.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public val default: Config?
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The map of children item descriptors (both nodes and values)
|
||||||
|
*/
|
||||||
|
public val items: Map<String, ItemDescriptor>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The map of children node descriptors
|
||||||
|
*/
|
||||||
|
public val nodes: Map<String, NodeDescriptor>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The list of children value descriptors
|
||||||
|
*/
|
||||||
|
public val values: Map<String, ValueDescriptor>
|
||||||
|
|
||||||
|
public companion object {
|
||||||
|
|
||||||
|
internal val ITEM_KEY: Name = "item".asName()
|
||||||
|
internal val IS_NODE_KEY: Name = "@isNode".asName()
|
||||||
|
|
||||||
|
//TODO infer descriptor from spec
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@DFBuilder
|
||||||
|
public class NodeDescriptorBuilder(config: Config = Config()) : ItemDescriptorBuilder(config), NodeDescriptor {
|
||||||
|
init {
|
||||||
|
config[IS_NODE_KEY] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* True if the node is required
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
override var required: Boolean by config.boolean { default == null }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default for this node. Null if there is no default.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
override var default: Config? by config.node()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The map of children item descriptors (both nodes and values)
|
||||||
|
*/
|
||||||
|
override val items: Map<String, ItemDescriptor>
|
||||||
|
get() = config.getIndexed(ITEM_KEY).entries.associate { (name, item) ->
|
||||||
|
if (name == null) error("Child item index should not be null")
|
||||||
|
val node = item.node ?: error("Node descriptor must be a node")
|
||||||
|
if (node[IS_NODE_KEY].boolean == true) {
|
||||||
|
name to NodeDescriptorBuilder(node as Config)
|
||||||
|
} else {
|
||||||
|
name to ValueDescriptorBuilder(node as Config)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The map of children node descriptors
|
||||||
|
*/
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
override val nodes: Map<String, NodeDescriptor>
|
||||||
|
get() = config.getIndexed(ITEM_KEY).entries.filter {
|
||||||
|
it.value.node[IS_NODE_KEY].boolean == true
|
||||||
|
}.associate { (name, item) ->
|
||||||
|
if (name == null) error("Child node index should not be null")
|
||||||
|
val node = item.node ?: error("Node descriptor must be a node")
|
||||||
|
name to NodeDescriptorBuilder(node as Config)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The list of children value descriptors
|
||||||
|
*/
|
||||||
|
override val values: Map<String, ValueDescriptor>
|
||||||
|
get() = config.getIndexed(ITEM_KEY).entries.filter {
|
||||||
|
it.value.node[IS_NODE_KEY].boolean != true
|
||||||
|
}.associate { (name, item) ->
|
||||||
|
if (name == null) error("Child value index should not be null")
|
||||||
|
val node = item.node ?: error("Node descriptor must be a node")
|
||||||
|
name to ValueDescriptorBuilder(node as Config)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun buildNode(name: Name): NodeDescriptorBuilder {
|
||||||
|
return when (name.length) {
|
||||||
|
0 -> this
|
||||||
|
1 -> {
|
||||||
|
val token = NameToken(ITEM_KEY.toString(), name.toString())
|
||||||
|
val config: Config = config[token].node ?: Config().also {
|
||||||
|
it[IS_NODE_KEY] = true
|
||||||
|
config[token] = it
|
||||||
|
}
|
||||||
|
NodeDescriptorBuilder(config)
|
||||||
|
}
|
||||||
|
else -> buildNode(name.firstOrNull()?.asName()!!).buildNode(name.cutFirst())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define a child item descriptor for this node
|
||||||
|
*/
|
||||||
|
private fun newItem(key: String, descriptor: ItemDescriptor) {
|
||||||
|
if (items.keys.contains(key)) error("The key $key already exists in descriptor")
|
||||||
|
val token = ITEM_KEY.withIndex(key)
|
||||||
|
config[token] = descriptor.toMeta()
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun item(name: Name, descriptor: ItemDescriptor) {
|
||||||
|
buildNode(name.cutLast()).newItem(name.lastOrNull().toString(), descriptor)
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun item(name: String, descriptor: ItemDescriptor) {
|
||||||
|
item(name.toName(), descriptor)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create and configure a child node descriptor
|
||||||
|
*/
|
||||||
|
public fun node(name: Name, block: NodeDescriptorBuilder.() -> Unit) {
|
||||||
|
item(name, NodeDescriptorBuilder().apply(block))
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun node(name: String, block: NodeDescriptorBuilder.() -> Unit) {
|
||||||
|
node(name.toName(), block)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create and configure child value descriptor
|
||||||
|
*/
|
||||||
|
public fun value(name: Name, block: ValueDescriptorBuilder.() -> Unit) {
|
||||||
|
require(name.length >= 1) { "Name length for value descriptor must be non-empty" }
|
||||||
|
item(name, ValueDescriptorBuilder().apply(block))
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun value(name: String, block: ValueDescriptorBuilder.() -> Unit) {
|
||||||
|
value(name.toName(), block)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun build(): NodeDescriptor = NodeDescriptorBuilder(config.copy())
|
||||||
|
|
||||||
|
public companion object {
|
||||||
|
|
||||||
|
internal val ITEM_KEY: Name = "item".asName()
|
||||||
|
internal val IS_NODE_KEY: Name = "@isNode".asName()
|
||||||
|
|
||||||
|
//TODO infer descriptor from spec
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public inline fun NodeDescriptor(block: NodeDescriptorBuilder.() -> Unit): NodeDescriptor =
|
||||||
|
NodeDescriptorBuilder().apply(block)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Merge two node descriptors into one using first one as primary
|
||||||
|
*/
|
||||||
|
public operator fun NodeDescriptor.plus(other: NodeDescriptor): NodeDescriptor {
|
||||||
|
return NodeDescriptorBuilder().apply {
|
||||||
|
config.update(other.toMeta())
|
||||||
|
config.update(this@plus.toMeta())
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,135 @@
|
|||||||
|
package space.kscience.dataforge.meta.descriptors
|
||||||
|
|
||||||
|
import space.kscience.dataforge.meta.*
|
||||||
|
import space.kscience.dataforge.misc.DFBuilder
|
||||||
|
import space.kscience.dataforge.values.*
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A descriptor for meta value
|
||||||
|
*
|
||||||
|
* Descriptor can have non-atomic path. It is resolved when descriptor is added to the node
|
||||||
|
*
|
||||||
|
* @author Alexander Nozik
|
||||||
|
*/
|
||||||
|
@DFBuilder
|
||||||
|
public sealed interface ValueDescriptor: ItemDescriptor{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* True if the value is required
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
override val required: Boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default for this value. Null if there is no default.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public val default: Value?
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A list of allowed ValueTypes. Empty if any value type allowed
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public val type: List<ValueType>?
|
||||||
|
/**
|
||||||
|
* Check if given value is allowed for here. The type should be allowed and
|
||||||
|
* if it is value should be within allowed values
|
||||||
|
*
|
||||||
|
* @param value
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public fun isAllowedValue(value: Value): Boolean =
|
||||||
|
(type?.let { it.contains(ValueType.STRING) || it.contains(value.type) } ?: true)
|
||||||
|
&& (allowedValues.isEmpty() || allowedValues.contains(value))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A list of allowed values with descriptions. If empty than any value is
|
||||||
|
* allowed.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public val allowedValues: List<Value>
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A builder fir [ValueDescriptor]
|
||||||
|
*/
|
||||||
|
@DFBuilder
|
||||||
|
public class ValueDescriptorBuilder(config: Config = Config()) : ItemDescriptorBuilder(config), ValueDescriptor {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* True if the value is required
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
override var required: Boolean by config.boolean { default == null }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default for this value. Null if there is no default.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
override var default: Value? by config.value()
|
||||||
|
|
||||||
|
public fun default(v: Any) {
|
||||||
|
this.default = Value.of(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A list of allowed ValueTypes. Empty if any value type allowed
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
override var type: List<ValueType>? by config.listValue { ValueType.valueOf(it.string) }
|
||||||
|
|
||||||
|
public fun type(vararg t: ValueType) {
|
||||||
|
this.type = listOf(*t)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if given value is allowed for here. The type should be allowed and
|
||||||
|
* if it is value should be within allowed values
|
||||||
|
*
|
||||||
|
* @param value
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
override fun isAllowedValue(value: Value): Boolean {
|
||||||
|
return (type?.let { it.contains(ValueType.STRING) || it.contains(value.type) } ?: true)
|
||||||
|
&& (allowedValues.isEmpty() || allowedValues.contains(value))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A list of allowed values with descriptions. If empty than any value is
|
||||||
|
* allowed.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
override var allowedValues: List<Value> by config.item().convert(
|
||||||
|
reader = {
|
||||||
|
val value = it.value
|
||||||
|
when {
|
||||||
|
value?.list != null -> value.list
|
||||||
|
type?.let { type -> type.size == 1 && type[0] === ValueType.BOOLEAN } ?: false -> listOf(True, False)
|
||||||
|
else -> emptyList()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
writer = {
|
||||||
|
MetaItemValue(it.asValue())
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allow given list of value and forbid others
|
||||||
|
*/
|
||||||
|
public fun allow(vararg v: Any) {
|
||||||
|
this.allowedValues = v.map { Value.of(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun build(): ValueDescriptor = ValueDescriptorBuilder(config.copy())
|
||||||
|
}
|
@ -4,7 +4,7 @@ import space.kscience.dataforge.names.Name
|
|||||||
import space.kscience.dataforge.values.ValueType
|
import space.kscience.dataforge.values.ValueType
|
||||||
import space.kscience.dataforge.values.asValue
|
import space.kscience.dataforge.values.asValue
|
||||||
|
|
||||||
public inline fun <reified E : Enum<E>> NodeDescriptor.enum(
|
public inline fun <reified E : Enum<E>> NodeDescriptorBuilder.enum(
|
||||||
key: Name,
|
key: Name,
|
||||||
default: E?,
|
default: E?,
|
||||||
crossinline modifier: ValueDescriptor.() -> Unit = {},
|
crossinline modifier: ValueDescriptor.() -> Unit = {},
|
||||||
|
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@ -1,5 +1,5 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.3-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
@ -7,7 +7,7 @@ pluginManagement {
|
|||||||
maven("https://dl.bintray.com/kotlin/kotlin-eap")
|
maven("https://dl.bintray.com/kotlin/kotlin-eap")
|
||||||
}
|
}
|
||||||
|
|
||||||
val toolsVersion = "0.9.4"
|
val toolsVersion = "0.9.5-dev"
|
||||||
val kotlinVersion = "1.5.0-M2"
|
val kotlinVersion = "1.5.0-M2"
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
|
Loading…
Reference in New Issue
Block a user