Fixed all IO issues (for now)

This commit is contained in:
Alexander Nozik 2019-10-27 16:50:05 +03:00
parent 87ae41886b
commit d3dd15884c
17 changed files with 349 additions and 102 deletions

View File

@ -1,9 +1,10 @@
plugins { plugins {
id("scientifik.mpp") version "0.2.1" apply false id("scientifik.mpp") version "0.2.1" apply false
id("scientifik.jvm") version "0.2.1" apply false
id("scientifik.publish") version "0.2.1" apply false id("scientifik.publish") version "0.2.1" apply false
} }
val dataforgeVersion by extra("0.1.4-dev-6") val dataforgeVersion by extra("0.1.4-dev-8")
val bintrayRepo by extra("dataforge") val bintrayRepo by extra("dataforge")
val githubProject by extra("dataforge-core") val githubProject by extra("dataforge-core")

View File

@ -0,0 +1,9 @@
plugins {
id("scientifik.jvm")
}
description = "YAML meta IO"
dependencies{
api(project(":dataforge-io"))
}

View File

@ -11,26 +11,54 @@ import hep.dataforge.names.asName
import hep.dataforge.provider.Type import hep.dataforge.provider.Type
import hep.dataforge.values.Value import hep.dataforge.values.Value
import kotlinx.io.core.* import kotlinx.io.core.*
import kotlinx.io.pool.ObjectPool
import kotlinx.serialization.ImplicitReflectionSerializer import kotlinx.serialization.ImplicitReflectionSerializer
import kotlinx.serialization.KSerializer import kotlinx.serialization.KSerializer
import kotlinx.serialization.cbor.Cbor import kotlinx.serialization.cbor.Cbor
import kotlinx.serialization.serializer import kotlinx.serialization.serializer
import kotlin.math.min
import kotlin.reflect.KClass import kotlin.reflect.KClass
/** /**
* And interface for serialization facilities * And interface for reading and writing objects into with IO streams
*/ */
interface IOFormat<T : Any> { interface IOFormat<T : Any> {
fun Output.writeThis(obj: T) fun Output.writeThis(obj: T)
fun Input.readThis(): T fun Input.readThis(): T
} }
fun <T : Any> IOFormat<T>.writePacket(obj: T): ByteReadPacket = buildPacket { writeThis(obj) } fun <T : Any> Input.readWith(format: IOFormat<T>): T = format.run { readThis() }
fun <T : Any> IOFormat<T>.writeBytes(obj: T): ByteArray = buildPacket { writeThis(obj) }.readBytes() fun <T : Any> Output.writeWith(format: IOFormat<T>, obj: T) = format.run { writeThis(obj) }
fun <T : Any> IOFormat<T>.readBytes(array: ByteArray): T = ByteReadPacket(array).readThis()
class ListIOFormat<T : Any>(val format: IOFormat<T>) : IOFormat<List<T>> {
override fun Output.writeThis(obj: List<T>) {
writeInt(obj.size)
format.run {
obj.forEach {
writeThis(it)
}
}
}
override fun Input.readThis(): List<T> {
val size = readInt()
return format.run {
List(size) { readThis() }
}
}
}
val <T : Any> IOFormat<T>.list get() = ListIOFormat(this)
fun ObjectPool<IoBuffer>.fill(block: IoBuffer.() -> Unit): IoBuffer {
val buffer = borrow()
return try {
buffer.apply(block)
} catch (ex: Exception) {
//recycle(buffer)
throw ex
}
}
@Type(IO_FORMAT_TYPE) @Type(IO_FORMAT_TYPE)
interface IOFormatFactory<T : Any> : Factory<IOFormat<T>>, Named { interface IOFormatFactory<T : Any> : Factory<IOFormat<T>>, Named {
@ -44,6 +72,65 @@ interface IOFormatFactory<T : Any> : Factory<IOFormat<T>>, Named {
} }
} }
@Deprecated("To be removed in io-2")
inline fun buildPacketWithoutPool(headerSizeHint: Int = 0, block: BytePacketBuilder.() -> Unit): ByteReadPacket {
val builder = BytePacketBuilder(headerSizeHint, IoBuffer.NoPool)
block(builder)
return builder.build()
}
//@Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_OVERRIDE")
//internal fun <R> Input.useAtMost(most: Int, reader: Input.() -> R): R {
// val limitedInput: Input = object : AbstractInput(
// IoBuffer.Pool.borrow(),
// remaining = most.toLong(),
// pool = IoBuffer.Pool
// ) {
// var read = 0
// override fun closeSource() {
// this@useAtMost.close()
// }
//
// override fun fill(): IoBuffer? {
// if (read >= most) return null
// return IoBuffer.Pool.fill {
// reserveEndGap(IoBuffer.ReservedSize)
// read += this@useAtMost.peekTo(this, max = most - read)
// }
// }
//
// }
// return limitedInput.reader()
//}
fun <T : Any> IOFormat<T>.writePacket(obj: T): ByteReadPacket = buildPacket { writeThis(obj) }
fun <T : Any> IOFormat<T>.writeBytes(obj: T): ByteArray = buildPacket { writeThis(obj) }.readBytes()
fun <T : Any> IOFormat<T>.readBytes(array: ByteArray): T {
//= ByteReadPacket(array).readThis()
val byteArrayInput: Input = object : AbstractInput(
IoBuffer.Pool.borrow(),
remaining = array.size.toLong(),
pool = IoBuffer.Pool
) {
var written = 0
override fun closeSource() {
// do nothing
}
override fun fill(): IoBuffer? {
if (array.size - written <= 0) return null
return IoBuffer.Pool.fill {
reserveEndGap(IoBuffer.ReservedSize)
val toWrite = min(capacity, array.size - written)
writeFully(array, written, toWrite)
written += toWrite
}
}
}
return byteArrayInput.readThis()
}
object DoubleIOFormat : IOFormat<Double>, IOFormatFactory<Double> { object DoubleIOFormat : IOFormat<Double>, IOFormatFactory<Double> {
override fun invoke(meta: Meta, context: Context): IOFormat<Double> = this override fun invoke(meta: Meta, context: Context): IOFormat<Double> = this

View File

@ -24,7 +24,7 @@ import kotlin.collections.component2
import kotlin.collections.set import kotlin.collections.set
class JsonMetaFormat(private val json: Json = Json.plain) : MetaFormat { class JsonMetaFormat(private val json: Json = Json.indented) : MetaFormat {
override fun Output.writeMeta(meta: Meta, descriptor: NodeDescriptor?) { override fun Output.writeMeta(meta: Meta, descriptor: NodeDescriptor?) {
val jsonObject = meta.toJson(descriptor) val jsonObject = meta.toJson(descriptor)
@ -34,12 +34,7 @@ class JsonMetaFormat(private val json: Json = Json.plain) : MetaFormat {
override fun Input.readMeta(descriptor: NodeDescriptor?): Meta { override fun Input.readMeta(descriptor: NodeDescriptor?): Meta {
val str = readText() val str = readText()
val jsonElement = json.parseJson(str) val jsonElement = json.parseJson(str)
return jsonElement.toMeta()
if (jsonElement is JsonObject) {
return jsonElement.toMeta()
} else {
TODO("Non-object root not supported")
}
} }
companion object : MetaFormatFactory { companion object : MetaFormatFactory {
@ -92,7 +87,12 @@ fun Meta.toJson(descriptor: NodeDescriptor? = null): JsonObject {
return JsonObject(map) return JsonObject(map)
} }
fun JsonObject.toMeta(descriptor: NodeDescriptor? = null): JsonMeta = JsonMeta(this, descriptor) fun JsonElement.toMeta(descriptor: NodeDescriptor? = null): Meta {
return when (val item = toMetaItem(descriptor)) {
is MetaItem.NodeItem<*> -> item.node
is MetaItem.ValueItem ->item.value.toMeta()
}
}
fun JsonPrimitive.toValue(descriptor: ValueDescriptor?): Value { fun JsonPrimitive.toValue(descriptor: ValueDescriptor?): Value {
return when (this) { return when (this) {
@ -107,7 +107,7 @@ fun JsonElement.toMetaItem(descriptor: ItemDescriptor? = null): MetaItem<JsonMet
MetaItem.ValueItem(value) MetaItem.ValueItem(value)
} }
is JsonObject -> { is JsonObject -> {
val meta = toMeta(descriptor as? NodeDescriptor) val meta = JsonMeta(this, descriptor as? NodeDescriptor)
MetaItem.NodeItem(meta) MetaItem.NodeItem(meta)
} }
is JsonArray -> { is JsonArray -> {
@ -143,7 +143,7 @@ class JsonMeta(val json: JsonObject, val descriptor: NodeDescriptor? = null) : M
this[name] = MetaItem.ValueItem(value.toValue(itemDescriptor as? ValueDescriptor)) as MetaItem<JsonMeta> this[name] = MetaItem.ValueItem(value.toValue(itemDescriptor as? ValueDescriptor)) as MetaItem<JsonMeta>
} }
is JsonObject -> { is JsonObject -> {
this[name] = MetaItem.NodeItem(value.toMeta(itemDescriptor as? NodeDescriptor)) this[name] = MetaItem.NodeItem(JsonMeta(value, itemDescriptor as? NodeDescriptor))
} }
is JsonArray -> { is JsonArray -> {
when { when {

View File

@ -20,7 +20,7 @@ interface MetaFormat : IOFormat<Meta> {
writeMeta(obj, null) writeMeta(obj, null)
} }
override fun Input.readThis(): Meta = readMeta(null) override fun Input.readThis(): Meta = readMeta()
fun Output.writeMeta(meta: Meta, descriptor: NodeDescriptor? = null) fun Output.writeMeta(meta: Meta, descriptor: NodeDescriptor? = null)
fun Input.readMeta(descriptor: NodeDescriptor? = null): Meta fun Input.readMeta(descriptor: NodeDescriptor? = null): Meta

View File

@ -1,67 +0,0 @@
package hep.dataforge.io
import hep.dataforge.meta.Config
import hep.dataforge.meta.Meta
import hep.dataforge.meta.toConfig
import hep.dataforge.names.Name
import hep.dataforge.names.NameToken
import hep.dataforge.names.toName
import kotlinx.serialization.*
import kotlinx.serialization.internal.StringDescriptor
import kotlinx.serialization.json.JsonObjectSerializer
@Serializer(Name::class)
object NameSerializer : KSerializer<Name> {
override val descriptor: SerialDescriptor = StringDescriptor.withName("Name")
override fun deserialize(decoder: Decoder): Name {
return decoder.decodeString().toName()
}
override fun serialize(encoder: Encoder, obj: Name) {
encoder.encodeString(obj.toString())
}
}
@Serializer(NameToken::class)
object NameTokenSerializer : KSerializer<NameToken> {
override val descriptor: SerialDescriptor = StringDescriptor.withName("NameToken")
override fun deserialize(decoder: Decoder): NameToken {
return decoder.decodeString().toName().first()!!
}
override fun serialize(encoder: Encoder, obj: NameToken) {
encoder.encodeString(obj.toString())
}
}
/**
* Serialized for meta
*/
@Serializer(Meta::class)
object MetaSerializer : KSerializer<Meta> {
override val descriptor: SerialDescriptor = JsonObjectSerializer.descriptor
override fun deserialize(decoder: Decoder): Meta {
//currently just delegates serialization to json serializer
return JsonObjectSerializer.deserialize(decoder).toMeta()
}
override fun serialize(encoder: Encoder, obj: Meta) {
JsonObjectSerializer.serialize(encoder, obj.toJson())
}
}
@Serializer(Config::class)
object ConfigSerializer : KSerializer<Config> {
override val descriptor: SerialDescriptor = JsonObjectSerializer.descriptor
override fun deserialize(decoder: Decoder): Config {
return JsonObjectSerializer.deserialize(decoder).toMeta().toConfig()
}
override fun serialize(encoder: Encoder, obj: Config) {
JsonObjectSerializer.serialize(encoder, obj.toJson())
}
}

View File

@ -70,10 +70,10 @@ class TaglessEnvelopeFormat(
if (line.startsWith(metaStart)) { if (line.startsWith(metaStart)) {
val metaFormat = properties[META_TYPE_PROPERTY]?.let { io.metaFormat(it) } ?: JsonMetaFormat.default val metaFormat = properties[META_TYPE_PROPERTY]?.let { io.metaFormat(it) } ?: JsonMetaFormat.default
meta = if (properties.containsKey(META_LENGTH_PROPERTY)) { val metaSize = properties.get(META_LENGTH_PROPERTY)?.toInt()
val bytes = ByteArray(properties[META_LENGTH_PROPERTY]!!.toInt()) meta = if (metaSize != null) {
readFully(bytes) val metaPacket = ByteReadPacket(readBytes(metaSize))
metaFormat.readBytes(bytes) metaFormat.run { metaPacket.readThis() }
} else { } else {
metaFormat.run { metaFormat.run {
readThis() readThis()
@ -123,11 +123,12 @@ class TaglessEnvelopeFormat(
if (line.startsWith(metaStart)) { if (line.startsWith(metaStart)) {
val metaFormat = properties[META_TYPE_PROPERTY]?.let { io.metaFormat(it) } ?: JsonMetaFormat.default val metaFormat = properties[META_TYPE_PROPERTY]?.let { io.metaFormat(it) } ?: JsonMetaFormat.default
meta = if (properties.containsKey(META_LENGTH_PROPERTY)) {
val bytes = ByteArray(properties[META_LENGTH_PROPERTY]!!.toInt()) val metaSize = properties.get(META_LENGTH_PROPERTY)?.toInt()
readFully(bytes) meta = if (metaSize != null) {
offset += bytes.size.toUInt() val metaPacket = ByteReadPacket(readBytes(metaSize))
metaFormat.readBytes(bytes) offset += metaSize.toUInt()
metaFormat.run { metaPacket.readThis() }
} else { } else {
error("Can't partially read an envelope with undefined meta size") error("Can't partially read an envelope with undefined meta size")
} }

View File

@ -0,0 +1,135 @@
package hep.dataforge.io
import hep.dataforge.io.serialization.descriptor
import hep.dataforge.meta.*
import hep.dataforge.names.NameToken
import hep.dataforge.values.*
import kotlinx.serialization.*
import kotlinx.serialization.internal.*
import kotlinx.serialization.json.JsonInput
import kotlinx.serialization.json.JsonObjectSerializer
import kotlinx.serialization.json.JsonOutput
@Serializer(Value::class)
object ValueSerializer : KSerializer<Value> {
private val valueTypeSerializer = EnumSerializer(ValueType::class)
private val listSerializer by lazy { ArrayListSerializer(ValueSerializer)}
override val descriptor: SerialDescriptor = descriptor("Value") {
element("isList")
element("valueType")
element("value")
}
private fun Decoder.decodeValue(): Value {
return when (decode(valueTypeSerializer)) {
ValueType.NULL -> Null
ValueType.NUMBER -> decodeDouble().asValue() //TODO differentiate?
ValueType.BOOLEAN -> decodeBoolean().asValue()
ValueType.STRING -> decodeString().asValue()
else -> decodeString().parseValue()
}
}
override fun deserialize(decoder: Decoder): Value {
val isList = decoder.decodeBoolean()
return if (isList) {
listSerializer.deserialize(decoder).asValue()
} else {
decoder.decodeValue()
}
}
private fun Encoder.encodeValue(value: Value) {
encode(valueTypeSerializer, value.type)
when (value.type) {
ValueType.NULL -> {
// do nothing
}
ValueType.NUMBER -> encodeDouble(value.double)
ValueType.BOOLEAN -> encodeBoolean(value.boolean)
ValueType.STRING -> encodeString(value.string)
else -> encodeString(value.string)
}
}
override fun serialize(encoder: Encoder, obj: Value) {
encoder.encodeBoolean(obj.isList())
if (obj.isList()) {
listSerializer.serialize(encoder, obj.list)
} else {
encoder.encodeValue(obj)
}
}
}
@Serializer(MetaItem::class)
object MetaItemSerializer : KSerializer<MetaItem<*>> {
override val descriptor: SerialDescriptor = descriptor("MetaItem") {
element("isNode")
element("value")
}
override fun deserialize(decoder: Decoder): MetaItem<*> {
val isNode = decoder.decodeBoolean()
return if (isNode) {
MetaItem.NodeItem(decoder.decode(MetaSerializer))
} else {
MetaItem.ValueItem(decoder.decode(ValueSerializer))
}
}
override fun serialize(encoder: Encoder, obj: MetaItem<*>) {
encoder.encodeBoolean(obj is MetaItem.NodeItem)
when (obj) {
is MetaItem.NodeItem -> MetaSerializer.serialize(encoder, obj.node)
is MetaItem.ValueItem -> ValueSerializer.serialize(encoder, obj.value)
}
}
}
private class DeserializedMeta(override val items: Map<NameToken, MetaItem<*>>) : MetaBase()
/**
* Serialized for meta
*/
@Serializer(Meta::class)
object MetaSerializer : KSerializer<Meta> {
private val mapSerializer =
HashMapSerializer(StringSerializer, MetaItemSerializer)
override val descriptor: SerialDescriptor =
NamedMapClassDescriptor("Meta", StringSerializer.descriptor, MetaItemSerializer.descriptor)
override fun deserialize(decoder: Decoder): Meta {
return if (decoder is JsonInput) {
JsonObjectSerializer.deserialize(decoder).toMeta()
} else {
DeserializedMeta(mapSerializer.deserialize(decoder).mapKeys { NameToken(it.key) })
}
}
override fun serialize(encoder: Encoder, obj: Meta) {
if (encoder is JsonOutput) {
JsonObjectSerializer.serialize(encoder, obj.toJson())
} else {
mapSerializer.serialize(encoder, obj.items.mapKeys { it.key.toString() })
}
}
}
@Serializer(Config::class)
object ConfigSerializer : KSerializer<Config> {
override val descriptor: SerialDescriptor = MetaSerializer.descriptor
override fun deserialize(decoder: Decoder): Config {
return MetaSerializer.deserialize(decoder).toConfig()
}
override fun serialize(encoder: Encoder, obj: Config) {
MetaSerializer.serialize(encoder, obj)
}
}

View File

@ -0,0 +1,33 @@
package hep.dataforge.io.serialization
import hep.dataforge.names.Name
import hep.dataforge.names.NameToken
import hep.dataforge.names.toName
import kotlinx.serialization.*
import kotlinx.serialization.internal.StringDescriptor
@Serializer(Name::class)
object NameSerializer : KSerializer<Name> {
override val descriptor: SerialDescriptor = StringDescriptor.withName("Name")
override fun deserialize(decoder: Decoder): Name {
return decoder.decodeString().toName()
}
override fun serialize(encoder: Encoder, obj: Name) {
encoder.encodeString(obj.toString())
}
}
@Serializer(NameToken::class)
object NameTokenSerializer : KSerializer<NameToken> {
override val descriptor: SerialDescriptor = StringDescriptor.withName("NameToken")
override fun deserialize(decoder: Decoder): NameToken {
return decoder.decodeString().toName().first()!!
}
override fun serialize(encoder: Encoder, obj: NameToken) {
encoder.encodeString(obj.toString())
}
}

View File

@ -0,0 +1,32 @@
package hep.dataforge.io.serialization
import kotlinx.serialization.CompositeDecoder
import kotlinx.serialization.Decoder
import kotlinx.serialization.KSerializer
import kotlinx.serialization.SerialDescriptor
import kotlinx.serialization.internal.SerialClassDescImpl
inline class SerialDescriptorBuilder(private val impl: SerialClassDescImpl) {
fun element(name: String, isOptional: Boolean = false) = impl.addElement(name, isOptional)
fun annotation(a: Annotation) = impl.pushAnnotation(a)
fun classAnnotation(a: Annotation) = impl.pushClassAnnotation(a)
fun descriptor(name: String, block: SerialDescriptorBuilder.() -> Unit) = impl.pushDescriptor(
SerialDescriptorBuilder(SerialClassDescImpl(name)).apply(block).build()
)
fun build(): SerialDescriptor = impl
}
inline fun KSerializer<*>.descriptor(name: String, block: SerialDescriptorBuilder.() -> Unit): SerialDescriptor =
SerialDescriptorBuilder(SerialClassDescImpl(name)).apply(block).build()
fun Decoder.decodeStructure(
desc: SerialDescriptor,
vararg typeParams: KSerializer<*> = emptyArray(),
block: CompositeDecoder.() -> Unit
) {
beginStructure(desc, *typeParams).apply(block).endStructure(desc)
}

View File

@ -4,13 +4,11 @@ import hep.dataforge.meta.*
import kotlinx.serialization.json.JsonPrimitive import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.json import kotlinx.serialization.json.json
import kotlinx.serialization.json.jsonArray import kotlinx.serialization.json.jsonArray
import kotlin.test.Ignore
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals
class MetaFormatTest { class MetaFormatTest {
@Test @Test
@Ignore
fun testBinaryMetaFormat() { fun testBinaryMetaFormat() {
val meta = buildMeta { val meta = buildMeta {
"a" to 22 "a" to 22

View File

@ -1,7 +1,11 @@
package hep.dataforge.io package hep.dataforge.io
import hep.dataforge.io.serialization.NameSerializer
import hep.dataforge.meta.buildMeta import hep.dataforge.meta.buildMeta
import hep.dataforge.names.toName import hep.dataforge.names.toName
import kotlinx.io.charsets.Charsets
import kotlinx.io.core.String
import kotlinx.serialization.cbor.Cbor
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals
@ -23,6 +27,23 @@ class MetaSerializerTest {
assertEquals(restored, meta) assertEquals(restored, meta)
} }
@Test
fun testCborSerialization() {
val meta = buildMeta {
"a" to 22
"node" to {
"b" to "DDD"
"c" to 11.1
"array" to doubleArrayOf(1.0, 2.0, 3.0)
}
}
val bytes = Cbor.dump(MetaSerializer, meta)
println(String(bytes, charset = Charsets.ISO_8859_1))
val restored = Cbor.load(MetaSerializer, bytes)
assertEquals(restored, meta)
}
@Test @Test
fun testNameSerialization() { fun testNameSerialization() {
val name = "a.b.c".toName() val name = "a.b.c".toName()

View File

@ -17,7 +17,6 @@ internal class InputStreamAsInput(
override fun fill(): IoBuffer? { override fun fill(): IoBuffer? {
val packet = stream.readPacketAtMost(4096) val packet = stream.readPacketAtMost(4096)
return pool.borrow().apply { return pool.borrow().apply {
resetForWrite(4096) resetForWrite(4096)

View File

@ -2,7 +2,6 @@ package hep.dataforge.io
import hep.dataforge.context.Global import hep.dataforge.context.Global
import java.nio.file.Files import java.nio.file.Files
import kotlin.test.Ignore
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertTrue import kotlin.test.assertTrue
@ -22,7 +21,6 @@ class FileEnvelopeTest {
} }
@Test @Test
@Ignore
fun testFileWriteRead() { fun testFileWriteRead() {
val tmpPath = Files.createTempFile("dataforge_test", ".df") val tmpPath = Files.createTempFile("dataforge_test", ".df")
Global.io.writeEnvelopeFile(tmpPath,envelope) Global.io.writeEnvelopeFile(tmpPath,envelope)

View File

@ -45,7 +45,6 @@ class EnvelopeServerTest {
@Test @Test
fun doEchoTest() { fun doEchoTest() {
val request = Envelope.invoke { val request = Envelope.invoke {
type = "test.echo" type = "test.echo"
meta { meta {

View File

@ -12,7 +12,7 @@ pluginManagement {
eachPlugin { eachPlugin {
when (requested.id.id) { when (requested.id.id) {
"kotlinx-atomicfu" -> useModule("org.jetbrains.kotlinx:atomicfu-gradle-plugin:${requested.version}") "kotlinx-atomicfu" -> useModule("org.jetbrains.kotlinx:atomicfu-gradle-plugin:${requested.version}")
"scientifik.mpp", "scientifik.publish" -> useModule("scientifik:gradle-tools:${requested.version}") "scientifik.mpp", "scientifik.jvm", "scientifik.publish" -> useModule("scientifik:gradle-tools:${requested.version}")
} }
} }
} }
@ -24,6 +24,7 @@ enableFeaturePreview("GRADLE_METADATA")
include( include(
":dataforge-meta", ":dataforge-meta",
":dataforge-io", ":dataforge-io",
":dataforge-io:dataforge-io-yaml",
":dataforge-context", ":dataforge-context",
":dataforge-data", ":dataforge-data",
":dataforge-output", ":dataforge-output",