This repository has been archived by the owner on Jul 21, 2023. It is now read-only.
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Michael Rittmeister
committed
May 13, 2022
1 parent
c67a2ec
commit d8b9ac4
Showing
7 changed files
with
237 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
package dev.nycode.omsilauncher.vdf | ||
|
||
import kotlinx.serialization.DeserializationStrategy | ||
import kotlinx.serialization.SerializationStrategy | ||
import kotlinx.serialization.StringFormat | ||
import kotlinx.serialization.modules.EmptySerializersModule | ||
import kotlinx.serialization.modules.SerializersModule | ||
|
||
private const val tab = "\t" | ||
private const val delimiter = "\t\t" | ||
|
||
open class VDF internal constructor(override val serializersModule: SerializersModule) : StringFormat { | ||
|
||
fun decodeToVDFObject(string: String) = decodeToVDFObject(VDFLineParser(string.lines()), false) | ||
|
||
fun <T> decodeFromVDFObject(deserializer: DeserializationStrategy<T>, element: VDFObject): T { | ||
val decoder = VDFObjectDecoder(this, element) | ||
return decoder.decodeSerializableValue(deserializer) | ||
} | ||
|
||
override fun <T> decodeFromString(deserializer: DeserializationStrategy<T>, string: String): T = | ||
decodeFromVDFObject(deserializer, decodeToVDFObject(string)) | ||
|
||
override fun <T> encodeToString(serializer: SerializationStrategy<T>, value: T): String = | ||
encodeFromVDFElement(encodeToVDFElement(serializer, value)) | ||
|
||
fun <T> encodeToVDFElement(serializer: SerializationStrategy<T>, value: T): VDFObject { | ||
val encoder = VDFObjectEncoder(this) | ||
encoder.encodeSerializableValue(serializer, value) | ||
return encoder.toElement() | ||
} | ||
|
||
fun encodeFromVDFElement(element: VDFElement) = encode(element, 0, true) | ||
|
||
private fun decodeToVDFObject(parser: VDFLineParser, isChildDecode: Boolean): VDFObject { | ||
@Suppress("RemoveExplicitTypeArguments") // inference doesn't work for some reason | ||
val map = buildMap<String, VDFElement> { | ||
while (parser.hasNext()) { | ||
val line = parser.next().trim { it.isWhitespace() }.split(delimiter) | ||
if (line[0].startsWith("\"") && line[0].endsWith("\"")) { | ||
if (line.size == 2) { | ||
put(line[0].removeQuotes(), VDFPrimitive(line[1].removeQuotes())) | ||
} else if (line.size == 1) { | ||
put(line[0].removeQuotes(), decodeToVDFObject(parser, true)) | ||
} | ||
} else if (line[0].contains("}") && isChildDecode) { | ||
break // return currentObject | ||
} | ||
} | ||
} | ||
|
||
return VDFObject(map) | ||
} | ||
|
||
private fun encode(element: VDFElement, indent: Int, isRoot: Boolean): String = buildString { | ||
when (element) { | ||
is VDFPrimitive -> appendLine(element.content.enquote()) | ||
is VDFObject -> append(encode(element, indent, isRoot)) | ||
} | ||
} | ||
|
||
private fun encode(obj: VDFObject, indent: Int, isRoot: Boolean) = buildString { | ||
appendLine() | ||
if (!isRoot) { | ||
appendIndent(indent - 1) | ||
appendLine('{') | ||
} | ||
obj.forEach { key, value -> | ||
appendIndent(indent) | ||
append(key.enquote()) | ||
append(delimiter) | ||
append(encode(value, indent + 1, false)) | ||
} | ||
if (!isRoot) { | ||
appendIndent(indent - 1) | ||
appendLine('}') | ||
} | ||
} | ||
|
||
companion object Default : VDF(EmptySerializersModule) | ||
} | ||
|
||
private class VDFLineParser(private val lines: List<String>) : Iterator<String> { | ||
private var index = 0 | ||
|
||
override fun next(): String = lines[index++] | ||
|
||
override fun hasNext(): Boolean = index < lines.size | ||
} | ||
|
||
private fun String.removeQuotes(): String = trim().run { substring(1, lastIndex) } | ||
private fun String.enquote() = """"$this"""" | ||
|
||
private fun StringBuilder.appendIndent(amount: Int) = append(tab.repeat(amount)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
package dev.nycode.omsilauncher.vdf | ||
|
||
import kotlinx.serialization.encoding.CompositeDecoder | ||
import kotlinx.serialization.encoding.Decoder | ||
|
||
interface VDFDecoder : Decoder, CompositeDecoder |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
package dev.nycode.omsilauncher.vdf | ||
|
||
sealed class VDFElement | ||
|
||
data class VDFObject(private val content: Map<String, VDFElement>) : VDFElement(), Map<String, VDFElement> by content | ||
|
||
sealed class VDFPrimitive : VDFElement() { | ||
abstract val isString: Boolean | ||
abstract val content: String | ||
|
||
override fun toString(): String = content | ||
} | ||
|
||
fun VDFPrimitive(string: String): VDFPrimitive = VDFString(string) | ||
|
||
data class VDFString(override val content: String) : VDFPrimitive() { | ||
override val isString: Boolean = true | ||
override fun toString(): String = content | ||
} | ||
|
||
val VDFElement.vdfObject: VDFObject | ||
get() = this as? VDFObject ?: error("Cannot cast ${this::class::simpleName} to object") | ||
|
||
val VDFElement.vdfPrimitive: VDFPrimitive | ||
get() = this as? VDFPrimitive ?: error("Cannot cast ${this::class::simpleName} to primitive") | ||
|
||
val VDFElement.vdfString: VDFString | ||
get() = this as? VDFString ?: error("Cannot cast ${this::class::simpleName} to string") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
package dev.nycode.omsilauncher.vdf | ||
|
||
import kotlinx.serialization.encoding.CompositeEncoder | ||
import kotlinx.serialization.encoding.Encoder | ||
|
||
interface VDFEncoder : Encoder, CompositeEncoder |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
package dev.nycode.omsilauncher.vdf | ||
|
||
import kotlinx.serialization.SerializationException | ||
import kotlinx.serialization.descriptors.SerialDescriptor | ||
import kotlinx.serialization.encoding.CompositeDecoder | ||
import kotlinx.serialization.internal.NamedValueDecoder | ||
import java.util.LinkedList | ||
|
||
class VDFObjectDecoder(private val vdf: VDF, private val obj: VDFObject) : NamedValueDecoder(), VDFDecoder { | ||
private val list = LinkedList(obj.toList()) | ||
|
||
private var currentElement: VDFElement? = null | ||
|
||
override fun decodeTaggedValue(tag: String): VDFElement = | ||
obj[tag]?.let { currentElement ?: it } ?: throw SerializationException("Could not find value for tag: $tag") | ||
|
||
override fun decodeElementIndex(descriptor: SerialDescriptor): Int { | ||
return if (list.isEmpty()) { | ||
CompositeDecoder.DECODE_DONE | ||
} else { | ||
val (name, element) = list.poll() | ||
currentElement = element | ||
descriptor.getElementIndex(name) | ||
} | ||
} | ||
|
||
override fun beginStructure(descriptor: SerialDescriptor): CompositeDecoder { | ||
val currentObject = currentElement as? VDFObject ?: return super.beginStructure(descriptor) | ||
|
||
return VDFObjectDecoder(vdf, currentObject) | ||
} | ||
|
||
override fun decodeTaggedString(tag: String): String = decodeTaggedValue(tag).vdfString.content | ||
override fun decodeTaggedBoolean(tag: String): Boolean = decodeTaggedString(tag).toBooleanStrict() | ||
override fun decodeTaggedByte(tag: String): Byte = decodeTaggedString(tag).toByte() | ||
override fun decodeTaggedDouble(tag: String): Double = decodeTaggedString(tag).toDouble() | ||
override fun decodeTaggedFloat(tag: String): Float = decodeTaggedString(tag).toFloat() | ||
override fun decodeTaggedInt(tag: String): Int = decodeTaggedString(tag).toInt() | ||
override fun decodeTaggedLong(tag: String): Long = decodeTaggedString(tag).toLong() | ||
override fun decodeTaggedShort(tag: String): Short = decodeTaggedString(tag).toShort() | ||
override fun decodeTaggedNotNullMark(tag: String): Boolean = | ||
throw SerializationException("VDF does not support nulls") | ||
|
||
override fun decodeTaggedNull(tag: String): Nothing = | ||
throw SerializationException("VDF does not support nulls") | ||
|
||
override fun decodeTaggedChar(tag: String): Char { | ||
val sequence = decodeTaggedString(tag) | ||
if (sequence.length != 1) { | ||
throw SerializationException("'$sequence' is not a char") | ||
} | ||
|
||
return sequence.first() | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
package dev.nycode.omsilauncher.vdf | ||
|
||
import kotlinx.serialization.SerializationException | ||
import kotlinx.serialization.descriptors.SerialDescriptor | ||
import kotlinx.serialization.encoding.CompositeEncoder | ||
import kotlinx.serialization.internal.NamedValueEncoder | ||
import kotlinx.serialization.modules.SerializersModule | ||
|
||
internal class VDFObjectEncoder(private val vdf: VDF, private val parent: VDFObjectEncoder? = null) : | ||
NamedValueEncoder(), VDFEncoder { | ||
private val elements = mutableMapOf<String, VDFElement>() | ||
|
||
private var structureEncoder: VDFObjectEncoder? = null | ||
|
||
override val serializersModule: SerializersModule get() = vdf.serializersModule | ||
|
||
override fun encodeNull() = throw SerializationException("VDF does not support null") | ||
|
||
override fun encodeTaggedValue(tag: String, value: Any) { | ||
elements += tag to VDFPrimitive(value.toString()) | ||
} | ||
|
||
override fun beginStructure(descriptor: SerialDescriptor): CompositeEncoder { | ||
return if (currentTagOrNull == null) { | ||
super.beginStructure(descriptor) | ||
} else { | ||
require(structureEncoder == null) { "Cannot decode two structures at the same time" } | ||
structureEncoder = VDFObjectEncoder(vdf, this) | ||
structureEncoder!! | ||
} | ||
} | ||
|
||
override fun endEncode(descriptor: SerialDescriptor) { | ||
super.endEncode(descriptor) | ||
if (parent != null) { | ||
parent.structureEncoder = null | ||
if (parent.currentTagOrNull != null) { | ||
parent.elements += parent.currentTag to toElement() | ||
// For some reason the structure tag needs to get removed manually here | ||
parent.popTag() | ||
} | ||
} | ||
} | ||
|
||
fun toElement() = VDFObject(elements) | ||
} |