metadata.kt
src/jvmMain/kotlin/afield/document/workspace/metadata.kt · 107 lines
package afield.document.workspace
import afield.data.table.div
import afield.data.table.get
import afield.data.table.minus
import afield.data.table.plus
import afield.data.table.rem
import afield.data.table.times
import afield.runtime.execution.plus
import afield.runtime.lifecycle.plus
import afield.runtime.lifecycle.rem
import afield.audio.synth.*
import afield.document.graph.Box
import afield.graphics.vulkan.*
import afield.media.stream.*
import afield.media.video.*
import afield.runtime.lifecycle.map
import afield.ui.output.templates.filter
import afield.document.properties.Delegation
import org.yaml.snakeyaml.Yaml
import java.io.File
Per-box YAML metadata and the tags read from it. A box's metadata property holds YAML text, saved
as the <uid>_metadata_.yaml sidecar; tagsForBox reads one key of it as a list of strings, and
tagDatabase scans every document folder under a workspace root for those sidecars and collects
each box's tags.
tagsForBox returns null when the YAML is not a map or lacks the key; tagDatabase skips a sidecar whose YAML is not a map. A YAML syntax error is not caught in either and propagates.
var Box.metadata by Delegation<String>(defaultValue = { "" })
The box's YAML metadata text; empty by default.
fun idFromFilename(fn: String) = fn.split("_")[0]
The box uid a sidecar filename begins with, taken as everything before its first underscore.
data class BoxReference
One box in one document, as tagDatabase names the boxes it found.
data class BoxReference(
/** The absolute path of the document folder, not the document's name. */
val document: String,
/** The box uid, read off the sidecar's filename by [idFromFilename]. */
val id: String
)
fun tagDatabase(root: String): List<Pair<BoxReference, List<String>>>
Every tagged box under a workspace root, as a box and the tags its metadata sidecar gives it. Reads each
document folder's <uid>_metadata_.yaml files and takes their tags key, accepting a single value as a
one-element list. A sidecar whose YAML is not a map, or which has no tags, is left out rather than
reported; a YAML syntax error propagates, and so does a root that is not a directory.
fun tagDatabase(root: String): List<Pair<BoxReference, List<String>>> {
return File(root).listFiles {
it.isDirectory
}.flatMap { parent ->
parent.listFiles().filter { f ->
f.name.contains("_metadata_.yaml")
}.mapNotNull {
try {
val id = idFromFilename(it.name)
val t = (Yaml().load(it.readText()) as Map<String, Any?>)["tags"]
val t2 = if (t is Collection<*>) t.map { it.toString() } else listOf(t.toString())
BoxReference(parent.canonicalFile.absolutePath, id) to t2
} catch (e: ClassCastException) {
null
} catch (e: NullPointerException) {
null
}
}
}
}
fun parseMetadata(s: String) = Yaml().load(s) as Map<String, Any?>
A box's YAML metadata as a map. Nothing is caught here: empty text loads as null and fails the cast, and YAML that is a list or a scalar does the same, so a caller that does not know the text is a map should wrap this.
fun tagsForBox(b: Box, key: String? = null) = tagsForString(b.metadata, key)
tagsForString over a box's own metadata property. The key has no useful default: left out, the
lookup asks for a null key and the result is null for all but the rare YAML that has one.
fun tagsForString(s: String, key: String? = null): List<String>?
One key of a YAML map as a list of strings, with a single value read as a one-element list and everything
stringified. Null when the text is not a map, when the key is absent, and when its value is null, so a
caller cannot tell those apart; callers in this repo pass "tags" and treat null as no tags.
fun tagsForString(s: String, key: String? = null): List<String>? {
try {
val a = Yaml().load(s) as Map<String, Any?>
val v = a[key]!!
return if (v is Collection<*>) return v.toList().map { it.toString() }
else listOf(v.toString())
} catch (e: ClassCastException) {
return null
} catch (e: NullPointerException) {
return null
}
}