Basic design for workspaces
This commit is contained in:
parent
2d8810110e
commit
012ee93ab2
@ -4,8 +4,6 @@ import hep.dataforge.meta.*
|
|||||||
import hep.dataforge.names.Name
|
import hep.dataforge.names.Name
|
||||||
import hep.dataforge.names.toName
|
import hep.dataforge.names.toName
|
||||||
import hep.dataforge.provider.Provider
|
import hep.dataforge.provider.Provider
|
||||||
import hep.dataforge.provider.Types
|
|
||||||
import hep.dataforge.provider.provideAll
|
|
||||||
import hep.dataforge.values.Value
|
import hep.dataforge.values.Value
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
@ -54,7 +52,7 @@ interface Context : Named, MetaRepr, Provider, CoroutineScope {
|
|||||||
override fun provideTop(target: String, name: Name): Any? {
|
override fun provideTop(target: String, name: Name): Any? {
|
||||||
return when (target) {
|
return when (target) {
|
||||||
Plugin.PLUGIN_TARGET -> plugins[PluginTag.fromString(name.toString())]
|
Plugin.PLUGIN_TARGET -> plugins[PluginTag.fromString(name.toString())]
|
||||||
Value.VALUE_TARGET -> properties[name]?.value
|
Value.TYPE -> properties[name]?.value
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -62,7 +60,7 @@ interface Context : Named, MetaRepr, Provider, CoroutineScope {
|
|||||||
override fun listTop(target: String): Sequence<Name> {
|
override fun listTop(target: String): Sequence<Name> {
|
||||||
return when (target) {
|
return when (target) {
|
||||||
Plugin.PLUGIN_TARGET -> plugins.asSequence().map { it.name.toName() }
|
Plugin.PLUGIN_TARGET -> plugins.asSequence().map { it.name.toName() }
|
||||||
Value.VALUE_TARGET -> properties.asValueSequence().map { it.first }
|
Value.TYPE -> properties.asValueSequence().map { it.first }
|
||||||
else -> emptySequence()
|
else -> emptySequence()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -92,15 +90,9 @@ interface Context : Named, MetaRepr, Provider, CoroutineScope {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* A sequences of all objects provided by plugins with given target and type
|
|
||||||
*/
|
|
||||||
inline fun <reified T : Any> Context.provideAll(target: String = Types[T::class]): Sequence<T> {
|
|
||||||
return plugins.asSequence().flatMap { it.provideAll(target) }.filterIsInstance<T>()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A global root context
|
* A global root context. Closing [Global] terminates the framework.
|
||||||
*/
|
*/
|
||||||
expect object Global : Context {
|
expect object Global : Context {
|
||||||
fun getContext(name: String): Context
|
fun getContext(name: String): Context
|
||||||
|
@ -88,10 +88,4 @@ fun Provider.provideAll(target: String): Sequence<Any> {
|
|||||||
return listTop(target).map { provideTop(target, it) ?: error("The element $it is declared but not provided") }
|
return listTop(target).map { provideTop(target, it) ?: error("The element $it is declared but not provided") }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Provide an object with given name inferring target from its type using [Type] annotation
|
|
||||||
*/
|
|
||||||
inline fun <reified T : Any> Provider.provideByType(name: String): T? {
|
|
||||||
val target = Types[T::class]
|
|
||||||
return provide(target, name)
|
|
||||||
}
|
|
||||||
|
@ -0,0 +1,10 @@
|
|||||||
|
package hep.dataforge.provider
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A text label for internal DataForge type classification. Alternative for mime container type.
|
||||||
|
*
|
||||||
|
* The DataForge type notation presumes that type `A.B.C` is the subtype of `A.B`
|
||||||
|
*/
|
||||||
|
@MustBeDocumented
|
||||||
|
@Target(AnnotationTarget.CLASS)
|
||||||
|
annotation class Type(val id: String)
|
@ -1,26 +0,0 @@
|
|||||||
package hep.dataforge.provider
|
|
||||||
|
|
||||||
import kotlin.reflect.KClass
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A text label for internal DataForge type classification. Alternative for mime container type.
|
|
||||||
*
|
|
||||||
* The DataForge type notation presumes that type `A.B.C` is the subtype of `A.B`
|
|
||||||
*/
|
|
||||||
@MustBeDocumented
|
|
||||||
@Target(AnnotationTarget.CLASS)
|
|
||||||
annotation class Type(val id: String)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Utils to get type of classes and objects
|
|
||||||
*/
|
|
||||||
object Types {
|
|
||||||
operator fun get(cl: KClass<*>): String {
|
|
||||||
return cl.annotations.filterIsInstance<Type>().firstOrNull()?.id ?: cl.simpleName ?: ""
|
|
||||||
}
|
|
||||||
|
|
||||||
operator fun get(obj: Any): String {
|
|
||||||
return get(obj::class)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,37 @@
|
|||||||
|
package hep.dataforge.provider
|
||||||
|
|
||||||
|
import hep.dataforge.context.Context
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
import kotlin.reflect.full.findAnnotation
|
||||||
|
|
||||||
|
|
||||||
|
object Types {
|
||||||
|
operator fun get(cl: KClass<*>): String {
|
||||||
|
return cl.findAnnotation<Type>()?.id ?: cl.simpleName ?: ""
|
||||||
|
}
|
||||||
|
|
||||||
|
operator fun get(obj: Any): String {
|
||||||
|
return get(obj::class)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provide an object with given name inferring target from its type using [Type] annotation
|
||||||
|
*/
|
||||||
|
inline fun <reified T : Any> Provider.provideByType(name: String): T? {
|
||||||
|
val target = Types[T::class]
|
||||||
|
return provide(target, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <reified T : Any> Provider.provideAllByType(): Sequence<T> {
|
||||||
|
val target = Types[T::class]
|
||||||
|
return provideAll(target).filterIsInstance<T>()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A sequences of all objects provided by plugins with given target and type
|
||||||
|
*/
|
||||||
|
inline fun <reified T : Any> Context.components(): Sequence<T> {
|
||||||
|
return plugins.asSequence().flatMap { it.provideAll(Types[T::class]) }.filterIsInstance<T>()
|
||||||
|
}
|
||||||
|
|
@ -1,38 +0,0 @@
|
|||||||
plugins {
|
|
||||||
id "org.jetbrains.kotlin.multiplatform"
|
|
||||||
}
|
|
||||||
|
|
||||||
repositories {
|
|
||||||
jcenter()
|
|
||||||
}
|
|
||||||
|
|
||||||
kotlin {
|
|
||||||
targets {
|
|
||||||
fromPreset(presets.jvm, 'jvm')
|
|
||||||
fromPreset(presets.js, 'js')
|
|
||||||
// For ARM, preset should be changed to presets.iosArm32 or presets.iosArm64
|
|
||||||
// For Linux, preset should be changed to e.g. presets.linuxX64
|
|
||||||
// For MacOS, preset should be changed to e.g. presets.macosX64
|
|
||||||
//fromPreset(presets.iosX64, 'ios')
|
|
||||||
}
|
|
||||||
sourceSets {
|
|
||||||
commonMain {
|
|
||||||
dependencies {
|
|
||||||
api project(":dataforge-meta")
|
|
||||||
api "org.jetbrains.kotlinx:kotlinx-coroutines-core-common:$coroutinesVersion"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
jvmMain {
|
|
||||||
dependencies {
|
|
||||||
api "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
jsMain {
|
|
||||||
dependencies {
|
|
||||||
api "org.jetbrains.kotlinx:kotlinx-coroutines-core-js:$coroutinesVersion"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
30
dataforge-data/build.gradle.kts
Normal file
30
dataforge-data/build.gradle.kts
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
plugins {
|
||||||
|
kotlin("multiplatform")
|
||||||
|
}
|
||||||
|
|
||||||
|
val coroutinesVersion: String by rootProject.extra
|
||||||
|
|
||||||
|
kotlin {
|
||||||
|
jvm()
|
||||||
|
js()
|
||||||
|
sourceSets {
|
||||||
|
val commonMain by getting{
|
||||||
|
dependencies {
|
||||||
|
api(project(":dataforge-meta"))
|
||||||
|
api("org.jetbrains.kotlinx:kotlinx-coroutines-core-common:$coroutinesVersion")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val jvmMain by getting{
|
||||||
|
dependencies {
|
||||||
|
api("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val jsMain by getting{
|
||||||
|
dependencies {
|
||||||
|
api("org.jetbrains.kotlinx:kotlinx-coroutines-core-js:$coroutinesVersion")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -38,7 +38,7 @@ infix fun <T : Any, I : Any, R : Any> Action<T, I>.then(action: Action<I, R>): A
|
|||||||
*/
|
*/
|
||||||
class PipeAction<in T : Any, out R : Any>(val transform: (Name, Data<T>, Meta) -> Data<R>?) : Action<T, R> {
|
class PipeAction<in T : Any, out R : Any>(val transform: (Name, Data<T>, Meta) -> Data<R>?) : Action<T, R> {
|
||||||
override fun invoke(node: DataNode<T>, meta: Meta): DataNode<R> = DataNode.build {
|
override fun invoke(node: DataNode<T>, meta: Meta): DataNode<R> = DataNode.build {
|
||||||
node.asSequence().forEach { (name, data) ->
|
node.dataSequence().forEach { (name, data) ->
|
||||||
val res = transform(name, data, meta)
|
val res = transform(name, data, meta)
|
||||||
if (res != null) {
|
if (res != null) {
|
||||||
set(name, res)
|
set(name, res)
|
||||||
|
@ -2,10 +2,6 @@ package hep.dataforge.data
|
|||||||
|
|
||||||
import hep.dataforge.meta.Meta
|
import hep.dataforge.meta.Meta
|
||||||
import hep.dataforge.meta.MetaRepr
|
import hep.dataforge.meta.MetaRepr
|
||||||
import hep.dataforge.names.Name
|
|
||||||
import hep.dataforge.names.NameToken
|
|
||||||
import hep.dataforge.names.plus
|
|
||||||
import hep.dataforge.names.toName
|
|
||||||
import kotlin.coroutines.CoroutineContext
|
import kotlin.coroutines.CoroutineContext
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
@ -30,6 +26,8 @@ interface Data<out T : Any> : MetaRepr {
|
|||||||
override fun toMeta(): Meta = meta
|
override fun toMeta(): Meta = meta
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
const val TYPE = "data"
|
||||||
|
|
||||||
fun <T : Any> of(type: KClass<out T>, goal: Goal<T>, meta: Meta): Data<T> = DataImpl(type, goal, meta)
|
fun <T : Any> of(type: KClass<out T>, goal: Goal<T>, meta: Meta): Data<T> = DataImpl(type, goal, meta)
|
||||||
inline fun <reified T : Any> of(goal: Goal<T>, meta: Meta): Data<T> = of(T::class, goal, meta)
|
inline fun <reified T : Any> of(goal: Goal<T>, meta: Meta): Data<T> = of(T::class, goal, meta)
|
||||||
fun <T : Any> of(name: String, type: KClass<out T>, goal: Goal<T>, meta: Meta): Data<T> =
|
fun <T : Any> of(name: String, type: KClass<out T>, goal: Goal<T>, meta: Meta): Data<T> =
|
||||||
@ -43,6 +41,8 @@ interface Data<out T : Any> : MetaRepr {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun <T: Any> Data<T>.await(): T = goal.await()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generic Data implementation
|
* Generic Data implementation
|
||||||
*/
|
*/
|
||||||
@ -54,167 +54,3 @@ private class DataImpl<out T : Any>(
|
|||||||
|
|
||||||
class NamedData<out T : Any>(val name: String, data: Data<T>) : Data<T> by data
|
class NamedData<out T : Any>(val name: String, data: Data<T>) : Data<T> by data
|
||||||
|
|
||||||
/**
|
|
||||||
* A tree-like data structure grouped into the node. All data inside the node must inherit its type
|
|
||||||
*/
|
|
||||||
interface DataNode<out T : Any> {
|
|
||||||
/**
|
|
||||||
* Get the specific data if it exists
|
|
||||||
*/
|
|
||||||
operator fun get(name: Name): Data<T>?
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a subnode with given name if it exists.
|
|
||||||
*/
|
|
||||||
fun getNode(name: Name): DataNode<T>?
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Walk the tree upside down and provide all data nodes with full names
|
|
||||||
*/
|
|
||||||
fun asSequence(): Sequence<Pair<Name, Data<T>>>
|
|
||||||
|
|
||||||
operator fun iterator(): Iterator<Pair<Name, Data<T>>> = asSequence().iterator()
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
fun <T : Any> build(block: DataTreeBuilder<T>.() -> Unit) = DataTreeBuilder<T>().apply(block).build()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
internal sealed class DataTreeItem<out T : Any> {
|
|
||||||
class Node<out T : Any>(val tree: DataTree<T>) : DataTreeItem<T>()
|
|
||||||
class Value<out T : Any>(val value: Data<T>) : DataTreeItem<T>()
|
|
||||||
}
|
|
||||||
|
|
||||||
class DataTree<out T : Any> internal constructor(private val items: Map<NameToken, DataTreeItem<T>>) : DataNode<T> {
|
|
||||||
//TODO add node-level meta?
|
|
||||||
|
|
||||||
override fun get(name: Name): Data<T>? = when (name.length) {
|
|
||||||
0 -> error("Empty name")
|
|
||||||
1 -> (items[name.first()] as? DataTreeItem.Value)?.value
|
|
||||||
else -> getNode(name.first()!!.toName())?.get(name.cutFirst())
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getNode(name: Name): DataTree<T>? = when (name.length) {
|
|
||||||
0 -> this
|
|
||||||
1 -> (items[name.first()] as? DataTreeItem.Node)?.tree
|
|
||||||
else -> getNode(name.first()!!.toName())?.getNode(name.cutFirst())
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun asSequence(): Sequence<Pair<Name, Data<T>>> {
|
|
||||||
return kotlin.sequences.sequence {
|
|
||||||
items.forEach { (head, tree) ->
|
|
||||||
when (tree) {
|
|
||||||
is DataTreeItem.Value -> yield(head.toName() to tree.value)
|
|
||||||
is DataTreeItem.Node -> {
|
|
||||||
val subSequence = tree.tree.asSequence().map { (name, data) -> (head.toName() + name) to data }
|
|
||||||
yieldAll(subSequence)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private sealed class DataTreeBuilderItem<out T : Any> {
|
|
||||||
class Node<T : Any>(val tree: DataTreeBuilder<T>) : DataTreeBuilderItem<T>()
|
|
||||||
class Value<T : Any>(val value: Data<T>) : DataTreeBuilderItem<T>()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A builder for a DataTree.
|
|
||||||
*/
|
|
||||||
class DataTreeBuilder<T : Any> {
|
|
||||||
private val map = HashMap<NameToken, DataTreeBuilderItem<T>>()
|
|
||||||
|
|
||||||
operator fun set(token: NameToken, node: DataTreeBuilder<T>) {
|
|
||||||
if (map.containsKey(token)) error("Tree entry with name $token is not empty")
|
|
||||||
map[token] = DataTreeBuilderItem.Node(node)
|
|
||||||
}
|
|
||||||
|
|
||||||
operator fun set(token: NameToken, data: Data<T>) {
|
|
||||||
if (map.containsKey(token)) error("Tree entry with name $token is not empty")
|
|
||||||
map[token] = DataTreeBuilderItem.Value(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun buildNode(token: NameToken): DataTreeBuilder<T> {
|
|
||||||
return if (!map.containsKey(token)) {
|
|
||||||
DataTreeBuilder<T>().also { map[token] = DataTreeBuilderItem.Node(it) }
|
|
||||||
} else {
|
|
||||||
(map[token] as? DataTreeBuilderItem.Node ?: error("The node with name $token is occupied by leaf")).tree
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun buildNode(name: Name): DataTreeBuilder<T> {
|
|
||||||
return when (name.length) {
|
|
||||||
0 -> this
|
|
||||||
1 -> buildNode(name.first()!!)
|
|
||||||
else -> buildNode(name.first()!!).buildNode(name.cutFirst())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
operator fun set(name: Name, data: Data<T>) {
|
|
||||||
when (name.length) {
|
|
||||||
0 -> error("Can't add data with empty name")
|
|
||||||
1 -> set(name.first()!!, data)
|
|
||||||
2 -> buildNode(name.cutLast())[name.last()!!] = data
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
operator fun set(name: Name, node: DataTreeBuilder<T>) {
|
|
||||||
when (name.length) {
|
|
||||||
0 -> error("Can't add data with empty name")
|
|
||||||
1 -> set(name.first()!!, node)
|
|
||||||
2 -> buildNode(name.cutLast())[name.last()!!] = node
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
operator fun set(name: Name, node: DataNode<T>) = set(name, node.builder())
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Append data to node
|
|
||||||
*/
|
|
||||||
infix fun String.to(data: Data<T>) = set(toName(), data)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Append node
|
|
||||||
*/
|
|
||||||
infix fun String.to(node: DataNode<T>) = set(toName(), node)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Build and append node
|
|
||||||
*/
|
|
||||||
infix fun String.to(block: DataTreeBuilder<T>.() -> Unit) = set(toName(), DataTreeBuilder<T>().apply(block))
|
|
||||||
|
|
||||||
fun build(): DataTree<T> {
|
|
||||||
val resMap = map.mapValues { (_, value) ->
|
|
||||||
when (value) {
|
|
||||||
is DataTreeBuilderItem.Value -> DataTreeItem.Value(value.value)
|
|
||||||
is DataTreeBuilderItem.Node -> DataTreeItem.Node(value.tree.build())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return DataTree(resMap)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate a mutable builder from this node. Node content is not changed
|
|
||||||
*/
|
|
||||||
fun <T : Any> DataNode<T>.builder(): DataTreeBuilder<T> = DataTreeBuilder<T>().apply {
|
|
||||||
asSequence().forEach { (name, data) -> this[name] = data }
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Start computation for all goals in data node
|
|
||||||
*/
|
|
||||||
fun DataNode<*>.startAll() = asSequence().forEach { (_, data) -> data.goal.start() }
|
|
||||||
|
|
||||||
fun <T : Any> DataNode<T>.filter(predicate: (Name, Data<T>) -> Boolean): DataNode<T> = DataNode.build {
|
|
||||||
asSequence().forEach { (name, data) ->
|
|
||||||
if (predicate(name, data)) {
|
|
||||||
this[name] = data
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//fun <T : Any, R: T> DataNode<T>.filterIsInstance(type: KClass<R>): DataNode<R> = filter{_,data -> type.}
|
|
@ -0,0 +1,46 @@
|
|||||||
|
package hep.dataforge.data
|
||||||
|
|
||||||
|
import hep.dataforge.meta.*
|
||||||
|
import hep.dataforge.names.toName
|
||||||
|
|
||||||
|
|
||||||
|
class DataFilter(override val config: Config) : Specification {
|
||||||
|
var from by string()
|
||||||
|
var to by string()
|
||||||
|
var pattern by string("*.")
|
||||||
|
// val prefix by string()
|
||||||
|
// val suffix by string()
|
||||||
|
|
||||||
|
companion object : SpecificationCompanion<DataFilter> {
|
||||||
|
override fun wrap(config: Config): DataFilter = DataFilter(config)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply meta-based filter to given data node
|
||||||
|
*/
|
||||||
|
fun <T : Any> DataNode<T>.filter(filter: DataFilter): DataNode<T> {
|
||||||
|
val sourceNode = filter.from?.let { getNode(it.toName()) } ?: this@filter
|
||||||
|
val regex = filter.pattern.toRegex()
|
||||||
|
val targetNode = DataTreeBuilder<T>().apply {
|
||||||
|
sourceNode.dataSequence().forEach { (name, data) ->
|
||||||
|
if (name.toString().matches(regex)) {
|
||||||
|
this[name] = data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return filter.to?.let {
|
||||||
|
DataTreeBuilder<T>().apply { this[it.toName()] = targetNode }.build()
|
||||||
|
} ?: targetNode.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter data using [DataFilter] specification
|
||||||
|
*/
|
||||||
|
fun <T : Any> DataNode<T>.filter(filter: Meta): DataNode<T> = filter(DataFilter.wrap(filter))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter data using [DataFilter] builder
|
||||||
|
*/
|
||||||
|
fun <T : Any> DataNode<T>.filter(filterBuilder: DataFilter.() -> Unit): DataNode<T> =
|
||||||
|
filter(DataFilter.build(filterBuilder))
|
@ -0,0 +1,192 @@
|
|||||||
|
package hep.dataforge.data
|
||||||
|
|
||||||
|
import hep.dataforge.names.Name
|
||||||
|
import hep.dataforge.names.NameToken
|
||||||
|
import hep.dataforge.names.plus
|
||||||
|
import hep.dataforge.names.toName
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A tree-like data structure grouped into the node. All data inside the node must inherit its type
|
||||||
|
*/
|
||||||
|
interface DataNode<out T : Any> {
|
||||||
|
/**
|
||||||
|
* Get the specific data if it exists
|
||||||
|
*/
|
||||||
|
operator fun get(name: Name): Data<T>?
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a subnode with given name if it exists.
|
||||||
|
*/
|
||||||
|
fun getNode(name: Name): DataNode<T>?
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Walk the tree upside down and provide all data nodes with full names
|
||||||
|
*/
|
||||||
|
fun dataSequence(): Sequence<Pair<Name, Data<T>>>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A sequence of all nodes in the tree walking upside down, excluding self
|
||||||
|
*/
|
||||||
|
fun nodeSequence(): Sequence<Pair<Name, DataNode<T>>>
|
||||||
|
|
||||||
|
operator fun iterator(): Iterator<Pair<Name, Data<T>>> = dataSequence().iterator()
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val TYPE = "dataNode"
|
||||||
|
|
||||||
|
fun <T : Any> build(block: DataTreeBuilder<T>.() -> Unit) = DataTreeBuilder<T>().apply(block).build()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
internal sealed class DataTreeItem<out T : Any> {
|
||||||
|
class Node<out T : Any>(val tree: DataTree<T>) : DataTreeItem<T>()
|
||||||
|
class Value<out T : Any>(val value: Data<T>) : DataTreeItem<T>()
|
||||||
|
}
|
||||||
|
|
||||||
|
class DataTree<out T : Any> internal constructor(private val items: Map<NameToken, DataTreeItem<T>>) : DataNode<T> {
|
||||||
|
//TODO add node-level meta?
|
||||||
|
|
||||||
|
override fun get(name: Name): Data<T>? = when (name.length) {
|
||||||
|
0 -> error("Empty name")
|
||||||
|
1 -> (items[name.first()] as? DataTreeItem.Value)?.value
|
||||||
|
else -> getNode(name.first()!!.toName())?.get(name.cutFirst())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getNode(name: Name): DataTree<T>? = when (name.length) {
|
||||||
|
0 -> this
|
||||||
|
1 -> (items[name.first()] as? DataTreeItem.Node)?.tree
|
||||||
|
else -> getNode(name.first()!!.toName())?.getNode(name.cutFirst())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun dataSequence(): Sequence<Pair<Name, Data<T>>> {
|
||||||
|
return sequence {
|
||||||
|
items.forEach { (head, tree) ->
|
||||||
|
when (tree) {
|
||||||
|
is DataTreeItem.Value -> yield(head.toName() to tree.value)
|
||||||
|
is DataTreeItem.Node -> {
|
||||||
|
val subSequence =
|
||||||
|
tree.tree.dataSequence().map { (name, data) -> (head.toName() + name) to data }
|
||||||
|
yieldAll(subSequence)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun nodeSequence(): Sequence<Pair<Name, DataNode<T>>> {
|
||||||
|
return sequence {
|
||||||
|
items.forEach { (head, tree) ->
|
||||||
|
if (tree is DataTreeItem.Node) {
|
||||||
|
yield(head.toName() to tree.tree)
|
||||||
|
val subSequence =
|
||||||
|
tree.tree.nodeSequence().map { (name, node) -> (head.toName() + name) to node }
|
||||||
|
yieldAll(subSequence)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class DataTreeBuilderItem<out T : Any> {
|
||||||
|
class Node<T : Any>(val tree: DataTreeBuilder<T>) : DataTreeBuilderItem<T>()
|
||||||
|
class Value<T : Any>(val value: Data<T>) : DataTreeBuilderItem<T>()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A builder for a DataTree.
|
||||||
|
*/
|
||||||
|
class DataTreeBuilder<T : Any> {
|
||||||
|
private val map = HashMap<NameToken, DataTreeBuilderItem<T>>()
|
||||||
|
|
||||||
|
operator fun set(token: NameToken, node: DataTreeBuilder<T>) {
|
||||||
|
if (map.containsKey(token)) error("Tree entry with name $token is not empty")
|
||||||
|
map[token] = DataTreeBuilderItem.Node(node)
|
||||||
|
}
|
||||||
|
|
||||||
|
operator fun set(token: NameToken, data: Data<T>) {
|
||||||
|
if (map.containsKey(token)) error("Tree entry with name $token is not empty")
|
||||||
|
map[token] = DataTreeBuilderItem.Value(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun buildNode(token: NameToken): DataTreeBuilder<T> {
|
||||||
|
return if (!map.containsKey(token)) {
|
||||||
|
DataTreeBuilder<T>().also { map[token] = DataTreeBuilderItem.Node(it) }
|
||||||
|
} else {
|
||||||
|
(map[token] as? DataTreeBuilderItem.Node ?: error("The node with name $token is occupied by leaf")).tree
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun buildNode(name: Name): DataTreeBuilder<T> {
|
||||||
|
return when (name.length) {
|
||||||
|
0 -> this
|
||||||
|
1 -> buildNode(name.first()!!)
|
||||||
|
else -> buildNode(name.first()!!).buildNode(name.cutFirst())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
operator fun set(name: Name, data: Data<T>) {
|
||||||
|
when (name.length) {
|
||||||
|
0 -> error("Can't add data with empty name")
|
||||||
|
1 -> set(name.first()!!, data)
|
||||||
|
2 -> buildNode(name.cutLast())[name.last()!!] = data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
operator fun set(name: Name, node: DataTreeBuilder<T>) {
|
||||||
|
when (name.length) {
|
||||||
|
0 -> error("Can't add data with empty name")
|
||||||
|
1 -> set(name.first()!!, node)
|
||||||
|
2 -> buildNode(name.cutLast())[name.last()!!] = node
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
operator fun set(name: Name, node: DataNode<T>) = set(name, node.builder())
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Append data to node
|
||||||
|
*/
|
||||||
|
infix fun String.to(data: Data<T>) = set(toName(), data)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Append node
|
||||||
|
*/
|
||||||
|
infix fun String.to(node: DataNode<T>) = set(toName(), node)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build and append node
|
||||||
|
*/
|
||||||
|
infix fun String.to(block: DataTreeBuilder<T>.() -> Unit) = set(toName(), DataTreeBuilder<T>().apply(block))
|
||||||
|
|
||||||
|
fun build(): DataTree<T> {
|
||||||
|
val resMap = map.mapValues { (_, value) ->
|
||||||
|
when (value) {
|
||||||
|
is DataTreeBuilderItem.Value -> DataTreeItem.Value(value.value)
|
||||||
|
is DataTreeBuilderItem.Node -> DataTreeItem.Node(value.tree.build())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return DataTree(resMap)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a mutable builder from this node. Node content is not changed
|
||||||
|
*/
|
||||||
|
fun <T : Any> DataNode<T>.builder(): DataTreeBuilder<T> = DataTreeBuilder<T>().apply {
|
||||||
|
dataSequence().forEach { (name, data) -> this[name] = data }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start computation for all goals in data node
|
||||||
|
*/
|
||||||
|
fun DataNode<*>.startAll() = dataSequence().forEach { (_, data) -> data.goal.start() }
|
||||||
|
|
||||||
|
fun <T : Any> DataNode<T>.filter(predicate: (Name, Data<T>) -> Boolean): DataNode<T> = DataNode.build {
|
||||||
|
dataSequence().forEach { (name, data) ->
|
||||||
|
if (predicate(name, data)) {
|
||||||
|
this[name] = data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//fun <T : Any, R: T> DataNode<T>.filterIsInstance(type: KClass<R>): DataNode<R> = filter{_,data -> type.}
|
@ -0,0 +1,8 @@
|
|||||||
|
package hep.dataforge.data
|
||||||
|
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Block the thread and get data content
|
||||||
|
*/
|
||||||
|
fun <T : Any> Data<T>.get(): T = runBlocking { await() }
|
@ -1,8 +1,10 @@
|
|||||||
package hep.dataforge.io
|
package hep.dataforge.io
|
||||||
|
|
||||||
import hep.dataforge.context.Context
|
import hep.dataforge.context.Context
|
||||||
import hep.dataforge.context.provideAll
|
import hep.dataforge.io.TextRenderer.Companion.TEXT_RENDERER_TYPE
|
||||||
import hep.dataforge.meta.Meta
|
import hep.dataforge.meta.Meta
|
||||||
|
import hep.dataforge.provider.Type
|
||||||
|
import hep.dataforge.provider.provideAll
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
@ -18,7 +20,8 @@ class TextOutput(override val context: Context, private val output: kotlinx.io.c
|
|||||||
} else {
|
} else {
|
||||||
val value = cache[obj::class]
|
val value = cache[obj::class]
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
val answer = context.provideAll<TextRenderer>().filter { it.type.isInstance(obj) }.firstOrNull()
|
val answer = context.provideAll(TEXT_RENDERER_TYPE).filterIsInstance<TextRenderer>()
|
||||||
|
.filter { it.type.isInstance(obj) }.firstOrNull()
|
||||||
if (answer != null) {
|
if (answer != null) {
|
||||||
cache[obj::class] = answer
|
cache[obj::class] = answer
|
||||||
answer
|
answer
|
||||||
@ -35,6 +38,7 @@ class TextOutput(override val context: Context, private val output: kotlinx.io.c
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Type(TEXT_RENDERER_TYPE)
|
||||||
interface TextRenderer {
|
interface TextRenderer {
|
||||||
/**
|
/**
|
||||||
* The priority of this renderer compared to other renderers
|
* The priority of this renderer compared to other renderers
|
||||||
@ -46,6 +50,10 @@ interface TextRenderer {
|
|||||||
val type: KClass<*>
|
val type: KClass<*>
|
||||||
|
|
||||||
suspend fun kotlinx.io.core.Output.render(obj: Any)
|
suspend fun kotlinx.io.core.Output.render(obj: Any)
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val TEXT_RENDERER_TYPE = "dataforge.textRenderer"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object DefaultTextRenderer : TextRenderer {
|
object DefaultTextRenderer : TextRenderer {
|
||||||
|
@ -42,6 +42,7 @@ interface Meta : MetaRepr {
|
|||||||
override fun toMeta(): Meta = this
|
override fun toMeta(): Meta = this
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
const val TYPE = "meta"
|
||||||
/**
|
/**
|
||||||
* A key for single value node
|
* A key for single value node
|
||||||
*/
|
*/
|
||||||
|
@ -161,7 +161,8 @@ fun <M : MutableMetaNode<M>> M.setIndexed(
|
|||||||
metas: Iterable<Meta>,
|
metas: Iterable<Meta>,
|
||||||
queryFactory: (Int) -> String = { it.toString() }
|
queryFactory: (Int) -> String = { it.toString() }
|
||||||
) {
|
) {
|
||||||
setIndexed(name, metas.map { wrap(name, it) }, queryFactory)
|
setIndexed(name, metas.map { MetaItem.NodeItem(wrap(name, it)) }, queryFactory)
|
||||||
}
|
}
|
||||||
|
|
||||||
operator fun <M : MutableMetaNode<M>> M.set(name: Name, metas: Iterable<Meta>) = setIndexed(name, metas)
|
operator fun <M : MutableMetaNode<M>> M.set(name: Name, metas: Iterable<Meta>) = setIndexed(name, metas)
|
||||||
|
operator fun <M : MutableMetaNode<M>> M.set(name: String, metas: Iterable<Meta>) = setIndexed(name.toName(), metas)
|
||||||
|
@ -8,47 +8,52 @@ interface Specification : Configurable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Specification allows to apply custom configuration in a type safe way to simple untyped configuration
|
* Allows to apply custom configuration in a type safe way to simple untyped configuration.
|
||||||
|
* By convention [Specification] companion should inherit this class
|
||||||
|
*
|
||||||
*/
|
*/
|
||||||
interface SpecificationBuilder<T : Specification> {
|
interface SpecificationCompanion<T : Specification> {
|
||||||
/**
|
/**
|
||||||
* Update given configuration using given type as a builder
|
* Update given configuration using given type as a builder
|
||||||
*/
|
*/
|
||||||
fun update(config: Config, action: T.() -> Unit) {
|
fun update(config: Config, action: T.() -> Unit): T {
|
||||||
wrap(config).apply(action)
|
return wrap(config).apply(action)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun build(action: T.() -> Unit) = update(Config(), action)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wrap generic configuration producing instance of desired type
|
* Wrap generic configuration producing instance of desired type
|
||||||
*/
|
*/
|
||||||
fun wrap(config: Config): T
|
fun wrap(config: Config): T
|
||||||
|
|
||||||
fun wrap(meta: Meta): T = wrap(meta.toConfig())
|
fun wrap(meta: Meta): T = wrap(meta.toConfig())
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun <T : Specification> specification(wrapper: (Config) -> T): SpecificationBuilder<T> =
|
fun <T : Specification> specification(wrapper: (Config) -> T): SpecificationCompanion<T> =
|
||||||
object : SpecificationBuilder<T> {
|
object : SpecificationCompanion<T> {
|
||||||
override fun wrap(config: Config): T = wrapper(config)
|
override fun wrap(config: Config): T = wrapper(config)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Apply specified configuration to configurable
|
* Apply specified configuration to configurable
|
||||||
*/
|
*/
|
||||||
fun <T : Configurable, C : Specification, S : SpecificationBuilder<C>> T.configure(spec: S, action: C.() -> Unit) =
|
fun <T : Configurable, C : Specification, S : SpecificationCompanion<C>> T.configure(spec: S, action: C.() -> Unit) =
|
||||||
apply { spec.update(config, action) }
|
apply { spec.update(config, action) }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update configuration using given specification
|
* Update configuration using given specification
|
||||||
*/
|
*/
|
||||||
fun <C : Specification, S : SpecificationBuilder<C>> Specification.update(spec: S, action: C.() -> Unit) =
|
fun <C : Specification, S : SpecificationCompanion<C>> Specification.update(spec: S, action: C.() -> Unit) =
|
||||||
apply { spec.update(config, action) }
|
apply { spec.update(config, action) }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a style based on given specification
|
* Create a style based on given specification
|
||||||
*/
|
*/
|
||||||
fun <C : Specification, S : SpecificationBuilder<C>> S.createStyle(action: C.() -> Unit): Meta =
|
fun <C : Specification, S : SpecificationCompanion<C>> S.createStyle(action: C.() -> Unit): Meta =
|
||||||
Config().also { update(it, action) }
|
Config().also { update(it, action) }
|
||||||
|
|
||||||
|
|
||||||
fun <C : Specification> Specification.spec(spec: SpecificationBuilder<C>, key: String? = null) =
|
fun <C : Specification> Specification.spec(spec: SpecificationCompanion<C>, key: String? = null) =
|
||||||
ChildConfigDelegate<C>(key) { spec.wrap(config) }
|
ChildConfigDelegate<C>(key) { spec.wrap(config) }
|
@ -103,4 +103,6 @@ operator fun Name.plus(other: String): Name = this + other.toName()
|
|||||||
|
|
||||||
fun NameToken.toName() = Name(listOf(this))
|
fun NameToken.toName() = Name(listOf(this))
|
||||||
|
|
||||||
val EmptyName = Name(emptyList())
|
val EmptyName = Name(emptyList())
|
||||||
|
|
||||||
|
fun Name.isEmpty(): Boolean = this.length == 0
|
@ -44,7 +44,7 @@ interface Value {
|
|||||||
get() = listOf(this)
|
get() = listOf(this)
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val VALUE_TARGET = "value"
|
const val TYPE = "value"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert object to value
|
* Convert object to value
|
||||||
|
@ -1,25 +0,0 @@
|
|||||||
plugins {
|
|
||||||
id "org.jetbrains.kotlin.multiplatform"
|
|
||||||
}
|
|
||||||
|
|
||||||
repositories {
|
|
||||||
jcenter()
|
|
||||||
}
|
|
||||||
|
|
||||||
kotlin {
|
|
||||||
targets {
|
|
||||||
fromPreset(presets.jvm, 'jvm')
|
|
||||||
//fromPreset(presets.js, 'js')
|
|
||||||
// For ARM, preset should be changed to presets.iosArm32 or presets.iosArm64
|
|
||||||
// For Linux, preset should be changed to e.g. presets.linuxX64
|
|
||||||
// For MacOS, preset should be changed to e.g. presets.macosX64
|
|
||||||
//fromPreset(presets.iosX64, 'ios')
|
|
||||||
}
|
|
||||||
sourceSets {
|
|
||||||
commonMain {
|
|
||||||
dependencies {
|
|
||||||
api project(":dataforge-context")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
16
dataforge-workspace/build.gradle.kts
Normal file
16
dataforge-workspace/build.gradle.kts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
plugins {
|
||||||
|
kotlin("multiplatform")
|
||||||
|
}
|
||||||
|
|
||||||
|
kotlin {
|
||||||
|
jvm()
|
||||||
|
js()
|
||||||
|
sourceSets {
|
||||||
|
val commonMain by getting{
|
||||||
|
dependencies {
|
||||||
|
api(project(":dataforge-context"))
|
||||||
|
api(project(":dataforge-data"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,51 @@
|
|||||||
|
package hep.dataforge.workspace
|
||||||
|
|
||||||
|
import hep.dataforge.data.DataFilter
|
||||||
|
import hep.dataforge.data.DataNode
|
||||||
|
import hep.dataforge.data.DataTreeBuilder
|
||||||
|
import hep.dataforge.data.filter
|
||||||
|
import hep.dataforge.meta.Meta
|
||||||
|
import hep.dataforge.meta.MetaRepr
|
||||||
|
import hep.dataforge.meta.buildMeta
|
||||||
|
import hep.dataforge.names.EmptyName
|
||||||
|
import hep.dataforge.names.Name
|
||||||
|
import hep.dataforge.names.isEmpty
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A dependency of the task which allows to lazily create a data tree for single dependency
|
||||||
|
*/
|
||||||
|
sealed class Dependency : MetaRepr {
|
||||||
|
abstract fun apply(workspace: Workspace): DataNode<Any>
|
||||||
|
}
|
||||||
|
|
||||||
|
class DataDependency(val filter: DataFilter) : Dependency() {
|
||||||
|
override fun apply(workspace: Workspace): DataNode<Any> =
|
||||||
|
workspace.data.filter(filter)
|
||||||
|
|
||||||
|
override fun toMeta(): Meta = filter.config
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val all: DataDependency = DataDependency(DataFilter.build { })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class TaskModelDependency(val name: String, val meta: Meta, val placement: Name = EmptyName) : Dependency() {
|
||||||
|
override fun apply(workspace: Workspace): DataNode<Any> {
|
||||||
|
val model =
|
||||||
|
workspace.tasks[name]?.build(workspace, meta) ?: error("Task with name $name not found in $workspace")
|
||||||
|
|
||||||
|
val task = workspace.tasks[model.name] ?: error("Task with name ${model.name} is not found in the workspace")
|
||||||
|
if (task.isTerminal) TODO("Support terminal task")
|
||||||
|
val result = task.run(model)
|
||||||
|
return if (placement.isEmpty()) {
|
||||||
|
result
|
||||||
|
} else {
|
||||||
|
DataTreeBuilder<Any>().apply { this[placement] = result }.build()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toMeta(): Meta = buildMeta {
|
||||||
|
"name" to name
|
||||||
|
"meta" to meta
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,52 @@
|
|||||||
|
package hep.dataforge.workspace
|
||||||
|
|
||||||
|
import hep.dataforge.context.Named
|
||||||
|
import hep.dataforge.data.DataNode
|
||||||
|
import hep.dataforge.meta.Meta
|
||||||
|
import hep.dataforge.provider.Type
|
||||||
|
import hep.dataforge.workspace.Task.Companion.TYPE
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
|
@Type(TYPE)
|
||||||
|
interface Task<out R : Any> : Named {
|
||||||
|
/**
|
||||||
|
* Terminal task is the one that could not build model lazily
|
||||||
|
*/
|
||||||
|
val isTerminal: Boolean get() = false
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The explicit type of the node returned by the task
|
||||||
|
*/
|
||||||
|
val type: KClass<out R>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build a model for this task
|
||||||
|
*
|
||||||
|
* @param workspace
|
||||||
|
* @param taskConfig
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
fun build(workspace: Workspace, taskConfig: Meta): TaskModel
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the model is valid and is acceptable by the task. Throw exception if not.
|
||||||
|
*
|
||||||
|
* @param model
|
||||||
|
*/
|
||||||
|
fun validate(model: TaskModel) {
|
||||||
|
if(this.name != model.name) error("The task $name could not be run with model from task ${model.name}")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run given task model. Type check expected to be performed before actual
|
||||||
|
* calculation.
|
||||||
|
*
|
||||||
|
* @param model
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
fun run(model: TaskModel): DataNode<R>
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val TYPE = "task"
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,97 @@
|
|||||||
|
/*
|
||||||
|
* To change this license header, choose License Headers in Project Properties.
|
||||||
|
* To change this template file, choose Tools | Templates
|
||||||
|
* and open the template in the editor.
|
||||||
|
*/
|
||||||
|
package hep.dataforge.workspace
|
||||||
|
|
||||||
|
import hep.dataforge.data.DataFilter
|
||||||
|
import hep.dataforge.data.DataTree
|
||||||
|
import hep.dataforge.data.DataTreeBuilder
|
||||||
|
import hep.dataforge.meta.*
|
||||||
|
import hep.dataforge.names.EmptyName
|
||||||
|
import hep.dataforge.names.Name
|
||||||
|
import hep.dataforge.names.toName
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A model for task execution
|
||||||
|
* @param name the name of the task
|
||||||
|
* @param meta the meta for the task (not for the whole configuration)
|
||||||
|
* @param dependencies a list of direct dependencies for this task
|
||||||
|
*/
|
||||||
|
data class TaskModel(
|
||||||
|
val name: String,
|
||||||
|
val meta: Meta,
|
||||||
|
val dependencies: Collection<Dependency>
|
||||||
|
) : MetaRepr {
|
||||||
|
//TODO provide a way to get task descriptor
|
||||||
|
//TODO add pre-run check of task result type?
|
||||||
|
|
||||||
|
override fun toMeta(): Meta = buildMeta {
|
||||||
|
"name" to name
|
||||||
|
"meta" to meta
|
||||||
|
"dependsOn" to {
|
||||||
|
val dataDependencies = dependencies.filterIsInstance<DataDependency>()
|
||||||
|
val taskDependencies = dependencies.filterIsInstance<TaskModelDependency>()
|
||||||
|
setIndexed("data".toName(), dataDependencies.map { it.toMeta() })
|
||||||
|
setIndexed("task".toName(), taskDependencies.map { it.toMeta() }) { taskDependencies[it].name }
|
||||||
|
//TODO ensure all dependencies are listed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build input for the task
|
||||||
|
*/
|
||||||
|
fun TaskModel.buildInput(workspace: Workspace): DataTree<Any> {
|
||||||
|
return DataTreeBuilder<Any>().apply {
|
||||||
|
dependencies.asSequence().flatMap { it.apply(workspace).dataSequence() }.forEach { (name, data) ->
|
||||||
|
//TODO add concise error on replacement
|
||||||
|
this[name] = data
|
||||||
|
}
|
||||||
|
}.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A builder for [TaskModel]
|
||||||
|
*/
|
||||||
|
class TaskModelBuilder(val name: String, meta: Meta = EmptyMeta) {
|
||||||
|
/**
|
||||||
|
* Meta for current task. By default uses the whole input meta
|
||||||
|
*/
|
||||||
|
var meta: MetaBuilder = meta.builder()
|
||||||
|
val dependencies = HashSet<Dependency>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add dependency for
|
||||||
|
*/
|
||||||
|
fun dependsOn(name: String, meta: Meta, placement: Name = EmptyName) {
|
||||||
|
dependencies.add(TaskModelDependency(name, meta, placement))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add custom data dependency
|
||||||
|
*/
|
||||||
|
fun data(action: DataFilter.() -> Unit) {
|
||||||
|
dependencies.add(DataDependency(DataFilter.build(action)))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* User-friendly way to add data dependency
|
||||||
|
*/
|
||||||
|
fun data(pattern: String? = null, from: String? = null, to: String? = null) = data {
|
||||||
|
pattern?.let { this.pattern = it }
|
||||||
|
from?.let { this.from = it }
|
||||||
|
to?.let { this.to = it }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add all data as root node
|
||||||
|
*/
|
||||||
|
fun allData() {
|
||||||
|
dependencies.add(DataDependency.all)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun build(): TaskModel = TaskModel(name, meta.seal(), dependencies)
|
||||||
|
}
|
@ -0,0 +1,55 @@
|
|||||||
|
package hep.dataforge.workspace
|
||||||
|
|
||||||
|
import hep.dataforge.context.ContextAware
|
||||||
|
import hep.dataforge.context.Named
|
||||||
|
import hep.dataforge.data.Data
|
||||||
|
import hep.dataforge.data.DataNode
|
||||||
|
import hep.dataforge.meta.Meta
|
||||||
|
import hep.dataforge.names.Name
|
||||||
|
import hep.dataforge.names.toName
|
||||||
|
import hep.dataforge.provider.Provider
|
||||||
|
import hep.dataforge.provider.Type
|
||||||
|
|
||||||
|
|
||||||
|
@Type(Workspace.TYPE)
|
||||||
|
interface Workspace : ContextAware, Named, Provider {
|
||||||
|
/**
|
||||||
|
* The whole data node for current workspace
|
||||||
|
*/
|
||||||
|
val data: DataNode<Any>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* All targets associated with the workspace
|
||||||
|
*/
|
||||||
|
val targets: Map<String, Meta>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* All tasks associated with the workspace
|
||||||
|
*/
|
||||||
|
val tasks: Map<String, Task<*>>
|
||||||
|
|
||||||
|
override fun provideTop(target: String, name: Name): Any? {
|
||||||
|
return when (target) {
|
||||||
|
"target", Meta.TYPE -> targets[name.toString()]
|
||||||
|
Task.TYPE -> tasks[name.toString()]
|
||||||
|
Data.TYPE -> data[name]
|
||||||
|
DataNode.TYPE -> data.getNode(name)
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun listTop(target: String): Sequence<Name> {
|
||||||
|
return when (target) {
|
||||||
|
"target", Meta.TYPE -> targets.keys.asSequence().map { it.toName() }
|
||||||
|
Task.TYPE -> tasks.keys.asSequence().map { it.toName() }
|
||||||
|
Data.TYPE -> data.dataSequence().map { it.first }
|
||||||
|
DataNode.TYPE -> data.nodeSequence().map { it.first }
|
||||||
|
else -> emptySequence()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val TYPE = "workspace"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -13,5 +13,6 @@ include(
|
|||||||
":dataforge-meta-io",
|
":dataforge-meta-io",
|
||||||
":dataforge-context",
|
":dataforge-context",
|
||||||
":dataforge-data",
|
":dataforge-data",
|
||||||
":dataforge-io"
|
":dataforge-io",
|
||||||
|
":dataforge-workspace"
|
||||||
)
|
)
|
||||||
|
Loading…
Reference in New Issue
Block a user