diff --git a/build.gradle.kts b/build.gradle.kts index 1fe59eff..ad2766a4 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -6,7 +6,7 @@ plugins { id("scientifik.publish") version toolsVersion apply false } -val dataforgeVersion by extra("0.1.5") +val dataforgeVersion by extra("0.1.6-dev") val bintrayRepo by extra("dataforge") val githubProject by extra("dataforge-core") diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/descriptors/ItemDescriptor.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/descriptors/ItemDescriptor.kt index 84d0d5c0..54a01ace 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/descriptors/ItemDescriptor.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/descriptors/ItemDescriptor.kt @@ -2,15 +2,13 @@ package hep.dataforge.meta.descriptors import hep.dataforge.meta.* import hep.dataforge.meta.scheme.* -import hep.dataforge.names.Name -import hep.dataforge.names.NameToken -import hep.dataforge.names.asName -import hep.dataforge.names.isEmpty +import hep.dataforge.names.* import hep.dataforge.values.False import hep.dataforge.values.True import hep.dataforge.values.Value import hep.dataforge.values.ValueType +@DFBuilder sealed class ItemDescriptor : Scheme() { /** @@ -68,8 +66,7 @@ fun ItemDescriptor.validateItem(item: MetaItem<*>?): Boolean { * @author Alexander Nozik */ @DFBuilder -class NodeDescriptor : ItemDescriptor() { - +class NodeDescriptor private constructor() : ItemDescriptor() { /** * True if the node is required * @@ -84,86 +81,98 @@ class NodeDescriptor : ItemDescriptor() { */ var default by node() + val items: Map + get() = config.getIndexed(ITEM_KEY).mapValues { (_, item) -> + val node = item.node ?: error("Node descriptor must be a node") + if (node[IS_NODE_KEY].boolean == true) { + NodeDescriptor.wrap(node) + } else { + ValueDescriptor.wrap(node) + } + } + /** * The map of children node descriptors */ + @Suppress("UNCHECKED_CAST") val nodes: Map - get() = config.getIndexed(NODE_KEY.asName()).entries.associate { (name, node) -> - name to wrap(node.node ?: error("Node descriptor must be a node")) + get() = config.getIndexed(ITEM_KEY).entries.filter { + it.value.node[IS_NODE_KEY].boolean == true + }.associate { (name, item) -> + val node = item.node ?: error("Node descriptor must be a node") + name to NodeDescriptor.wrap(node) } /** - * Define a child item descriptor for this node + * The list of value descriptors */ - fun defineItem(name: String, descriptor: ItemDescriptor) { - if (items.keys.contains(name)) error("The key $name already exists in descriptor") - val token = when (descriptor) { - is NodeDescriptor -> NameToken(NODE_KEY, name) - is ValueDescriptor -> NameToken(VALUE_KEY, name) + val values: Map + get() = config.getIndexed(ITEM_KEY).entries.filter { + it.value.node[IS_NODE_KEY].boolean != true + }.associate { (name, item) -> + val node = item.node ?: error("Node descriptor must be a node") + name to ValueDescriptor.wrap(node) } - config[token] = descriptor.config - - } - - - fun defineNode(name: String, block: NodeDescriptor.() -> Unit) { - val token = NameToken(NODE_KEY, name) - if (config[token] == null) { - config[token] = NodeDescriptor(block) - } else { - NodeDescriptor.update(config[token].node ?: error("Node expected"), block) - } - } private fun buildNode(name: Name): NodeDescriptor { return when (name.length) { 0 -> this 1 -> { - val token = NameToken(NODE_KEY, name.toString()) - val config: Config = config[token].node ?: Config().also { config[token] = it } + val token = NameToken(ITEM_KEY.toString(), name.toString()) + val config: Config = config[token].node ?: Config().also { + it[IS_NODE_KEY] = true + config[token] = it + } wrap(config) } else -> buildNode(name.first()?.asName()!!).buildNode(name.cutFirst()) } } - fun defineNode(name: Name, block: NodeDescriptor.() -> Unit) { - buildNode(name).apply(block) + /** + * 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 } - /** - * The list of value descriptors - */ - val values: Map - get() = config.getIndexed(VALUE_KEY.asName()).entries.associate { (name, node) -> - name to ValueDescriptor.wrap(node.node ?: error("Value descriptor must be a node")) - } + fun defineItem(name: Name, descriptor: ItemDescriptor) { + buildNode(name.cutLast()).newItem(name.last().toString(), descriptor) + } + fun defineItem(name: String, descriptor: ItemDescriptor) { + defineItem(name.toName(), descriptor) + } - /** - * Add a value descriptor using block for - */ - fun defineValue(name: String, block: ValueDescriptor.() -> Unit) { - defineItem(name, ValueDescriptor(block)) + fun defineNode(name: Name, block: NodeDescriptor.() -> Unit) { + defineItem(name, NodeDescriptor(block)) + } + + fun defineNode(name: String, block: NodeDescriptor.() -> Unit) { + defineNode(name.toName(), block) } fun defineValue(name: Name, block: ValueDescriptor.() -> Unit) { require(name.length >= 1) { "Name length for value descriptor must be non-empty" } - buildNode(name.cutLast()).defineValue(name.last().toString(), block) + defineItem(name, ValueDescriptor(block)) } - val items: Map get() = nodes + values - - -//override val descriptor: NodeDescriptor = empty("descriptor") + fun defineValue(name: String, block: ValueDescriptor.() -> Unit) { + defineValue(name.toName(), block) + } companion object : SchemeSpec(::NodeDescriptor) { - // const val ITEM_KEY = "item" - const val NODE_KEY = "node" - const val VALUE_KEY = "value" + val ITEM_KEY = "item".asName() + val IS_NODE_KEY = "@isNode".asName() - //override fun wrap(config: Config): NodeDescriptor = NodeDescriptor(config) + override fun empty(): NodeDescriptor { + return super.empty().apply { + config[IS_NODE_KEY] = true + } + } //TODO infer descriptor from spec } @@ -187,9 +196,9 @@ operator fun ItemDescriptor.get(name: Name): ItemDescriptor? { * * @author Alexander Nozik */ +@DFBuilder class ValueDescriptor : ItemDescriptor() { - /** * True if the value is required * @@ -255,68 +264,5 @@ class ValueDescriptor : ItemDescriptor() { this.allowedValues = v.map { Value.of(it) } } - companion object : SchemeSpec(::ValueDescriptor) { -// inline fun > enum(name: String) = ValueDescriptor { -// type(ValueType.STRING) -// this.allowedValues = enumValues().map { Value.of(it.name) } -// } - -// /** -// * Build a value descriptor from annotation -// */ -// fun build(def: ValueDef): ValueDescriptor { -// val builder = MetaBuilder("value") -// .setValue("name", def.key) -// -// if (def.type.isNotEmpty()) { -// builder.setValue("type", def.type) -// } -// -// if (def.multiple) { -// builder.setValue("multiple", def.multiple) -// } -// -// if (!def.info.isEmpty()) { -// builder.setValue("info", def.info) -// } -// -// if (def.allowed.isNotEmpty()) { -// builder.setValue("allowedValues", def.allowed) -// } else if (def.enumeration != Any::class) { -// if (def.enumeration.java.isEnum) { -// val values = def.enumeration.java.enumConstants -// builder.setValue("allowedValues", values.map { it.toString() }) -// } else { -// throw RuntimeException("Only enumeration classes are allowed in 'enumeration' annotation property") -// } -// } -// -// if (def.def.isNotEmpty()) { -// builder.setValue("default", def.def) -// } else if (!def.required) { -// builder.setValue("required", def.required) -// } -// -// if (def.tags.isNotEmpty()) { -// builder.setValue("tags", def.tags) -// } -// return ValueDescriptor(builder) -// } -// -// /** -// * Build empty value descriptor -// */ -// fun empty(valueName: String): ValueDescriptor { -// val builder = MetaBuilder("value") -// .setValue("name", valueName) -// return ValueDescriptor(builder) -// } -// -// /** -// * Merge two separate value descriptors -// */ -// fun merge(primary: ValueDescriptor, secondary: ValueDescriptor): ValueDescriptor { -// return ValueDescriptor(Laminate(primary.meta, secondary.meta)) -// } - } + companion object : SchemeSpec(::ValueDescriptor) } \ No newline at end of file diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/metaMatcher.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/metaMatcher.kt index 263483a2..03df113d 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/metaMatcher.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/metaMatcher.kt @@ -6,12 +6,11 @@ import hep.dataforge.names.toName /** * Get all items matching given name. */ -@DFExperimental fun Meta.getIndexed(name: Name): Map> { val root = when (name.length) { - 0 -> error("Can't use empty name for that") + 0 -> error("Can't use empty name for 'getIndexed'") 1 -> this - else -> (this[name.cutLast()] as? MetaItem.NodeItem<*>)?.node + else -> this[name.cutLast()].node } val (body, index) = name.last()!! @@ -23,16 +22,13 @@ fun Meta.getIndexed(name: Name): Map> { ?: emptyMap() } -@DFExperimental fun Meta.getIndexed(name: String): Map> = this@getIndexed.getIndexed(name.toName()) /** * Get all items matching given name. */ @Suppress("UNCHECKED_CAST") -@DFExperimental fun > M.getIndexed(name: Name): Map> = (this as Meta).getIndexed(name) as Map> -@DFExperimental fun > M.getIndexed(name: String): Map> = getIndexed(name.toName()) \ No newline at end of file diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/ConfigurableDelegate.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/ConfigurableDelegate.kt index c582282b..ae6722cf 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/ConfigurableDelegate.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/ConfigurableDelegate.kt @@ -168,9 +168,10 @@ fun Configurable.float(default: Float, key: Name? = null): ReadWriteProperty> Configurable.enum( - default: E, key: Name? = null, resolve: MetaItem<*>.() -> E? -): ReadWriteProperty = item(default, key).transform { it?.resolve() ?: default } +inline fun > Configurable.enum( + default: E, key: Name? = null +): ReadWriteProperty = + item(default, key).transform { item -> item?.string?.let { enumValueOf(it) } ?: default } /* * Extra delegates for special cases @@ -225,7 +226,7 @@ fun Configurable.spec( ): ReadWriteProperty = object : ReadWriteProperty { override fun getValue(thisRef: Any?, property: KProperty<*>): T { val name = key ?: property.name.asName() - return config[name].node?.let { spec.wrap(it) }?:default + return config[name].node?.let { spec.wrap(it) } ?: default } override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) { diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/Scheme.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/Scheme.kt index b8d6257f..be954bbe 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/Scheme.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/Scheme.kt @@ -60,8 +60,10 @@ inline operator fun T.invoke(block: T.() -> Unit) = apply(block) * A specification for simplified generation of wrappers */ open class SchemeSpec(val builder: () -> T) : Specification { + override fun empty(): T = builder() + override fun wrap(config: Config, defaultProvider: (Name) -> MetaItem<*>?): T { - return builder().apply { + return empty().apply { this.config = config this.defaultProvider = defaultProvider } diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/Specification.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/Specification.kt index 1783e841..c2c6f8bb 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/Specification.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/scheme/Specification.kt @@ -17,7 +17,7 @@ interface Specification { return wrap(config).apply(action) } - operator fun invoke(action: T.() -> Unit) = update(Config(), action) + operator fun invoke(action: T.() -> Unit) = empty().apply(action) fun empty() = wrap() @@ -34,9 +34,7 @@ interface Specification { /** * Wrap a configuration using static meta as default */ - fun wrap(default: Meta): T = wrap( - Config() - ) { default[it] } + fun wrap(default: Meta): T = wrap(Config()) { default[it] } } /** diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/names/Name.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/names/Name.kt index 2b8908ed..141af13c 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/names/Name.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/names/Name.kt @@ -59,7 +59,8 @@ class Name(val tokens: List) { val EMPTY = Name(emptyList()) - override val descriptor: SerialDescriptor = PrimitiveDescriptor("hep.dataforge.names.Name", PrimitiveKind.STRING) + override val descriptor: SerialDescriptor = + PrimitiveDescriptor("hep.dataforge.names.Name", PrimitiveKind.STRING) override fun deserialize(decoder: Decoder): Name { return decoder.decodeString().toName() @@ -99,7 +100,8 @@ data class NameToken(val body: String, val index: String = "") { @Serializer(NameToken::class) companion object : KSerializer { - override val descriptor: SerialDescriptor = PrimitiveDescriptor("hep.dataforge.names.NameToken", PrimitiveKind.STRING) + override val descriptor: SerialDescriptor = + PrimitiveDescriptor("hep.dataforge.names.NameToken", PrimitiveKind.STRING) override fun deserialize(decoder: Decoder): NameToken { return decoder.decodeString().toName().first()!! @@ -188,8 +190,12 @@ fun Name.isEmpty(): Boolean = this.length == 0 * Set or replace last token index */ fun Name.withIndex(index: String): Name { - val tokens = ArrayList(tokens) val last = NameToken(tokens.last().body, index) + if (length == 0) error("Can't add index to empty name") + if (length == 1) { + return last.asName() + } + val tokens = ArrayList(tokens) tokens.removeAt(tokens.size - 1) tokens.add(last) return Name(tokens) diff --git a/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/MetaDelegateTest.kt b/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/MetaDelegateTest.kt index 277c2a6c..cb359a51 100644 --- a/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/MetaDelegateTest.kt +++ b/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/MetaDelegateTest.kt @@ -20,7 +20,7 @@ class MetaDelegateTest { class TestScheme : Scheme() { var myValue by string() var safeValue by double(2.2) - var enumValue by enum(TestEnum.YES) { enum() } + var enumValue by enum(TestEnum.YES) var inner by spec(InnerSpec) companion object : SchemeSpec(::TestScheme) diff --git a/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/descriptors/DescriptorTest.kt b/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/descriptors/DescriptorTest.kt index 1fa07382..77e670c8 100644 --- a/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/descriptors/DescriptorTest.kt +++ b/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/descriptors/DescriptorTest.kt @@ -26,6 +26,6 @@ class DescriptorTest { @Test fun testAllowedValues() { val allowed = descriptor.nodes["aNode"]?.values?.get("b")?.allowedValues - assertEquals(allowed, emptyList()) + assertEquals(emptyList(), allowed) } } \ No newline at end of file diff --git a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnScheme.kt b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnScheme.kt index 7d364784..2b65b234 100644 --- a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnScheme.kt +++ b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnScheme.kt @@ -1,6 +1,5 @@ package hep.dataforge.tables -import hep.dataforge.meta.enum import hep.dataforge.meta.scheme.Scheme import hep.dataforge.meta.scheme.SchemeSpec import hep.dataforge.meta.scheme.enum @@ -14,5 +13,5 @@ open class ColumnScheme : Scheme() { } class ValueColumnScheme : ColumnScheme() { - var valueType by enum(ValueType.STRING){enum()} + var valueType by enum(ValueType.STRING) } \ No newline at end of file