Merge pull request #7 from altavir/dev

Dev
This commit is contained in:
Alexander Nozik 2019-05-09 13:58:11 +03:00 committed by GitHub
commit a7e1009d2b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
91 changed files with 3022 additions and 842 deletions

4
.gitignore vendored
View File

@ -1,9 +1,9 @@
.idea/ .idea/
*.iws *.iws
*/out/** out/
.gradle .gradle
*/build/** build/
!gradle-wrapper.jar !gradle-wrapper.jar

View File

@ -1,142 +1,18 @@
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension val dataforgeVersion by extra("0.1.2")
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
buildscript {
val kotlinVersion: String by rootProject.extra("1.3.21")
val ioVersion: String by rootProject.extra("0.1.5")
val coroutinesVersion: String by rootProject.extra("1.1.1")
val atomicfuVersion: String by rootProject.extra("0.12.1")
val dokkaVersion: String by rootProject.extra("0.9.17")
val serializationVersion: String by rootProject.extra("0.10.0")
repositories {
jcenter()
maven("https://dl.bintray.com/kotlin/kotlin-eap")
}
dependencies {
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion")
classpath("org.jfrog.buildinfo:build-info-extractor-gradle:4+")
classpath("com.jfrog.bintray.gradle:gradle-bintray-plugin:1.8.4")
classpath("org.jetbrains.dokka:dokka-gradle-plugin:$dokkaVersion")
classpath("org.jetbrains.kotlin:kotlin-frontend-plugin:0.0.45")
classpath("org.openjfx:javafx-plugin:0.0.7")
}
}
plugins {
id("com.jfrog.artifactory") version "4.8.1" apply false
// id("org.jetbrains.kotlin.multiplatform") apply false
}
allprojects { allprojects {
apply(plugin = "maven")
apply(plugin = "maven-publish")
apply(plugin = "com.jfrog.artifactory")
repositories { repositories {
jcenter() jcenter()
maven("https://kotlin.bintray.com/kotlinx") maven("https://kotlin.bintray.com/kotlinx")
} }
group = "hep.dataforge" group = "hep.dataforge"
version = "0.1.1-dev-5" version = dataforgeVersion
// apply bintray configuration
apply(from = "${rootProject.rootDir}/gradle/bintray.gradle")
//apply artifactory configuration
apply(from = "${rootProject.rootDir}/gradle/artifactory.gradle")
} }
subprojects { subprojects {
if (name.startsWith("dataforge")) {
// dokka { apply(plugin = "npm-bintray")
// outputFormat = "html" apply(plugin = "npm-artifactory")
// outputDirectory = javadoc.destinationDir
// }
//
// task dokkaJar (type: Jar, dependsOn: dokka) {
// from javadoc . destinationDir
// classifier = "javadoc"
// }
// Create empty jar for sources classifier to satisfy maven requirements
val stubSources by tasks.registering(Jar::class) {
archiveClassifier.set("sources")
//from(sourceSets.main.get().allSource)
} }
// Create empty jar for javadoc classifier to satisfy maven requirements
val stubJavadoc by tasks.registering(Jar::class) {
archiveClassifier.set("javadoc")
}
tasks.withType<KotlinCompile> {
kotlinOptions{
jvmTarget = "1.8"
}
}
afterEvaluate {
extensions.findByType<KotlinMultiplatformExtension>()?.apply {
jvm {
compilations.all {
kotlinOptions {
jvmTarget = "1.8"
}
}
}
js {
compilations.all {
tasks.getByName(compileKotlinTaskName) {
kotlinOptions {
metaInfo = true
sourceMap = true
sourceMapEmbedSources = "always"
moduleKind = "umd"
}
}
}
configure(listOf(compilations["main"])) {
tasks.getByName(compileKotlinTaskName) {
kotlinOptions {
main = "call"
}
}
}
}
targets.all {
sourceSets.all {
languageSettings.progressiveMode = true
}
}
configure<PublishingExtension> {
publications.filterIsInstance<MavenPublication>().forEach { publication ->
if (publication.name == "kotlinMultiplatform") {
// for our root metadata publication, set artifactId with a package and project name
publication.artifactId = project.name
} else {
// for targets, set artifactId with a package, project name and target name (e.g. iosX64)
publication.artifactId = "${project.name}-${publication.name}"
}
}
targets.all {
val publication = publications.findByName(name) as MavenPublication
// Patch publications with fake javadoc
publication.artifact(stubJavadoc.get())
}
}
}
}
} }

20
buildSrc/build.gradle.kts Normal file
View File

@ -0,0 +1,20 @@
plugins {
`kotlin-dsl`
}
repositories {
gradlePluginPortal()
jcenter()
}
val kotlinVersion = "1.3.31"
// Add plugins used in buildSrc as dependencies, also we should specify version only here
dependencies {
implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion")
implementation("org.jfrog.buildinfo:build-info-extractor-gradle:4.9.5")
implementation("com.jfrog.bintray.gradle:gradle-bintray-plugin:1.8.4")
implementation("org.jetbrains.dokka:dokka-gradle-plugin:0.9.18")
implementation("com.moowork.gradle:gradle-node-plugin:1.3.1")
implementation("org.openjfx:javafx-plugin:0.0.7")
}

View File

View File

@ -0,0 +1,9 @@
// Instead of defining runtime properties and use them dynamically
// define version in buildSrc and have autocompletion and compile-time check
// Also dependencies itself can be moved here
object Versions {
val ioVersion = "0.1.8"
val coroutinesVersion = "1.2.1"
val atomicfuVersion = "0.12.6"
val serializationVersion = "0.11.0"
}

View File

@ -0,0 +1,59 @@
import org.jetbrains.dokka.gradle.DokkaTask
plugins {
kotlin("multiplatform")
id("org.jetbrains.dokka")
`maven-publish`
}
kotlin {
val dokka by tasks.getting(DokkaTask::class) {
outputFormat = "html"
outputDirectory = "$buildDir/javadoc"
jdkVersion = 8
kotlinTasks {
// dokka fails to retrieve sources from MPP-tasks so we only define the jvm task
listOf(tasks.getByPath("compileKotlinJvm"))
}
sourceRoot {
// assuming only single source dir
path = sourceSets["commonMain"].kotlin.srcDirs.first().toString()
platforms = listOf("Common")
}
// although the JVM sources are now taken from the task,
// we still define the jvm source root to get the JVM marker in the generated html
sourceRoot {
// assuming only single source dir
path = sourceSets["jvmMain"].kotlin.srcDirs.first().toString()
platforms = listOf("JVM")
}
}
val javadocJar by tasks.registering(Jar::class) {
dependsOn(dokka)
archiveClassifier.set("javadoc")
from("$buildDir/javadoc")
}
publishing {
// publications.filterIsInstance<MavenPublication>().forEach { publication ->
// if (publication.name == "kotlinMultiplatform") {
// // for our root metadata publication, set artifactId with a package and project name
// publication.artifactId = project.name
// } else {
// // for targets, set artifactId with a package, project name and target name (e.g. iosX64)
// publication.artifactId = "${project.name}-${publication.name}"
// }
// }
targets.all {
val publication = publications.findByName(name) as MavenPublication
// Patch publications with fake javadoc
publication.artifact(javadocJar.get())
}
}
}

View File

@ -0,0 +1,44 @@
import com.moowork.gradle.node.npm.NpmTask
import com.moowork.gradle.node.task.NodeTask
import org.gradle.kotlin.dsl.*
import org.jetbrains.kotlin.gradle.tasks.Kotlin2JsCompile
plugins {
id("com.moowork.node")
kotlin("multiplatform")
}
node {
nodeModulesDir = file("$buildDir/node_modules")
}
val compileKotlinJs by tasks.getting(Kotlin2JsCompile::class)
val compileTestKotlinJs by tasks.getting(Kotlin2JsCompile::class)
val populateNodeModules by tasks.registering(Copy::class) {
dependsOn(compileKotlinJs)
from(compileKotlinJs.destinationDir)
kotlin.js().compilations["test"].runtimeDependencyFiles.forEach {
if (it.exists() && !it.isDirectory) {
from(zipTree(it.absolutePath).matching { include("*.js") })
}
}
into("$buildDir/node_modules")
}
val installMocha by tasks.registering(NpmTask::class) {
setWorkingDir(buildDir)
setArgs(listOf("install", "mocha"))
}
val runMocha by tasks.registering(NodeTask::class) {
dependsOn(compileTestKotlinJs, populateNodeModules, installMocha)
setScript(file("$buildDir/node_modules/mocha/bin/mocha"))
setArgs(listOf(compileTestKotlinJs.outputFile))
}
tasks["jsTest"].dependsOn(runMocha)

View File

@ -0,0 +1,38 @@
import groovy.lang.GroovyObject
import org.jfrog.gradle.plugin.artifactory.dsl.PublisherConfig
import org.jfrog.gradle.plugin.artifactory.dsl.ResolverConfig
plugins {
id("com.jfrog.artifactory")
}
artifactory {
val artifactoryUser: String? by project
val artifactoryPassword: String? by project
val artifactoryContextUrl = "http://npm.mipt.ru:8081/artifactory"
setContextUrl(artifactoryContextUrl)//The base Artifactory URL if not overridden by the publisher/resolver
publish(delegateClosureOf<PublisherConfig> {
repository(delegateClosureOf<GroovyObject> {
setProperty("repoKey", "gradle-dev-local")
setProperty("username", artifactoryUser)
setProperty("password", artifactoryPassword)
})
defaults(delegateClosureOf<GroovyObject>{
invokeMethod("publications", arrayOf("jvm", "js", "kotlinMultiplatform", "metadata"))
//TODO: This property is not available for ArtifactoryTask
//setProperty("publishBuildInfo", false)
setProperty("publishArtifacts", true)
setProperty("publishPom", true)
setProperty("publishIvy", false)
})
})
resolve(delegateClosureOf<ResolverConfig> {
repository(delegateClosureOf<GroovyObject> {
setProperty("repoKey", "gradle-dev")
setProperty("username", artifactoryUser)
setProperty("password", artifactoryPassword)
})
})
}

View File

@ -0,0 +1,97 @@
@file:Suppress("UnstableApiUsage")
import com.jfrog.bintray.gradle.BintrayExtension.PackageConfig
import com.jfrog.bintray.gradle.BintrayExtension.VersionConfig
// Old bintray.gradle script converted to real Gradle plugin (precompiled script plugin)
// It now has own dependencies and support type safe accessors
// Syntax is pretty close to what we had in Groovy
// (excluding Property.set and bintray dynamic configs)
plugins {
id("com.jfrog.bintray")
`maven-publish`
}
val vcs = "https://github.com/mipt-npm/kmath"
// Configure publishing
publishing {
repositories {
maven("https://bintray.com/mipt-npm/scientifik")
}
// Process each publication we have in this project
publications.filterIsInstance<MavenPublication>().forEach { publication ->
// use type safe pom config GSL insterad of old dynamic
publication.pom {
name.set(project.name)
description.set(project.description)
url.set(vcs)
licenses {
license {
name.set("The Apache Software License, Version 2.0")
url.set("http://www.apache.org/licenses/LICENSE-2.0.txt")
distribution.set("repo")
}
}
developers {
developer {
id.set("MIPT-NPM")
name.set("MIPT nuclear physics methods laboratory")
organization.set("MIPT")
organizationUrl.set("http://npm.mipt.ru")
}
}
scm {
url.set(vcs)
}
}
}
}
bintray {
// delegates for runtime properties
val bintrayUser: String? by project
val bintrayApiKey: String? by project
user = bintrayUser ?: System.getenv("BINTRAY_USER")
key = bintrayApiKey ?: System.getenv("BINTRAY_API_KEY")
publish = true
override = true // for multi-platform Kotlin/Native publishing
// We have to use delegateClosureOf because bintray supports only dynamic groovy syntax
// this is a problem of this plugin
pkg(delegateClosureOf<PackageConfig> {
userOrg = "mipt-npm"
repo = "scientifik"
name = "scientifik.kmath"
issueTrackerUrl = "https://github.com/mipt-npm/kmath/issues"
setLicenses("Apache-2.0")
vcsUrl = vcs
version(delegateClosureOf<VersionConfig> {
name = project.version.toString()
vcsTag = project.version.toString()
released = java.util.Date().toString()
})
})
tasks {
bintrayUpload {
dependsOn(publishToMavenLocal)
doFirst {
setPublications(project.publishing.publications
.filterIsInstance<MavenPublication>()
.filter { !it.name.contains("-test") && it.name != "kotlinMultiplatform" }
.map {
println("""Uploading artifact "${it.groupId}:${it.artifactId}:${it.version}" from publication "${it.name}""")
it.name //https://github.com/bintray/gradle-bintray-plugin/issues/256
})
}
}
}
}

View File

@ -0,0 +1,86 @@
import org.gradle.kotlin.dsl.*
plugins {
kotlin("multiplatform")
`maven-publish`
}
kotlin {
jvm {
compilations.all {
kotlinOptions {
jvmTarget = "1.8"
}
}
}
js {
compilations.all {
kotlinOptions {
metaInfo = true
sourceMap = true
sourceMapEmbedSources = "always"
moduleKind = "commonjs"
}
}
compilations.named("main") {
kotlinOptions {
main = "call"
}
}
}
sourceSets {
val commonMain by getting {
dependencies {
api(kotlin("stdlib"))
}
}
val commonTest by getting {
dependencies {
implementation(kotlin("test-common"))
implementation(kotlin("test-annotations-common"))
}
}
val jvmMain by getting {
dependencies {
api(kotlin("stdlib-jdk8"))
}
}
val jvmTest by getting {
dependencies {
implementation(kotlin("test"))
implementation(kotlin("test-junit"))
}
}
val jsMain by getting {
dependencies {
api(kotlin("stdlib-js"))
}
}
val jsTest by getting {
dependencies {
implementation(kotlin("test-js"))
}
}
}
targets.all {
sourceSets.all {
languageSettings.progressiveMode = true
languageSettings.enableLanguageFeature("InlineClasses")
}
}
apply(plugin = "dokka-publish")
// Apply JS test configuration
val runJsTests by ext(false)
if (runJsTests) {
apply(plugin = "js-test")
}
}

View File

@ -1,10 +1,10 @@
plugins { plugins {
kotlin("multiplatform") `npm-multiplatform`
} }
description = "Context and provider definitions" description = "Context and provider definitions"
val coroutinesVersion: String by rootProject.extra val coroutinesVersion: String = Versions.coroutinesVersion
kotlin { kotlin {
jvm() jvm()
@ -22,6 +22,7 @@ kotlin {
val jvmMain by getting { val jvmMain by getting {
dependencies { dependencies {
api("io.github.microutils:kotlin-logging:1.6.10") api("io.github.microutils:kotlin-logging:1.6.10")
api("ch.qos.logback:logback-classic:1.2.3")
api("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion") api("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion")
} }
} }

View File

@ -1,16 +1,15 @@
package hep.dataforge.context package hep.dataforge.context
import hep.dataforge.meta.Config import hep.dataforge.meta.EmptyMeta
import hep.dataforge.meta.Meta
import hep.dataforge.names.Name import hep.dataforge.names.Name
abstract class AbstractPlugin : Plugin { abstract class AbstractPlugin(override val meta: Meta = EmptyMeta) : Plugin {
private var _context: Context? = null private var _context: Context? = null
override val context: Context override val context: Context
get() = _context ?: error("Plugin $tag is not attached") get() = _context ?: error("Plugin $tag is not attached")
override val config = Config()
override fun attach(context: Context) { override fun attach(context: Context) {
this._context = context this._context = context
} }
@ -19,9 +18,7 @@ abstract class AbstractPlugin : Plugin {
this._context = null this._context = null
} }
//TODO make configuration activation-safe
override fun provideTop(target: String, name: Name): Any? = null override fun provideTop(target: String, name: Name): Any? = null
override fun listTop(target: String): Sequence<Name> = emptySequence() override fun listNames(target: String): Sequence<Name> = emptySequence()
} }

View File

@ -2,9 +2,10 @@ package hep.dataforge.context
import hep.dataforge.meta.* import hep.dataforge.meta.*
import hep.dataforge.names.Name import hep.dataforge.names.Name
import hep.dataforge.names.appendLeft
import hep.dataforge.names.toName import hep.dataforge.names.toName
import hep.dataforge.provider.Provider import hep.dataforge.provider.Provider
import hep.dataforge.provider.provideAll import hep.dataforge.provider.top
import hep.dataforge.values.Value import hep.dataforge.values.Value
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import mu.KLogger import mu.KLogger
@ -66,10 +67,10 @@ open class Context(final override val name: String, val parent: Context? = Globa
} }
} }
override fun listTop(target: String): Sequence<Name> { override fun listNames(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.TYPE -> properties.asValueSequence().map { it.first } Value.TYPE -> properties.values().map { it.first }
else -> emptySequence() else -> emptySequence()
} }
} }
@ -118,12 +119,13 @@ open class Context(final override val name: String, val parent: Context? = Globa
/** /**
* A sequences of all objects provided by plugins with given target and type * A sequences of all objects provided by plugins with given target and type
*/ */
fun Context.members(target: String): Sequence<Any> = fun Context.content(target: String): Map<Name, Any> = content<Any>(target)
plugins.asSequence().flatMap { it.provideAll(target) }
@JvmName("typedMembers") @JvmName("typedContent")
inline fun <reified T : Any> Context.members(target: String) = inline fun <reified T : Any> Context.content(target: String): Map<Name, T> =
members(target).filterIsInstance<T>() plugins.flatMap { plugin ->
plugin.top<T>(target).entries.map { (it.key.appendLeft(plugin.name)) to it.value }
}.associate { it }
/** /**

View File

@ -1,8 +1,7 @@
package hep.dataforge.context package hep.dataforge.context
import hep.dataforge.meta.Config
import hep.dataforge.meta.MetaBuilder import hep.dataforge.meta.MetaBuilder
import hep.dataforge.meta.configure import hep.dataforge.meta.buildMeta
/** /**
* A convenience builder for context * A convenience builder for context
@ -19,11 +18,11 @@ class ContextBuilder(var name: String = "@anonimous", val parent: Context = Glob
plugins.add(plugin) plugins.add(plugin)
} }
fun plugin(tag: PluginTag, action: Config.() -> Unit) { fun plugin(tag: PluginTag, action: MetaBuilder.() -> Unit) {
plugins.add(PluginRepository.fetch(tag).configure(action)) plugins.add(PluginRepository.fetch(tag, buildMeta(action)))
} }
fun plugin(name: String, group: String = "", version: String = "", action: Config.() -> Unit) { fun plugin(name: String, group: String = "", version: String = "", action: MetaBuilder.() -> Unit) {
plugin(PluginTag(name, group, version), action) plugin(PluginTag(name, group, version), action)
} }

View File

@ -1,6 +1,5 @@
package hep.dataforge.context package hep.dataforge.context
import hep.dataforge.meta.Configurable
import hep.dataforge.meta.Meta import hep.dataforge.meta.Meta
import hep.dataforge.meta.MetaRepr import hep.dataforge.meta.MetaRepr
import hep.dataforge.meta.buildMeta import hep.dataforge.meta.buildMeta
@ -22,7 +21,7 @@ import hep.dataforge.provider.Provider
* *
* @author Alexander Nozik * @author Alexander Nozik
*/ */
interface Plugin : Named, ContextAware, Provider, MetaRepr, Configurable { interface Plugin : Named, ContextAware, Provider, MetaRepr {
/** /**
* Get tag for this plugin * Get tag for this plugin
@ -31,13 +30,14 @@ interface Plugin : Named, ContextAware, Provider, MetaRepr, Configurable {
*/ */
val tag: PluginTag val tag: PluginTag
val meta: Meta
/** /**
* The name of this plugin ignoring version and group * The name of this plugin ignoring version and group
* *
* @return * @return
*/ */
override val name: String override val name: String get() = tag.name
get() = tag.name
/** /**
* Plugin dependencies which are required to attach this plugin. Plugin * Plugin dependencies which are required to attach this plugin. Plugin
@ -46,7 +46,7 @@ interface Plugin : Named, ContextAware, Provider, MetaRepr, Configurable {
* *
* @return * @return
*/ */
fun dependsOn(): List<PluginTag> = emptyList() fun dependsOn(): List<PluginFactory<*>> = emptyList()
/** /**
* Start this plugin and attach registration info to the context. This method * Start this plugin and attach registration info to the context. This method
@ -67,7 +67,7 @@ interface Plugin : Named, ContextAware, Provider, MetaRepr, Configurable {
"context" to context.name "context" to context.name
"type" to this::class.simpleName "type" to this::class.simpleName
"tag" to tag "tag" to tag
"meta" to config "meta" to meta
} }
companion object { companion object {

View File

@ -1,6 +1,9 @@
package hep.dataforge.context package hep.dataforge.context
import hep.dataforge.meta.* import hep.dataforge.meta.EmptyMeta
import hep.dataforge.meta.Meta
import hep.dataforge.meta.MetaBuilder
import hep.dataforge.meta.buildMeta
import kotlin.reflect.KClass import kotlin.reflect.KClass
/** /**
@ -112,12 +115,21 @@ class PluginManager(override val context: Context) : ContextAware, Iterable<Plug
fun load(tag: PluginTag, meta: Meta = EmptyMeta): Plugin { fun load(tag: PluginTag, meta: Meta = EmptyMeta): Plugin {
val loaded = get(tag, false) val loaded = get(tag, false)
return when { return when {
loaded == null -> load(PluginRepository.fetch(tag)).configure(meta) loaded == null -> load(PluginRepository.fetch(tag,meta))
loaded.config == meta -> loaded // if meta is the same, return existing plugin loaded.meta == meta -> loaded // if meta is the same, return existing plugin
else -> throw RuntimeException("Can't load plugin with tag $tag. Plugin with this tag and different configuration already exists in context.") else -> throw RuntimeException("Can't load plugin with tag $tag. Plugin with this tag and different configuration already exists in context.")
} }
} }
fun load(factory: PluginFactory<*>, meta: Meta = EmptyMeta): Plugin{
val loaded = get(factory.tag, false)
return when {
loaded == null -> load(factory(meta))
loaded.meta == meta -> loaded // if meta is the same, return existing plugin
else -> throw RuntimeException("Can't load plugin with tag ${factory.tag}. Plugin with this tag and different configuration already exists in context.")
}
}
/** /**
* Load plugin by its class and meta. Ignore if plugin with this meta is already loaded. * Load plugin by its class and meta. Ignore if plugin with this meta is already loaded.
* Throw an exception if there exists plugin with the same type, but different meta * Throw an exception if there exists plugin with the same type, but different meta
@ -126,7 +138,7 @@ class PluginManager(override val context: Context) : ContextAware, Iterable<Plug
val loaded = get(type, false) val loaded = get(type, false)
return when { return when {
loaded == null -> { loaded == null -> {
val plugin = PluginRepository.list().first { it.type == type }.build(meta) val plugin = PluginRepository.list().first { it.type == type }.invoke(meta)
if (type.isInstance(plugin)) { if (type.isInstance(plugin)) {
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
load(plugin as T) load(plugin as T)
@ -134,7 +146,7 @@ class PluginManager(override val context: Context) : ContextAware, Iterable<Plug
error("Corrupt type information in plugin repository") error("Corrupt type information in plugin repository")
} }
} }
loaded.config == meta -> loaded // if meta is the same, return existing plugin loaded.meta == meta -> loaded // if meta is the same, return existing plugin
else -> throw RuntimeException("Can't load plugin with type $type. Plugin with this type and different configuration already exists in context.") else -> throw RuntimeException("Can't load plugin with type $type. Plugin with this type and different configuration already exists in context.")
} }
} }

View File

@ -1,48 +1,49 @@
package hep.dataforge.context package hep.dataforge.context
import hep.dataforge.meta.EmptyMeta
import hep.dataforge.meta.Meta import hep.dataforge.meta.Meta
import hep.dataforge.meta.configure
import kotlin.reflect.KClass import kotlin.reflect.KClass
interface PluginFactory { interface PluginFactory<T : Plugin> {
val tag: PluginTag val tag: PluginTag
val type: KClass<out Plugin> val type: KClass<out T>
fun build(): Plugin operator fun invoke(meta: Meta = EmptyMeta): T
} }
fun PluginFactory.build(meta: Meta) = build().configure(meta)
expect object PluginRepository { expect object PluginRepository {
fun register(factory: PluginFactory) fun register(factory: PluginFactory<*>)
/** /**
* List plugins available in the repository * List plugins available in the repository
*/ */
fun list(): Sequence<PluginFactory> fun list(): Sequence<PluginFactory<*>>
} }
/** /**
* Fetch specific plugin and instantiate it with given meta * Fetch specific plugin and instantiate it with given meta
*/ */
fun PluginRepository.fetch(tag: PluginTag): Plugin = fun PluginRepository.fetch(tag: PluginTag, meta: Meta = EmptyMeta): Plugin =
PluginRepository.list().find { it.tag.matches(tag) }?.build() list().find { it.tag.matches(tag) }?.invoke(meta) ?: error("Plugin with tag $tag not found in the repository")
?: error("Plugin with tag $tag not found in the repository")
fun PluginRepository.register(tag: PluginTag, type: KClass<out Plugin>, constructor: () -> Plugin) { fun <T : Plugin> PluginRepository.register(
val factory = object : PluginFactory { tag: PluginTag,
type: KClass<out T>,
constructor: (Meta) -> T
): PluginFactory<T> {
val factory = object : PluginFactory<T> {
override val tag: PluginTag = tag override val tag: PluginTag = tag
override val type: KClass<out Plugin> = type override val type: KClass<out T> = type
override fun build(): Plugin = constructor() override fun invoke(meta: Meta): T = constructor(meta)
} }
PluginRepository.register(factory) register(factory)
return factory
} }
inline fun <reified T : Plugin> PluginRepository.register(tag: PluginTag, noinline constructor: () -> T) = inline fun <reified T : Plugin> PluginRepository.register(tag: PluginTag, noinline constructor: (Meta) -> T) =
register(tag, T::class, constructor) register(tag, T::class, constructor)
fun PluginRepository.register(plugin: Plugin) = register(plugin.tag, plugin::class) { plugin } fun PluginRepository.register(plugin: Plugin) = register(plugin.tag, plugin::class) { plugin }

View File

@ -17,6 +17,7 @@ package hep.dataforge.provider
import hep.dataforge.names.Name import hep.dataforge.names.Name
import hep.dataforge.names.toName import hep.dataforge.names.toName
import kotlin.jvm.JvmName
/** /**
* A marker utility interface for providers. * A marker utility interface for providers.
@ -51,7 +52,7 @@ interface Provider {
* @param target * @param target
* @return * @return
*/ */
fun listTop(target: String): Sequence<Name> fun listNames(target: String): Sequence<Name>
} }
fun Provider.provide(path: Path, targetOverride: String? = null): Any? { fun Provider.provide(path: Path, targetOverride: String? = null): Any? {
@ -77,15 +78,23 @@ inline fun <reified T : Any> Provider.provide(path: String): T? {
return provide(Path.parse(path)) as? T return provide(Path.parse(path)) as? T
} }
inline fun <reified T : Any> Provider.provide(target: String, name: String): T? { inline fun <reified T : Any> Provider.provide(target: String, name: Name): T? {
return provide(PathToken(name.toName(), target).toPath()) as? T return provide(PathToken(name, target).toPath()) as? T
} }
inline fun <reified T : Any> Provider.provide(target: String, name: String): T? =
provide(target, name.toName())
/** /**
* [Sequence] of all elements with given target * A top level content with names
*/ */
fun Provider.provideAll(target: String): Sequence<Any> { fun Provider.top(target: String): Map<Name, Any> = top<Any>(target)
return listTop(target).map { provideTop(target, it) ?: error("The element $it is declared but not provided") }
@JvmName("typedTop")
inline fun <reified T : Any> Provider.top(target: String): Map<Name, T> {
return listNames(target).associate {
it to (provideTop(target, it) as? T ?: error("The element $it is declared but not provided"))
}
} }

View File

@ -0,0 +1,40 @@
package hep.dataforge.context
import hep.dataforge.names.Name
import hep.dataforge.names.appendLeft
import hep.dataforge.names.toName
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue
class ContextTest {
class DummyPlugin : AbstractPlugin() {
override val tag get() = PluginTag("test")
override fun provideTop(target: String, name: Name): Any? {
return when (target) {
"test" -> return name
else -> super.provideTop(target, name)
}
}
override fun listNames(target: String): Sequence<Name> {
return when (target) {
"test" -> sequenceOf("a", "b", "c.d").map { it.toName() }
else -> super.listNames(target)
}
}
}
@Test
fun testPluginManager() {
Global.plugins.load(DummyPlugin())
val members = Global.content<Name>("test")
assertEquals(3, members.count())
members.forEach {
assertTrue{it.key == it.value.appendLeft("test")}
}
}
}

View File

@ -3,14 +3,14 @@ package hep.dataforge.context
actual object PluginRepository { actual object PluginRepository {
private val factories: MutableSet<PluginFactory> = HashSet() private val factories: MutableSet<PluginFactory<*>> = HashSet()
actual fun register(factory: PluginFactory) { actual fun register(factory: PluginFactory<*>) {
factories.add(factory) factories.add(factory)
} }
/** /**
* List plugins available in the repository * List plugins available in the repository
*/ */
actual fun list(): Sequence<PluginFactory> = factories.asSequence() actual fun list(): Sequence<PluginFactory<*>> = factories.asSequence()
} }

View File

@ -2,16 +2,16 @@ package hep.dataforge.context
actual object PluginRepository { actual object PluginRepository {
private val factories: MutableSet<PluginFactory> = HashSet() private val factories: MutableSet<PluginFactory<*>> = HashSet()
actual fun register(factory: PluginFactory) { actual fun register(factory: PluginFactory<*>) {
factories.add(factory) factories.add(factory)
} }
/** /**
* List plugins available in the repository * List plugins available in the repository
*/ */
actual fun list(): Sequence<PluginFactory> = actual fun list(): Sequence<PluginFactory<*>> =
factories.asSequence() + Global.services() factories.asSequence() + Global.services()
} }

View File

@ -1,11 +1,14 @@
package hep.dataforge.provider package hep.dataforge.provider
import hep.dataforge.context.Context import hep.dataforge.context.Context
import hep.dataforge.context.members import hep.dataforge.context.content
import hep.dataforge.names.Name
import kotlin.reflect.KClass import kotlin.reflect.KClass
import kotlin.reflect.full.findAnnotation import kotlin.reflect.full.findAnnotation
/**
*
*/
object Types { object Types {
operator fun get(cl: KClass<*>): String { operator fun get(cl: KClass<*>): String {
return cl.findAnnotation<Type>()?.id ?: cl.simpleName ?: "" return cl.findAnnotation<Type>()?.id ?: cl.simpleName ?: ""
@ -24,13 +27,20 @@ inline fun <reified T : Any> Provider.provideByType(name: String): T? {
return provide(target, name) return provide(target, name)
} }
inline fun <reified T : Any> Provider.provideAllByType(): Sequence<T> { inline fun <reified T : Any> Provider.provideByType(name: Name): T? {
val target = Types[T::class] val target = Types[T::class]
return provideAll(target).filterIsInstance<T>() return provide(target, name)
}
inline fun <reified T : Any> Provider.top(): Map<Name, T> {
val target = Types[T::class]
return listNames(target).associate { name ->
name to (provideByType<T>(name) ?: error("The element $name is declared but not provided"))
}
} }
/** /**
* A sequences of all objects provided by plugins with given target and type * A sequences of all objects provided by plugins with given target and type
*/ */
inline fun <reified T : Any> Context.members(): Sequence<T> = members<T>(Types[T::class]) inline fun <reified T : Any> Context.content(): Map<Name, T> = content<T>(Types[T::class])

View File

@ -1,8 +1,8 @@
plugins { plugins {
kotlin("multiplatform") `npm-multiplatform`
} }
val coroutinesVersion: String by rootProject.extra val coroutinesVersion: String = Versions.coroutinesVersion
kotlin { kotlin {
jvm() jvm()
@ -11,6 +11,7 @@ kotlin {
val commonMain by getting{ val commonMain by getting{
dependencies { dependencies {
api(project(":dataforge-meta")) api(project(":dataforge-meta"))
api(kotlin("reflect"))
api("org.jetbrains.kotlinx:kotlinx-coroutines-core-common:$coroutinesVersion") api("org.jetbrains.kotlinx:kotlinx-coroutines-core-common:$coroutinesVersion")
} }
} }

View File

@ -20,9 +20,10 @@ interface Action<in T : Any, out R : Any> {
} }
/** /**
* Action composition. The result is terminal if one of parts is terminal * Action composition. The result is terminal if one of its parts is terminal
*/ */
infix fun <T : Any, I : Any, R : Any> Action<T, I>.then(action: Action<I, R>): Action<T, R> { infix fun <T : Any, I : Any, R : Any> Action<T, I>.then(action: Action<I, R>): Action<T, R> {
// TODO introduce composite action and add optimize by adding action to the list
return object : Action<T, R> { return object : Action<T, R> {
override fun invoke(node: DataNode<T>, meta: Meta): DataNode<R> { override fun invoke(node: DataNode<T>, meta: Meta): DataNode<R> {
return action(this@then.invoke(node, meta), meta) return action(this@then.invoke(node, meta), meta)
@ -33,28 +34,19 @@ infix fun <T : Any, I : Any, R : Any> Action<T, I>.then(action: Action<I, R>): A
} }
} }
/**
* An action that performs the same transformation on each of input data nodes. Null results are ignored.
*/
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 {
node.dataSequence().forEach { (name, data) ->
val res = transform(name, data, meta)
if (res != null) {
set(name, res)
}
}
}
companion object { ///**
/** // * An action that performs the same transformation on each of input data nodes. Null results are ignored.
* A simple pipe that performs transformation on the data and copies input meta into the output // * The transformation is non-suspending because it is lazy.
*/ // */
inline fun <T : Any, reified R : Any> simple(noinline transform: suspend (Name, T, Meta) -> R) = //class PipeAction<in T : Any, out R : Any>(val transform: (Name, Data<T>, Meta) -> Data<R>?) : Action<T, R> {
PipeAction { name, data: Data<T>, meta -> // override fun invoke(node: DataNode<T>, meta: Meta): DataNode<R> = DataNode.build {
val goal = data.goal.pipe { transform(name, it, meta) } // node.data().forEach { (name, data) ->
return@PipeAction Data.of(goal, data.meta) // val res = transform(name, data, meta)
} // if (res != null) {
} // set(name, res)
} // }
// }
// }
//}

View File

@ -2,7 +2,7 @@ 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 kotlin.coroutines.CoroutineContext import kotlinx.coroutines.CoroutineScope
import kotlin.reflect.KClass import kotlin.reflect.KClass
/** /**
@ -29,19 +29,32 @@ interface Data<out T : Any> : MetaRepr {
const val TYPE = "data" 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> =
NamedData(name, of(type, goal, meta)) NamedData(name, of(type, goal, meta))
inline fun <reified T : Any> of(name: String, goal: Goal<T>, meta: Meta): Data<T> = inline fun <reified T : Any> of(name: String, goal: Goal<T>, meta: Meta): Data<T> =
of(name, T::class, goal, meta) of(name, T::class, goal, meta)
fun <T : Any> static(context: CoroutineContext, value: T, meta: Meta): Data<T> = fun <T : Any> static(scope: CoroutineScope, value: T, meta: Meta): Data<T> =
DataImpl(value::class, Goal.static(context, value), meta) DataImpl(value::class, Goal.static(scope, value), meta)
} }
} }
suspend fun <T: Any> Data<T>.await(): T = goal.await() /**
* Upcast a [Data] to a supertype
*/
inline fun <reified R : Any, reified T : R> Data<T>.cast(): Data<R> {
return Data.of(R::class, goal, meta)
}
fun <R : Any, T : R> Data<T>.cast(type: KClass<R>): Data<R> {
return Data.of(type, goal, meta)
}
suspend fun <T : Any> Data<T>.await(): T = goal.await()
/** /**
* Generic Data implementation * Generic Data implementation

View File

@ -4,14 +4,14 @@ import hep.dataforge.meta.*
import hep.dataforge.names.toName import hep.dataforge.names.toName
class DataFilter(override val config: Config) : Specification { class DataFilter(override val config: Config) : Specific {
var from by string() var from by string()
var to by string() var to by string()
var pattern by string("*.") var pattern by string("*.")
// val prefix by string() // val prefix by string()
// val suffix by string() // val suffix by string()
companion object : SpecificationCompanion<DataFilter> { companion object : Specification<DataFilter> {
override fun wrap(config: Config): DataFilter = DataFilter(config) override fun wrap(config: Config): DataFilter = DataFilter(config)
} }
} }
@ -22,15 +22,15 @@ class DataFilter(override val config: Config) : Specification {
fun <T : Any> DataNode<T>.filter(filter: DataFilter): DataNode<T> { fun <T : Any> DataNode<T>.filter(filter: DataFilter): DataNode<T> {
val sourceNode = filter.from?.let { getNode(it.toName()) } ?: this@filter val sourceNode = filter.from?.let { getNode(it.toName()) } ?: this@filter
val regex = filter.pattern.toRegex() val regex = filter.pattern.toRegex()
val targetNode = DataTreeBuilder<T>().apply { val targetNode = DataTreeBuilder(type).apply {
sourceNode.dataSequence().forEach { (name, data) -> sourceNode.data().forEach { (name, data) ->
if (name.toString().matches(regex)) { if (name.toString().matches(regex)) {
this[name] = data this[name] = data
} }
} }
} }
return filter.to?.let { return filter.to?.let {
DataTreeBuilder<T>().apply { this[it.toName()] = targetNode }.build() DataTreeBuilder(type).apply { this[it.toName()] = targetNode }.build()
} ?: targetNode.build() } ?: targetNode.build()
} }

View File

@ -1,14 +1,18 @@
package hep.dataforge.data package hep.dataforge.data
import hep.dataforge.names.Name import hep.dataforge.names.*
import hep.dataforge.names.NameToken import kotlin.reflect.KClass
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 * A tree-like data structure grouped into the node. All data inside the node must inherit its type
*/ */
interface DataNode<out T : Any> { interface DataNode<out T : Any> {
/**
* The minimal common ancestor to all data in the node
*/
val type: KClass<out T>
/** /**
* Get the specific data if it exists * Get the specific data if it exists
*/ */
@ -22,21 +26,23 @@ interface DataNode<out T : Any> {
/** /**
* Walk the tree upside down and provide all data nodes with full names * Walk the tree upside down and provide all data nodes with full names
*/ */
fun dataSequence(): Sequence<Pair<Name, Data<T>>> fun data(): Sequence<Pair<Name, Data<T>>>
/** /**
* A sequence of all nodes in the tree walking upside down, excluding self * A sequence of all nodes in the tree walking upside down, excluding self
*/ */
fun nodeSequence(): Sequence<Pair<Name, DataNode<T>>> fun nodes(): Sequence<Pair<Name, DataNode<T>>>
operator fun iterator(): Iterator<Pair<Name, Data<T>>> = dataSequence().iterator() operator fun iterator(): Iterator<Pair<Name, Data<T>>> = data().iterator()
companion object { companion object {
const val TYPE = "dataNode" const val TYPE = "dataNode"
fun <T : Any> build(block: DataTreeBuilder<T>.() -> Unit) = DataTreeBuilder<T>().apply(block).build() fun <T : Any> build(type: KClass<out T>, block: DataTreeBuilder<T>.() -> Unit) =
} DataTreeBuilder<T>(type).apply(block).build()
fun <T : Any> builder(type: KClass<out T>) = DataTreeBuilder(type)
}
} }
internal sealed class DataTreeItem<out T : Any> { internal sealed class DataTreeItem<out T : Any> {
@ -44,29 +50,32 @@ internal sealed class DataTreeItem<out T : Any> {
class Value<out T : Any>(val value: Data<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> { class DataTree<out T : Any> internal constructor(
override val type: KClass<out T>,
private val items: Map<NameToken, DataTreeItem<T>>
) : DataNode<T> {
//TODO add node-level meta? //TODO add node-level meta?
override fun get(name: Name): Data<T>? = when (name.length) { override fun get(name: Name): Data<T>? = when (name.length) {
0 -> error("Empty name") 0 -> error("Empty name")
1 -> (items[name.first()] as? DataTreeItem.Value)?.value 1 -> (items[name.first()] as? DataTreeItem.Value)?.value
else -> getNode(name.first()!!.toName())?.get(name.cutFirst()) else -> getNode(name.first()!!.asName())?.get(name.cutFirst())
} }
override fun getNode(name: Name): DataTree<T>? = when (name.length) { override fun getNode(name: Name): DataTree<T>? = when (name.length) {
0 -> this 0 -> this
1 -> (items[name.first()] as? DataTreeItem.Node)?.tree 1 -> (items[name.first()] as? DataTreeItem.Node)?.tree
else -> getNode(name.first()!!.toName())?.getNode(name.cutFirst()) else -> getNode(name.first()!!.asName())?.getNode(name.cutFirst())
} }
override fun dataSequence(): Sequence<Pair<Name, Data<T>>> { override fun data(): Sequence<Pair<Name, Data<T>>> {
return sequence { return sequence {
items.forEach { (head, tree) -> items.forEach { (head, tree) ->
when (tree) { when (tree) {
is DataTreeItem.Value -> yield(head.toName() to tree.value) is DataTreeItem.Value -> yield(head.asName() to tree.value)
is DataTreeItem.Node -> { is DataTreeItem.Node -> {
val subSequence = val subSequence =
tree.tree.dataSequence().map { (name, data) -> (head.toName() + name) to data } tree.tree.data().map { (name, data) -> (head.asName() + name) to data }
yieldAll(subSequence) yieldAll(subSequence)
} }
} }
@ -74,13 +83,13 @@ class DataTree<out T : Any> internal constructor(private val items: Map<NameToke
} }
} }
override fun nodeSequence(): Sequence<Pair<Name, DataNode<T>>> { override fun nodes(): Sequence<Pair<Name, DataNode<T>>> {
return sequence { return sequence {
items.forEach { (head, tree) -> items.forEach { (head, tree) ->
if (tree is DataTreeItem.Node) { if (tree is DataTreeItem.Node) {
yield(head.toName() to tree.tree) yield(head.asName() to tree.tree)
val subSequence = val subSequence =
tree.tree.nodeSequence().map { (name, node) -> (head.toName() + name) to node } tree.tree.nodes().map { (name, node) -> (head.asName() + name) to node }
yieldAll(subSequence) yieldAll(subSequence)
} }
} }
@ -96,7 +105,7 @@ private sealed class DataTreeBuilderItem<out T : Any> {
/** /**
* A builder for a DataTree. * A builder for a DataTree.
*/ */
class DataTreeBuilder<T : Any> { class DataTreeBuilder<T : Any>(private val type: KClass<out T>) {
private val map = HashMap<NameToken, DataTreeBuilderItem<T>>() private val map = HashMap<NameToken, DataTreeBuilderItem<T>>()
operator fun set(token: NameToken, node: DataTreeBuilder<T>) { operator fun set(token: NameToken, node: DataTreeBuilder<T>) {
@ -111,7 +120,7 @@ class DataTreeBuilder<T : Any> {
private fun buildNode(token: NameToken): DataTreeBuilder<T> { private fun buildNode(token: NameToken): DataTreeBuilder<T> {
return if (!map.containsKey(token)) { return if (!map.containsKey(token)) {
DataTreeBuilder<T>().also { map[token] = DataTreeBuilderItem.Node(it) } DataTreeBuilder<T>(type).also { map[token] = DataTreeBuilderItem.Node(it) }
} else { } else {
(map[token] as? DataTreeBuilderItem.Node ?: error("The node with name $token is occupied by leaf")).tree (map[token] as? DataTreeBuilderItem.Node ?: error("The node with name $token is occupied by leaf")).tree
} }
@ -156,7 +165,15 @@ class DataTreeBuilder<T : Any> {
/** /**
* Build and append node * Build and append node
*/ */
infix fun String.to(block: DataTreeBuilder<T>.() -> Unit) = set(toName(), DataTreeBuilder<T>().apply(block)) infix fun String.to(block: DataTreeBuilder<T>.() -> Unit) = set(toName(), DataTreeBuilder<T>(type).apply(block))
fun update(node: DataNode<T>){
node.data().forEach {
//TODO check if the place is occupied
this[it.first] = it.second
}
}
fun build(): DataTree<T> { fun build(): DataTree<T> {
val resMap = map.mapValues { (_, value) -> val resMap = map.mapValues { (_, value) ->
@ -165,28 +182,35 @@ class DataTreeBuilder<T : Any> {
is DataTreeBuilderItem.Node -> DataTreeItem.Node(value.tree.build()) is DataTreeBuilderItem.Node -> DataTreeItem.Node(value.tree.build())
} }
} }
return DataTree(resMap) return DataTree(type, resMap)
} }
} }
/** /**
* Generate a mutable builder from this node. Node content is not changed * Generate a mutable builder from this node. Node content is not changed
*/ */
fun <T : Any> DataNode<T>.builder(): DataTreeBuilder<T> = DataTreeBuilder<T>().apply { fun <T : Any> DataNode<T>.builder(): DataTreeBuilder<T> = DataTreeBuilder(type).apply {
dataSequence().forEach { (name, data) -> this[name] = data } data().forEach { (name, data) -> this[name] = data }
} }
/** /**
* Start computation for all goals in data node * Start computation for all goals in data node
*/ */
fun DataNode<*>.startAll() = dataSequence().forEach { (_, data) -> data.goal.start() } fun DataNode<*>.startAll() = data().forEach { (_, data) -> data.goal.start() }
fun <T : Any> DataNode<T>.filter(predicate: (Name, Data<T>) -> Boolean): DataNode<T> = DataNode.build { fun <T : Any> DataNode<T>.filter(predicate: (Name, Data<T>) -> Boolean): DataNode<T> = DataNode.build(type) {
dataSequence().forEach { (name, data) -> data().forEach { (name, data) ->
if (predicate(name, data)) { if (predicate(name, data)) {
this[name] = data this[name] = data
} }
} }
} }
fun <T: Any> DataNode<T>.first(): Data<T> = data().first().second
/**
* Check that node is compatible with given type meaning that each element could be cast to the type
*/
expect fun DataNode<*>.checkType(type: KClass<*>)
//fun <T : Any, R: T> DataNode<T>.filterIsInstance(type: KClass<R>): DataNode<R> = filter{_,data -> type.} //fun <T : Any, R: T> DataNode<T>.filterIsInstance(type: KClass<R>): DataNode<R> = filter{_,data -> type.}

View File

@ -2,33 +2,37 @@ package hep.dataforge.data
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
/** /**
* A special deferred with explicit dependencies and some additional information like progress and unique id * A special deferred with explicit dependencies and some additional information like progress and unique id
*/ */
interface Goal<out T> : Deferred<T>, CoroutineScope { interface Goal<out T> : Deferred<T>, CoroutineScope {
val scope: CoroutineScope
override val coroutineContext get() = scope.coroutineContext
val dependencies: Collection<Goal<*>> val dependencies: Collection<Goal<*>>
val status: String val totalWork: Double get() = dependencies.sumByDouble { totalWork } + (monitor?.totalWork ?: 0.0)
val workDone: Double get() = dependencies.sumByDouble { workDone } + (monitor?.workDone ?: 0.0)
val totalWork: Double val status: String get() = monitor?.status ?: ""
val workDone: Double
val progress: Double get() = workDone / totalWork val progress: Double get() = workDone / totalWork
companion object { companion object {
/** /**
* Create goal wrapping static value. This goal is always completed * Create goal wrapping static value. This goal is always completed
*/ */
fun <T> static(context: CoroutineContext, value: T): Goal<T> = fun <T> static(scope: CoroutineScope, value: T): Goal<T> =
StaticGoalImpl(context, CompletableDeferred(value)) StaticGoalImpl(scope, CompletableDeferred(value))
} }
} }
/** /**
* A monitor of goal state that could be accessed only form inside the goal * A monitor of goal state that could be accessed only form inside the goal
*/ */
class GoalMonitor { class GoalMonitor : CoroutineContext.Element {
override val key: CoroutineContext.Key<*> get() = GoalMonitor
var totalWork: Double = 1.0 var totalWork: Double = 1.0
var workDone: Double = 0.0 var workDone: Double = 0.0
var status: String = "" var status: String = ""
@ -46,26 +50,24 @@ class GoalMonitor {
fun finish() { fun finish() {
workDone = totalWork workDone = totalWork
} }
companion object : CoroutineContext.Key<GoalMonitor>
} }
val CoroutineScope.monitor: GoalMonitor? get() = coroutineContext[GoalMonitor]
private class GoalImpl<T>( private class GoalImpl<T>(
override val scope: CoroutineScope,
override val dependencies: Collection<Goal<*>>, override val dependencies: Collection<Goal<*>>,
val monitor: GoalMonitor,
deferred: Deferred<T> deferred: Deferred<T>
) : Goal<T>, Deferred<T> by deferred { ) : Goal<T>, Deferred<T> by deferred
override val coroutineContext: CoroutineContext get() = this
override val totalWork: Double get() = dependencies.sumByDouble { totalWork } + monitor.totalWork
override val workDone: Double get() = dependencies.sumByDouble { workDone } + monitor.workDone
override val status: String get() = monitor.status
}
private class StaticGoalImpl<T>(val context: CoroutineContext, deferred: CompletableDeferred<T>) : Goal<T>, private class StaticGoalImpl<T>(override val scope: CoroutineScope, deferred: CompletableDeferred<T>) : Goal<T>,
Deferred<T> by deferred { Deferred<T> by deferred {
override val dependencies: Collection<Goal<*>> get() = emptyList() override val dependencies: Collection<Goal<*>> get() = emptyList()
override val status: String get() = "" override val status: String get() = ""
override val totalWork: Double get() = 0.0 override val totalWork: Double get() = 0.0
override val workDone: Double get() = 0.0 override val workDone: Double get() = 0.0
override val coroutineContext: CoroutineContext get() = context
} }
@ -75,23 +77,32 @@ private class StaticGoalImpl<T>(val context: CoroutineContext, deferred: Complet
* *
* **Important:** Unlike regular deferred, the [Goal] is started lazily, so the actual calculation is called only when result is requested. * **Important:** Unlike regular deferred, the [Goal] is started lazily, so the actual calculation is called only when result is requested.
*/ */
fun <R> CoroutineScope.createGoal(dependencies: Collection<Goal<*>>, block: suspend GoalMonitor.() -> R): Goal<R> { fun <R> CoroutineScope.createGoal(
val monitor = GoalMonitor() dependencies: Collection<Goal<*>>,
val deferred = async(start = CoroutineStart.LAZY) { context: CoroutineContext = EmptyCoroutineContext,
block: suspend CoroutineScope.() -> R
): Goal<R> {
val deferred = async(context + GoalMonitor(), start = CoroutineStart.LAZY) {
dependencies.forEach { it.start() } dependencies.forEach { it.start() }
monitor.start() monitor?.start()
return@async supervisorScope { monitor.block() } //Running in supervisor scope in order to allow manual error handling
}.also { return@async supervisorScope {
monitor.finish() block().also {
monitor?.finish()
}
}
} }
return GoalImpl(dependencies, monitor, deferred) return GoalImpl(this, dependencies, deferred)
} }
/** /**
* Create a one-to-one goal based on existing goal * Create a one-to-one goal based on existing goal
*/ */
fun <T, R> Goal<T>.pipe(block: suspend GoalMonitor.(T) -> R): Goal<R> = createGoal(listOf(this)) { block(await()) } fun <T, R> Goal<T>.pipe(
context: CoroutineContext = EmptyCoroutineContext,
block: suspend CoroutineScope.(T) -> R
): Goal<R> = createGoal(listOf(this), context) { block(await()) }
/** /**
* Create a joining goal. * Create a joining goal.
@ -99,8 +110,22 @@ fun <T, R> Goal<T>.pipe(block: suspend GoalMonitor.(T) -> R): Goal<R> = createGo
*/ */
fun <T, R> Collection<Goal<T>>.join( fun <T, R> Collection<Goal<T>>.join(
scope: CoroutineScope = first(), scope: CoroutineScope = first(),
block: suspend GoalMonitor.(Collection<T>) -> R context: CoroutineContext = EmptyCoroutineContext,
): Goal<R> = block: suspend CoroutineScope.(Collection<T>) -> R
scope.createGoal(this) { ): Goal<R> = scope.createGoal(this, context) {
block(map { it.await() }) block(map { it.await() })
} }
/**
* A joining goal for a map
* @param K type of the map key
* @param T type of the input goal
* @param R type of the result goal
*/
fun <K, T, R> Map<K, Goal<T>>.join(
scope: CoroutineScope = values.first(),
context: CoroutineContext = EmptyCoroutineContext,
block: suspend CoroutineScope.(Map<K, T>) -> R
): Goal<R> = scope.createGoal(this.values, context) {
block(mapValues { it.value.await() })
}

View File

@ -0,0 +1,75 @@
/*
* Copyright 2015 Alexander Nozik.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package hep.dataforge.data
import hep.dataforge.meta.Meta
import hep.dataforge.meta.get
import hep.dataforge.meta.string
interface GroupRule {
operator fun <T : Any> invoke(node: DataNode<T>): Map<String, DataNode<T>>
}
/**
* The class to builder groups of content with annotation defined rules
*
* @author Alexander Nozik
*/
object GroupBuilder {
/**
* Create grouping rule that creates groups for different values of value
* field with name [key]
*
* @param key
* @param defaultTagValue
* @return
*/
fun byValue(key: String, defaultTagValue: String): GroupRule = object :
GroupRule {
override fun <T : Any> invoke(node: DataNode<T>): Map<String, DataNode<T>> {
val map = HashMap<String, DataTreeBuilder<T>>()
node.data().forEach { (name, data) ->
val tagValue = data.meta[key]?.string ?: defaultTagValue
map.getOrPut(tagValue) { DataNode.builder(node.type) }[name] = data
}
return map.mapValues { it.value.build() }
}
}
// @ValueDef(key = "byValue", required = true, info = "The name of annotation value by which grouping should be made")
// @ValueDef(
// key = "defaultValue",
// def = "default",
// info = "Default value which should be used for content in which the grouping value is not presented"
// )
fun byMeta(config: Meta): GroupRule {
//TODO expand grouping options
return config["byValue"]?.string?.let {
byValue(
it,
config["defaultValue"]?.string ?: "default"
)
}
?: object : GroupRule {
override fun <T : Any> invoke(node: DataNode<T>): Map<String, DataNode<T>> = mapOf("" to node)
}
}
}

View File

@ -0,0 +1,111 @@
package hep.dataforge.data
import hep.dataforge.meta.Laminate
import hep.dataforge.meta.Meta
import hep.dataforge.meta.MetaBuilder
import hep.dataforge.meta.builder
import hep.dataforge.names.Name
import hep.dataforge.names.toName
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
import kotlin.reflect.KClass
class JoinGroup<T : Any, R : Any>(var name: String, internal val node: DataNode<T>) {
var meta: MetaBuilder = MetaBuilder()
lateinit var result: suspend ActionEnv.(Map<Name, T>) -> R
fun result(f: suspend ActionEnv.(Map<Name, T>) -> R) {
this.result = f;
}
}
class JoinGroupBuilder<T : Any, R : Any>(val actionMeta: Meta) {
private val groupRules: MutableList<(DataNode<T>) -> List<JoinGroup<T, R>>> = ArrayList();
/**
* introduce grouping by value name
*/
fun byValue(tag: String, defaultTag: String = "@default", action: JoinGroup<T, R>.() -> Unit) {
groupRules += { node ->
GroupBuilder.byValue(tag, defaultTag).invoke(node).map {
JoinGroup<T, R>(it.key, it.value).apply(action)
}
}
}
/**
* Add a single fixed group to grouping rules
*/
fun group(groupName: String, filter: DataFilter, action: JoinGroup<T, R>.() -> Unit) {
groupRules += { node ->
listOf(
JoinGroup<T, R>(groupName, node.filter(filter)).apply(action)
)
}
}
fun group(groupName: String, filter: (Name, Data<T>) -> Boolean, action: JoinGroup<T, R>.() -> Unit) {
groupRules += { node ->
listOf(
JoinGroup<T, R>(groupName, node.filter(filter)).apply(action)
)
}
}
/**
* Apply transformation to the whole node
*/
fun result(resultName: String, f: suspend ActionEnv.(Map<Name, T>) -> R) {
groupRules += { node ->
listOf(JoinGroup<T, R>(resultName, node).apply { result(f) })
}
}
internal fun buildGroups(input: DataNode<T>): List<JoinGroup<T, R>> {
return groupRules.flatMap { it.invoke(input) }
}
}
/**
* The same rules as for KPipe
*/
class JoinAction<T : Any, R : Any>(
val inputType: KClass<T>,
val outputType: KClass<R>,
val context: CoroutineContext = EmptyCoroutineContext,
private val action: JoinGroupBuilder<T, R>.() -> Unit
) : Action<T, R> {
override fun invoke(node: DataNode<T>, meta: Meta): DataNode<R> {
node.checkType(inputType)
return DataNode.build(outputType) {
JoinGroupBuilder<T, R>(meta).apply(action).buildGroups(node).forEach { group ->
val laminate = Laminate(group.meta, meta)
val goalMap: Map<Name, Goal<T>> = group.node
.data()
.associate { it.first to it.second.goal }
val groupName: String = group.name;
val env = ActionEnv(groupName.toName(), laminate.builder())
val goal = goalMap.join(context = context) { group.result.invoke(env, it) }
val res = Data.of(outputType, goal, env.meta)
set(env.name, res)
}
}
}
}
operator fun <T> Map<Name,T>.get(name:String) = get(name.toName())

View File

@ -0,0 +1,65 @@
package hep.dataforge.data
import hep.dataforge.meta.*
import hep.dataforge.names.Name
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
import kotlin.reflect.KClass
class ActionEnv(val name: Name, val meta: Meta)
/**
* Action environment
*/
class PipeBuilder<T, R>(var name: Name, var meta: MetaBuilder) {
lateinit var result: suspend ActionEnv.(T) -> R
/**
* Calculate the result of goal
*/
fun result(f: suspend ActionEnv.(T) -> R) {
result = f;
}
}
class PipeAction<T : Any, R : Any>(
val inputType: KClass<T>,
val outputType: KClass<R>,
val context: CoroutineContext = EmptyCoroutineContext,
private val block: PipeBuilder<T, R>.() -> Unit
) : Action<T, R> {
override fun invoke(node: DataNode<T>, meta: Meta): DataNode<R> {
node.checkType(inputType)
return DataNode.build(outputType) {
node.data().forEach { (name, data) ->
//merging data meta with action meta (data meta is primary)
val oldMeta = meta.builder().apply { update(data.meta) }
// creating environment from old meta and name
val env = ActionEnv(name, oldMeta)
//applying transformation from builder
val builder = PipeBuilder<T, R>(name, oldMeta).apply(block)
//getting new name
val newName = builder.name
//getting new meta
val newMeta = builder.meta.seal()
//creating a goal with custom context if provided
val goal = data.goal.pipe(context) { builder.result(env, it) }
//setting the data node
this[newName] = Data.of(outputType, goal, newMeta)
}
}
}
}
inline fun <reified T : Any, reified R : Any> DataNode<T>.pipe(
meta: Meta,
context: CoroutineContext = EmptyCoroutineContext,
noinline action: PipeBuilder<T, R>.() -> Unit
): DataNode<R> = PipeAction(T::class, R::class, context, action).invoke(this, meta)

View File

@ -0,0 +1,69 @@
package hep.dataforge.data
import hep.dataforge.meta.Laminate
import hep.dataforge.meta.Meta
import hep.dataforge.meta.MetaBuilder
import hep.dataforge.meta.builder
import hep.dataforge.names.Name
import hep.dataforge.names.toName
import kotlin.collections.set
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
import kotlin.reflect.KClass
class FragmentRule<T : Any, R : Any>(val name: Name, var meta: MetaBuilder) {
lateinit var result: suspend (T) -> R
fun result(f: suspend (T) -> R) {
result = f;
}
}
class SplitBuilder<T : Any, R : Any>(val name: Name, val meta: Meta) {
internal val fragments: MutableMap<Name, FragmentRule<T, R>.() -> Unit> = HashMap()
/**
* Add new fragment building rule. If the framgent not defined, result won't be available even if it is present in the map
* @param name the name of a fragment
* @param rule the rule to transform fragment name and meta using
*/
fun fragment(name: String, rule: FragmentRule<T, R>.() -> Unit) {
fragments[name.toName()] = rule
}
}
class SplitAction<T : Any, R : Any>(
val inputType: KClass<T>,
val outputType: KClass<R>,
val context: CoroutineContext = EmptyCoroutineContext,
private val action: SplitBuilder<T, R>.() -> Unit
) : Action<T, R> {
override fun invoke(node: DataNode<T>, meta: Meta): DataNode<R> {
node.checkType(inputType)
return DataNode.build(outputType) {
node.data().forEach { (name, data) ->
val laminate = Laminate(data.meta, meta)
val split = SplitBuilder<T, R>(name, data.meta).apply(action)
// apply individual fragment rules to result
split.fragments.forEach { (fragmentName, rule) ->
val env = FragmentRule<T, R>(fragmentName, laminate.builder())
rule(env)
val goal = data.goal.pipe(context = context) { env.result(it) }
val res = Data.of(outputType, goal, env.meta)
set(env.name, res)
}
}
}
}
}

View File

@ -0,0 +1,10 @@
package hep.dataforge.data
import kotlin.reflect.KClass
/**
* Check that node is compatible with given type meaning that each element could be cast to the type
*/
actual fun DataNode<*>.checkType(type: KClass<*>) {
//Not supported in js yet
}

View File

@ -0,0 +1,46 @@
package hep.dataforge.data
import hep.dataforge.names.Name
import kotlin.reflect.KClass
import kotlin.reflect.full.isSubclassOf
fun <T : Any, R : Any> Data<T>.safeCast(type: KClass<R>): Data<R>? {
return if (type.isSubclassOf(type)) {
@Suppress("UNCHECKED_CAST")
Data.of(type, goal as Goal<R>, meta)
} else {
null
}
}
/**
* Filter a node by data and node type. Resulting node and its subnodes is guaranteed to have border type [type],
* but could contain empty nodes
*/
fun <T : Any, R : Any> DataNode<T>.cast(type: KClass<R>): DataNode<R> {
return if (this is CastDataNode) {
origin.cast(type)
} else {
CastDataNode(this, type)
}
}
inline fun <T : Any, reified R : Any> DataNode<T>.cast(): DataNode<R> = cast(R::class)
class CastDataNode<out T : Any>(val origin: DataNode<Any>, override val type: KClass<out T>) : DataNode<T> {
override fun get(name: Name): Data<T>? =
origin[name]?.safeCast(type)
override fun getNode(name: Name): DataNode<T>? {
return origin.getNode(name)?.cast(type)
}
override fun data(): Sequence<Pair<Name, Data<T>>> =
origin.data().mapNotNull { pair ->
pair.second.safeCast(type)?.let { pair.first to it }
}
override fun nodes(): Sequence<Pair<Name, DataNode<T>>> =
origin.nodes().map { it.first to it.second.cast(type) }
}

View File

@ -0,0 +1,13 @@
package hep.dataforge.data
import kotlin.reflect.KClass
import kotlin.reflect.full.isSuperclassOf
/**
* Check that node is compatible with given type meaning that each element could be cast to the type
*/
actual fun DataNode<*>.checkType(type: KClass<*>) {
if (!type.isSuperclassOf(type)) {
error("$type expected, but $type received")
}
}

View File

@ -1,16 +1,57 @@
plugins { plugins {
kotlin("multiplatform") `npm-multiplatform`
} }
description = "IO for meta"
val ioVersion: String = Versions.ioVersion
val serializationVersion: String = Versions.serializationVersion
kotlin { kotlin {
jvm() jvm()
js() js()
sourceSets { sourceSets {
val commonMain by getting{ val commonMain by getting{
dependencies { dependencies {
api(project(":dataforge-context")) api(project(":dataforge-meta"))
api(project(":dataforge-meta-io")) //implementation 'org.jetbrains.kotlin:kotlin-reflect'
api("org.jetbrains.kotlinx:kotlinx-serialization-runtime-common:$serializationVersion")
api("org.jetbrains.kotlinx:kotlinx-io:$ioVersion")
} }
} }
val commonTest by getting {
dependencies {
implementation("org.jetbrains.kotlin:kotlin-test-common")
implementation("org.jetbrains.kotlin:kotlin-test-annotations-common")
}
}
val jvmMain by getting {
dependencies {
api("org.jetbrains.kotlinx:kotlinx-serialization-runtime:$serializationVersion")
api("org.jetbrains.kotlinx:kotlinx-io-jvm:$ioVersion")
}
}
val jvmTest by getting {
dependencies {
implementation("org.jetbrains.kotlin:kotlin-test")
implementation("org.jetbrains.kotlin:kotlin-test-junit")
}
}
val jsMain by getting {
dependencies {
api("org.jetbrains.kotlinx:kotlinx-serialization-runtime-js:$serializationVersion")
api("org.jetbrains.kotlinx:kotlinx-io-js:$ioVersion")
}
}
val jsTest by getting {
dependencies {
implementation("org.jetbrains.kotlin:kotlin-test-js")
}
}
// iosMain {
// }
// iosTest {
// }
} }
} }

View File

@ -1,4 +1,4 @@
package hep.dataforge.meta.io package hep.dataforge.io
import hep.dataforge.meta.* import hep.dataforge.meta.*
import hep.dataforge.values.* import hep.dataforge.values.*
@ -8,8 +8,8 @@ import kotlinx.io.core.readText
import kotlinx.io.core.writeText import kotlinx.io.core.writeText
object BinaryMetaFormat : MetaFormat { object BinaryMetaFormat : MetaFormat {
override fun write(meta: Meta, out: Output) { override fun write(obj: Meta, out: Output) {
out.writeMeta(meta) out.writeMeta(obj)
} }
override fun read(input: Input): Meta { override fun read(input: Input): Meta {

View File

@ -1,21 +1,15 @@
package hep.dataforge.meta.io package hep.dataforge.io
import hep.dataforge.meta.Meta import hep.dataforge.meta.Meta
import hep.dataforge.meta.get import hep.dataforge.meta.get
import hep.dataforge.meta.string import hep.dataforge.meta.string
import kotlinx.io.core.Input
interface Envelope { interface Envelope {
val meta: Meta val meta: Meta
val data: Binary? val data: Input?
companion object { companion object {
// /**
// * Property keys
// */
// const val TYPE_PROPERTY = "type"
// const val META_TYPE_PROPERTY = "metaType"
// const val META_LENGTH_PROPERTY = "metaLength"
// const val DATA_LENGTH_PROPERTY = "dataLength"
/** /**
* meta keys * meta keys
@ -25,9 +19,16 @@ interface Envelope {
const val ENVELOPE_DATA_TYPE_KEY = "$ENVELOPE_NODE.dataType" const val ENVELOPE_DATA_TYPE_KEY = "$ENVELOPE_NODE.dataType"
const val ENVELOPE_DESCRIPTION_KEY = "$ENVELOPE_NODE.description" const val ENVELOPE_DESCRIPTION_KEY = "$ENVELOPE_NODE.description"
//const val ENVELOPE_TIME_KEY = "@envelope.time" //const val ENVELOPE_TIME_KEY = "@envelope.time"
} }
} }
class SimpleEnvelope(override val meta: Meta, val dataProvider: () -> Input?) : Envelope{
override val data: Input?
get() = dataProvider()
}
/** /**
* The purpose of the envelope * The purpose of the envelope
* *
@ -48,3 +49,4 @@ val Envelope.dataType: String? get() = meta[Envelope.ENVELOPE_DATA_TYPE_KEY].str
* @return * @return
*/ */
val Envelope.description: String? get() = meta[Envelope.ENVELOPE_DESCRIPTION_KEY].string val Envelope.description: String? get() = meta[Envelope.ENVELOPE_DESCRIPTION_KEY].string

View File

@ -0,0 +1,10 @@
package hep.dataforge.io
import kotlinx.io.core.Input
import kotlinx.io.core.Output
interface IOFormat<T : Any> {
fun write(obj: T, out: Output)
fun read(input: Input): T
}

View File

@ -0,0 +1,107 @@
package hep.dataforge.io
import hep.dataforge.meta.Meta
import hep.dataforge.meta.MetaItem
import hep.dataforge.names.NameToken
import hep.dataforge.names.toName
import hep.dataforge.values.*
import kotlinx.io.core.Input
import kotlinx.io.core.Output
import kotlinx.io.core.readText
import kotlinx.io.core.writeText
import kotlinx.serialization.json.*
object JsonMetaFormat : MetaFormat {
override fun write(obj: Meta, out: Output) {
val str = obj.toJson().toString()
out.writeText(str)
}
override fun read(input: Input): Meta {
val str = input.readText()
val json = Json.plain.parseJson(str)
if (json is JsonObject) {
return json.toMeta()
} else {
TODO("Non-object root not supported")
}
}
}
fun Value.toJson(): JsonElement {
return if(isList()){
JsonArray(list.map { it.toJson() })
} else {
when (type) {
ValueType.NUMBER -> JsonPrimitive(number)
ValueType.STRING -> JsonPrimitive(string)
ValueType.BOOLEAN -> JsonPrimitive(boolean)
ValueType.NULL -> JsonNull
}
}
}
fun Meta.toJson(): JsonObject {
val map = this.items.mapValues { entry ->
val value = entry.value
when (value) {
is MetaItem.ValueItem -> value.value.toJson()
is MetaItem.NodeItem -> value.node.toJson()
}
}.mapKeys { it.key.toString() }
return JsonObject(map)
}
fun JsonObject.toMeta() = JsonMeta(this)
class JsonMeta(val json: JsonObject) : Meta {
private fun JsonPrimitive.toValue(): Value {
return when (this) {
JsonNull -> Null
else -> this.content.parseValue() // Optimize number and boolean parsing
}
}
private operator fun MutableMap<String, MetaItem<JsonMeta>>.set(key: String, value: JsonElement) = when (value) {
is JsonPrimitive -> this[key] = MetaItem.ValueItem(value.toValue())
is JsonObject -> this[key] = MetaItem.NodeItem(value.toMeta())
is JsonArray -> {
when {
value.all { it is JsonPrimitive } -> {
val listValue = ListValue(
value.map {
//We already checked that all values are primitives
(it as JsonPrimitive).toValue()
}
)
this[key] = MetaItem.ValueItem(listValue)
}
else -> value.forEachIndexed { index, jsonElement ->
when (jsonElement) {
is JsonObject -> this["$key[$index]"] = MetaItem.NodeItem(JsonMeta(jsonElement))
is JsonPrimitive -> this["$key[$index]"] = MetaItem.ValueItem(jsonElement.toValue())
is JsonArray -> TODO("Nested arrays not supported")
}
}
}
}
}
override val items: Map<NameToken, MetaItem<JsonMeta>> by lazy {
val map = HashMap<String, MetaItem<JsonMeta>>()
json.forEach { (key, value) -> map[key] = value }
map.mapKeys { it.key.toName().first()!! }
}
}
class JsonMetaFormatFactory : MetaFormatFactory {
override val name: String = "json"
override val key: Short = 0x4a53//"JS"
override fun build() = JsonMetaFormat
}

View File

@ -1,15 +1,14 @@
package hep.dataforge.meta.io package hep.dataforge.io
import hep.dataforge.meta.Meta import hep.dataforge.meta.Meta
import kotlinx.io.core.* import kotlinx.io.core.BytePacketBuilder
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.toByteArray
/** /**
* A format for meta serialization * A format for meta serialization
*/ */
interface MetaFormat { interface MetaFormat: IOFormat<Meta>
fun write(meta: Meta, out: Output)
fun read(input: Input): Meta
}
/** /**
* ServiceLoader compatible factory * ServiceLoader compatible factory

View File

@ -1,63 +0,0 @@
package hep.dataforge.io
import hep.dataforge.context.AbstractPlugin
import hep.dataforge.context.Plugin
import hep.dataforge.context.PluginTag
import hep.dataforge.context.PluginTag.Companion.DATAFORGE_GROUP
import hep.dataforge.meta.EmptyMeta
import hep.dataforge.meta.Meta
import hep.dataforge.names.EmptyName
import hep.dataforge.names.Name
import kotlinx.coroutines.CoroutineDispatcher
import kotlin.reflect.KClass
/**
* A manager for outputs
*/
interface OutputManager : Plugin {
/**
* Provide an output for given name and stage.
*
* @param stage represents the node or directory for the output. Empty means root node.
* @param name represents the name inside the node.
* @param meta configuration for [Output] (not for rendered object)
*
*/
operator fun get(name: Name, stage: Name = EmptyName, meta: Meta = EmptyMeta): Output<Any>
/**
* Get an output specialized for giver ntype
*/
fun <T : Any> typed(type: KClass<T>, name: Name, stage: Name = EmptyName, meta: Meta = EmptyMeta): Output<T>
}
/**
* Get an output with given [name], [stage] and reified content type
*/
inline fun <reified T : Any> OutputManager.typed(
name: Name,
stage: Name = EmptyName,
meta: Meta = EmptyMeta
): Output<T> {
return typed(T::class, name, stage, meta)
}
/**
* System console output.
* The [ConsoleOutput] is used when no other [OutputManager] is provided.
*/
expect val ConsoleOutput: Output<Any>
object ConsoleOutputManager : AbstractPlugin(), OutputManager {
override val tag: PluginTag = PluginTag("output.console", group = DATAFORGE_GROUP)
override fun get(name: Name, stage: Name, meta: Meta): Output<Any> = ConsoleOutput
override fun <T : Any> typed(type: KClass<T>, name: Name, stage: Name, meta: Meta): Output<T> = ConsoleOutput
}
/**
* A dispatcher for output tasks.
*/
expect val OutputDispatcher : CoroutineDispatcher

View File

@ -1,4 +1,4 @@
package hep.dataforge.meta.io package hep.dataforge.io
import hep.dataforge.meta.buildMeta import hep.dataforge.meta.buildMeta
import kotlin.test.Test import kotlin.test.Test
@ -26,6 +26,7 @@ class MetaFormatTest {
"node" to { "node" to {
"b" to "DDD" "b" to "DDD"
"c" to 11.1 "c" to 11.1
"array" to doubleArrayOf(1.0,2.0,3.0)
} }
} }
val string = meta.asString(JsonMetaFormat) val string = meta.asString(JsonMetaFormat)

View File

@ -0,0 +1,2 @@
hep.dataforge.io.BinaryMetaFormatFactory
hep.dataforge.io.JsonMetaFormatFactory

View File

@ -1,57 +0,0 @@
plugins {
kotlin("multiplatform")
}
description = "IO for meta"
val ioVersion: String by rootProject.extra
val serializationVersion: String by rootProject.extra
kotlin {
jvm()
js()
sourceSets {
val commonMain by getting{
dependencies {
api(project(":dataforge-meta"))
//implementation 'org.jetbrains.kotlin:kotlin-reflect'
api("org.jetbrains.kotlinx:kotlinx-serialization-runtime-common:$serializationVersion")
api("org.jetbrains.kotlinx:kotlinx-io:$ioVersion")
}
}
val commonTest by getting {
dependencies {
implementation("org.jetbrains.kotlin:kotlin-test-common")
implementation("org.jetbrains.kotlin:kotlin-test-annotations-common")
}
}
val jvmMain by getting {
dependencies {
api("org.jetbrains.kotlinx:kotlinx-serialization-runtime:$serializationVersion")
api("org.jetbrains.kotlinx:kotlinx-io-jvm:$ioVersion")
}
}
val jvmTest by getting {
dependencies {
implementation("org.jetbrains.kotlin:kotlin-test")
implementation("org.jetbrains.kotlin:kotlin-test-junit")
}
}
val jsMain by getting {
dependencies {
api("org.jetbrains.kotlinx:kotlinx-serialization-runtime-js:$serializationVersion")
api("org.jetbrains.kotlinx:kotlinx-io-js:$ioVersion")
}
}
val jsTest by getting {
dependencies {
implementation("org.jetbrains.kotlin:kotlin-test-js")
}
}
// iosMain {
// }
// iosTest {
// }
}
}

View File

@ -1,7 +0,0 @@
package hep.dataforge.meta.io
import kotlinx.io.ByteBuffer
import kotlinx.io.core.Input
//TODO replace by abstraction
typealias Binary = Input

View File

@ -1,89 +0,0 @@
package hep.dataforge.meta.io
import hep.dataforge.meta.Meta
import hep.dataforge.meta.MetaItem
import hep.dataforge.names.NameToken
import hep.dataforge.values.*
import kotlinx.io.core.Input
import kotlinx.io.core.Output
import kotlinx.io.core.readText
import kotlinx.io.core.writeText
import kotlinx.serialization.json.*
object JsonMetaFormat : MetaFormat {
override fun write(meta: Meta, out: Output) {
val str = meta.toJson().toString()
out.writeText(str)
}
override fun read(input: Input): Meta {
val str = input.readText()
val json = Json.plain.parseJson(str)
if(json is JsonObject) {
return json.toMeta()
} else {
TODO("non-object root")
}
}
}
fun Value.toJson(): JsonElement {
return when (type) {
ValueType.NUMBER -> JsonPrimitive(number)
ValueType.STRING -> JsonPrimitive(string)
ValueType.BOOLEAN -> JsonPrimitive(boolean)
ValueType.NULL -> JsonNull
}
}
fun Meta.toJson(): JsonObject {
val map = this.items.mapValues { entry ->
val value = entry.value
when (value) {
is MetaItem.ValueItem -> value.value.toJson()
is MetaItem.NodeItem -> value.node.toJson()
}
}.mapKeys { it.key.toString() }
return JsonObject(map)
}
fun JsonElement.toMetaItem() = when (this) {
is JsonPrimitive -> MetaItem.ValueItem<JsonMeta>(this.toValue())
is JsonObject -> MetaItem.NodeItem(this.toMeta())
is JsonArray -> {
if (this.all { it is JsonPrimitive }) {
val value = ListValue(this.map { (it as JsonPrimitive).toValue() })
MetaItem.ValueItem<JsonMeta>(value)
} else {
TODO("mixed nodes json")
}
}
}
fun JsonObject.toMeta() = JsonMeta(this)
private fun JsonPrimitive.toValue(): Value {
return when (this) {
JsonNull -> Null
else -> this.content.parseValue() // Optimize number and boolean parsing
}
}
class JsonMeta(val json: JsonObject) : Meta {
override val items: Map<NameToken, MetaItem<out Meta>> by lazy {
json.mapKeys { NameToken(it.key) }.mapValues { entry ->
entry.value.toMetaItem()
}
}
}
class JsonMetaFormatFactory : MetaFormatFactory {
override val name: String = "json"
override val key: Short = 0x4a53//"JS"
override fun build() = JsonMetaFormat
}

View File

@ -1,2 +0,0 @@
hep.dataforge.meta.io.BinaryMetaFormatFactory
hep.dataforge.meta.io.JsonMetaFormatFactory

View File

@ -1,5 +1,5 @@
plugins { plugins {
kotlin("multiplatform") `npm-multiplatform`
} }
description = "Meta definition and basic operations on meta" description = "Meta definition and basic operations on meta"
@ -7,54 +7,4 @@ description = "Meta definition and basic operations on meta"
kotlin { kotlin {
jvm() jvm()
js() js()
sourceSets {
val commonMain by getting {
dependencies {
api(kotlin("stdlib"))
}
}
val commonTest by getting {
dependencies {
implementation(kotlin("test-common"))
implementation(kotlin("test-annotations-common"))
}
}
val jvmMain by getting {
dependencies {
api(kotlin("stdlib-jdk8"))
}
}
val jvmTest by getting {
dependencies {
implementation(kotlin("test"))
implementation(kotlin("test-junit"))
}
}
val jsMain by getting {
dependencies {
api(kotlin("stdlib-js"))
}
}
val jsTest by getting {
dependencies {
implementation(kotlin("test-js"))
}
}
// mingwMain {
// }
// mingwTest {
// }
}
} }
//tasks.withType<Kotlin2JsCompile>{
// kotlinOptions{
// metaInfo = true
// outputFile = "${project.buildDir.path}/js/${project.name}.js"
// sourceMap = true
// moduleKind = "umd"
// main = "call"
// }
//}

View File

@ -0,0 +1,5 @@
package hep.dataforge.descriptors
interface Described {
val descriptor: NodeDescriptor
}

View File

@ -0,0 +1,128 @@
/*
* Copyright 2018 Alexander Nozik.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* 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.descriptors
import hep.dataforge.meta.*
import hep.dataforge.names.NameToken
import hep.dataforge.names.toName
/**
* Descriptor for meta node. Could contain additional information for viewing
* and editing.
*
* @author Alexander Nozik
*/
class NodeDescriptor(override val config: Config) : Specific {
/**
* The name of this node
*
* @return
*/
var name: String by string { error("Anonymous descriptors are not allowed") }
/**
* The default for this node. Null if there is no default.
*
* @return
*/
var default: Meta? by node()
/**
* True if multiple children with this nodes name are allowed. Anonymous
* nodes are always single
*
* @return
*/
var multiple: Boolean by boolean(false)
/**
* True if the node is required
*
* @return
*/
var required: Boolean by boolean { default == null }
/**
* The node description
*
* @return
*/
var info: String? by string()
/**
* A list of tags for this node. Tags used to customize node usage
*
* @return
*/
var tags: List<String> by value{ value ->
value?.list?.map { it.string } ?: emptyList()
}
/**
* The list of value descriptors
*/
val values: Map<String, ValueDescriptor>
get() = config.getAll("value".toName()).entries.associate { (name, node) ->
name to ValueDescriptor.wrap(node.node ?: error("Value descriptor must be a node"))
}
fun value(name: String, descriptor: ValueDescriptor) {
val token = NameToken("value", name)
config[token] = descriptor.config
}
/**
* Add a value descriptor using block for
*/
fun value(name: String, block: ValueDescriptor.() -> Unit) {
value(name, ValueDescriptor.build { this.name = name }.apply(block))
}
/**
* The map of children node descriptors
*/
val nodes: Map<String, NodeDescriptor>
get() = config.getAll("node".toName()).entries.associate { (name, node) ->
name to NodeDescriptor.wrap(node.node ?: error("Node descriptor must be a node"))
}
fun node(name: String, descriptor: NodeDescriptor) {
val token = NameToken("node", name)
config[token] = descriptor.config
}
fun node(name: String, block: NodeDescriptor.() -> Unit) {
node(name, NodeDescriptor.build { this.name = name }.apply(block))
}
//override val descriptor: NodeDescriptor = empty("descriptor")
companion object : Specification<NodeDescriptor> {
override fun wrap(config: Config): NodeDescriptor = NodeDescriptor(config)
}
}

View File

@ -0,0 +1,193 @@
/*
* Copyright 2018 Alexander Nozik.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package hep.dataforge.descriptors
import hep.dataforge.meta.*
import hep.dataforge.values.False
import hep.dataforge.values.True
import hep.dataforge.values.Value
import hep.dataforge.values.ValueType
/**
* A descriptor for meta value
*
* Descriptor can have non-atomic path. It is resolved when descriptor is added to the node
*
* @author Alexander Nozik
*/
class ValueDescriptor(override val config: Config) : Specific {
/**
* The default for this value. Null if there is no default.
*
* @return
*/
var default: Value? by value()
fun default(v: Any) {
this.default = Value.of(v)
}
/**
* True if multiple values with this name are allowed.
*
* @return
*/
var multiple: Boolean by boolean(false)
/**
* True if the value is required
*
* @return
*/
var required: Boolean by boolean { default == null }
/**
* Value name
*
* @return
*/
var name: String by string { error("Anonymous descriptors are not allowed") }
/**
* The value info
*
* @return
*/
var info: String? by string()
/**
* A list of allowed ValueTypes. Empty if any value type allowed
*
* @return
*/
var type: List<ValueType> by value {
it?.list?.map { v -> ValueType.valueOf(v.string) } ?: emptyList()
}
fun type(vararg t: ValueType) {
this.type = listOf(*t)
}
var tags: List<String> by value { value ->
value?.list?.map { it.string } ?: emptyList()
}
/**
* 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
*/
fun isAllowedValue(value: Value): Boolean {
return (type.isEmpty() || type.contains(ValueType.STRING) || type.contains(value.type)) && (allowedValues.isEmpty() || allowedValues.contains(
value
))
}
/**
* A list of allowed values with descriptions. If empty than any value is
* allowed.
*
* @return
*/
var allowedValues: List<Value> by value {
it?.list ?: if (type.size == 1 && type[0] === ValueType.BOOLEAN) {
listOf(True, False)
} else {
emptyList()
}
}
/**
* Allow given list of value and forbid others
*/
fun allow(vararg v: Any) {
this.allowedValues = v.map { Value.of(it) }
}
companion object : Specification<ValueDescriptor> {
override fun wrap(config: Config): ValueDescriptor = ValueDescriptor(config)
inline fun <reified E : Enum<E>> enum(name: String) =
ValueDescriptor.build {
this.name = name
type(ValueType.STRING)
this.allowedValues = enumValues<E>().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))
// }
}
}

View File

@ -0,0 +1,149 @@
/*
* Copyright 2018 Alexander Nozik.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package hep.dataforge.descriptors
import hep.dataforge.values.ValueType
import kotlin.reflect.KClass
@Target(AnnotationTarget.PROPERTY)
@MustBeDocumented
annotation class ValueDef(
val key: String,
val type: Array<ValueType> = arrayOf(ValueType.STRING),
val multiple: Boolean = false,
val def: String = "",
val info: String = "",
val required: Boolean = true,
val allowed: Array<String> = emptyArray(),
val enumeration: KClass<*> = Any::class,
val tags: Array<String> = emptyArray()
)
@MustBeDocumented
annotation class NodeDef(
val key: String,
val info: String = "",
val multiple: Boolean = false,
val required: Boolean = false,
val tags: Array<String> = emptyArray(),
/**
* A list of child value descriptors
*/
val values: Array<ValueDef> = emptyArray(),
/**
* A target class for this node to describe
* @return
*/
val type: KClass<*> = Any::class,
/**
* The DataForge path to the resource containing the description. Following targets are supported:
*
* 1. resource
* 1. file
* 1. class
* 1. method
* 1. property
*
*
* Does not work if [type] is provided
*
* @return
*/
val descriptor: String = ""
)
/**
* Description text for meta property, node or whole object
*/
@Target(AnnotationTarget.CLASS, AnnotationTarget.PROPERTY)
@Retention(AnnotationRetention.RUNTIME)
@MustBeDocumented
annotation class Description(val value: String)
/**
* Annotation for value property which states that lists are expected
*/
@Target(AnnotationTarget.PROPERTY)
@Retention(AnnotationRetention.RUNTIME)
@MustBeDocumented
annotation class Multiple
/**
* Descriptor target
* The DataForge path to the resource containing the description. Following targets are supported:
* 1. resource
* 1. file
* 1. class
* 1. method
* 1. property
*
*
* Does not work if [type] is provided
*/
@Target(AnnotationTarget.CLASS, AnnotationTarget.PROPERTY, AnnotationTarget.VALUE_PARAMETER)
@Retention(AnnotationRetention.RUNTIME)
@MustBeDocumented
annotation class Descriptor(val value: String)
/**
* Aggregator class for descriptor nodes
*/
@Target(AnnotationTarget.CLASS, AnnotationTarget.PROPERTY, AnnotationTarget.FUNCTION, AnnotationTarget.VALUE_PARAMETER)
@Retention(AnnotationRetention.RUNTIME)
@MustBeDocumented
annotation class DescriptorNodes(vararg val nodes: NodeDef)
/**
* Aggregator class for descriptor values
*/
@Target(AnnotationTarget.CLASS, AnnotationTarget.PROPERTY, AnnotationTarget.FUNCTION, AnnotationTarget.VALUE_PARAMETER)
@Retention(AnnotationRetention.RUNTIME)
@MustBeDocumented
annotation class DescriptorValues(vararg val nodes: ValueDef)
/**
* Alternative name for property descriptor declaration
*/
@Target(AnnotationTarget.PROPERTY, AnnotationTarget.VALUE_PARAMETER)
@Retention(AnnotationRetention.RUNTIME)
@MustBeDocumented
annotation class DescriptorName(val name: String)
@Target(AnnotationTarget.PROPERTY)
@Retention(AnnotationRetention.RUNTIME)
@MustBeDocumented
annotation class DescriptorValue(val def: ValueDef)
//TODO enter fields directly?
@Target(AnnotationTarget.PROPERTY)
@Retention(AnnotationRetention.RUNTIME)
@MustBeDocumented
annotation class ValueProperty(
val name: String = "",
val type: Array<ValueType> = arrayOf(ValueType.STRING),
val multiple: Boolean = false,
val def: String = "",
val enumeration: KClass<*> = Any::class,
val tags: Array<String> = emptyArray()
)
@Target(AnnotationTarget.PROPERTY)
@Retention(AnnotationRetention.RUNTIME)
@MustBeDocumented
annotation class NodeProperty(val name: String = "")

View File

@ -2,7 +2,7 @@ package hep.dataforge.meta
import hep.dataforge.names.Name import hep.dataforge.names.Name
import hep.dataforge.names.NameToken import hep.dataforge.names.NameToken
import hep.dataforge.names.toName import hep.dataforge.names.asName
//TODO add validator to configuration //TODO add validator to configuration
@ -28,7 +28,7 @@ operator fun Config.get(token: NameToken): MetaItem<Config>? = items[token]
fun Meta.toConfig(): Config = this as? Config ?: Config().also { builder -> fun Meta.toConfig(): Config = this as? Config ?: Config().also { builder ->
this.items.mapValues { entry -> this.items.mapValues { entry ->
val item = entry.value val item = entry.value
builder[entry.key.toName()] = when (item) { builder[entry.key.asName()] = when (item) {
is MetaItem.ValueItem -> MetaItem.ValueItem(item.value) is MetaItem.ValueItem -> MetaItem.ValueItem(item.value)
is MetaItem.NodeItem -> MetaItem.NodeItem(item.node.toConfig()) is MetaItem.NodeItem -> MetaItem.NodeItem(item.node.toConfig())
} }

View File

@ -10,33 +10,104 @@ import kotlin.jvm.JvmName
/** /**
* A property delegate that uses custom key * A property delegate that uses custom key
*/ */
fun Configurable.value(default: Value = Null, key: String? = null) = fun Configurable.value(default: Any = Null, key: String? = null) =
ValueConfigDelegate(config, key, default) MutableValueDelegate(config, key, Value.of(default))
fun <T> Configurable.value(default: T? = null, key: String? = null, transform: (Value?) -> T) =
MutableValueDelegate(config, key, Value.of(default)).transform(reader = transform)
fun Configurable.stringList(key: String? = null) =
value(key) { it?.list?.map { value -> value.string } ?: emptyList() }
fun Configurable.numberList(key: String? = null) =
value(key) { it?.list?.map { value -> value.number } ?: emptyList() }
fun Configurable.string(default: String? = null, key: String? = null) = fun Configurable.string(default: String? = null, key: String? = null) =
StringConfigDelegate(config, key, default) MutableStringDelegate(config, key, default)
fun Configurable.boolean(default: Boolean? = null, key: String? = null) = fun Configurable.boolean(default: Boolean? = null, key: String? = null) =
BooleanConfigDelegate(config, key, default) MutableBooleanDelegate(config, key, default)
fun Configurable.number(default: Number? = null, key: String? = null) = fun Configurable.number(default: Number? = null, key: String? = null) =
NumberConfigDelegate(config, key, default) MutableNumberDelegate(config, key, default)
fun Configurable.child(key: String? = null) = MetaNodeDelegate(config, key) /* Number delegates*/
fun Configurable.int(default: Int? = null, key: String? = null) =
number(default, key).int
fun Configurable.double(default: Double? = null, key: String? = null) =
number(default, key).double
fun Configurable.long(default: Long? = null, key: String? = null) =
number(default, key).long
fun Configurable.short(default: Short? = null, key: String? = null) =
number(default, key).short
fun Configurable.float(default: Float? = null, key: String? = null) =
number(default, key).float
//fun <T : Configurable> Configurable.spec(spec: Specification<T>, key: String? = null) = ChildConfigDelegate<T>(key) { spec.wrap(this) }
@JvmName("safeString") @JvmName("safeString")
fun Configurable.string(default: String, key: String? = null) = fun Configurable.string(default: String, key: String? = null) =
SafeStringConfigDelegate(config, key, default) MutableSafeStringDelegate(config, key) { default }
@JvmName("safeBoolean") @JvmName("safeBoolean")
fun Configurable.boolean(default: Boolean, key: String? = null) = fun Configurable.boolean(default: Boolean, key: String? = null) =
SafeBooleanConfigDelegate(config, key, default) MutableSafeBooleanDelegate(config, key) { default }
@JvmName("safeNumber") @JvmName("safeNumber")
fun Configurable.number(default: Number, key: String? = null) = fun Configurable.number(default: Number, key: String? = null) =
SafeNumberConfigDelegate(config, key, default) MutableSafeNumberDelegate(config, key) { default }
@JvmName("safeString")
fun Configurable.string(key: String? = null, default: () -> String) =
MutableSafeStringDelegate(config, key, default)
@JvmName("safeBoolean")
fun Configurable.boolean(key: String? = null, default: () -> Boolean) =
MutableSafeBooleanDelegate(config, key, default)
@JvmName("safeNumber")
fun Configurable.number(key: String? = null, default: () -> Number) =
MutableSafeNumberDelegate(config, key, default)
/* Safe number delegates*/
@JvmName("safeInt")
fun Configurable.int(default: Int, key: String? = null) =
number(default, key).int
@JvmName("safeDouble")
fun Configurable.double(default: Double, key: String? = null) =
number(default, key).double
@JvmName("safeLong")
fun Configurable.long(default: Long, key: String? = null) =
number(default, key).long
@JvmName("safeShort")
fun Configurable.short(default: Short, key: String? = null) =
number(default, key).short
@JvmName("safeFloat")
fun Configurable.float(default: Float, key: String? = null) =
number(default, key).float
/**
* Enum delegate
*/
inline fun <reified E : Enum<E>> Configurable.enum(default: E, key: String? = null) = inline fun <reified E : Enum<E>> Configurable.enum(default: E, key: String? = null) =
SafeEnumvConfigDelegate(config, key, default) { enumValueOf(it) } MutableSafeEnumvDelegate(config, key, default) { enumValueOf(it) }
/* Node delegates */
fun Configurable.node(key: String? = null) = MutableNodeDelegate(config, key)
fun <T : Specific> Configurable.spec(spec: Specification<T>, key: String? = null) =
MutableMorphDelegate(config, key) { spec.wrap(it) }
fun <T : Specific> Configurable.spec(builder: (Config) -> T, key: String? = null) =
MutableMorphDelegate(config, key) { specification(builder).wrap(it) }

View File

@ -56,22 +56,40 @@ class DelegateWrapper<T, R>(val delegate: ReadOnlyProperty<Any?, T>, val reader:
//Delegates with non-null values //Delegates with non-null values
class SafeStringDelegate(val meta: Meta, private val key: String? = null, private val default: String) : class SafeStringDelegate(
ReadOnlyProperty<Any?, String> { val meta: Meta,
private val key: String? = null,
default: () -> String
) : ReadOnlyProperty<Any?, String> {
private val default: String by lazy(default)
override fun getValue(thisRef: Any?, property: KProperty<*>): String { override fun getValue(thisRef: Any?, property: KProperty<*>): String {
return meta[key ?: property.name]?.string ?: default return meta[key ?: property.name]?.string ?: default
} }
} }
class SafeBooleanDelegate(val meta: Meta, private val key: String? = null, private val default: Boolean) : class SafeBooleanDelegate(
ReadOnlyProperty<Any?, Boolean> { val meta: Meta,
private val key: String? = null,
default: () -> Boolean
) : ReadOnlyProperty<Any?, Boolean> {
private val default: Boolean by lazy(default)
override fun getValue(thisRef: Any?, property: KProperty<*>): Boolean { override fun getValue(thisRef: Any?, property: KProperty<*>): Boolean {
return meta[key ?: property.name]?.boolean ?: default return meta[key ?: property.name]?.boolean ?: default
} }
} }
class SafeNumberDelegate(val meta: Meta, private val key: String? = null, private val default: Number) : class SafeNumberDelegate(
ReadOnlyProperty<Any?, Number> { val meta: Meta,
private val key: String? = null,
default: () -> Number
) : ReadOnlyProperty<Any?, Number> {
private val default: Number by lazy(default)
override fun getValue(thisRef: Any?, property: KProperty<*>): Number { override fun getValue(thisRef: Any?, property: KProperty<*>): Number {
return meta[key ?: property.name]?.number ?: default return meta[key ?: property.name]?.number ?: default
} }
@ -118,96 +136,116 @@ fun Meta.number(default: Number? = null, key: String? = null) = NumberDelegate(t
fun Meta.child(key: String? = null) = ChildDelegate(this, key) { it } fun Meta.child(key: String? = null) = ChildDelegate(this, key) { it }
@JvmName("safeString") @JvmName("safeString")
fun Meta.string(default: String, key: String? = null) = SafeStringDelegate(this, key, default) fun Meta.string(default: String, key: String? = null) =
SafeStringDelegate(this, key) { default }
@JvmName("safeBoolean") @JvmName("safeBoolean")
fun Meta.boolean(default: Boolean, key: String? = null) = SafeBooleanDelegate(this, key, default) fun Meta.boolean(default: Boolean, key: String? = null) =
SafeBooleanDelegate(this, key) { default }
@JvmName("safeNumber") @JvmName("safeNumber")
fun Meta.number(default: Number, key: String? = null) = SafeNumberDelegate(this, key, default) fun Meta.number(default: Number, key: String? = null) =
SafeNumberDelegate(this, key) { default }
@JvmName("safeString")
fun Meta.string(key: String? = null, default: () -> String) =
SafeStringDelegate(this, key, default)
@JvmName("safeBoolean")
fun Meta.boolean(key: String? = null, default: () -> Boolean) =
SafeBooleanDelegate(this, key, default)
@JvmName("safeNumber")
fun Meta.number(key: String? = null, default: () -> Number) =
SafeNumberDelegate(this, key, default)
inline fun <reified E : Enum<E>> Meta.enum(default: E, key: String? = null) = inline fun <reified E : Enum<E>> Meta.enum(default: E, key: String? = null) =
SafeEnumDelegate(this, key, default) { enumValueOf(it) } SafeEnumDelegate(this, key, default) { enumValueOf(it) }
/* Config delegates */ /* Read-write delegates */
class ValueConfigDelegate<M : MutableMeta<M>>( class MutableValueDelegate<M : MutableMeta<M>>(
val config: M, val meta: M,
private val key: String? = null, private val key: String? = null,
private val default: Value? = null private val default: Value? = null
) : ReadWriteProperty<Any?, Value?> { ) : ReadWriteProperty<Any?, Value?> {
override fun getValue(thisRef: Any?, property: KProperty<*>): Value? { override fun getValue(thisRef: Any?, property: KProperty<*>): Value? {
return config[key ?: property.name]?.value ?: default return meta[key ?: property.name]?.value ?: default
} }
override fun setValue(thisRef: Any?, property: KProperty<*>, value: Value?) { override fun setValue(thisRef: Any?, property: KProperty<*>, value: Value?) {
val name = key ?: property.name val name = key ?: property.name
if (value == null) { if (value == null) {
config.remove(name) meta.remove(name)
} else { } else {
config.setValue(name, value) meta.setValue(name, value)
} }
} }
fun <T> transform(writer: (T) -> Value? = { Value.of(it) }, reader: (Value?) -> T) =
ReadWriteDelegateWrapper(this, reader, writer)
} }
class StringConfigDelegate<M : MutableMeta<M>>( class MutableStringDelegate<M : MutableMeta<M>>(
val config: M, val meta: M,
private val key: String? = null, private val key: String? = null,
private val default: String? = null private val default: String? = null
) : ReadWriteProperty<Any?, String?> { ) : ReadWriteProperty<Any?, String?> {
override fun getValue(thisRef: Any?, property: KProperty<*>): String? { override fun getValue(thisRef: Any?, property: KProperty<*>): String? {
return config[key ?: property.name]?.string ?: default return meta[key ?: property.name]?.string ?: default
} }
override fun setValue(thisRef: Any?, property: KProperty<*>, value: String?) { override fun setValue(thisRef: Any?, property: KProperty<*>, value: String?) {
val name = key ?: property.name val name = key ?: property.name
if (value == null) { if (value == null) {
config.remove(name) meta.remove(name)
} else { } else {
config.setValue(name, value.asValue()) meta.setValue(name, value.asValue())
} }
} }
} }
class BooleanConfigDelegate<M : MutableMeta<M>>( class MutableBooleanDelegate<M : MutableMeta<M>>(
val config: M, val meta: M,
private val key: String? = null, private val key: String? = null,
private val default: Boolean? = null private val default: Boolean? = null
) : ReadWriteProperty<Any?, Boolean?> { ) : ReadWriteProperty<Any?, Boolean?> {
override fun getValue(thisRef: Any?, property: KProperty<*>): Boolean? { override fun getValue(thisRef: Any?, property: KProperty<*>): Boolean? {
return config[key ?: property.name]?.boolean ?: default return meta[key ?: property.name]?.boolean ?: default
} }
override fun setValue(thisRef: Any?, property: KProperty<*>, value: Boolean?) { override fun setValue(thisRef: Any?, property: KProperty<*>, value: Boolean?) {
val name = key ?: property.name val name = key ?: property.name
if (value == null) { if (value == null) {
config.remove(name) meta.remove(name)
} else { } else {
config.setValue(name, value.asValue()) meta.setValue(name, value.asValue())
} }
} }
} }
class NumberConfigDelegate<M : MutableMeta<M>>( class MutableNumberDelegate<M : MutableMeta<M>>(
val config: M, val meta: M,
private val key: String? = null, private val key: String? = null,
private val default: Number? = null private val default: Number? = null
) : ReadWriteProperty<Any?, Number?> { ) : ReadWriteProperty<Any?, Number?> {
override fun getValue(thisRef: Any?, property: KProperty<*>): Number? { override fun getValue(thisRef: Any?, property: KProperty<*>): Number? {
return config[key ?: property.name]?.number ?: default return meta[key ?: property.name]?.number ?: default
} }
override fun setValue(thisRef: Any?, property: KProperty<*>, value: Number?) { override fun setValue(thisRef: Any?, property: KProperty<*>, value: Number?) {
val name = key ?: property.name val name = key ?: property.name
if (value == null) { if (value == null) {
config.remove(name) meta.remove(name)
} else { } else {
config.setValue(name, value.asValue()) meta.setValue(name, value.asValue())
} }
} }
val double get() = ReadWriteDelegateWrapper(this, reader = { it?.toDouble() }, writer = { it }) val double get() = ReadWriteDelegateWrapper(this, reader = { it?.toDouble() }, writer = { it })
val float get() = ReadWriteDelegateWrapper(this, reader = { it?.toFloat() }, writer = { it })
val int get() = ReadWriteDelegateWrapper(this, reader = { it?.toInt() }, writer = { it }) val int get() = ReadWriteDelegateWrapper(this, reader = { it?.toInt() }, writer = { it })
val short get() = ReadWriteDelegateWrapper(this, reader = { it?.toShort() }, writer = { it }) val short get() = ReadWriteDelegateWrapper(this, reader = { it?.toShort() }, writer = { it })
val long get() = ReadWriteDelegateWrapper(this, reader = { it?.toLong() }, writer = { it }) val long get() = ReadWriteDelegateWrapper(this, reader = { it?.toLong() }, writer = { it })
@ -215,95 +253,108 @@ class NumberConfigDelegate<M : MutableMeta<M>>(
//Delegates with non-null values //Delegates with non-null values
class SafeStringConfigDelegate<M : MutableMeta<M>>( class MutableSafeStringDelegate<M : MutableMeta<M>>(
val config: M, val meta: M,
private val key: String? = null, private val key: String? = null,
private val default: String default: () -> String
) : ReadWriteProperty<Any?, String> { ) : ReadWriteProperty<Any?, String> {
private val default: String by lazy(default)
override fun getValue(thisRef: Any?, property: KProperty<*>): String { override fun getValue(thisRef: Any?, property: KProperty<*>): String {
return config[key ?: property.name]?.string ?: default return meta[key ?: property.name]?.string ?: default
} }
override fun setValue(thisRef: Any?, property: KProperty<*>, value: String) { override fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
config.setValue(key ?: property.name, value.asValue()) meta.setValue(key ?: property.name, value.asValue())
} }
} }
class SafeBooleanConfigDelegate<M : MutableMeta<M>>( class MutableSafeBooleanDelegate<M : MutableMeta<M>>(
val config: M, val meta: M,
private val key: String? = null, private val key: String? = null,
private val default: Boolean default: () -> Boolean
) : ReadWriteProperty<Any?, Boolean> { ) : ReadWriteProperty<Any?, Boolean> {
private val default: Boolean by lazy(default)
override fun getValue(thisRef: Any?, property: KProperty<*>): Boolean { override fun getValue(thisRef: Any?, property: KProperty<*>): Boolean {
return config[key ?: property.name]?.boolean ?: default return meta[key ?: property.name]?.boolean ?: default
} }
override fun setValue(thisRef: Any?, property: KProperty<*>, value: Boolean) { override fun setValue(thisRef: Any?, property: KProperty<*>, value: Boolean) {
config.setValue(key ?: property.name, value.asValue()) meta.setValue(key ?: property.name, value.asValue())
} }
} }
class SafeNumberConfigDelegate<M : MutableMeta<M>>( class MutableSafeNumberDelegate<M : MutableMeta<M>>(
val config: M, val meta: M,
private val key: String? = null, private val key: String? = null,
private val default: Number default: () -> Number
) : ReadWriteProperty<Any?, Number> { ) : ReadWriteProperty<Any?, Number> {
private val default: Number by lazy(default)
override fun getValue(thisRef: Any?, property: KProperty<*>): Number { override fun getValue(thisRef: Any?, property: KProperty<*>): Number {
return config[key ?: property.name]?.number ?: default return meta[key ?: property.name]?.number ?: default
} }
override fun setValue(thisRef: Any?, property: KProperty<*>, value: Number) { override fun setValue(thisRef: Any?, property: KProperty<*>, value: Number) {
config.setValue(key ?: property.name, value.asValue()) meta.setValue(key ?: property.name, value.asValue())
} }
val double get() = ReadWriteDelegateWrapper(this, reader = { it.toDouble() }, writer = { it }) val double get() = ReadWriteDelegateWrapper(this, reader = { it.toDouble() }, writer = { it })
val float get() = ReadWriteDelegateWrapper(this, reader = { it.toFloat() }, writer = { it })
val int get() = ReadWriteDelegateWrapper(this, reader = { it.toInt() }, writer = { it }) val int get() = ReadWriteDelegateWrapper(this, reader = { it.toInt() }, writer = { it })
val short get() = ReadWriteDelegateWrapper(this, reader = { it.toShort() }, writer = { it }) val short get() = ReadWriteDelegateWrapper(this, reader = { it.toShort() }, writer = { it })
val long get() = ReadWriteDelegateWrapper(this, reader = { it.toLong() }, writer = { it }) val long get() = ReadWriteDelegateWrapper(this, reader = { it.toLong() }, writer = { it })
} }
class SafeEnumvConfigDelegate<M : MutableMeta<M>, E : Enum<E>>( class MutableSafeEnumvDelegate<M : MutableMeta<M>, E : Enum<E>>(
val config: M, val meta: M,
private val key: String? = null, private val key: String? = null,
private val default: E, private val default: E,
private val resolver: (String) -> E private val resolver: (String) -> E
) : ReadWriteProperty<Any?, E> { ) : ReadWriteProperty<Any?, E> {
override fun getValue(thisRef: Any?, property: KProperty<*>): E { override fun getValue(thisRef: Any?, property: KProperty<*>): E {
return (config[key ?: property.name]?.string)?.let { resolver(it) } ?: default return (meta[key ?: property.name]?.string)?.let { resolver(it) } ?: default
} }
override fun setValue(thisRef: Any?, property: KProperty<*>, value: E) { override fun setValue(thisRef: Any?, property: KProperty<*>, value: E) {
config.setValue(key ?: property.name, value.name.asValue()) meta.setValue(key ?: property.name, value.name.asValue())
} }
} }
//Child node delegate //Child node delegate
class MetaNodeDelegate<M : MutableMetaNode<M>>( class MutableNodeDelegate<M : MutableMetaNode<M>>(
val config: M, val meta: M,
private val key: String? = null private val key: String? = null
) : ReadWriteProperty<Any?, Meta> { ) : ReadWriteProperty<Any?, Meta?> {
override fun getValue(thisRef: Any?, property: KProperty<*>): Meta { override fun getValue(thisRef: Any?, property: KProperty<*>): Meta? {
return config[key ?: property.name]?.node ?: EmptyMeta return meta[key ?: property.name]?.node
} }
override fun setValue(thisRef: Any?, property: KProperty<*>, value: Meta) { override fun setValue(thisRef: Any?, property: KProperty<*>, value: Meta?) {
config[key ?: property.name] = value meta[key ?: property.name] = value
} }
} }
class ChildConfigDelegate<M : MutableMetaNode<M>, T : Configurable>( class MutableMorphDelegate<M : MutableMetaNode<M>, T : Configurable>(
val config: M, val meta: M,
private val key: String? = null, private val key: String? = null,
private val converter: (Meta) -> T private val converter: (Meta) -> T
) : ) : ReadWriteProperty<Any?, T?> {
ReadWriteProperty<Any?, T> { override fun getValue(thisRef: Any?, property: KProperty<*>): T? {
override fun getValue(thisRef: Any?, property: KProperty<*>): T { return meta[key ?: property.name]?.node?.let(converter)
return converter(config[key ?: property.name]?.node ?: EmptyMeta)
} }
override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) { override fun setValue(thisRef: Any?, property: KProperty<*>, value: T?) {
config[key ?: property.name] = value.config if (value == null) {
meta.remove(key ?: property.name)
} else {
meta[key ?: property.name] = value.config
}
} }
} }
@ -328,32 +379,43 @@ class ReadWriteDelegateWrapper<T, R>(
* A property delegate that uses custom key * A property delegate that uses custom key
*/ */
fun <M : MutableMeta<M>> M.value(default: Value = Null, key: String? = null) = fun <M : MutableMeta<M>> M.value(default: Value = Null, key: String? = null) =
ValueConfigDelegate(this, key, default) MutableValueDelegate(this, key, default)
fun <M : MutableMeta<M>> M.string(default: String? = null, key: String? = null) = fun <M : MutableMeta<M>> M.string(default: String? = null, key: String? = null) =
StringConfigDelegate(this, key, default) MutableStringDelegate(this, key, default)
fun <M : MutableMeta<M>> M.boolean(default: Boolean? = null, key: String? = null) = fun <M : MutableMeta<M>> M.boolean(default: Boolean? = null, key: String? = null) =
BooleanConfigDelegate(this, key, default) MutableBooleanDelegate(this, key, default)
fun <M : MutableMeta<M>> M.number(default: Number? = null, key: String? = null) = fun <M : MutableMeta<M>> M.number(default: Number? = null, key: String? = null) =
NumberConfigDelegate(this, key, default) MutableNumberDelegate(this, key, default)
fun <M : MutableMetaNode<M>> M.child(key: String? = null) = MetaNodeDelegate(this, key) fun <M : MutableMetaNode<M>> M.node(key: String? = null) = MutableNodeDelegate(this, key)
//fun <T : Configurable> Configurable.spec(spec: Specification<T>, key: String? = null) = ChildConfigDelegate<T>(key) { spec.wrap(this) }
@JvmName("safeString") @JvmName("safeString")
fun <M : MutableMeta<M>> M.string(default: String, key: String? = null) = fun <M : MutableMeta<M>> M.string(default: String, key: String? = null) =
SafeStringConfigDelegate(this, key, default) MutableSafeStringDelegate(this, key) { default }
@JvmName("safeBoolean") @JvmName("safeBoolean")
fun <M : MutableMeta<M>> M.boolean(default: Boolean, key: String? = null) = fun <M : MutableMeta<M>> M.boolean(default: Boolean, key: String? = null) =
SafeBooleanConfigDelegate(this, key, default) MutableSafeBooleanDelegate(this, key) { default }
@JvmName("safeNumber") @JvmName("safeNumber")
fun <M : MutableMeta<M>> M.number(default: Number, key: String? = null) = fun <M : MutableMeta<M>> M.number(default: Number, key: String? = null) =
SafeNumberConfigDelegate(this, key, default) MutableSafeNumberDelegate(this, key) { default }
@JvmName("safeString")
fun <M : MutableMeta<M>> M.string(key: String? = null, default: () -> String) =
MutableSafeStringDelegate(this, key, default)
@JvmName("safeBoolean")
fun <M : MutableMeta<M>> M.boolean(key: String? = null, default: () -> Boolean) =
MutableSafeBooleanDelegate(this, key, default)
@JvmName("safeNumber")
fun <M : MutableMeta<M>> M.number(key: String? = null, default: () -> Number) =
MutableSafeNumberDelegate(this, key, default)
inline fun <M : MutableMeta<M>, reified E : Enum<E>> M.enum(default: E, key: String? = null) = inline fun <M : MutableMeta<M>, reified E : Enum<E>> M.enum(default: E, key: String? = null) =
SafeEnumvConfigDelegate(this, key, default) { enumValueOf(it) } MutableSafeEnumvDelegate(this, key, default) { enumValueOf(it) }

View File

@ -33,4 +33,4 @@ fun Configurable.stringList(vararg default: String = emptyArray(), key: String?
fun <T : Metoid> Metoid.child(key: String? = null, converter: (Meta) -> T) = ChildDelegate(meta, key, converter) fun <T : Metoid> Metoid.child(key: String? = null, converter: (Meta) -> T) = ChildDelegate(meta, key, converter)
fun <T : Configurable> Configurable.child(key: String? = null, converter: (Meta) -> T) = fun <T : Configurable> Configurable.child(key: String? = null, converter: (Meta) -> T) =
ChildConfigDelegate(config, key, converter) MutableMorphDelegate(config, key, converter)

View File

@ -3,10 +3,7 @@ package hep.dataforge.meta
import hep.dataforge.meta.Meta.Companion.VALUE_KEY import hep.dataforge.meta.Meta.Companion.VALUE_KEY
import hep.dataforge.meta.MetaItem.NodeItem import hep.dataforge.meta.MetaItem.NodeItem
import hep.dataforge.meta.MetaItem.ValueItem import hep.dataforge.meta.MetaItem.ValueItem
import hep.dataforge.names.Name import hep.dataforge.names.*
import hep.dataforge.names.NameToken
import hep.dataforge.names.plus
import hep.dataforge.names.toName
import hep.dataforge.values.EnumValue import hep.dataforge.values.EnumValue
import hep.dataforge.values.Value import hep.dataforge.values.Value
import hep.dataforge.values.boolean import hep.dataforge.values.boolean
@ -76,30 +73,37 @@ operator fun Meta?.get(key: String): MetaItem<out Meta>? = get(key.toName())
* Get all items matching given name. * Get all items matching given name.
*/ */
fun Meta.getAll(name: Name): Map<String, MetaItem<out Meta>> { fun Meta.getAll(name: Name): Map<String, MetaItem<out Meta>> {
if (name.length == 0) error("Can't use empty name for that") val root = when (name.length) {
val (body, query) = name.last()!! 0 -> error("Can't use empty name for that")
val regex = query.toRegex() 1 -> this
return (this[name.cutLast()] as? NodeItem<*>)?.node?.items else -> (this[name.cutLast()] as? NodeItem<*>)?.node
?.filter { it.key.body == body && (query.isEmpty() || regex.matches(it.key.query)) } }
?.mapKeys { it.key.query }
?: emptyMap()
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 Meta.getAll(name: String): Map<String, MetaItem<out Meta>> = getAll(name.toName())
/** /**
* Transform meta to sequence of [Name]-[Value] pairs * Get a sequence of [Name]-[Value] pairs
*/ */
fun Meta.asValueSequence(): Sequence<Pair<Name, Value>> { fun Meta.values(): Sequence<Pair<Name, Value>> {
return items.asSequence().flatMap { entry -> return items.asSequence().flatMap { entry ->
val item = entry.value val item = entry.value
when (item) { when (item) {
is ValueItem -> sequenceOf(entry.key.toName() to item.value) is ValueItem -> sequenceOf(entry.key.asName() to item.value)
is NodeItem -> item.node.asValueSequence().map { pair -> (entry.key.toName() + 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<Pair<Name, Value>> = asValueSequence().iterator() operator fun Meta.iterator(): Iterator<Pair<Name, Value>> = values().iterator()
/** /**
* A meta node that ensures that all of its descendants has at least the same type * A meta node that ensures that all of its descendants has at least the same type
@ -108,6 +112,27 @@ interface MetaNode<M : MetaNode<M>> : Meta {
override val items: Map<NameToken, MetaItem<M>> override val items: Map<NameToken, MetaItem<M>>
} }
/**
* Get all items matching given name.
*/
fun <M : MetaNode<M>> MetaNode<M>.getAll(name: Name): Map<String, MetaItem<M>> {
val root: MetaNode<M>? = when (name.length) {
0 -> error("Can't use empty name for that")
1 -> this
else -> (this[name.cutLast()] as? NodeItem<M>)?.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 : MetaNode<M>> M.getAll(name: String): Map<String, MetaItem<M>> = getAll(name.toName())
operator fun <M : MetaNode<M>> MetaNode<M>.get(name: Name): MetaItem<M>? { operator fun <M : MetaNode<M>> MetaNode<M>.get(name: Name): MetaItem<M>? {
return name.first()?.let { token -> return name.first()?.let { token ->
val tail = name.cutFirst() val tail = name.cutFirst()
@ -118,7 +143,7 @@ operator fun <M : MetaNode<M>> MetaNode<M>.get(name: Name): MetaItem<M>? {
} }
} }
operator fun <M : MetaNode<M>> MetaNode<M>.get(key: String): MetaItem<M>? = get(key.toName()) operator fun <M : MetaNode<M>> MetaNode<M>?.get(key: String): MetaItem<M>? = this?.let { get(key.toName()) }
/** /**
* Equals and hash code implementation for meta node * Equals and hash code implementation for meta node

View File

@ -1,7 +1,7 @@
package hep.dataforge.meta package hep.dataforge.meta
import hep.dataforge.names.Name import hep.dataforge.names.Name
import hep.dataforge.names.toName import hep.dataforge.names.asName
import hep.dataforge.values.Value import hep.dataforge.values.Value
/** /**
@ -38,7 +38,7 @@ fun Meta.builder(): MetaBuilder {
return MetaBuilder().also { builder -> return MetaBuilder().also { builder ->
items.mapValues { entry -> items.mapValues { entry ->
val item = entry.value val item = entry.value
builder[entry.key.toName()] = when (item) { builder[entry.key.asName()] = when (item) {
is MetaItem.ValueItem -> MetaItem.ValueItem<MetaBuilder>(item.value) is MetaItem.ValueItem -> MetaItem.ValueItem<MetaBuilder>(item.value)
is MetaItem.NodeItem -> MetaItem.NodeItem(item.node.builder()) is MetaItem.NodeItem -> MetaItem.NodeItem(item.node.builder())
} }

View File

@ -1,9 +1,6 @@
package hep.dataforge.meta package hep.dataforge.meta
import hep.dataforge.names.Name import hep.dataforge.names.*
import hep.dataforge.names.NameToken
import hep.dataforge.names.plus
import hep.dataforge.names.toName
import hep.dataforge.values.Value import hep.dataforge.values.Value
internal data class MetaListener( internal data class MetaListener(
@ -62,7 +59,7 @@ abstract class MutableMetaNode<M : MutableMetaNode<M>> : AbstractMetaNode<M>(),
} }
} }
} }
itemChanged(key.toName(), oldItem, newItem) itemChanged(key.asName(), oldItem, newItem)
} }
/** /**
@ -103,7 +100,7 @@ fun <M : MutableMeta<M>> MutableMeta<M>.setItem(name: String, item: MetaItem<M>)
fun <M : MutableMeta<M>> MutableMeta<M>.setValue(name: String, value: Value) = fun <M : MutableMeta<M>> MutableMeta<M>.setValue(name: String, value: Value) =
set(name.toName(), MetaItem.ValueItem(value)) set(name.toName(), MetaItem.ValueItem(value))
fun <M : MutableMeta<M>> MutableMeta<M>.setItem(token: NameToken, item: MetaItem<M>?) = set(token.toName(), item) fun <M : MutableMeta<M>> MutableMeta<M>.setItem(token: NameToken, item: MetaItem<M>?) = set(token.asName(), item)
fun <M : MutableMetaNode<M>> MutableMetaNode<M>.setNode(name: Name, node: Meta) = fun <M : MutableMetaNode<M>> MutableMetaNode<M>.setNode(name: Name, node: Meta) =
set(name, MetaItem.NodeItem(wrap(name, node))) set(name, MetaItem.NodeItem(wrap(name, node)))
@ -121,10 +118,13 @@ operator fun <M : MutableMetaNode<M>> M.set(name: Name, value: Any?) {
is MetaItem.NodeItem<*> -> setNode(name, value.node) is MetaItem.NodeItem<*> -> setNode(name, value.node)
} }
is Meta -> setNode(name, value) is Meta -> setNode(name, value)
is Specific -> setNode(name, value.config)
else -> setValue(name, Value.of(value)) else -> setValue(name, Value.of(value))
} }
} }
operator fun <M : MutableMetaNode<M>> M.set(name: NameToken, value: Any?) = set(name.asName(), value)
operator fun <M : MutableMetaNode<M>> M.set(key: String, value: Any?) = set(key.toName(), value) operator fun <M : MutableMetaNode<M>> M.set(key: String, value: Any?) = set(key.toName(), value)
/** /**
@ -137,9 +137,9 @@ fun <M : MutableMetaNode<M>> M.update(meta: Meta) {
meta.items.forEach { entry -> meta.items.forEach { entry ->
val value = entry.value val value = entry.value
when (value) { when (value) {
is MetaItem.ValueItem -> setValue(entry.key.toName(), value.value) is MetaItem.ValueItem -> setValue(entry.key.asName(), value.value)
is MetaItem.NodeItem -> (this[entry.key.toName()] as? MetaItem.NodeItem)?.node?.update(value.node) is MetaItem.NodeItem -> (this[entry.key.asName()] as? MetaItem.NodeItem)?.node?.update(value.node)
?: run { setNode(entry.key.toName(), value.node) } ?: run { setNode(entry.key.asName(), value.node) }
} }
} }
} }
@ -149,12 +149,12 @@ fun <M : MutableMetaNode<M>> M.update(meta: Meta) {
fun <M : MutableMeta<M>> M.setIndexed( fun <M : MutableMeta<M>> M.setIndexed(
name: Name, name: Name,
items: Iterable<MetaItem<M>>, items: Iterable<MetaItem<M>>,
queryFactory: (Int) -> String = { it.toString() } indexFactory: MetaItem<M>.(index: Int) -> String = { it.toString() }
) { ) {
val tokens = name.tokens.toMutableList() val tokens = name.tokens.toMutableList()
val last = tokens.last() val last = tokens.last()
items.forEachIndexed { index, meta -> items.forEachIndexed { index, meta ->
val indexedToken = NameToken(last.body, last.query + queryFactory(index)) val indexedToken = NameToken(last.body, last.index + meta.indexFactory(index))
tokens[tokens.lastIndex] = indexedToken tokens[tokens.lastIndex] = indexedToken
set(Name(tokens), meta) set(Name(tokens), meta)
} }
@ -163,10 +163,26 @@ fun <M : MutableMeta<M>> M.setIndexed(
fun <M : MutableMetaNode<M>> M.setIndexed( fun <M : MutableMetaNode<M>> M.setIndexed(
name: Name, name: Name,
metas: Iterable<Meta>, metas: Iterable<Meta>,
queryFactory: (Int) -> String = { it.toString() } indexFactory: MetaItem<M>.(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 : 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) operator fun <M : MutableMetaNode<M>> M.set(name: String, metas: Iterable<Meta>) = setIndexed(name.toName(), metas)
/**
* Append the node with a same-name-sibling, automatically generating numerical index
*/
fun <M : MutableMetaNode<M>> 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)
}
}
fun <M : MutableMetaNode<M>> M.append(name: String, value: Any?) = append(name.toName(), value)

View File

@ -3,16 +3,16 @@ package hep.dataforge.meta
/** /**
* Marker interface for specifications * Marker interface for specifications
*/ */
interface Specification : Configurable { interface Specific : Configurable {
operator fun get(name: String): MetaItem<Config>? = config[name] operator fun get(name: String): MetaItem<Config>? = config[name]
} }
/** /**
* 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 * By convention [Specific] companion should inherit this class
* *
*/ */
interface SpecificationCompanion<T : Specification> { interface Specification<T : Specific> {
/** /**
* Update given configuration using given type as a builder * Update given configuration using given type as a builder
*/ */
@ -22,41 +22,41 @@ interface SpecificationCompanion<T : Specification> {
fun build(action: T.() -> Unit) = update(Config(), action) fun build(action: T.() -> Unit) = update(Config(), action)
fun empty() = build { }
/** /**
* 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): SpecificationCompanion<T> = fun <T : Specific> specification(wrapper: (Config) -> T): Specification<T> =
object : SpecificationCompanion<T> { object : Specification<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 : SpecificationCompanion<C>> T.configure(spec: S, action: C.() -> Unit) = fun <T : Configurable, C : Specific, S : Specification<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 : SpecificationCompanion<C>> Specification.update(spec: S, action: C.() -> Unit) = fun <C : Specific, S : Specification<C>> Specific.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 : SpecificationCompanion<C>> S.createStyle(action: C.() -> Unit): Meta = fun <C : Specific, S : Specification<C>> S.createStyle(action: C.() -> Unit): Meta =
Config().also { update(it, action) } Config().also { update(it, action) }
fun <M : MutableMetaNode<M>, C : Specification> Specification.spec( fun <C : Specific> Specific.spec(
spec: SpecificationCompanion<C>, spec: Specification<C>,
key: String? = null key: String? = null
) = ) = MutableMorphDelegate(config, key) { spec.wrap(it) }
ChildConfigDelegate(config, key) { spec.wrap(config) }

View File

@ -48,7 +48,7 @@ class Styled(val base: Meta, val style: Config = Config().empty()) : MutableMeta
} }
} }
fun Styled.configure(meta: Meta) = apply { style.update(style) } fun Styled.configure(meta: Meta) = apply { style.update(meta) }
fun Meta.withStyle(style: Meta = EmptyMeta) = if (this is Styled) { fun Meta.withStyle(style: Meta = EmptyMeta) = if (this is Styled) {
this.apply { this.configure(style) } this.apply { this.configure(style) }

View File

@ -4,7 +4,7 @@ package hep.dataforge.names
/** /**
* The general interface for working with names. * The general interface for working with names.
* The name is a dot separated list of strings like `token1.token2.token3`. * The name is a dot separated list of strings like `token1.token2.token3`.
* Each token could contain additional query in square brackets. * Each token could contain additional index in square brackets.
*/ */
inline class Name constructor(val tokens: List<NameToken>) { inline class Name constructor(val tokens: List<NameToken>) {
@ -43,24 +43,25 @@ inline class Name constructor(val tokens: List<NameToken>) {
/** /**
* A single name token. Body is not allowed to be empty. * A single name token. Body is not allowed to be empty.
* Following symbols are prohibited in name tokens: `{}.:\`. * Following symbols are prohibited in name tokens: `{}.:\`.
* A name token could have appendix in square brackets called *query* * A name token could have appendix in square brackets called *index*
*/ */
data class NameToken(val body: String, val query: String = "") { data class NameToken(val body: String, val index: String = "") {
init { init {
if (body.isEmpty()) error("Syntax error: Name token body is empty") if (body.isEmpty()) error("Syntax error: Name token body is empty")
} }
override fun toString(): String = if (hasQuery()) { override fun toString(): String = if (hasIndex()) {
"$body[$query]" "$body[$index]"
} else { } else {
body body
} }
fun hasQuery() = query.isNotEmpty() fun hasIndex() = index.isNotEmpty()
} }
fun String.toName(): Name { fun String.toName(): Name {
if (isBlank()) return EmptyName
val tokens = sequence { val tokens = sequence {
var bodyBuilder = StringBuilder() var bodyBuilder = StringBuilder()
var queryBuilder = StringBuilder() var queryBuilder = StringBuilder()
@ -84,7 +85,7 @@ fun String.toName(): Name {
'[' -> bracketCount++ '[' -> bracketCount++
']' -> error("Syntax error: closing bracket ] not have not matching open bracket") ']' -> error("Syntax error: closing bracket ] not have not matching open bracket")
else -> { else -> {
if (queryBuilder.isNotEmpty()) error("Syntax error: only name end and name separator are allowed after query") if (queryBuilder.isNotEmpty()) error("Syntax error: only name end and name separator are allowed after index")
bodyBuilder.append(it) bodyBuilder.append(it)
} }
} }
@ -101,8 +102,24 @@ operator fun Name.plus(other: Name): Name = Name(this.tokens + other.tokens)
operator fun Name.plus(other: String): Name = this + other.toName() operator fun Name.plus(other: String): Name = this + other.toName()
fun NameToken.toName() = Name(listOf(this)) fun Name.appendLeft(other: String): Name = NameToken(other) + this
fun NameToken.asName() = Name(listOf(this))
val EmptyName = Name(emptyList()) val EmptyName = Name(emptyList())
fun Name.isEmpty(): Boolean = this.length == 0 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)
}
operator fun <T> Map<Name, T>.get(name: String) = get(name.toName())
operator fun <T> MutableMap<Name, T>.set(name: String, value: T) = set(name.toName(), value)

View File

@ -55,8 +55,15 @@ interface Value {
is Value -> value is Value -> value
true -> True true -> True
false -> False false -> False
is Number -> NumberValue(value) is Number -> value.asValue()
is Iterable<*> -> ListValue(value.map { of(it) }) is Iterable<*> -> ListValue(value.map { of(it) })
is DoubleArray -> value.asValue()
is IntArray -> value.asValue()
is FloatArray -> value.asValue()
is ShortArray -> value.asValue()
is LongArray -> value.asValue()
is ByteArray -> value.asValue()
is Array<*> -> ListValue(value.map { of(it) })
is Enum<*> -> EnumValue(value) is Enum<*> -> EnumValue(value)
is CharSequence -> StringValue(value.toString()) is CharSequence -> StringValue(value.toString())
else -> throw IllegalArgumentException("Unrecognized type of the object (${value::class}) converted to Value") else -> throw IllegalArgumentException("Unrecognized type of the object (${value::class}) converted to Value")
@ -171,6 +178,18 @@ class ListValue(override val list: List<Value>) : Value {
override val string: String get() = list.first().string override val string: String get() = list.first().string
override fun toString(): String = value.toString() override fun toString(): String = value.toString()
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is Value) return false
return list == other.list
}
override fun hashCode(): Int {
return list.hashCode()
}
} }
/** /**
@ -185,7 +204,20 @@ fun Boolean.asValue(): Value = if (this) True else False
fun String.asValue(): Value = StringValue(this) fun String.asValue(): Value = StringValue(this)
fun Collection<Value>.asValue(): Value = ListValue(this.toList()) fun Iterable<Value>.asValue(): Value = ListValue(this.toList())
//TODO maybe optimized storage performance
fun DoubleArray.asValue(): Value = ListValue(map{NumberValue(it)})
fun IntArray.asValue(): Value = ListValue(map{NumberValue(it)})
fun LongArray.asValue(): Value = ListValue(map{NumberValue(it)})
fun ShortArray.asValue(): Value = ListValue(map{NumberValue(it)})
fun FloatArray.asValue(): Value = ListValue(map{NumberValue(it)})
fun ByteArray.asValue(): Value = ListValue(map{NumberValue(it)})
/** /**

View File

@ -23,4 +23,18 @@ class MetaBuilderTest {
assertEquals(true, meta["node.childNode.f"]?.boolean) assertEquals(true, meta["node.childNode.f"]?.boolean)
} }
@Test
fun testSNS(){
val meta = buildMeta {
repeat(10){
"b.a[$it]" to it
}
}.seal()
assertEquals(10, meta.values().count())
val nodes = meta.getAll("b.a")
assertEquals(3, nodes["3"]?.int)
}
} }

View File

@ -12,17 +12,30 @@ class MetaDelegateTest {
@Test @Test
fun delegateTest() { fun delegateTest() {
val testObject = object : Specification {
class InnerSpec(override val config: Config) : Specific {
var innerValue by string()
}
val innerSpec = specification(::InnerSpec)
val testObject = object : Specific {
override val config: Config = Config() override val config: Config = Config()
var myValue by config.string() var myValue by string()
var safeValue by config.number(2.2) var safeValue by double(2.2)
var enumValue by config.enum(TestEnum.YES) var enumValue by enum(TestEnum.YES)
var inner by spec(innerSpec)
} }
testObject.config["myValue"] = "theString" testObject.config["myValue"] = "theString"
testObject.enumValue = TestEnum.NO testObject.enumValue = TestEnum.NO
testObject.inner = innerSpec.build { innerValue = "ddd"}
assertEquals("theString", testObject.myValue) assertEquals("theString", testObject.myValue)
assertEquals(TestEnum.NO, testObject.enumValue) assertEquals(TestEnum.NO, testObject.enumValue)
assertEquals(2.2, testObject.safeValue) assertEquals(2.2, testObject.safeValue)
assertEquals("ddd", testObject.inner?.innerValue)
} }
} }

View File

@ -0,0 +1,29 @@
package hep.dataforge.meta
import kotlin.test.Test
import kotlin.test.assertEquals
class StyledTest{
@Test
fun testSNS(){
val meta = buildMeta {
repeat(10){
"b.a[$it]" to {
"d" to it
}
}
}.seal().withStyle()
assertEquals(10, meta.values().count())
val bNode = meta["b"].node
val aNodes = bNode?.getAll("a")
val allNodes = meta.getAll("b.a")
assertEquals(3, aNodes?.get("3").node["d"].int)
assertEquals(3, allNodes["3"].node["d"].int)
}
}

View File

@ -0,0 +1,63 @@
package hep.dataforge.meta
import hep.dataforge.names.NameToken
import hep.dataforge.values.Null
import hep.dataforge.values.Value
//TODO add Meta wrapper for dynamic
/**
* Represent or copy this [Meta] to dynamic object to be passed to JS libraries
*/
fun Meta.toDynamic(): dynamic {
if(this is DynamicMeta) return this.obj
fun MetaItem<*>.toDynamic(): dynamic = when (this) {
is MetaItem.ValueItem -> this.value.value.asDynamic()
is MetaItem.NodeItem -> this.node.toDynamic()
}
val res = js("{}")
this.items.entries.groupBy { it.key.body }.forEach { (key, value) ->
val list = value.map { it.value }
res[key] = when (list.size) {
1 -> list.first().toDynamic()
else -> list.map { it.toDynamic() }
}
}
return res
}
class DynamicMeta(val obj: dynamic) : Meta {
private fun keys() = js("Object.keys(this.obj)") as Array<String>
private fun isArray(@Suppress("UNUSED_PARAMETER") obj: dynamic): Boolean =
js("Array.isArray(obj)") as Boolean
private fun asItem(obj: dynamic): MetaItem<DynamicMeta>? {
if (obj == null) return MetaItem.ValueItem(Null)
return when (jsTypeOf(obj)) {
"boolean" -> MetaItem.ValueItem(Value.of(obj as Boolean))
"number" -> MetaItem.ValueItem(Value.of(obj as Number))
"string" -> MetaItem.ValueItem(Value.of(obj as String))
"object" -> MetaItem.NodeItem(DynamicMeta(obj))
else -> null
}
}
override val items: Map<NameToken, MetaItem<DynamicMeta>>
get() = keys().flatMap<String, Pair<NameToken, MetaItem<DynamicMeta>>> { key ->
val value = obj[key] ?: return@flatMap emptyList()
if (isArray(value)) {
return@flatMap (value as Array<dynamic>)
.mapIndexedNotNull() { index, it ->
val item = asItem(it) ?: return@mapIndexedNotNull null
NameToken(key, index.toString()) to item
}
} else {
val item = asItem(value) ?: return@flatMap emptyList()
listOf(NameToken(key) to item)
}
}.associate { it }
}

View File

@ -0,0 +1,24 @@
package hep.dataforge.meta
import kotlin.test.Test
import kotlin.test.assertEquals
class DynamicMetaTest {
@Test
fun testDynamicMeta() {
val d = js("{}")
d.a = 22
d.array = arrayOf(1,2,3)
d.b = "myString"
d.ob = js("{}")
d.ob.childNode = 18
d.ob.booleanNode = true
val meta = DynamicMeta(d)
assertEquals(true, meta["ob.booleanNode"].boolean)
assertEquals(2,meta["array[1]"].int)
}
}

View File

@ -0,0 +1,16 @@
plugins {
`npm-multiplatform`
}
kotlin {
jvm()
js()
sourceSets {
val commonMain by getting{
dependencies {
api(project(":dataforge-context"))
api(project(":dataforge-io"))
}
}
}
}

View File

@ -0,0 +1,28 @@
plugins {
`npm-multiplatform`
}
val htmlVersion by rootProject.extra("0.6.12")
kotlin {
jvm()
js()
sourceSets {
val commonMain by getting {
dependencies {
api(project(":dataforge-output"))
api("org.jetbrains.kotlinx:kotlinx-html-common:$htmlVersion")
}
}
val jsMain by getting {
dependencies {
api("org.jetbrains.kotlinx:kotlinx-html-js:$htmlVersion")
}
}
val jvmMain by getting{
dependencies {
api("org.jetbrains.kotlinx:kotlinx-html-jvm:$htmlVersion")
}
}
}
}

View File

@ -0,0 +1,77 @@
package hep.dataforge.output.html
import hep.dataforge.context.Context
import hep.dataforge.meta.Meta
import hep.dataforge.output.Output
import hep.dataforge.output.TextRenderer
import hep.dataforge.output.html.HtmlBuilder.Companion.HTML_CONVERTER_TYPE
import hep.dataforge.provider.Type
import hep.dataforge.provider.top
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.html.TagConsumer
import kotlinx.html.p
import kotlin.reflect.KClass
class HtmlOutput<T : Any>(override val context: Context, private val consumer: TagConsumer<*>) : Output<T> {
private val cache = HashMap<KClass<*>, HtmlBuilder<*>>()
/**
* Find the first [TextRenderer] matching the given object type.
*/
override fun render(obj: T, meta: Meta) {
val builder: HtmlBuilder<*> = if (obj is CharSequence) {
DefaultHtmlBuilder
} else {
val value = cache[obj::class]
if (value == null) {
val answer = context.top<HtmlBuilder<*>>().values
.filter { it.type.isInstance(obj) }.firstOrNull()
if (answer != null) {
cache[obj::class] = answer
answer
} else {
DefaultHtmlBuilder
}
} else {
value
}
}
context.launch(Dispatchers.Output) {
(builder as HtmlBuilder<T>).run { consumer.render(obj) }
}
}
}
/**
* A text or binary renderer based on [kotlinx.io.core.Output]
*/
@Type(HTML_CONVERTER_TYPE)
interface HtmlBuilder<T : Any> {
/**
* The priority of this renderer compared to other renderers
*/
val priority: Int
/**
* The type of the content served by this renderer
*/
val type: KClass<T>
suspend fun TagConsumer<*>.render(obj: T)
companion object {
const val HTML_CONVERTER_TYPE = "dataforge.htmlBuilder"
}
}
object DefaultHtmlBuilder : HtmlBuilder<Any> {
override val priority: Int = Int.MAX_VALUE
override val type: KClass<Any> = Any::class
override suspend fun TagConsumer<*>.render(obj: Any) {
p { +obj.toString() }
}
}

View File

@ -1,4 +1,4 @@
package hep.dataforge.io package hep.dataforge.output
import hep.dataforge.context.ContextAware import hep.dataforge.context.ContextAware
import hep.dataforge.meta.EmptyMeta import hep.dataforge.meta.EmptyMeta

View File

@ -0,0 +1,77 @@
package hep.dataforge.output
import hep.dataforge.context.*
import hep.dataforge.context.PluginTag.Companion.DATAFORGE_GROUP
import hep.dataforge.meta.EmptyMeta
import hep.dataforge.meta.Meta
import hep.dataforge.names.EmptyName
import hep.dataforge.names.Name
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
import kotlin.reflect.KClass
/**
* A manager for outputs
*/
interface OutputManager : Plugin {
/**
* Get an output specialized for given type, name and stage.
* @param stage represents the node or directory for the output. Empty means root node.
* @param name represents the name inside the node.
* @param meta configuration for [Output] (not for rendered object)
*/
operator fun <T : Any> get(
type: KClass<out T>,
name: Name,
stage: Name = EmptyName,
meta: Meta = EmptyMeta
): Output<T>
}
/**
* Get an output manager for a context
*/
val Context.output: OutputManager get() = plugins.get() ?: ConsoleOutputManager()
/**
* Get an output with given [name], [stage] and reified content type
*/
inline operator fun <reified T : Any> OutputManager.get(
name: Name,
stage: Name = EmptyName,
meta: Meta = EmptyMeta
): Output<T> {
return get(T::class, name, stage, meta)
}
/**
* Directly render an object using the most suitable renderer
*/
fun OutputManager.render(obj: Any, name: Name, stage: Name = EmptyName, meta: Meta = EmptyMeta) =
get(obj::class, name, stage).render(obj, meta)
/**
* System console output.
* The [ConsoleOutput] is used when no other [OutputManager] is provided.
*/
expect val ConsoleOutput: Output<Any>
class ConsoleOutputManager : AbstractPlugin(), OutputManager {
override val tag: PluginTag get() = ConsoleOutputManager.tag
override fun <T : Any> get(type: KClass<out T>, name: Name, stage: Name, meta: Meta): Output<T> = ConsoleOutput
companion object : PluginFactory<ConsoleOutputManager> {
override val tag = PluginTag("output.console", group = DATAFORGE_GROUP)
override val type = ConsoleOutputManager::class
override fun invoke(meta:Meta) = ConsoleOutputManager()
}
}
/**
* A dispatcher for output tasks.
*/
expect val Dispatchers.Output: CoroutineDispatcher

View File

@ -1,10 +1,11 @@
package hep.dataforge.io package hep.dataforge.output
import hep.dataforge.context.Context import hep.dataforge.context.Context
import hep.dataforge.io.TextRenderer.Companion.TEXT_RENDERER_TYPE
import hep.dataforge.meta.Meta import hep.dataforge.meta.Meta
import hep.dataforge.output.TextRenderer.Companion.TEXT_RENDERER_TYPE
import hep.dataforge.provider.Type import hep.dataforge.provider.Type
import hep.dataforge.provider.provideAll import hep.dataforge.provider.top
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlin.reflect.KClass import kotlin.reflect.KClass
@ -20,8 +21,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(TEXT_RENDERER_TYPE).filterIsInstance<TextRenderer>() val answer =
.filter { it.type.isInstance(obj) }.firstOrNull() context.top<TextRenderer>(TEXT_RENDERER_TYPE).values.firstOrNull { it.type.isInstance(obj) }
if (answer != null) { if (answer != null) {
cache[obj::class] = answer cache[obj::class] = answer
answer answer
@ -32,12 +33,15 @@ class TextOutput(override val context: Context, private val output: kotlinx.io.c
value value
} }
} }
context.launch(OutputDispatcher) { context.launch(Dispatchers.Output) {
renderer.run { output.render(obj) } renderer.run { output.render(obj) }
} }
} }
} }
/**
* A text or binary renderer based on [kotlinx.io.core.Output]
*/
@Type(TEXT_RENDERER_TYPE) @Type(TEXT_RENDERER_TYPE)
interface TextRenderer { interface TextRenderer {
/** /**

View File

@ -1,4 +1,4 @@
package hep.dataforge.io package hep.dataforge.output
import hep.dataforge.context.Context import hep.dataforge.context.Context
import hep.dataforge.context.Global import hep.dataforge.context.Global
@ -19,4 +19,4 @@ actual val ConsoleOutput: Output<Any> = object : Output<Any> {
} }
actual val OutputDispatcher: CoroutineDispatcher = Dispatchers.Default actual val Dispatchers.Output: CoroutineDispatcher get() = Dispatchers.Default

View File

@ -1,4 +1,4 @@
package hep.dataforge.io package hep.dataforge.output
import hep.dataforge.context.Global import hep.dataforge.context.Global
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@ -10,4 +10,4 @@ import kotlinx.io.streams.asOutput
*/ */
actual val ConsoleOutput: Output<Any> = TextOutput(Global, System.out.asOutput()) actual val ConsoleOutput: Output<Any> = TextOutput(Global, System.out.asOutput())
actual val OutputDispatcher = Dispatchers.IO actual val Dispatchers.Output get() = Dispatchers.IO

View File

@ -1,5 +1,5 @@
plugins { plugins {
kotlin("multiplatform") `npm-multiplatform`
} }
kotlin { kotlin {

View File

@ -2,6 +2,7 @@ package hep.dataforge.scripting
import hep.dataforge.context.Context import hep.dataforge.context.Context
import hep.dataforge.context.Global import hep.dataforge.context.Global
import hep.dataforge.workspace.SimpleWorkspaceBuilder
import hep.dataforge.workspace.Workspace import hep.dataforge.workspace.Workspace
import hep.dataforge.workspace.WorkspaceBuilder import hep.dataforge.workspace.WorkspaceBuilder
import java.io.File import java.io.File
@ -14,11 +15,12 @@ import kotlin.script.experimental.jvmhost.BasicJvmScriptingHost
object Builders { object Builders {
fun buildWorkspace(source: SourceCode, context: Context = Global): Workspace { fun buildWorkspace(source: SourceCode, context: Context = Global): Workspace {
val builder = WorkspaceBuilder(context) val builder = SimpleWorkspaceBuilder(context)
val workspaceScriptConfiguration = ScriptCompilationConfiguration { val workspaceScriptConfiguration = ScriptCompilationConfiguration {
baseClass(Any::class) baseClass(Any::class)
implicitReceivers(WorkspaceBuilder::class) implicitReceivers(WorkspaceBuilder::class)
defaultImports("hep.dataforge.workspace.*")
jvm { jvm {
dependenciesFromCurrentContext(wholeClasspath = true) dependenciesFromCurrentContext(wholeClasspath = true)
} }
@ -31,8 +33,10 @@ object Builders {
BasicJvmScriptingHost().eval(source, workspaceScriptConfiguration, evaluationConfiguration).onFailure { BasicJvmScriptingHost().eval(source, workspaceScriptConfiguration, evaluationConfiguration).onFailure {
it.reports.forEach { scriptDiagnostic -> it.reports.forEach { scriptDiagnostic ->
when (scriptDiagnostic.severity) { when (scriptDiagnostic.severity) {
ScriptDiagnostic.Severity.FATAL, ScriptDiagnostic.Severity.ERROR -> ScriptDiagnostic.Severity.FATAL, ScriptDiagnostic.Severity.ERROR -> {
context.logger.error(scriptDiagnostic.exception) { scriptDiagnostic.toString() } context.logger.error(scriptDiagnostic.exception) { scriptDiagnostic.toString() }
error(scriptDiagnostic.toString())
}
ScriptDiagnostic.Severity.WARNING -> context.logger.warn { scriptDiagnostic.toString() } ScriptDiagnostic.Severity.WARNING -> context.logger.warn { scriptDiagnostic.toString() }
ScriptDiagnostic.Severity.INFO -> context.logger.info { scriptDiagnostic.toString() } ScriptDiagnostic.Severity.INFO -> context.logger.info { scriptDiagnostic.toString() }
ScriptDiagnostic.Severity.DEBUG -> context.logger.debug { scriptDiagnostic.toString() } ScriptDiagnostic.Severity.DEBUG -> context.logger.debug { scriptDiagnostic.toString() }

View File

@ -1,20 +1,33 @@
package hep.dataforge.scripting package hep.dataforge.scripting
import hep.dataforge.context.Global
import hep.dataforge.meta.get import hep.dataforge.meta.get
import hep.dataforge.meta.int import hep.dataforge.meta.int
import hep.dataforge.workspace.*
import org.junit.Test import org.junit.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals
class BuildersKtTest { class BuildersKtTest {
@Test
fun checkBuilder(){
val workspace = SimpleWorkspaceBuilder(Global).apply {
println("I am working")
context("test")
target("testTarget"){
"a" to 12
}
}
}
@Test @Test
fun testWorkspaceBuilder() { fun testWorkspaceBuilder() {
val script = """ val script = """
println("I am working") println("I am working")
context{ context("test")
name = "test"
}
target("testTarget"){ target("testTarget"){
"a" to 12 "a" to 12

View File

@ -1,5 +1,5 @@
plugins { plugins {
kotlin("multiplatform") `npm-multiplatform`
} }
kotlin { kotlin {

View File

@ -2,13 +2,13 @@ package hep.dataforge.workspace
import hep.dataforge.data.DataFilter import hep.dataforge.data.DataFilter
import hep.dataforge.data.DataNode import hep.dataforge.data.DataNode
import hep.dataforge.data.DataTreeBuilder
import hep.dataforge.data.filter import hep.dataforge.data.filter
import hep.dataforge.meta.Meta import hep.dataforge.meta.Meta
import hep.dataforge.meta.MetaRepr import hep.dataforge.meta.MetaRepr
import hep.dataforge.meta.buildMeta import hep.dataforge.meta.buildMeta
import hep.dataforge.names.EmptyName import hep.dataforge.names.EmptyName
import hep.dataforge.names.Name import hep.dataforge.names.Name
import hep.dataforge.names.get
import hep.dataforge.names.isEmpty import hep.dataforge.names.isEmpty
/** /**
@ -18,31 +18,51 @@ sealed class Dependency : MetaRepr {
abstract fun apply(workspace: Workspace): DataNode<Any> abstract fun apply(workspace: Workspace): DataNode<Any>
} }
class DataDependency(val filter: DataFilter) : Dependency() { class DataDependency(val filter: DataFilter, val placement: Name = EmptyName) : 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> { override fun apply(workspace: Workspace): DataNode<Any> {
val task = workspace.tasks[name] ?: error("Task with name ${name} is not found in the workspace") val result = workspace.data.filter(filter)
if (task.isTerminal) TODO("Support terminal task")
val result = with(workspace) { task(meta) }
return if (placement.isEmpty()) { return if (placement.isEmpty()) {
result result
} else { } else {
DataTreeBuilder<Any>().apply { this[placement] = result }.build() DataNode.build(Any::class) { this[placement] = result }
} }
} }
override fun toMeta(): Meta = buildMeta { override fun toMeta(): Meta = buildMeta {
"name" to name "data" to filter.config
"meta" to meta "to" to placement
}
}
class AllDataDependency(val placement: Name = EmptyName) : Dependency() {
override fun apply(workspace: Workspace): DataNode<Any> = if (placement.isEmpty()) {
workspace.data
} else {
DataNode.build(Any::class) { this[placement] = workspace.data }
}
override fun toMeta() = buildMeta {
"data" to "*"
"to" to placement
}
}
class TaskModelDependency(val name: String, val meta: Meta, val placement: Name = EmptyName) : Dependency() {
override fun apply(workspace: Workspace): DataNode<Any> {
val task = workspace.tasks[name] ?: error("Task with name $name is not found in the workspace")
if (task.isTerminal) TODO("Support terminal task")
val result = workspace.run(task, meta)
return if (placement.isEmpty()) {
result
} else {
DataNode.build(Any::class) { this[placement] = result }
}
}
override fun toMeta(): Meta = buildMeta {
"task" to name
"meta" to meta
"to" to placement
} }
} }

View File

@ -0,0 +1,61 @@
package hep.dataforge.workspace
import hep.dataforge.data.*
import hep.dataforge.descriptors.NodeDescriptor
import hep.dataforge.meta.Meta
import hep.dataforge.meta.get
import hep.dataforge.meta.node
import kotlin.reflect.KClass
//data class TaskEnv(val workspace: Workspace, val model: TaskModel)
class GenericTask<R : Any>(
override val name: String,
override val type: KClass<out R>,
override val descriptor: NodeDescriptor,
private val modelTransform: TaskModelBuilder.(Meta) -> Unit,
private val dataTransform: Workspace.() -> TaskModel.(DataNode<Any>) -> DataNode<R>
) : Task<R> {
private fun gather(workspace: Workspace, model: TaskModel): DataNode<Any> {
return DataNode.build(Any::class) {
model.dependencies.forEach { dep ->
update(dep.apply(workspace))
}
}
}
override fun run(workspace: Workspace, model: TaskModel): DataNode<R> {
//validate model
validate(model)
// gather data
val input = gather(workspace, model)
//execute
workspace.context.logger.info{"Starting task '$name' on ${model.target} with meta: \n${model.meta}"}
val output = dataTransform(workspace).invoke(model, input)
//handle result
//output.handle(model.context.dispatcher) { this.handle(it) }
return output
}
/**
* Build new TaskModel and apply specific model transformation for this
* task. By default model uses the meta node with the same node as the name of the task.
*
* @param workspace
* @param taskConfig
* @return
*/
override fun build(workspace: Workspace, taskConfig: Meta): TaskModel {
val taskMeta = taskConfig[name]?.node ?: taskConfig
val builder = TaskModelBuilder(name, taskMeta)
modelTransform.invoke(builder, taskMeta)
return builder.build()
}
//TODO add validation
}

View File

@ -0,0 +1,29 @@
package hep.dataforge.workspace
import hep.dataforge.context.Context
import hep.dataforge.context.Global
import hep.dataforge.data.DataNode
import hep.dataforge.meta.Meta
import hep.dataforge.names.Name
import hep.dataforge.names.toName
import hep.dataforge.provider.top
/**
* A simple workspace without caching
*/
class SimpleWorkspace(
override val context: Context,
override val data: DataNode<Any>,
override val targets: Map<String, Meta>,
tasks: Collection<Task<Any>>
) : Workspace {
override val tasks: Map<Name, Task<*>> by lazy {
context.top<Task<*>>(Task.TYPE) + tasks.associate { it.name.toName() to it }
}
companion object {
fun build(parent: Context = Global, block: SimpleWorkspaceBuilder.() -> Unit): SimpleWorkspace =
SimpleWorkspaceBuilder(parent).apply(block).build()
}
}

View File

@ -2,13 +2,14 @@ package hep.dataforge.workspace
import hep.dataforge.context.Named import hep.dataforge.context.Named
import hep.dataforge.data.DataNode import hep.dataforge.data.DataNode
import hep.dataforge.descriptors.Described
import hep.dataforge.meta.Meta import hep.dataforge.meta.Meta
import hep.dataforge.provider.Type import hep.dataforge.provider.Type
import hep.dataforge.workspace.Task.Companion.TYPE import hep.dataforge.workspace.Task.Companion.TYPE
import kotlin.reflect.KClass import kotlin.reflect.KClass
@Type(TYPE) @Type(TYPE)
interface Task<out R : Any> : Named { interface Task<out R : Any> : Named, Described {
/** /**
* Terminal task is the one that could not build model lazily * Terminal task is the one that could not build model lazily
*/ */
@ -41,10 +42,11 @@ interface Task<out R : Any> : Named {
* Run given task model. Type check expected to be performed before actual * Run given task model. Type check expected to be performed before actual
* calculation. * calculation.
* *
* @param model * @param workspace - a workspace to run task model in
* @param model - a model to be executed
* @return * @return
*/ */
fun run(model: TaskModel): DataNode<R> fun run(workspace: Workspace, model: TaskModel): DataNode<R>
companion object { companion object {
const val TYPE = "task" const val TYPE = "task"

View File

@ -12,6 +12,7 @@ import hep.dataforge.meta.*
import hep.dataforge.names.EmptyName import hep.dataforge.names.EmptyName
import hep.dataforge.names.Name import hep.dataforge.names.Name
import hep.dataforge.names.toName import hep.dataforge.names.toName
import hep.dataforge.workspace.TaskModel.Companion.MODEL_TARGET_KEY
/** /**
@ -39,23 +40,31 @@ data class TaskModel(
//TODO ensure all dependencies are listed //TODO ensure all dependencies are listed
} }
} }
companion object {
const val MODEL_TARGET_KEY = "@target"
}
} }
/** /**
* Build input for the task * Build input for the task
*/ */
fun TaskModel.buildInput(workspace: Workspace): DataTree<Any> { fun TaskModel.buildInput(workspace: Workspace): DataTree<Any> {
return DataTreeBuilder<Any>().apply { return DataTreeBuilder(Any::class).apply {
dependencies.asSequence().flatMap { it.apply(workspace).dataSequence() }.forEach { (name, data) -> dependencies.asSequence().flatMap { it.apply(workspace).data() }.forEach { (name, data) ->
//TODO add concise error on replacement //TODO add concise error on replacement
this[name] = data this[name] = data
} }
}.build() }.build()
} }
@DslMarker
annotation class TaskBuildScope
/** /**
* A builder for [TaskModel] * A builder for [TaskModel]
*/ */
@TaskBuildScope
class TaskModelBuilder(val name: String, meta: Meta = EmptyMeta) { class TaskModelBuilder(val name: String, meta: Meta = EmptyMeta) {
/** /**
* Meta for current task. By default uses the whole input meta * Meta for current task. By default uses the whole input meta
@ -63,13 +72,21 @@ class TaskModelBuilder(val name: String, meta: Meta = EmptyMeta) {
var meta: MetaBuilder = meta.builder() var meta: MetaBuilder = meta.builder()
val dependencies = HashSet<Dependency>() val dependencies = HashSet<Dependency>()
var target: String by this.meta.string(key = MODEL_TARGET_KEY, default = "")
/** /**
* Add dependency for * Add dependency for
*/ */
fun dependsOn(name: String, meta: Meta, placement: Name = EmptyName) { fun dependsOn(name: String, meta: Meta = this.meta, placement: Name = EmptyName) {
dependencies.add(TaskModelDependency(name, meta, placement)) dependencies.add(TaskModelDependency(name, meta, placement))
} }
fun dependsOn(task: Task<*>, meta: Meta = this.meta, placement: Name = EmptyName) =
dependsOn(task.name, meta, placement)
fun dependsOn(task: Task<*>, placement: Name = EmptyName, metaBuilder: MetaBuilder.() -> Unit) =
dependsOn(task.name, buildMeta(metaBuilder), placement)
/** /**
* Add custom data dependency * Add custom data dependency
*/ */
@ -89,9 +106,12 @@ class TaskModelBuilder(val name: String, meta: Meta = EmptyMeta) {
/** /**
* Add all data as root node * Add all data as root node
*/ */
fun allData() { fun allData(to: Name = EmptyName) {
dependencies.add(DataDependency.all) dependencies.add(AllDataDependency(to))
} }
fun build(): TaskModel = TaskModel(name, meta.seal(), dependencies) fun build(): TaskModel = TaskModel(name, meta.seal(), dependencies)
} }
val TaskModel.target get() = meta[MODEL_TARGET_KEY]?.string ?: ""

View File

@ -1,11 +1,11 @@
package hep.dataforge.workspace package hep.dataforge.workspace
import hep.dataforge.context.Context
import hep.dataforge.context.ContextAware import hep.dataforge.context.ContextAware
import hep.dataforge.context.members
import hep.dataforge.data.Data import hep.dataforge.data.Data
import hep.dataforge.data.DataNode import hep.dataforge.data.DataNode
import hep.dataforge.meta.Meta import hep.dataforge.meta.Meta
import hep.dataforge.meta.MetaBuilder
import hep.dataforge.meta.buildMeta
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
@ -27,62 +27,68 @@ interface Workspace : ContextAware, Provider {
/** /**
* All tasks associated with the workspace * All tasks associated with the workspace
*/ */
val tasks: Map<String, Task<*>> val tasks: Map<Name, Task<*>>
override fun provideTop(target: String, name: Name): Any? { override fun provideTop(target: String, name: Name): Any? {
return when (target) { return when (target) {
"target", Meta.TYPE -> targets[name.toString()] "target", Meta.TYPE -> targets[name.toString()]
Task.TYPE -> tasks[name.toString()] Task.TYPE -> tasks[name]
Data.TYPE -> data[name] Data.TYPE -> data[name]
DataNode.TYPE -> data.getNode(name) DataNode.TYPE -> data.getNode(name)
else -> null else -> null
} }
} }
override fun listTop(target: String): Sequence<Name> { override fun listNames(target: String): Sequence<Name> {
return when (target) { return when (target) {
"target", Meta.TYPE -> targets.keys.asSequence().map { it.toName() } "target", Meta.TYPE -> targets.keys.asSequence().map { it.toName() }
Task.TYPE -> tasks.keys.asSequence().map { it.toName() } Task.TYPE -> tasks.keys.asSequence().map { it }
Data.TYPE -> data.dataSequence().map { it.first } Data.TYPE -> data.data().map { it.first }
DataNode.TYPE -> data.nodeSequence().map { it.first } DataNode.TYPE -> data.nodes().map { it.first }
else -> emptySequence() else -> emptySequence()
} }
} }
operator fun <R : Any> Task<R>.invoke(config: Meta): DataNode<R> {
/**
* Invoke a task in the workspace utilizing caching if possible
*/
fun <R : Any> run(task: Task<R>, config: Meta): DataNode<R> {
context.activate(this) context.activate(this)
try { try {
val model = build(this@Workspace, config) val model = task.build(this, config)
validate(model) task.validate(model)
return run(model) return task.run(this, model)
} finally { } finally {
context.deactivate(this) context.deactivate(this)
} }
} }
/** // /**
* Invoke a task in the workspace utilizing caching if possible // * Invoke a task in the workspace utilizing caching if possible
*/ // */
operator fun <R : Any> Task<R>.invoke(targetName: String): DataNode<R> { // operator fun <R : Any> Task<R>.invoke(targetName: String): DataNode<R> {
val target = targets[targetName] ?: error("A target with name $targetName not found in ${this@Workspace}") // val target = targets[targetName] ?: error("A target with name $targetName not found in ${this@Workspace}")
return invoke(target) // context.logger.info { "Running ${this.name} on $target" }
} // return invoke(target)
// }
companion object { companion object {
const val TYPE = "workspace" const val TYPE = "workspace"
} }
} }
class SimpleWorkspace( fun Workspace.run(task: Task<*>, target: String): DataNode<Any> {
override val context: Context, val meta = targets[target] ?: error("A target with name $target not found in ${this}")
override val data: DataNode<Any>, return run(task, meta)
override val targets: Map<String, Meta>,
tasks: Collection<Task<Any>>
) : Workspace {
override val tasks: Map<String, Task<*>> by lazy {
(context.members<Task<*>>(Task.TYPE) + tasks).associate { it.name to it }
}
} }
fun Workspace.run(task: String, target: String) =
tasks[task.toName()]?.let { run(it, target) } ?: error("Task with name $task not found")
fun Workspace.run(task: String, meta: Meta) =
tasks[task.toName()]?.let { run(it, meta) } ?: error("Task with name $task not found")
fun Workspace.run(task: String, block: MetaBuilder.() -> Unit = {}) =
run(task, buildMeta(block))

View File

@ -2,39 +2,91 @@ package hep.dataforge.workspace
import hep.dataforge.context.Context import hep.dataforge.context.Context
import hep.dataforge.context.ContextBuilder import hep.dataforge.context.ContextBuilder
import hep.dataforge.data.Data
import hep.dataforge.data.DataNode
import hep.dataforge.data.DataTreeBuilder import hep.dataforge.data.DataTreeBuilder
import hep.dataforge.meta.Meta import hep.dataforge.meta.*
import hep.dataforge.meta.MetaBuilder import hep.dataforge.names.Name
import hep.dataforge.meta.buildMeta import hep.dataforge.names.toName
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.GlobalScope
@TaskBuildScope
interface WorkspaceBuilder {
val parentContext: Context
var context: Context
var data: DataTreeBuilder<Any>
var tasks: MutableSet<Task<Any>>
var targets: MutableMap<String, Meta>
fun build(): Workspace
}
/** /**
* A builder for a workspace * Set the context for future workspcace
*/ */
class WorkspaceBuilder(var context: Context) { fun WorkspaceBuilder.context(name: String, block: ContextBuilder.() -> Unit = {}) {
val data = DataTreeBuilder<Any>() context = ContextBuilder(name, parentContext).apply(block).build()
val targets = HashMap<String, Meta>() }
val tasks = HashSet<Task<Any>>()
fun WorkspaceBuilder.data(name: Name, data: Data<Any>) {
fun context(action: ContextBuilder.() -> Unit) { this.data[name] = data
this.context = ContextBuilder().apply(action).build() }
}
fun WorkspaceBuilder.data(name: String, data: Data<Any>) = data(name.toName(), data)
fun data(action: DataTreeBuilder<Any>.() -> Unit) = data.apply(action)
fun WorkspaceBuilder.static(name: Name, data: Any, scope: CoroutineScope = GlobalScope, meta: Meta = EmptyMeta) =
fun target(name: String, meta: Meta) { data(name, Data.static(scope, data, meta))
targets[name] = meta
} fun WorkspaceBuilder.static(name: Name, data: Any, scope: CoroutineScope = GlobalScope, block: MetaBuilder.() -> Unit = {}) =
data(name, Data.static(scope, data, buildMeta(block)))
fun target(name: String, action: MetaBuilder.() -> Unit) = target(name, buildMeta(action))
fun WorkspaceBuilder.static(name: String, data: Any, scope: CoroutineScope = GlobalScope, block: MetaBuilder.() -> Unit = {}) =
fun task(task: Task<*>) { data(name, Data.static(scope, data, buildMeta(block)))
tasks.add(task)
} fun WorkspaceBuilder.data(name: Name, node: DataNode<Any>) {
this.data[name] = node
fun build(): Workspace = SimpleWorkspace( }
context,
data.build(), fun WorkspaceBuilder.data(name: String, node: DataNode<Any>) = data(name.toName(), node)
targets,
tasks fun WorkspaceBuilder.data(name: Name, block: DataTreeBuilder<Any>.() -> Unit) {
) this.data[name] = DataNode.build(Any::class, block)
}
fun WorkspaceBuilder.data(name: String, block: DataTreeBuilder<Any>.() -> Unit) = data(name.toName(), block)
fun WorkspaceBuilder.target(name: String, block: MetaBuilder.() -> Unit) {
targets[name] = buildMeta(block).seal()
}
/**
* Use existing target as a base updating it with the block
*/
fun WorkspaceBuilder.target(name: String, base: String, block: MetaBuilder.() -> Unit) {
val parentTarget = targets[base] ?: error("Base target with name $base not found")
targets[name] = parentTarget.builder()
.apply { "@baseTarget" to base }
.apply(block)
.seal()
}
fun WorkspaceBuilder.task(task: Task<Any>) {
this.tasks.add(task)
}
/**
* A builder for a simple workspace
*/
class SimpleWorkspaceBuilder(override val parentContext: Context) : WorkspaceBuilder {
override var context: Context = parentContext
override var data = DataTreeBuilder(Any::class)
override var tasks: MutableSet<Task<Any>> = HashSet()
override var targets: MutableMap<String, Meta> = HashMap()
override fun build(): SimpleWorkspace {
return SimpleWorkspace(context, data.build(), targets, tasks)
}
} }

View File

@ -0,0 +1,218 @@
package hep.dataforge.workspace
import hep.dataforge.context.Context
import hep.dataforge.data.*
import hep.dataforge.descriptors.NodeDescriptor
import hep.dataforge.meta.Meta
import hep.dataforge.meta.get
import hep.dataforge.meta.string
import hep.dataforge.names.Name
import hep.dataforge.names.toName
import kotlin.reflect.KClass
@TaskBuildScope
class TaskBuilder(val name: String) {
private var modelTransform: TaskModelBuilder.(Meta) -> Unit = { data("*") }
var descriptor: NodeDescriptor? = null
/**
* TODO will look better as extension class
*/
private class DataTransformation(
val from: String = "",
val to: String = "",
val transform: (Context, TaskModel, DataNode<Any>) -> DataNode<Any>
) {
operator fun invoke(workspace: Workspace, model: TaskModel, node: DataNode<Any>): DataNode<Any>? {
val localData = if (from.isEmpty()) {
node
} else {
node.getNode(from.toName()) ?: return null
}
return transform(workspace.context, model, localData)
}
}
private val dataTransforms: MutableList<DataTransformation> = ArrayList();
fun model(modelTransform: TaskModelBuilder.(Meta) -> Unit) {
this.modelTransform = modelTransform
}
fun <T : Any> transform(
inputType: KClass<T>,
from: String = "",
to: String = "",
block: TaskModel.(Context, DataNode<T>) -> DataNode<Any>
) {
dataTransforms += DataTransformation(from, to) { context, model, data ->
block(model, context, data.cast(inputType))
}
}
inline fun <reified T : Any> transform(
from: String = "",
to: String = "",
noinline block: TaskModel.(Context, DataNode<T>) -> DataNode<Any>
) {
transform(T::class, from, to, block)
}
/**
* Perform given action on data elements in `from` node in input and put the result to `to` node
*/
inline fun <reified T : Any, reified R : Any> action(
from: String = "",
to: String = "",
crossinline block: Context.() -> Action<T, R>
) {
transform(from, to) { context, data: DataNode<T> ->
block(context).invoke(data, meta)
}
}
class TaskEnv(val name: Name, val meta: Meta, val context: Context)
/**
* A customized pipe action with ability to change meta and name
*/
inline fun <reified T : Any, reified R : Any> customPipe(
from: String = "",
to: String = "",
crossinline block: PipeBuilder<T, R>.(Context) -> Unit
) {
action(from, to) {
val context = this
PipeAction(
inputType = T::class,
outputType = R::class
) { block(context) }
}
}
/**
* A simple pipe action without changing meta or name
*/
inline fun <reified T : Any, reified R : Any> pipe(
from: String = "",
to: String = "",
crossinline block: suspend TaskEnv.(T) -> R
) {
action(from, to) {
val context = this
PipeAction(
inputType = T::class,
outputType = R::class
) {
//TODO automatically append task meta
result = { data ->
TaskEnv(name, meta, context).block(data)
}
}
}
}
/**
* Join elements in gathered data by multiple groups
*/
inline fun <reified T : Any, reified R : Any> joinByGroup(
from: String = "",
to: String = "",
crossinline block: JoinGroupBuilder<T, R>.(Context) -> Unit
) {
action(from, to) {
JoinAction(
inputType = T::class,
outputType = R::class
) { block(this@action) }
}
}
/**
* Join all elemlents in gathered data matching input type
*/
inline fun <reified T : Any, reified R : Any> join(
from: String = "",
to: String = "",
crossinline block: suspend TaskEnv.(Map<Name, T>) -> R
) {
action(from, to) {
val context = this
JoinAction(
inputType = T::class,
outputType = R::class,
action = {
result(
actionMeta[TaskModel.MODEL_TARGET_KEY]?.string ?: "@anonymous"
) { data ->
TaskEnv(name, meta, context).block(data)
}
}
)
}
}
/**
* Split each element in gathered data into fixed number of fragments
*/
inline fun <reified T : Any, reified R : Any> split(
from: String = "",
to: String = "",
crossinline block: SplitBuilder<T, R>.(Context) -> Unit
) {
action(from, to) {
SplitAction(
inputType = T::class,
outputType = R::class
) { block(this@action) }
}
}
/**
* Use DSL to create a descriptor for this task
*/
fun description(transform: NodeDescriptor.() -> Unit) {
this.descriptor = NodeDescriptor.build(transform)
}
internal fun build(): GenericTask<Any> =
GenericTask(
name,
Any::class,
descriptor ?: NodeDescriptor.empty(),
modelTransform
) {
val workspace = this
{ data ->
val model = this
if (dataTransforms.isEmpty()) {
//return data node as is
logger.warn("No transformation present, returning input data")
data
} else {
val builder = DataTreeBuilder(Any::class)
dataTransforms.forEach { transformation ->
val res = transformation(workspace, model, data)
if (res != null) {
if (transformation.to.isEmpty()) {
builder.update(res)
} else {
builder[transformation.to.toName()] = res
}
}
}
builder.build()
}
}
}
}
fun task(name: String, builder: TaskBuilder.() -> Unit): GenericTask<Any> {
return TaskBuilder(name).apply(builder).build()
}
fun WorkspaceBuilder.task(name: String, builder: TaskBuilder.() -> Unit) {
task(TaskBuilder(name).apply(builder).build())
}
//TODO add delegates to build gradle-like tasks

View File

@ -0,0 +1,73 @@
package hep.dataforge.workspace
import hep.dataforge.data.first
import hep.dataforge.data.get
import org.junit.Test
import kotlin.test.assertEquals
class SimpleWorkspaceTest {
val workspace = SimpleWorkspace.build {
repeat(100) {
static("myData[$it]", it)
}
task("square") {
model {
allData()
}
pipe<Int, Int> { data ->
context.logger.info { "Starting square on $data" }
data * data
}
}
task("sum") {
model {
dependsOn("square")
}
join<Int, Int> { data ->
context.logger.info { "Starting sum" }
data.values.sum()
}
}
task("average") {
model {
allData()
}
joinByGroup<Int, Double> { context ->
group("even", filter = { name, data -> name.toString().toInt() % 2 == 0 }) {
result { data ->
context.logger.info { "Starting even" }
data.values.average()
}
}
group("odd", filter = { name, data -> name.toString().toInt() % 2 == 1 }) {
result { data ->
context.logger.info { "Starting odd" }
data.values.average()
}
}
}
}
task("delta"){
model{
dependsOn("average")
}
join<Double,Double> {data->
data["even"]!! - data["odd"]!!
}
}
}
@Test
fun testWorkspace() {
val node = workspace.run("sum")
val res = node.first()
assertEquals(328350, res.get())
}
}

View File

@ -18,13 +18,14 @@ pluginManagement {
enableFeaturePreview("GRADLE_METADATA") enableFeaturePreview("GRADLE_METADATA")
rootProject.name = "dataforge-core" //rootProject.name = "dataforge-core"
include( include(
":dataforge-meta", ":dataforge-meta",
":dataforge-meta-io", ":dataforge-io",
":dataforge-context", ":dataforge-context",
":dataforge-data", ":dataforge-data",
":dataforge-io", ":dataforge-output",
":dataforge-output:dataforge-output-html",
":dataforge-workspace", ":dataforge-workspace",
":dataforge-scripting" ":dataforge-scripting"
) )