Merge remote-tracking branch 'origin/dev' into dev
This commit is contained in:
commit
47e39ae0ac
4
.gitignore
vendored
4
.gitignore
vendored
@ -1,9 +1,9 @@
|
|||||||
|
|
||||||
.idea/
|
.idea/
|
||||||
*.iws
|
*.iws
|
||||||
*/out/**
|
out/
|
||||||
.gradle
|
.gradle
|
||||||
*/build/**
|
build/
|
||||||
|
|
||||||
|
|
||||||
!gradle-wrapper.jar
|
!gradle-wrapper.jar
|
||||||
|
@ -40,7 +40,7 @@ allprojects {
|
|||||||
}
|
}
|
||||||
|
|
||||||
group = "hep.dataforge"
|
group = "hep.dataforge"
|
||||||
version = "0.1.1-dev-5"
|
version = "0.1.2-dev-1"
|
||||||
|
|
||||||
// apply bintray configuration
|
// apply bintray configuration
|
||||||
apply(from = "${rootProject.rootDir}/gradle/bintray.gradle")
|
apply(from = "${rootProject.rootDir}/gradle/bintray.gradle")
|
||||||
|
@ -80,8 +80,8 @@ fun Meta.getAll(name: Name): Map<String, MetaItem<out Meta>> {
|
|||||||
val (body, query) = name.last()!!
|
val (body, query) = name.last()!!
|
||||||
val regex = query.toRegex()
|
val regex = query.toRegex()
|
||||||
return (this[name.cutLast()] as? NodeItem<*>)?.node?.items
|
return (this[name.cutLast()] as? NodeItem<*>)?.node?.items
|
||||||
?.filter { it.key.body == body && (query.isEmpty() || regex.matches(it.key.query)) }
|
?.filter { it.key.body == body && (query.isEmpty() || regex.matches(it.key.index)) }
|
||||||
?.mapKeys { it.key.query }
|
?.mapKeys { it.key.index }
|
||||||
?: emptyMap()
|
?: emptyMap()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -154,7 +154,7 @@ fun <M : MutableMeta<M>> M.setIndexed(
|
|||||||
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 + queryFactory(index))
|
||||||
tokens[tokens.lastIndex] = indexedToken
|
tokens[tokens.lastIndex] = indexedToken
|
||||||
set(Name(tokens), meta)
|
set(Name(tokens), meta)
|
||||||
}
|
}
|
||||||
|
@ -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,21 +43,21 @@ 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 {
|
||||||
@ -84,7 +84,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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
28
dataforge-output/dataforge-output-html/build.gradle.kts
Normal file
28
dataforge-output/dataforge-output-html/build.gradle.kts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
plugins {
|
||||||
|
kotlin("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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,78 @@
|
|||||||
|
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.TextRenderer.Companion.TEXT_RENDERER_TYPE
|
||||||
|
import hep.dataforge.output.html.HtmlBuilder.Companion.HTML_CONVERTER_TYPE
|
||||||
|
import hep.dataforge.provider.Type
|
||||||
|
import hep.dataforge.provider.provideAll
|
||||||
|
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.provideAll(HTML_CONVERTER_TYPE).filterIsInstance<HtmlBuilder<*>>()
|
||||||
|
.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() }
|
||||||
|
}
|
||||||
|
}
|
@ -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
|
@ -1,6 +1,7 @@
|
|||||||
package hep.dataforge.io
|
package hep.dataforge.output
|
||||||
|
|
||||||
import hep.dataforge.context.AbstractPlugin
|
import hep.dataforge.context.AbstractPlugin
|
||||||
|
import hep.dataforge.context.Context
|
||||||
import hep.dataforge.context.Plugin
|
import hep.dataforge.context.Plugin
|
||||||
import hep.dataforge.context.PluginTag
|
import hep.dataforge.context.PluginTag
|
||||||
import hep.dataforge.context.PluginTag.Companion.DATAFORGE_GROUP
|
import hep.dataforge.context.PluginTag.Companion.DATAFORGE_GROUP
|
||||||
@ -9,40 +10,45 @@ import hep.dataforge.meta.Meta
|
|||||||
import hep.dataforge.names.EmptyName
|
import hep.dataforge.names.EmptyName
|
||||||
import hep.dataforge.names.Name
|
import hep.dataforge.names.Name
|
||||||
import kotlinx.coroutines.CoroutineDispatcher
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A manager for outputs
|
* A manager for outputs
|
||||||
*/
|
*/
|
||||||
interface OutputManager : Plugin {
|
interface OutputManager : Plugin {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provide an output for given name and stage.
|
* 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 stage represents the node or directory for the output. Empty means root node.
|
||||||
* @param name represents the name inside the node.
|
* @param name represents the name inside the node.
|
||||||
* @param meta configuration for [Output] (not for rendered object)
|
* @param meta configuration for [Output] (not for rendered object)
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
operator fun get(name: Name, stage: Name = EmptyName, meta: Meta = EmptyMeta): Output<Any>
|
operator fun <T : Any> get(type: KClass<out T>, name: Name, stage: Name = EmptyName, meta: Meta = EmptyMeta): Output<T>
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get an output specialized for giver ntype
|
* Get an output manager for a context
|
||||||
*/
|
*/
|
||||||
fun <T : Any> typed(type: KClass<T>, name: Name, stage: Name = EmptyName, meta: Meta = EmptyMeta): Output<T>
|
val Context.output: OutputManager get() = plugins.get() ?: ConsoleOutputManager
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get an output with given [name], [stage] and reified content type
|
* Get an output with given [name], [stage] and reified content type
|
||||||
*/
|
*/
|
||||||
inline fun <reified T : Any> OutputManager.typed(
|
inline operator fun <reified T : Any> OutputManager.get(
|
||||||
name: Name,
|
name: Name,
|
||||||
stage: Name = EmptyName,
|
stage: Name = EmptyName,
|
||||||
meta: Meta = EmptyMeta
|
meta: Meta = EmptyMeta
|
||||||
): Output<T> {
|
): Output<T> {
|
||||||
return typed(T::class, name, stage, meta)
|
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.
|
* System console output.
|
||||||
* The [ConsoleOutput] is used when no other [OutputManager] is provided.
|
* The [ConsoleOutput] is used when no other [OutputManager] is provided.
|
||||||
@ -52,12 +58,10 @@ expect val ConsoleOutput: Output<Any>
|
|||||||
object ConsoleOutputManager : AbstractPlugin(), OutputManager {
|
object ConsoleOutputManager : AbstractPlugin(), OutputManager {
|
||||||
override val tag: PluginTag = PluginTag("output.console", group = DATAFORGE_GROUP)
|
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> get(type: KClass<out T>, name: Name, stage: Name, meta: Meta): Output<T> = ConsoleOutput
|
||||||
|
|
||||||
override fun <T : Any> typed(type: KClass<T>, name: Name, stage: Name, meta: Meta): Output<T> = ConsoleOutput
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A dispatcher for output tasks.
|
* A dispatcher for output tasks.
|
||||||
*/
|
*/
|
||||||
expect val OutputDispatcher : CoroutineDispatcher
|
expect val Dispatchers.Output: CoroutineDispatcher
|
@ -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.output.TextRenderer.Companion.TEXT_RENDERER_TYPE
|
||||||
import hep.dataforge.meta.Meta
|
import hep.dataforge.meta.Meta
|
||||||
import hep.dataforge.provider.Type
|
import hep.dataforge.provider.Type
|
||||||
import hep.dataforge.provider.provideAll
|
import hep.dataforge.provider.provideAll
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
@ -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 {
|
||||||
/**
|
/**
|
@ -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
|
@ -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
|
@ -24,7 +24,8 @@ include(
|
|||||||
":dataforge-meta-io",
|
":dataforge-meta-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"
|
||||||
)
|
)
|
Loading…
Reference in New Issue
Block a user