diff --git a/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/Context.kt b/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/Context.kt index d1ff801d..bf5c8924 100644 --- a/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/Context.kt +++ b/dataforge-context/src/commonMain/kotlin/hep/dataforge/context/Context.kt @@ -69,7 +69,7 @@ open class Context(final override val name: String, val parent: Context? = Globa override fun listTop(target: String): Sequence { return when (target) { Plugin.PLUGIN_TARGET -> plugins.asSequence().map { it.name.toName() } - Value.TYPE -> properties.asValueSequence().map { it.first } + Value.TYPE -> properties.values().map { it.first } else -> emptySequence() } } diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Meta.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Meta.kt index 6a714076..e62c3621 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Meta.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/Meta.kt @@ -91,19 +91,19 @@ fun Meta.getAll(name: Name): Map> { fun Meta.getAll(name: String): Map> = getAll(name.toName()) /** - * Transform meta to sequence of [Name]-[Value] pairs + * Get a sequence of [Name]-[Value] pairs */ -fun Meta.asValueSequence(): Sequence> { +fun Meta.values(): Sequence> { return items.asSequence().flatMap { entry -> val item = entry.value when (item) { is ValueItem -> sequenceOf(entry.key.asName() to item.value) - is NodeItem -> item.node.asValueSequence().map { pair -> (entry.key.asName() + pair.first) to pair.second } + is NodeItem -> item.node.values().map { pair -> (entry.key.asName() + pair.first) to pair.second } } } } -operator fun Meta.iterator(): Iterator> = asValueSequence().iterator() +operator fun Meta.iterator(): Iterator> = values().iterator() /** * A meta node that ensures that all of its descendants has at least the same type @@ -112,6 +112,27 @@ interface MetaNode> : Meta { override val items: Map> } +/** + * Get all items matching given name. + */ +fun > MetaNode.getAll(name: Name): Map> { + val root: MetaNode? = when (name.length) { + 0 -> error("Can't use empty name for that") + 1 -> this + else -> (this[name.cutLast()] as? NodeItem)?.node + } + + val (body, index) = name.last()!! + val regex = index.toRegex() + + return root?.items + ?.filter { it.key.body == body && (index.isEmpty() || regex.matches(it.key.index)) } + ?.mapKeys { it.key.index } + ?: emptyMap() +} + +fun > M.getAll(name: String): Map> = getAll(name.toName()) + operator fun > MetaNode.get(name: Name): MetaItem? { return name.first()?.let { token -> val tail = name.cutFirst() @@ -122,7 +143,7 @@ operator fun > MetaNode.get(name: Name): MetaItem? { } } -operator fun > MetaNode.get(key: String): MetaItem? = get(key.toName()) +operator fun > MetaNode?.get(key: String): MetaItem? = this?.let { get(key.toName()) } /** * Equals and hash code implementation for meta node diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MutableMeta.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MutableMeta.kt index 9600d9b1..f3b4f3ba 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MutableMeta.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MutableMeta.kt @@ -1,10 +1,6 @@ package hep.dataforge.meta -import hep.dataforge.names.Name -import hep.dataforge.names.NameToken -import hep.dataforge.names.plus -import hep.dataforge.names.toName -import hep.dataforge.names.asName +import hep.dataforge.names.* import hep.dataforge.values.Value internal data class MetaListener( @@ -152,12 +148,12 @@ fun > M.update(meta: Meta) { fun > M.setIndexed( name: Name, items: Iterable>, - queryFactory: (Int) -> String = { it.toString() } + indexFactory: MetaItem.(index: Int) -> String = { it.toString() } ) { val tokens = name.tokens.toMutableList() val last = tokens.last() items.forEachIndexed { index, meta -> - val indexedToken = NameToken(last.body, last.index + queryFactory(index)) + val indexedToken = NameToken(last.body, last.index + meta.indexFactory(index)) tokens[tokens.lastIndex] = indexedToken set(Name(tokens), meta) } @@ -166,10 +162,24 @@ fun > M.setIndexed( fun > M.setIndexed( name: Name, metas: Iterable, - queryFactory: (Int) -> String = { it.toString() } + indexFactory: MetaItem.(index: Int) -> String = { it.toString() } ) { - setIndexed(name, metas.map { MetaItem.NodeItem(wrap(name, it)) }, queryFactory) + setIndexed(name, metas.map { MetaItem.NodeItem(wrap(name, it)) }, indexFactory) } operator fun > M.set(name: Name, metas: Iterable) = setIndexed(name, metas) operator fun > M.set(name: String, metas: Iterable) = setIndexed(name.toName(), metas) + +/** + * Append the node with a same-name-sibling, automatically generating numerical index + */ +fun > M.append(name: Name, value: Any?) { + require(!name.isEmpty()) { "Name could not be empty for append operation" } + val newIndex = name.last()!!.index + if (newIndex.isNotEmpty()) { + set(name, value) + } else { + val index = (getAll(name).keys.mapNotNull { it.toIntOrNull() }.max() ?: -1) + 1 + set(name.withIndex(index.toString()), value) + } +} 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 759eb425..4ce3c1e5 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/names/Name.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/names/Name.kt @@ -105,4 +105,15 @@ fun NameToken.asName() = Name(listOf(this)) val EmptyName = Name(emptyList()) -fun Name.isEmpty(): Boolean = this.length == 0 \ No newline at end of file +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) + tokens.removeAt(tokens.size - 1) + tokens.add(last) + return Name(tokens) +} \ No newline at end of file diff --git a/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/MetaBuilderTest.kt b/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/MetaBuilderTest.kt index 0e399bad..849a34fc 100644 --- a/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/MetaBuilderTest.kt +++ b/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/MetaBuilderTest.kt @@ -30,7 +30,7 @@ class MetaBuilderTest { "b.a[$it]" to it } }.seal() - assertEquals(10, meta.asValueSequence().count()) + assertEquals(10, meta.values().count()) val nodes = meta.getAll("b.a") diff --git a/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/StyledTest.kt b/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/StyledTest.kt index 14e6027b..5a4699f9 100644 --- a/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/StyledTest.kt +++ b/dataforge-meta/src/commonTest/kotlin/hep/dataforge/meta/StyledTest.kt @@ -14,7 +14,7 @@ class StyledTest{ } } }.seal().withStyle() - assertEquals(10, meta.asValueSequence().count()) + assertEquals(10, meta.values().count()) val bNode = meta["b"].node @@ -22,8 +22,8 @@ class StyledTest{ val allNodes = meta.getAll("b.a") - assertEquals(3, aNodes?.get("3")?.node["d"]?.int) - assertEquals(3, allNodes["3"]?.node["d"]?.int) + assertEquals(3, aNodes?.get("3").node["d"].int) + assertEquals(3, allNodes["3"].node["d"].int) } } \ No newline at end of file diff --git a/dataforge-meta/src/jsMain/kotlin/hep/dataforge/meta/DynamicMeta.kt b/dataforge-meta/src/jsMain/kotlin/hep/dataforge/meta/DynamicMeta.kt index 02432dfb..4719831b 100644 --- a/dataforge-meta/src/jsMain/kotlin/hep/dataforge/meta/DynamicMeta.kt +++ b/dataforge-meta/src/jsMain/kotlin/hep/dataforge/meta/DynamicMeta.kt @@ -32,7 +32,8 @@ fun Meta.toDynamic(): dynamic { class DynamicMeta(val obj: dynamic) : Meta { private fun keys() = js("Object.keys(this.obj)") as Array - private fun isArray(obj: dynamic): Boolean = js("Array.isArray(obj)") as Boolean + private fun isArray(@Suppress("UNUSED_PARAMETER") obj: dynamic): Boolean = + js("Array.isArray(obj)") as Boolean private fun asItem(obj: dynamic): MetaItem? { if (obj == null) return MetaItem.ValueItem(Null)