From 99c90ddcee698225e57e68f01ade7e628c3c4c2e Mon Sep 17 00:00:00 2001 From: NightEule5 <24661563+NightEule5@users.noreply.github.com> Date: Fri, 24 Dec 2021 23:31:09 -0700 Subject: [PATCH 01/12] Create TomlTextWriter and its implementations Created the TomlTextWriter interface to handle low-level writing of Toml files. One implementation writes to a StringBuilder, the other writes to an Okio BufferedSink. KtomlConf has also been amended to include an "indentation" property. --- .../kotlin/com/akuleshov7/ktoml/KtomlConf.kt | 11 +- .../ktoml/writers/AbstractTomlTextWriter.kt | 116 ++++++++++++++++++ .../ktoml/writers/IntegerRepresentation.kt | 9 ++ .../ktoml/writers/TomlStringTextWriter.kt | 33 +++++ .../ktoml/writers/TomlTextWriter.kt | 46 +++++++ .../ktoml/file/TomlSinkTextWriter.kt | 40 ++++++ 6 files changed, 254 insertions(+), 1 deletion(-) create mode 100644 ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/writers/AbstractTomlTextWriter.kt create mode 100644 ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/writers/IntegerRepresentation.kt create mode 100644 ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/writers/TomlStringTextWriter.kt create mode 100644 ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/writers/TomlTextWriter.kt create mode 100644 ktoml-file/src/commonMain/kotlin/com/akuleshov7/ktoml/file/TomlSinkTextWriter.kt diff --git a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/KtomlConf.kt b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/KtomlConf.kt index 78ca0aa4..5994b110 100644 --- a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/KtomlConf.kt +++ b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/KtomlConf.kt @@ -8,5 +8,14 @@ package com.akuleshov7.ktoml public data class KtomlConf( val ignoreUnknownNames: Boolean = false, val emptyValuesAllowed: Boolean = true, - val escapedQuotesInLiteralStringsAllowed: Boolean = true + val escapedQuotesInLiteralStringsAllowed: Boolean = true, + val indentation: Indentation = Indentation.FourSpaces ) +{ + public enum class Indentation(public val string: String) { + None(""), + TwoSpaces(" "), + FourSpaces(" "), + Tab("\t") + } +} diff --git a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/writers/AbstractTomlTextWriter.kt b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/writers/AbstractTomlTextWriter.kt new file mode 100644 index 00000000..341b819f --- /dev/null +++ b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/writers/AbstractTomlTextWriter.kt @@ -0,0 +1,116 @@ +package com.akuleshov7.ktoml.writers + +import com.akuleshov7.ktoml.KtomlConf +import com.akuleshov7.ktoml.writers.IntegerRepresentation.* +import kotlin.jvm.JvmStatic + +public abstract class AbstractTomlTextWriter(ktomlConf: KtomlConf) : TomlTextWriter { + private val indentation = ktomlConf.indentation.string + + final override var indentDepth: Int = 0; protected set + + final override fun indent(): Int = ++indentDepth + final override fun dedent(): Int = --indentDepth + + protected abstract fun emit(fragment: String) + protected abstract fun emit(fragment: Char) + + protected abstract fun emit(fragment1: String, fragment2: String) + protected abstract fun emit(fragment1: String, fragment2: String, fragment3: String) + protected abstract fun emit(fragment1: Char, fragment2: String, fragment3: Char) + + protected open fun emit(vararg fragments: String): Unit = fragments.forEach(::emit) + + protected open fun emit(fragment: String, count: Int): Unit = repeat(count) { emit(fragment) } + protected open fun emit(fragment: Char, count: Int): Unit = repeat(count) { emit(fragment) } + + final override fun emitNewLine(): Unit = emit('\n') + + final override fun emitIndent(): Unit = emit(indentation, indentDepth) + + final override fun emitWhitespace(count: Int): Unit = emit(' ', count) + + final override fun emitComment(comment: String, endOfLine: Boolean): Unit = + if (endOfLine) + emit(" # ", comment) + else + emit("# ", comment) + + final override fun emitKey(key: String) { + if (key matches bareKeyRegex) + emitBareKey(key) + else + emitQuotedKey(key, isLiteral = key matches literalKeyCandidateRegex) + } + + final override fun emitBareKey(key: String): Unit = emit(key) + + final override fun emitQuotedKey(key: String, isLiteral: Boolean): Unit = + emitValue(string = key, isLiteral) + + final override fun emitKeyDot(): Unit = emit('.') + + final override fun startTableHeader(): Unit = emit('[') + + final override fun endTableHeader(): Unit = emit(']') + + final override fun startTableArrayHeaderStart(): Unit = emit("[[") + + final override fun emitTableArrayHeaderEnd(): Unit = emit("]]") + + final override fun emitValue(string: String, isLiteral: Boolean, isMultiline: Boolean): Unit = + if (isMultiline) { + val quotes = if (isLiteral) "'''" else "\"\"\"" + + emit("$quotes\n", string, quotes) + } + else { + val quote = if (isLiteral) '\'' else '"' + + emit(quote, string, quote) + } + + final override fun emitValue(integer: Long, representation: IntegerRepresentation): Unit = + when (representation) { + Decimal -> emit("$integer") + Hex -> emit("0x", integer.toString(16)) + Binary -> emit("0b", integer.toString(2 )) + Octal -> emit("0o", integer.toString(8 )) + Grouped -> TODO() + } + + final override fun emitValue(float: Double): Unit = + emit(when (float) { + Double.NaN -> "nan" + Double.POSITIVE_INFINITY -> "inf" + Double.NEGATIVE_INFINITY -> "-inf" + else -> "$float" + }) + + final override fun emitValue(boolean: Boolean): Unit = emit("$boolean") + + final override fun startArray(): Unit = emit('[') + + final override fun endArray(): Unit = emit(']') + + final override fun startInlineTable(): Unit = emit('{') + + final override fun endInlineTable(): Unit = emit('}') + + final override fun emitElementDelimiter(): Unit = emit(", ") + + final override fun emitPairDelimiter(): Unit = emit(" = ") + + public companion object + { + @JvmStatic + private val bareKeyRegex = Regex("[A-Za-z0-9_-]+") + + /** + * Matches a key with at least one unescaped double quote and no single + * quotes. + */ + @JvmStatic + private val literalKeyCandidateRegex = Regex("""[^'"]*((? Date: Fri, 31 Dec 2021 02:48:29 -0700 Subject: [PATCH 02/12] Rename TomlTextWriter to TomlComposer This interface roughly corresponds to the kotlinx-serialization.json library's Composer interface in function, so the name was changed to reflect that. This also clears up potential confusion with TomlWriter and TomlFileWriter, which will have a different purpose. --- .../{AbstractTomlTextWriter.kt => AbstractTomlComposer.kt} | 2 +- .../ktoml/writers/{TomlTextWriter.kt => TomlComposer.kt} | 2 +- .../{TomlStringTextWriter.kt => TomlStringComposer.kt} | 4 ++-- .../file/{TomlSinkTextWriter.kt => TomlSinkComposer.kt} | 6 +++--- 4 files changed, 7 insertions(+), 7 deletions(-) rename ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/writers/{AbstractTomlTextWriter.kt => AbstractTomlComposer.kt} (97%) rename ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/writers/{TomlTextWriter.kt => TomlComposer.kt} (97%) rename ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/writers/{TomlStringTextWriter.kt => TomlStringComposer.kt} (91%) rename ktoml-file/src/commonMain/kotlin/com/akuleshov7/ktoml/file/{TomlSinkTextWriter.kt => TomlSinkComposer.kt} (86%) diff --git a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/writers/AbstractTomlTextWriter.kt b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/writers/AbstractTomlComposer.kt similarity index 97% rename from ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/writers/AbstractTomlTextWriter.kt rename to ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/writers/AbstractTomlComposer.kt index 341b819f..e7f8e2d0 100644 --- a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/writers/AbstractTomlTextWriter.kt +++ b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/writers/AbstractTomlComposer.kt @@ -4,7 +4,7 @@ import com.akuleshov7.ktoml.KtomlConf import com.akuleshov7.ktoml.writers.IntegerRepresentation.* import kotlin.jvm.JvmStatic -public abstract class AbstractTomlTextWriter(ktomlConf: KtomlConf) : TomlTextWriter { +public abstract class AbstractTomlComposer(ktomlConf: KtomlConf) : TomlComposer { private val indentation = ktomlConf.indentation.string final override var indentDepth: Int = 0; protected set diff --git a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/writers/TomlTextWriter.kt b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/writers/TomlComposer.kt similarity index 97% rename from ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/writers/TomlTextWriter.kt rename to ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/writers/TomlComposer.kt index 335a992e..c75de4f0 100644 --- a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/writers/TomlTextWriter.kt +++ b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/writers/TomlComposer.kt @@ -1,6 +1,6 @@ package com.akuleshov7.ktoml.writers -public interface TomlTextWriter { +public interface TomlComposer { public val indentDepth: Int public fun indent(): Int diff --git a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/writers/TomlStringTextWriter.kt b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/writers/TomlStringComposer.kt similarity index 91% rename from ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/writers/TomlStringTextWriter.kt rename to ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/writers/TomlStringComposer.kt index 06a017f8..2d7c0d18 100644 --- a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/writers/TomlStringTextWriter.kt +++ b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/writers/TomlStringComposer.kt @@ -2,10 +2,10 @@ package com.akuleshov7.ktoml.writers import com.akuleshov7.ktoml.KtomlConf -internal class TomlStringTextWriter( +internal class TomlStringComposer( private val stringBuilder: StringBuilder, ktomlConf: KtomlConf -) : AbstractTomlTextWriter(ktomlConf) { +) : AbstractTomlComposer(ktomlConf) { override fun emit(fragment: String) { stringBuilder.append(fragment) } diff --git a/ktoml-file/src/commonMain/kotlin/com/akuleshov7/ktoml/file/TomlSinkTextWriter.kt b/ktoml-file/src/commonMain/kotlin/com/akuleshov7/ktoml/file/TomlSinkComposer.kt similarity index 86% rename from ktoml-file/src/commonMain/kotlin/com/akuleshov7/ktoml/file/TomlSinkTextWriter.kt rename to ktoml-file/src/commonMain/kotlin/com/akuleshov7/ktoml/file/TomlSinkComposer.kt index de45d6ca..1ded676c 100644 --- a/ktoml-file/src/commonMain/kotlin/com/akuleshov7/ktoml/file/TomlSinkTextWriter.kt +++ b/ktoml-file/src/commonMain/kotlin/com/akuleshov7/ktoml/file/TomlSinkComposer.kt @@ -1,14 +1,14 @@ package com.akuleshov7.ktoml.file import com.akuleshov7.ktoml.KtomlConf -import com.akuleshov7.ktoml.writers.AbstractTomlTextWriter +import com.akuleshov7.ktoml.writers.AbstractTomlComposer import okio.BufferedSink import okio.Closeable -internal class TomlSinkTextWriter( +internal class TomlSinkComposer( private val sink: BufferedSink, ktomlConf: KtomlConf -) : AbstractTomlTextWriter(ktomlConf), Closeable { +) : AbstractTomlComposer(ktomlConf), Closeable { override fun emit(fragment: String) { sink.writeUtf8(fragment) } From a3d3d1e9bfd4c821f9f4f4063327cdf59773bcfe Mon Sep 17 00:00:00 2001 From: NightEule5 <24661563+NightEule5@users.noreply.github.com> Date: Fri, 31 Dec 2021 03:44:33 -0700 Subject: [PATCH 03/12] Create TomlWriter and TomlFileWriter --- .../kotlin/com/akuleshov7/ktoml/Toml.kt | 10 ++- .../akuleshov7/ktoml/writers/TomlWriter.kt | 62 +++++++++++++++++++ .../com/akuleshov7/ktoml/file/FileUtils.kt | 12 ++++ .../akuleshov7/ktoml/file/TomlFileWriter.kt | 36 +++++++++++ 4 files changed, 117 insertions(+), 3 deletions(-) create mode 100644 ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/writers/TomlWriter.kt create mode 100644 ktoml-file/src/commonMain/kotlin/com/akuleshov7/ktoml/file/TomlFileWriter.kt diff --git a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/Toml.kt b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/Toml.kt index 2e330a06..9267ebbe 100644 --- a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/Toml.kt +++ b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/Toml.kt @@ -4,11 +4,14 @@ import com.akuleshov7.ktoml.decoders.TomlMainDecoder import com.akuleshov7.ktoml.exceptions.MissingRequiredFieldException import com.akuleshov7.ktoml.parsers.TomlParser import com.akuleshov7.ktoml.parsers.node.TomlFile - -import kotlin.native.concurrent.ThreadLocal -import kotlinx.serialization.* +import com.akuleshov7.ktoml.writers.TomlWriter +import kotlinx.serialization.DeserializationStrategy +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.SerializationStrategy +import kotlinx.serialization.StringFormat import kotlinx.serialization.modules.EmptySerializersModule import kotlinx.serialization.modules.SerializersModule +import kotlin.native.concurrent.ThreadLocal /** * Toml class - is a general entry point in the core, @@ -24,6 +27,7 @@ public open class Toml( ) : StringFormat { // parser is created once after the creation of the class, to reduce the number of created parsers for each toml public val tomlParser: TomlParser = TomlParser(config) + public val tomlWriter: TomlWriter = TomlWriter(config) // ================== basic overrides =============== diff --git a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/writers/TomlWriter.kt b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/writers/TomlWriter.kt new file mode 100644 index 00000000..987f61a8 --- /dev/null +++ b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/writers/TomlWriter.kt @@ -0,0 +1,62 @@ +package com.akuleshov7.ktoml.writers + +import com.akuleshov7.ktoml.KtomlConf +import com.akuleshov7.ktoml.parsers.node.* +import kotlin.jvm.JvmInline + +@JvmInline +public value class TomlWriter(private val ktomlConf: KtomlConf) { + public fun writeToString( + file: TomlFile, + stringBuilder: StringBuilder = StringBuilder() + ): String = "${write(file, stringBuilder)}" + + private fun write(file: TomlFile, stringBuilder: StringBuilder): StringBuilder + { + write(file, TomlStringComposer(stringBuilder, ktomlConf)) + + return stringBuilder + } + + public fun write(file: TomlFile, composer: TomlComposer): Unit = + file.children.forEach { composer.writeChild(it) } + + private fun TomlComposer.writeChild(node: TomlNode) = when (node) { + is TomlFile -> + error( + "A file node is not allowed as a child of another file node." + ) + is TomlKeyValueArray -> + { + + } + is TomlKeyValuePrimitive -> + { + + } + is TomlStubEmptyNode -> { } + is TomlTable -> + { + + } + } + + private fun TomlComposer.writeKey(key: TomlKey) + { + val keys = key.keyParts + + if (keys.isEmpty()) { + emitQuotedKey("") + + return + } + + emitKey(keys[0]) + + // Todo: Use an iterator instead of creating a new list. + keys.drop(1).forEach { + emitKeyDot() + emitKey(it) + } + } +} \ No newline at end of file diff --git a/ktoml-file/src/commonMain/kotlin/com/akuleshov7/ktoml/file/FileUtils.kt b/ktoml-file/src/commonMain/kotlin/com/akuleshov7/ktoml/file/FileUtils.kt index 2a968bdd..d0840475 100644 --- a/ktoml-file/src/commonMain/kotlin/com/akuleshov7/ktoml/file/FileUtils.kt +++ b/ktoml-file/src/commonMain/kotlin/com/akuleshov7/ktoml/file/FileUtils.kt @@ -4,9 +4,11 @@ package com.akuleshov7.ktoml.file +import okio.BufferedSink import okio.FileNotFoundException import okio.FileSystem import okio.Path.Companion.toPath +import okio.buffer /** * Simple file reading with okio (returning a list with strings) @@ -28,6 +30,16 @@ internal fun readAndParseFile(tomlFile: String): List { } } +internal fun openFileForWrite(tomlFile: String): BufferedSink { + try { + val ktomlPath = tomlFile.toPath() + return getOsSpecificFileSystem().sink(ktomlPath).buffer() + } catch (e: FileNotFoundException) { + println("Not able to fine toml-file in the following path: $tomlFile") + throw e + } +} + /** * Implementation for getting proper file system to read files with okio * diff --git a/ktoml-file/src/commonMain/kotlin/com/akuleshov7/ktoml/file/TomlFileWriter.kt b/ktoml-file/src/commonMain/kotlin/com/akuleshov7/ktoml/file/TomlFileWriter.kt new file mode 100644 index 00000000..f28ea1cd --- /dev/null +++ b/ktoml-file/src/commonMain/kotlin/com/akuleshov7/ktoml/file/TomlFileWriter.kt @@ -0,0 +1,36 @@ +package com.akuleshov7.ktoml.file + +import com.akuleshov7.ktoml.KtomlConf +import com.akuleshov7.ktoml.Toml +import com.akuleshov7.ktoml.encoders.TomlMainEncoder +import com.akuleshov7.ktoml.parsers.node.TomlFile +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.SerializationStrategy +import kotlinx.serialization.modules.EmptySerializersModule +import kotlinx.serialization.modules.SerializersModule +import okio.use + +@OptIn(ExperimentalSerializationApi::class) +public open class TomlFileWriter( + private val config: KtomlConf = KtomlConf(), + override val serializersModule: SerializersModule = EmptySerializersModule +) : Toml(config, serializersModule) { + public fun encodeToFile( + serializer: SerializationStrategy, + value: T, + tomlFilePath: String + ) + { + val fileTree = TomlFile(config) + val encoder = TomlMainEncoder(fileTree, config) + + serializer.serialize(encoder, value) + + TomlSinkComposer( + openFileForWrite(tomlFilePath), + config + ).use { + tomlWriter.write(fileTree, it) + } + } +} \ No newline at end of file From 89623966faab040e9135fa7e6fed4b7afd20b85e Mon Sep 17 00:00:00 2001 From: NightEule5 <24661563+NightEule5@users.noreply.github.com> Date: Fri, 31 Dec 2021 04:18:54 -0700 Subject: [PATCH 04/12] Move AST classes out of the "parsers" package --- .../commonMain/kotlin/com/akuleshov7/ktoml/Toml.kt | 2 +- .../ktoml/decoders/TomlAbstractDecoder.kt | 2 +- .../akuleshov7/ktoml/decoders/TomlArrayDecoder.kt | 10 +++++----- .../akuleshov7/ktoml/decoders/TomlMainDecoder.kt | 13 ++++++++----- .../ktoml/decoders/TomlPrimitiveDecoder.kt | 4 ++-- .../com/akuleshov7/ktoml/parsers/TomlParser.kt | 9 +-------- .../ktoml/{parsers/node => tree}/TomlKey.kt | 2 +- .../ktoml/{parsers/node => tree}/TomlKeyValue.kt | 2 +- .../ktoml/{parsers/node => tree}/TomlNode.kt | 2 +- .../ktoml/{parsers/node => tree}/TomlValue.kt | 2 +- .../com/akuleshov7/ktoml/writers/TomlWriter.kt | 2 +- .../kotlin/node/parser/CommonParserTest.kt | 4 ++-- .../kotlin/node/parser/DottedKeyParserTest.kt | 4 +++- .../commonTest/kotlin/node/parser/TomlNodeTest.kt | 4 ++-- .../commonTest/kotlin/node/parser/TomlTableTest.kt | 4 ++-- .../kotlin/node/parser/ValueParserTest.kt | 2 +- .../com/akuleshov7/ktoml/file/TomlFileWriter.kt | 2 +- .../commonTest/kotlin/file/TomlFileParserTest.kt | 2 +- 18 files changed, 35 insertions(+), 37 deletions(-) rename ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/{parsers/node => tree}/TomlKey.kt (96%) rename ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/{parsers/node => tree}/TomlKeyValue.kt (99%) rename ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/{parsers/node => tree}/TomlNode.kt (99%) rename ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/{parsers/node => tree}/TomlValue.kt (99%) diff --git a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/Toml.kt b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/Toml.kt index 9267ebbe..d51c9d59 100644 --- a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/Toml.kt +++ b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/Toml.kt @@ -3,7 +3,7 @@ package com.akuleshov7.ktoml import com.akuleshov7.ktoml.decoders.TomlMainDecoder import com.akuleshov7.ktoml.exceptions.MissingRequiredFieldException import com.akuleshov7.ktoml.parsers.TomlParser -import com.akuleshov7.ktoml.parsers.node.TomlFile +import com.akuleshov7.ktoml.tree.TomlFile import com.akuleshov7.ktoml.writers.TomlWriter import kotlinx.serialization.DeserializationStrategy import kotlinx.serialization.ExperimentalSerializationApi diff --git a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/decoders/TomlAbstractDecoder.kt b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/decoders/TomlAbstractDecoder.kt index dd8754cd..39541c01 100644 --- a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/decoders/TomlAbstractDecoder.kt +++ b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/decoders/TomlAbstractDecoder.kt @@ -2,7 +2,7 @@ package com.akuleshov7.ktoml.decoders import com.akuleshov7.ktoml.exceptions.IllegalTomlTypeException import com.akuleshov7.ktoml.exceptions.TomlCastException -import com.akuleshov7.ktoml.parsers.node.TomlKeyValue +import com.akuleshov7.ktoml.tree.TomlKeyValue import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.encoding.AbstractDecoder diff --git a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/decoders/TomlArrayDecoder.kt b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/decoders/TomlArrayDecoder.kt index f1e19a17..d0ffdc8b 100644 --- a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/decoders/TomlArrayDecoder.kt +++ b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/decoders/TomlArrayDecoder.kt @@ -1,11 +1,11 @@ package com.akuleshov7.ktoml.decoders import com.akuleshov7.ktoml.KtomlConf -import com.akuleshov7.ktoml.parsers.node.TomlKeyValue -import com.akuleshov7.ktoml.parsers.node.TomlKeyValueArray -import com.akuleshov7.ktoml.parsers.node.TomlKeyValuePrimitive -import com.akuleshov7.ktoml.parsers.node.TomlNull -import com.akuleshov7.ktoml.parsers.node.TomlValue +import com.akuleshov7.ktoml.tree.TomlKeyValue +import com.akuleshov7.ktoml.tree.TomlKeyValueArray +import com.akuleshov7.ktoml.tree.TomlKeyValuePrimitive +import com.akuleshov7.ktoml.tree.TomlNull +import com.akuleshov7.ktoml.tree.TomlValue import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.encoding.CompositeDecoder diff --git a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/decoders/TomlMainDecoder.kt b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/decoders/TomlMainDecoder.kt index 49ade972..fc788428 100644 --- a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/decoders/TomlMainDecoder.kt +++ b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/decoders/TomlMainDecoder.kt @@ -4,11 +4,14 @@ package com.akuleshov7.ktoml.decoders import com.akuleshov7.ktoml.KtomlConf import com.akuleshov7.ktoml.exceptions.* -import com.akuleshov7.ktoml.parsers.node.* -import kotlinx.serialization.* -import kotlinx.serialization.descriptors.* -import kotlinx.serialization.encoding.* -import kotlinx.serialization.modules.* +import com.akuleshov7.ktoml.tree.* +import kotlinx.serialization.DeserializationStrategy +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.descriptors.elementNames +import kotlinx.serialization.encoding.CompositeDecoder +import kotlinx.serialization.modules.EmptySerializersModule +import kotlinx.serialization.modules.SerializersModule /** * Main entry point into the decoding process. It can create less common decoders inside, for example: diff --git a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/decoders/TomlPrimitiveDecoder.kt b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/decoders/TomlPrimitiveDecoder.kt index bd6e0c32..b9a35aa4 100644 --- a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/decoders/TomlPrimitiveDecoder.kt +++ b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/decoders/TomlPrimitiveDecoder.kt @@ -1,7 +1,7 @@ package com.akuleshov7.ktoml.decoders -import com.akuleshov7.ktoml.parsers.node.TomlKeyValue -import com.akuleshov7.ktoml.parsers.node.TomlKeyValuePrimitive +import com.akuleshov7.ktoml.tree.TomlKeyValue +import com.akuleshov7.ktoml.tree.TomlKeyValuePrimitive import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.modules.EmptySerializersModule diff --git a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/parsers/TomlParser.kt b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/parsers/TomlParser.kt index 8a11f8fa..a606b045 100644 --- a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/parsers/TomlParser.kt +++ b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/parsers/TomlParser.kt @@ -2,14 +2,7 @@ package com.akuleshov7.ktoml.parsers import com.akuleshov7.ktoml.KtomlConf import com.akuleshov7.ktoml.exceptions.InternalAstException -import com.akuleshov7.ktoml.parsers.node.TomlFile -import com.akuleshov7.ktoml.parsers.node.TomlKeyValue -import com.akuleshov7.ktoml.parsers.node.TomlKeyValueArray -import com.akuleshov7.ktoml.parsers.node.TomlKeyValuePrimitive -import com.akuleshov7.ktoml.parsers.node.TomlNode -import com.akuleshov7.ktoml.parsers.node.TomlStubEmptyNode -import com.akuleshov7.ktoml.parsers.node.TomlTable -import com.akuleshov7.ktoml.parsers.node.splitKeyValue +import com.akuleshov7.ktoml.tree.* /** * @property ktomlConf - object that stores configuration options for a parser diff --git a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/parsers/node/TomlKey.kt b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/TomlKey.kt similarity index 96% rename from ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/parsers/node/TomlKey.kt rename to ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/TomlKey.kt index b6c2981d..90bbc645 100644 --- a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/parsers/node/TomlKey.kt +++ b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/TomlKey.kt @@ -1,4 +1,4 @@ -package com.akuleshov7.ktoml.parsers.node +package com.akuleshov7.ktoml.tree import com.akuleshov7.ktoml.parsers.splitKeyToTokens import com.akuleshov7.ktoml.parsers.trimQuotes diff --git a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/parsers/node/TomlKeyValue.kt b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/TomlKeyValue.kt similarity index 99% rename from ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/parsers/node/TomlKeyValue.kt rename to ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/TomlKeyValue.kt index e6f3a2bc..b51c8efd 100644 --- a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/parsers/node/TomlKeyValue.kt +++ b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/TomlKeyValue.kt @@ -1,4 +1,4 @@ -package com.akuleshov7.ktoml.parsers.node +package com.akuleshov7.ktoml.tree import com.akuleshov7.ktoml.KtomlConf import com.akuleshov7.ktoml.exceptions.TomlParsingException diff --git a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/parsers/node/TomlNode.kt b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/TomlNode.kt similarity index 99% rename from ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/parsers/node/TomlNode.kt rename to ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/TomlNode.kt index 1742e6e5..482a2cf6 100644 --- a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/parsers/node/TomlNode.kt +++ b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/TomlNode.kt @@ -2,7 +2,7 @@ * File contains all classes used in Toml AST node */ -package com.akuleshov7.ktoml.parsers.node +package com.akuleshov7.ktoml.tree import com.akuleshov7.ktoml.KtomlConf import com.akuleshov7.ktoml.exceptions.InternalAstException diff --git a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/parsers/node/TomlValue.kt b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/TomlValue.kt similarity index 99% rename from ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/parsers/node/TomlValue.kt rename to ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/TomlValue.kt index 19d3e06b..9cacb39f 100644 --- a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/parsers/node/TomlValue.kt +++ b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/TomlValue.kt @@ -2,7 +2,7 @@ * All representations of TOML value nodes are stored in this file */ -package com.akuleshov7.ktoml.parsers.node +package com.akuleshov7.ktoml.tree import com.akuleshov7.ktoml.KtomlConf import com.akuleshov7.ktoml.exceptions.TomlParsingException diff --git a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/writers/TomlWriter.kt b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/writers/TomlWriter.kt index 987f61a8..b65ec492 100644 --- a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/writers/TomlWriter.kt +++ b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/writers/TomlWriter.kt @@ -1,7 +1,7 @@ package com.akuleshov7.ktoml.writers import com.akuleshov7.ktoml.KtomlConf -import com.akuleshov7.ktoml.parsers.node.* +import com.akuleshov7.ktoml.tree.* import kotlin.jvm.JvmInline @JvmInline diff --git a/ktoml-core/src/commonTest/kotlin/node/parser/CommonParserTest.kt b/ktoml-core/src/commonTest/kotlin/node/parser/CommonParserTest.kt index b6373303..41e5790a 100644 --- a/ktoml-core/src/commonTest/kotlin/node/parser/CommonParserTest.kt +++ b/ktoml-core/src/commonTest/kotlin/node/parser/CommonParserTest.kt @@ -1,7 +1,7 @@ package node.parser -import com.akuleshov7.ktoml.parsers.node.TomlArray -import com.akuleshov7.ktoml.parsers.node.TomlBasicString +import com.akuleshov7.ktoml.tree.TomlArray +import com.akuleshov7.ktoml.tree.TomlBasicString import kotlin.test.Test import kotlin.test.assertEquals diff --git a/ktoml-core/src/commonTest/kotlin/node/parser/DottedKeyParserTest.kt b/ktoml-core/src/commonTest/kotlin/node/parser/DottedKeyParserTest.kt index 479a1d10..09e0596b 100644 --- a/ktoml-core/src/commonTest/kotlin/node/parser/DottedKeyParserTest.kt +++ b/ktoml-core/src/commonTest/kotlin/node/parser/DottedKeyParserTest.kt @@ -2,7 +2,9 @@ package com.akuleshov7.ktoml.test.node.parser import com.akuleshov7.ktoml.KtomlConf import com.akuleshov7.ktoml.exceptions.TomlParsingException -import com.akuleshov7.ktoml.parsers.node.* +import com.akuleshov7.ktoml.tree.TomlFile +import com.akuleshov7.ktoml.tree.TomlKey +import com.akuleshov7.ktoml.tree.TomlKeyValuePrimitive import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFailsWith diff --git a/ktoml-core/src/commonTest/kotlin/node/parser/TomlNodeTest.kt b/ktoml-core/src/commonTest/kotlin/node/parser/TomlNodeTest.kt index f7853d12..99a0526e 100644 --- a/ktoml-core/src/commonTest/kotlin/node/parser/TomlNodeTest.kt +++ b/ktoml-core/src/commonTest/kotlin/node/parser/TomlNodeTest.kt @@ -1,7 +1,7 @@ package com.akuleshov7.ktoml.test.node.parser -import com.akuleshov7.ktoml.parsers.node.TomlFile -import com.akuleshov7.ktoml.parsers.node.TomlTable +import com.akuleshov7.ktoml.tree.TomlFile +import com.akuleshov7.ktoml.tree.TomlTable import kotlin.test.Test import kotlin.test.assertTrue diff --git a/ktoml-core/src/commonTest/kotlin/node/parser/TomlTableTest.kt b/ktoml-core/src/commonTest/kotlin/node/parser/TomlTableTest.kt index ed106a5c..b73fc2e5 100644 --- a/ktoml-core/src/commonTest/kotlin/node/parser/TomlTableTest.kt +++ b/ktoml-core/src/commonTest/kotlin/node/parser/TomlTableTest.kt @@ -1,7 +1,7 @@ package com.akuleshov7.ktoml.test.node.parser -import com.akuleshov7.ktoml.parsers.node.TomlFile -import com.akuleshov7.ktoml.parsers.node.TomlTable +import com.akuleshov7.ktoml.tree.TomlFile +import com.akuleshov7.ktoml.tree.TomlTable import kotlin.test.Test import kotlin.test.assertEquals diff --git a/ktoml-core/src/commonTest/kotlin/node/parser/ValueParserTest.kt b/ktoml-core/src/commonTest/kotlin/node/parser/ValueParserTest.kt index a4847fe9..567aeaa6 100644 --- a/ktoml-core/src/commonTest/kotlin/node/parser/ValueParserTest.kt +++ b/ktoml-core/src/commonTest/kotlin/node/parser/ValueParserTest.kt @@ -2,7 +2,7 @@ package com.akuleshov7.ktoml.test.node.parser import com.akuleshov7.ktoml.KtomlConf import com.akuleshov7.ktoml.exceptions.TomlParsingException -import com.akuleshov7.ktoml.parsers.node.* +import com.akuleshov7.ktoml.tree.* import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFailsWith diff --git a/ktoml-file/src/commonMain/kotlin/com/akuleshov7/ktoml/file/TomlFileWriter.kt b/ktoml-file/src/commonMain/kotlin/com/akuleshov7/ktoml/file/TomlFileWriter.kt index f28ea1cd..3e4d01c1 100644 --- a/ktoml-file/src/commonMain/kotlin/com/akuleshov7/ktoml/file/TomlFileWriter.kt +++ b/ktoml-file/src/commonMain/kotlin/com/akuleshov7/ktoml/file/TomlFileWriter.kt @@ -3,7 +3,7 @@ package com.akuleshov7.ktoml.file import com.akuleshov7.ktoml.KtomlConf import com.akuleshov7.ktoml.Toml import com.akuleshov7.ktoml.encoders.TomlMainEncoder -import com.akuleshov7.ktoml.parsers.node.TomlFile +import com.akuleshov7.ktoml.tree.TomlFile import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.SerializationStrategy import kotlinx.serialization.modules.EmptySerializersModule diff --git a/ktoml-file/src/commonTest/kotlin/file/TomlFileParserTest.kt b/ktoml-file/src/commonTest/kotlin/file/TomlFileParserTest.kt index 3f43afad..30761991 100644 --- a/ktoml-file/src/commonTest/kotlin/file/TomlFileParserTest.kt +++ b/ktoml-file/src/commonTest/kotlin/file/TomlFileParserTest.kt @@ -2,7 +2,7 @@ package com.akuleshov7.ktoml.file import com.akuleshov7.ktoml.* import com.akuleshov7.ktoml.parsers.TomlParser -import com.akuleshov7.ktoml.parsers.node.TomlTable +import com.akuleshov7.ktoml.tree.TomlTable import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.Serializable import kotlinx.serialization.serializer From a4b78e99ba4fcbbdff694e3dfcca81d85ac2c4aa Mon Sep 17 00:00:00 2001 From: NightEule5 <24661563+NightEule5@users.noreply.github.com> Date: Thu, 6 Jan 2022 20:31:08 -0700 Subject: [PATCH 05/12] Refactor node constructors for easier encoding Classes deriving from TomlValue now expose their content property in an internal constructor, and parsing was moved to a secondary constructor. Since member functions are inaccessible before the super constructor is called, private functions used for parsing were moved to companion objects. --- .../com/akuleshov7/ktoml/tree/TomlValue.kt | 310 ++++++++++-------- 1 file changed, 174 insertions(+), 136 deletions(-) diff --git a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/TomlValue.kt b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/TomlValue.kt index 9cacb39f..ce49d9f2 100644 --- a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/TomlValue.kt +++ b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/TomlValue.kt @@ -23,105 +23,131 @@ public sealed class TomlValue(public val lineNo: Int) { * The only difference from the TOML specification (https://toml.io/en/v1.0.0) is that we will have one escaped symbol - * single quote and so it will be possible to use a single quote inside. */ -public class TomlLiteralString( - content: String, - lineNo: Int, - ktomlConf: KtomlConf = KtomlConf()) : TomlValue(lineNo) { - override var content: Any = if (content.startsWith("'") && content.endsWith("'")) { - val contentString = content.trimSingleQuotes() - if (ktomlConf.escapedQuotesInLiteralStringsAllowed) contentString.convertSingleQuotes() else contentString - } else { - throw TomlParsingException( - "Literal string should be wrapped with single quotes (''), it looks that you have forgotten" + - " the single quote in the end of the following string: <$content>", lineNo - ) - } +public class TomlLiteralString +internal constructor( + override var content: Any, + lineNo: Int +) : TomlValue(lineNo) { + public constructor( + content: String, + lineNo: Int, + ktomlConf: KtomlConf = KtomlConf() + ) : this(content.verifyAndTrimQuotes(lineNo, ktomlConf), lineNo) - /** - * According to the TOML standard (https://toml.io/en/v1.0.0#string) single quote is prohibited. - * But in ktoml we don't see any reason why we cannot escape it. Anyway, by the TOML specification we should fail, so - * why not to try to handle this situation at least somehow. - * - * Conversion is done after we have trimmed technical quotes and won't break cases when the user simply used a backslash - * as the last symbol (single quote) will be removed. - */ - private fun String.convertSingleQuotes(): String = this.replace("\\'", "'") + public companion object { + private fun String.verifyAndTrimQuotes(lineNo: Int, ktomlConf: KtomlConf): Any = + if (startsWith("'") && endsWith("'")) { + val contentString = trimSingleQuotes() + if (ktomlConf.escapedQuotesInLiteralStringsAllowed) contentString.convertSingleQuotes() else contentString + } else { + throw TomlParsingException( + "Literal string should be wrapped with single quotes (''), it looks that you have forgotten" + + " the single quote in the end of the following string: <$this>", lineNo + ) + } + + /** + * According to the TOML standard (https://toml.io/en/v1.0.0#string) single quote is prohibited. + * But in ktoml we don't see any reason why we cannot escape it. Anyway, by the TOML specification we should fail, so + * why not to try to handle this situation at least somehow. + * + * Conversion is done after we have trimmed technical quotes and won't break cases when the user simply used a backslash + * as the last symbol (single quote) will be removed. + */ + private fun String.convertSingleQuotes(): String = this.replace("\\'", "'") + } } /** * Toml AST Node for a representation of string values: key = "value" (always should have quotes due to TOML standard) */ -public class TomlBasicString(content: String, lineNo: Int) : TomlValue(lineNo) { - override var content: Any = if (content.startsWith("\"") && content.endsWith("\"")) { - content.trimQuotes() - .checkOtherQuotesAreEscaped() - .convertSpecialCharacters() - } else { - throw TomlParsingException( - "According to the TOML specification string values (even Enums)" + - " should be wrapped (start and end) with quotes (\"\"), but the following value was not: <$content>." + - " Please note that multiline strings are not yet supported.", - lineNo - ) - } +public class TomlBasicString +internal constructor( + override var content: Any, + lineNo: Int +) : TomlValue(lineNo) { + public constructor( + content: String, + lineNo: Int + ) : this(content.verifyAndTrimQuotes(lineNo), lineNo) - private fun String.checkOtherQuotesAreEscaped(): String { - this.forEachIndexed { index, ch -> - if (ch == '\"' && (index == 0 || this[index - 1] != '\\')) { + public companion object { + private fun String.verifyAndTrimQuotes(lineNo: Int): Any = + if (startsWith("\"") && endsWith("\"")) { + trimQuotes() + .checkOtherQuotesAreEscaped(lineNo) + .convertSpecialCharacters(lineNo) + } else { throw TomlParsingException( - "Found invalid quote that is not escaped." + - " Please remove the quote or use escaping" + - " in <$this> at position = [$index].", lineNo + "According to the TOML specification string values (even Enums)" + + " should be wrapped (start and end) with quotes (\"\"), but the following value was not: <$this>." + + " Please note that multiline strings are not yet supported.", + lineNo ) } - } - return this - } - private fun String.convertSpecialCharacters(): String { - val resultString = StringBuilder() - var updatedOnPreviousStep = false - var i = 0 - while (i < this.length) { - val newCharacter = if (this[i] == '\\' && i != this.length - 1) { - updatedOnPreviousStep = true - when (this[i + 1]) { - // table that is used to convert escaped string literals to proper char symbols - 't' -> '\t' - 'b' -> '\b' - 'r' -> '\r' - 'n' -> '\n' - '\\' -> '\\' - '\'' -> '\'' - '"' -> '"' - else -> throw TomlParsingException( - "According to TOML documentation unknown" + - " escape symbols are not allowed. Please check: [\\${this[i + 1]}]", - lineNo + private fun String.checkOtherQuotesAreEscaped(lineNo: Int): String { + this.forEachIndexed { index, ch -> + if (ch == '\"' && (index == 0 || this[index - 1] != '\\')) { + throw TomlParsingException( + "Found invalid quote that is not escaped." + + " Please remove the quote or use escaping" + + " in <$this> at position = [$index].", lineNo ) } - } else { - this[i] - } - // need to skip the next character if we have processed special escaped symbol - if (updatedOnPreviousStep) { - updatedOnPreviousStep = false - i += 2 - } else { - i += 1 } + return this + } - resultString.append(newCharacter) + private fun String.convertSpecialCharacters(lineNo: Int): String { + val resultString = StringBuilder() + var updatedOnPreviousStep = false + var i = 0 + while (i < this.length) { + val newCharacter = if (this[i] == '\\' && i != this.length - 1) { + updatedOnPreviousStep = true + when (this[i + 1]) { + // table that is used to convert escaped string literals to proper char symbols + 't' -> '\t' + 'b' -> '\b' + 'r' -> '\r' + 'n' -> '\n' + '\\' -> '\\' + '\'' -> '\'' + '"' -> '"' + else -> throw TomlParsingException( + "According to TOML documentation unknown" + + " escape symbols are not allowed. Please check: [\\${this[i + 1]}]", + lineNo + ) + } + } else { + this[i] + } + // need to skip the next character if we have processed special escaped symbol + if (updatedOnPreviousStep) { + updatedOnPreviousStep = false + i += 2 + } else { + i += 1 + } + + resultString.append(newCharacter) + } + return resultString.toString() } - return resultString.toString() } } /** * Toml AST Node for a representation of Arbitrary 64-bit signed integers: key = 1 */ -public class TomlLong(content: String, lineNo: Int) : TomlValue(lineNo) { - override var content: Any = content.toLong() +public class TomlLong +internal constructor( + override var content: Any, + lineNo: Int +) : TomlValue(lineNo) { + public constructor(content: String, lineNo: Int) : this(content.toLong(), lineNo) } /** @@ -129,15 +155,23 @@ public class TomlLong(content: String, lineNo: Int) : TomlValue(lineNo) { * Toml specification requires floating point numbers to be IEEE 754 binary64 values, * so it should be Kotlin Double (64 bits) */ -public class TomlDouble(content: String, lineNo: Int) : TomlValue(lineNo) { - override var content: Any = content.toDouble() +public class TomlDouble +internal constructor( + override var content: Any, + lineNo: Int +) : TomlValue(lineNo) { + public constructor(content: String, lineNo: Int) : this(content.toDouble(), lineNo) } /** * Toml AST Node for a representation of boolean types: key = true | false */ -public class TomlBoolean(content: String, lineNo: Int) : TomlValue(lineNo) { - override var content: Any = content.toBoolean() +public class TomlBoolean +internal constructor( + override var content: Any, + lineNo: Int +) : TomlValue(lineNo) { + public constructor(content: String, lineNo: Int) : this(content.toBoolean(), lineNo) } /** @@ -151,16 +185,18 @@ public class TomlNull(lineNo: Int) : TomlValue(lineNo) { /** * Toml AST Node for a representation of arrays: key = [value1, value2, value3] */ -public class TomlArray( +public class TomlArray +internal constructor( + override var content: Any, private val rawContent: String, - lineNo: Int, - ktomlConf: KtomlConf = KtomlConf() + lineNo: Int ) : TomlValue(lineNo) { - override lateinit var content: Any - - init { + public constructor( + rawContent: String, + lineNo: Int, + ktomlConf: KtomlConf = KtomlConf() + ) : this(rawContent.parse(lineNo, ktomlConf), rawContent, lineNo) { validateBrackets() - this.content = parse() } /** @@ -169,54 +205,7 @@ public class TomlArray( * @param ktomlConf * @return converted array to a list */ - public fun parse(ktomlConf: KtomlConf = KtomlConf()): List = rawContent.parse(ktomlConf) - - /** - * recursively parse TOML array from the string: [ParsingArray -> Trimming values -> Parsing Nested Arrays] - */ - private fun String.parse(ktomlConf: KtomlConf = KtomlConf()): List = - this.parseArray() - .map { it.trim() } - .map { if (it.startsWith("[")) it.parse(ktomlConf) else it.parseValue(lineNo, ktomlConf) } - - /** - * method for splitting the string to the array: "[[a, b], [c], [d]]" to -> [a,b] [c] [d] - */ - @Suppress("TOO_MANY_LINES_IN_LAMBDA") - private fun String.parseArray(): MutableList { - // covering cases when the array is intentionally blank: myArray = []. It should be empty and not contain null - if (this.trimBrackets().isBlank()) { - return mutableListOf() - } - - var numberOfOpenBrackets = 0 - var numberOfClosedBrackets = 0 - var bufferBetweenCommas = StringBuilder() - val result: MutableList = mutableListOf() - - this.trimBrackets().forEach { - when (it) { - '[' -> { - numberOfOpenBrackets++ - bufferBetweenCommas.append(it) - } - ']' -> { - numberOfClosedBrackets++ - bufferBetweenCommas.append(it) - } - // split only if we are on the highest level of brackets (all brackets are closed) - ',' -> if (numberOfClosedBrackets == numberOfOpenBrackets) { - result.add(bufferBetweenCommas.toString()) - bufferBetweenCommas = StringBuilder() - } else { - bufferBetweenCommas.append(it) - } - else -> bufferBetweenCommas.append(it) - } - } - result.add(bufferBetweenCommas.toString()) - return result - } + public fun parse(ktomlConf: KtomlConf = KtomlConf()): List = rawContent.parse(lineNo, ktomlConf) /** * small validation for quotes: each quote should be closed in a key @@ -229,4 +218,53 @@ public class TomlArray( ) } } + + public companion object { + /** + * recursively parse TOML array from the string: [ParsingArray -> Trimming values -> Parsing Nested Arrays] + */ + private fun String.parse(lineNo: Int, ktomlConf: KtomlConf = KtomlConf()): List = + this.parseArray() + .map { it.trim() } + .map { if (it.startsWith("[")) it.parse(lineNo, ktomlConf) else it.parseValue(lineNo, ktomlConf) } + + /** + * method for splitting the string to the array: "[[a, b], [c], [d]]" to -> [a,b] [c] [d] + */ + @Suppress("TOO_MANY_LINES_IN_LAMBDA") + private fun String.parseArray(): MutableList { + // covering cases when the array is intentionally blank: myArray = []. It should be empty and not contain null + if (this.trimBrackets().isBlank()) { + return mutableListOf() + } + + var numberOfOpenBrackets = 0 + var numberOfClosedBrackets = 0 + var bufferBetweenCommas = StringBuilder() + val result: MutableList = mutableListOf() + + this.trimBrackets().forEach { + when (it) { + '[' -> { + numberOfOpenBrackets++ + bufferBetweenCommas.append(it) + } + ']' -> { + numberOfClosedBrackets++ + bufferBetweenCommas.append(it) + } + // split only if we are on the highest level of brackets (all brackets are closed) + ',' -> if (numberOfClosedBrackets == numberOfOpenBrackets) { + result.add(bufferBetweenCommas.toString()) + bufferBetweenCommas = StringBuilder() + } else { + bufferBetweenCommas.append(it) + } + else -> bufferBetweenCommas.append(it) + } + } + result.add(bufferBetweenCommas.toString()) + return result + } + } } From c85313d6a9b5cdf11ac9b6dfca8478eab59137ab Mon Sep 17 00:00:00 2001 From: NightEule5 <24661563+NightEule5@users.noreply.github.com> Date: Mon, 10 Jan 2022 03:48:36 -0700 Subject: [PATCH 06/12] Prepare for pull request Cleaned formatting and fixed style inconsistencies Diktat caught. --- .../kotlin/com/akuleshov7/ktoml/KtomlConf.kt | 16 ++-- .../kotlin/com/akuleshov7/ktoml/Toml.kt | 3 +- .../akuleshov7/ktoml/parsers/TomlParser.kt | 9 +- .../com/akuleshov7/ktoml/tree/TomlValue.kt | 59 +++++++----- .../ktoml/writers/AbstractTomlComposer.kt | 92 +++++++++++-------- .../ktoml/writers/IntegerRepresentation.kt | 22 +++-- .../akuleshov7/ktoml/writers/TomlComposer.kt | 16 +++- .../ktoml/writers/TomlStringComposer.kt | 25 +++-- .../akuleshov7/ktoml/writers/TomlWriter.kt | 31 ++++--- .../com/akuleshov7/ktoml/file/FileUtils.kt | 7 ++ .../akuleshov7/ktoml/file/TomlFileWriter.kt | 13 ++- .../akuleshov7/ktoml/file/TomlSinkComposer.kt | 25 ++++- 12 files changed, 209 insertions(+), 109 deletions(-) diff --git a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/KtomlConf.kt b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/KtomlConf.kt index 5994b110..e1fc7122 100644 --- a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/KtomlConf.kt +++ b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/KtomlConf.kt @@ -4,18 +4,22 @@ package com.akuleshov7.ktoml * @property ignoreUnknownNames - a control to allow/prohibit unknown names during the deserialization * @property emptyValuesAllowed - a control to allow/prohibit empty values: a = # comment * @property escapedQuotesInLiteralStringsAllowed - a control to allow/prohibit escaping of single quotes in literal strings + * @property indentation */ public data class KtomlConf( val ignoreUnknownNames: Boolean = false, val emptyValuesAllowed: Boolean = true, val escapedQuotesInLiteralStringsAllowed: Boolean = true, val indentation: Indentation = Indentation.FourSpaces -) -{ +) { + /** + * @property string + */ public enum class Indentation(public val string: String) { - None(""), - TwoSpaces(" "), - FourSpaces(" "), - Tab("\t") + FOUR_SPACES(" "), + NONE(""), + TAB("\t"), + TWO_SPACES(" "), + ; } } diff --git a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/Toml.kt b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/Toml.kt index d51c9d59..45cd173d 100644 --- a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/Toml.kt +++ b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/Toml.kt @@ -5,13 +5,14 @@ import com.akuleshov7.ktoml.exceptions.MissingRequiredFieldException import com.akuleshov7.ktoml.parsers.TomlParser import com.akuleshov7.ktoml.tree.TomlFile import com.akuleshov7.ktoml.writers.TomlWriter + +import kotlin.native.concurrent.ThreadLocal import kotlinx.serialization.DeserializationStrategy import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.SerializationStrategy import kotlinx.serialization.StringFormat import kotlinx.serialization.modules.EmptySerializersModule import kotlinx.serialization.modules.SerializersModule -import kotlin.native.concurrent.ThreadLocal /** * Toml class - is a general entry point in the core, diff --git a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/parsers/TomlParser.kt b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/parsers/TomlParser.kt index a606b045..a70f51c0 100644 --- a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/parsers/TomlParser.kt +++ b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/parsers/TomlParser.kt @@ -2,7 +2,14 @@ package com.akuleshov7.ktoml.parsers import com.akuleshov7.ktoml.KtomlConf import com.akuleshov7.ktoml.exceptions.InternalAstException -import com.akuleshov7.ktoml.tree.* +import com.akuleshov7.ktoml.tree.TomlFile +import com.akuleshov7.ktoml.tree.TomlKeyValue +import com.akuleshov7.ktoml.tree.TomlKeyValueArray +import com.akuleshov7.ktoml.tree.TomlKeyValuePrimitive +import com.akuleshov7.ktoml.tree.TomlNode +import com.akuleshov7.ktoml.tree.TomlStubEmptyNode +import com.akuleshov7.ktoml.tree.TomlTable +import com.akuleshov7.ktoml.tree.splitKeyValue /** * @property ktomlConf - object that stores configuration options for a parser diff --git a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/TomlValue.kt b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/TomlValue.kt index ce49d9f2..19326d53 100644 --- a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/TomlValue.kt +++ b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/tree/TomlValue.kt @@ -22,6 +22,7 @@ public sealed class TomlValue(public val lineNo: Int) { * Toml AST Node for a representation of literal string values: key = 'value' (with single quotes and no escaped symbols) * The only difference from the TOML specification (https://toml.io/en/v1.0.0) is that we will have one escaped symbol - * single quote and so it will be possible to use a single quote inside. + * @property content */ public class TomlLiteralString internal constructor( @@ -36,15 +37,15 @@ internal constructor( public companion object { private fun String.verifyAndTrimQuotes(lineNo: Int, ktomlConf: KtomlConf): Any = - if (startsWith("'") && endsWith("'")) { - val contentString = trimSingleQuotes() - if (ktomlConf.escapedQuotesInLiteralStringsAllowed) contentString.convertSingleQuotes() else contentString - } else { - throw TomlParsingException( - "Literal string should be wrapped with single quotes (''), it looks that you have forgotten" + - " the single quote in the end of the following string: <$this>", lineNo - ) - } + if (startsWith("'") && endsWith("'")) { + val contentString = trimSingleQuotes() + if (ktomlConf.escapedQuotesInLiteralStringsAllowed) contentString.convertSingleQuotes() else contentString + } else { + throw TomlParsingException( + "Literal string should be wrapped with single quotes (''), it looks that you have forgotten" + + " the single quote in the end of the following string: <$this>", lineNo + ) + } /** * According to the TOML standard (https://toml.io/en/v1.0.0#string) single quote is prohibited. @@ -60,6 +61,7 @@ internal constructor( /** * Toml AST Node for a representation of string values: key = "value" (always should have quotes due to TOML standard) + * @property content */ public class TomlBasicString internal constructor( @@ -73,18 +75,18 @@ internal constructor( public companion object { private fun String.verifyAndTrimQuotes(lineNo: Int): Any = - if (startsWith("\"") && endsWith("\"")) { - trimQuotes() - .checkOtherQuotesAreEscaped(lineNo) - .convertSpecialCharacters(lineNo) - } else { - throw TomlParsingException( - "According to the TOML specification string values (even Enums)" + - " should be wrapped (start and end) with quotes (\"\"), but the following value was not: <$this>." + - " Please note that multiline strings are not yet supported.", - lineNo - ) - } + if (startsWith("\"") && endsWith("\"")) { + trimQuotes() + .checkOtherQuotesAreEscaped(lineNo) + .convertSpecialCharacters(lineNo) + } else { + throw TomlParsingException( + "According to the TOML specification string values (even Enums)" + + " should be wrapped (start and end) with quotes (\"\"), but the following value was not: <$this>." + + " Please note that multiline strings are not yet supported.", + lineNo + ) + } private fun String.checkOtherQuotesAreEscaped(lineNo: Int): String { this.forEachIndexed { index, ch -> @@ -141,6 +143,7 @@ internal constructor( /** * Toml AST Node for a representation of Arbitrary 64-bit signed integers: key = 1 + * @property content */ public class TomlLong internal constructor( @@ -154,6 +157,7 @@ internal constructor( * Toml AST Node for a representation of float types: key = 1.01. * Toml specification requires floating point numbers to be IEEE 754 binary64 values, * so it should be Kotlin Double (64 bits) + * @property content */ public class TomlDouble internal constructor( @@ -165,6 +169,7 @@ internal constructor( /** * Toml AST Node for a representation of boolean types: key = true | false + * @property content */ public class TomlBoolean internal constructor( @@ -184,6 +189,7 @@ public class TomlNull(lineNo: Int) : TomlValue(lineNo) { /** * Toml AST Node for a representation of arrays: key = [value1, value2, value3] + * @property content */ public class TomlArray internal constructor( @@ -195,7 +201,10 @@ internal constructor( rawContent: String, lineNo: Int, ktomlConf: KtomlConf = KtomlConf() - ) : this(rawContent.parse(lineNo, ktomlConf), rawContent, lineNo) { + ) : this( + rawContent.parse(lineNo, ktomlConf), + rawContent, + lineNo) { validateBrackets() } @@ -224,9 +233,9 @@ internal constructor( * recursively parse TOML array from the string: [ParsingArray -> Trimming values -> Parsing Nested Arrays] */ private fun String.parse(lineNo: Int, ktomlConf: KtomlConf = KtomlConf()): List = - this.parseArray() - .map { it.trim() } - .map { if (it.startsWith("[")) it.parse(lineNo, ktomlConf) else it.parseValue(lineNo, ktomlConf) } + this.parseArray() + .map { it.trim() } + .map { if (it.startsWith("[")) it.parse(lineNo, ktomlConf) else it.parseValue(lineNo, ktomlConf) } /** * method for splitting the string to the array: "[[a, b], [c], [d]]" to -> [a,b] [c] [d] diff --git a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/writers/AbstractTomlComposer.kt b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/writers/AbstractTomlComposer.kt index e7f8e2d0..148c3c59 100644 --- a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/writers/AbstractTomlComposer.kt +++ b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/writers/AbstractTomlComposer.kt @@ -1,13 +1,22 @@ package com.akuleshov7.ktoml.writers import com.akuleshov7.ktoml.KtomlConf -import com.akuleshov7.ktoml.writers.IntegerRepresentation.* +import com.akuleshov7.ktoml.writers.IntegerRepresentation.BINARY +import com.akuleshov7.ktoml.writers.IntegerRepresentation.DECIMAL +import com.akuleshov7.ktoml.writers.IntegerRepresentation.GROUPED +import com.akuleshov7.ktoml.writers.IntegerRepresentation.HEX +import com.akuleshov7.ktoml.writers.IntegerRepresentation.OCTAL import kotlin.jvm.JvmStatic +/** + * The based implementation of [TomlComposer]. + */ public abstract class AbstractTomlComposer(ktomlConf: KtomlConf) : TomlComposer { private val indentation = ktomlConf.indentation.string - final override var indentDepth: Int = 0; protected set + @Suppress("CUSTOM_GETTERS_SETTERS") + final override var indentDepth: Int = 0 + protected set final override fun indent(): Int = ++indentDepth final override fun dedent(): Int = --indentDepth @@ -16,8 +25,14 @@ public abstract class AbstractTomlComposer(ktomlConf: KtomlConf) : TomlComposer protected abstract fun emit(fragment: Char) protected abstract fun emit(fragment1: String, fragment2: String) - protected abstract fun emit(fragment1: String, fragment2: String, fragment3: String) - protected abstract fun emit(fragment1: Char, fragment2: String, fragment3: Char) + protected abstract fun emit( + fragment1: String, + fragment2: String, + fragment3: String) + protected abstract fun emit( + fragment1: Char, + fragment2: String, + fragment3: Char) protected open fun emit(vararg fragments: String): Unit = fragments.forEach(::emit) @@ -31,22 +46,24 @@ public abstract class AbstractTomlComposer(ktomlConf: KtomlConf) : TomlComposer final override fun emitWhitespace(count: Int): Unit = emit(' ', count) final override fun emitComment(comment: String, endOfLine: Boolean): Unit = - if (endOfLine) - emit(" # ", comment) - else - emit("# ", comment) + if (endOfLine) { + emit(" # ", comment) + } else { + emit("# ", comment) + } final override fun emitKey(key: String) { - if (key matches bareKeyRegex) + if (key matches bareKeyRegex) { emitBareKey(key) - else + } else { emitQuotedKey(key, isLiteral = key matches literalKeyCandidateRegex) + } } final override fun emitBareKey(key: String): Unit = emit(key) final override fun emitQuotedKey(key: String, isLiteral: Boolean): Unit = - emitValue(string = key, isLiteral) + emitValue(string = key, isLiteral) final override fun emitKeyDot(): Unit = emit('.') @@ -58,36 +75,38 @@ public abstract class AbstractTomlComposer(ktomlConf: KtomlConf) : TomlComposer final override fun emitTableArrayHeaderEnd(): Unit = emit("]]") - final override fun emitValue(string: String, isLiteral: Boolean, isMultiline: Boolean): Unit = - if (isMultiline) { - val quotes = if (isLiteral) "'''" else "\"\"\"" + final override fun emitValue( + string: String, + isLiteral: Boolean, + isMultiline: Boolean): Unit = + if (isMultiline) { + val quotes = if (isLiteral) "'''" else "\"\"\"" - emit("$quotes\n", string, quotes) - } - else { - val quote = if (isLiteral) '\'' else '"' + emit("$quotes\n", string, quotes) + } else { + val quote = if (isLiteral) '\'' else '"' - emit(quote, string, quote) - } + emit(quote, string, quote) + } final override fun emitValue(integer: Long, representation: IntegerRepresentation): Unit = - when (representation) { - Decimal -> emit("$integer") - Hex -> emit("0x", integer.toString(16)) - Binary -> emit("0b", integer.toString(2 )) - Octal -> emit("0o", integer.toString(8 )) - Grouped -> TODO() - } + when (representation) { + DECIMAL -> emit(integer.toString()) + HEX -> emit("0x", integer.toString(16)) + BINARY -> emit("0b", integer.toString(2)) + OCTAL -> emit("0o", integer.toString(8)) + GROUPED -> TODO() + } final override fun emitValue(float: Double): Unit = - emit(when (float) { - Double.NaN -> "nan" - Double.POSITIVE_INFINITY -> "inf" - Double.NEGATIVE_INFINITY -> "-inf" - else -> "$float" - }) + emit(when (float) { + Double.NaN -> "nan" + Double.POSITIVE_INFINITY -> "inf" + Double.NEGATIVE_INFINITY -> "-inf" + else -> float.toString() + }) - final override fun emitValue(boolean: Boolean): Unit = emit("$boolean") + final override fun emitValue(boolean: Boolean): Unit = emit(boolean.toString()) final override fun startArray(): Unit = emit('[') @@ -101,8 +120,7 @@ public abstract class AbstractTomlComposer(ktomlConf: KtomlConf) : TomlComposer final override fun emitPairDelimiter(): Unit = emit(" = ") - public companion object - { + public companion object { @JvmStatic private val bareKeyRegex = Regex("[A-Za-z0-9_-]+") @@ -113,4 +131,4 @@ public abstract class AbstractTomlComposer(ktomlConf: KtomlConf) : TomlComposer @JvmStatic private val literalKeyCandidateRegex = Regex("""[^'"]*((? error( "A file node is not allowed as a child of another file node." ) - is TomlKeyValueArray -> - { + is TomlKeyValueArray -> { } - is TomlKeyValuePrimitive -> - { + is TomlKeyValuePrimitive -> { } is TomlStubEmptyNode -> { } - is TomlTable -> - { + is TomlTable -> { } } - private fun TomlComposer.writeKey(key: TomlKey) - { + private fun TomlComposer.writeKey(key: TomlKey) { val keys = key.keyParts if (keys.isEmpty()) { @@ -59,4 +64,4 @@ public value class TomlWriter(private val ktomlConf: KtomlConf) { emitKey(it) } } -} \ No newline at end of file +} diff --git a/ktoml-file/src/commonMain/kotlin/com/akuleshov7/ktoml/file/FileUtils.kt b/ktoml-file/src/commonMain/kotlin/com/akuleshov7/ktoml/file/FileUtils.kt index d0840475..2e46de53 100644 --- a/ktoml-file/src/commonMain/kotlin/com/akuleshov7/ktoml/file/FileUtils.kt +++ b/ktoml-file/src/commonMain/kotlin/com/akuleshov7/ktoml/file/FileUtils.kt @@ -30,6 +30,13 @@ internal fun readAndParseFile(tomlFile: String): List { } } +/** + * Opens a file for writing via a [BufferedSink]. + * + * @param tomlFile The path string pointing to a .toml file. + * @return A [BufferedSink] writing to the specified [tomlFile] path. + * @throws FileNotFoundException + */ internal fun openFileForWrite(tomlFile: String): BufferedSink { try { val ktomlPath = tomlFile.toPath() diff --git a/ktoml-file/src/commonMain/kotlin/com/akuleshov7/ktoml/file/TomlFileWriter.kt b/ktoml-file/src/commonMain/kotlin/com/akuleshov7/ktoml/file/TomlFileWriter.kt index 3e4d01c1..e12aaa98 100644 --- a/ktoml-file/src/commonMain/kotlin/com/akuleshov7/ktoml/file/TomlFileWriter.kt +++ b/ktoml-file/src/commonMain/kotlin/com/akuleshov7/ktoml/file/TomlFileWriter.kt @@ -4,12 +4,18 @@ import com.akuleshov7.ktoml.KtomlConf import com.akuleshov7.ktoml.Toml import com.akuleshov7.ktoml.encoders.TomlMainEncoder import com.akuleshov7.ktoml.tree.TomlFile + +import okio.use + import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.SerializationStrategy import kotlinx.serialization.modules.EmptySerializersModule import kotlinx.serialization.modules.SerializersModule -import okio.use +/** + * Writes to a file in the TOML format. + * @property serializersModule + */ @OptIn(ExperimentalSerializationApi::class) public open class TomlFileWriter( private val config: KtomlConf = KtomlConf(), @@ -19,8 +25,7 @@ public open class TomlFileWriter( serializer: SerializationStrategy, value: T, tomlFilePath: String - ) - { + ) { val fileTree = TomlFile(config) val encoder = TomlMainEncoder(fileTree, config) @@ -33,4 +38,4 @@ public open class TomlFileWriter( tomlWriter.write(fileTree, it) } } -} \ No newline at end of file +} diff --git a/ktoml-file/src/commonMain/kotlin/com/akuleshov7/ktoml/file/TomlSinkComposer.kt b/ktoml-file/src/commonMain/kotlin/com/akuleshov7/ktoml/file/TomlSinkComposer.kt index 1ded676c..cd0ba053 100644 --- a/ktoml-file/src/commonMain/kotlin/com/akuleshov7/ktoml/file/TomlSinkComposer.kt +++ b/ktoml-file/src/commonMain/kotlin/com/akuleshov7/ktoml/file/TomlSinkComposer.kt @@ -1,10 +1,16 @@ +@file:Suppress("UNUSED_IMPORT")// TomlComposer used for documentation only + package com.akuleshov7.ktoml.file import com.akuleshov7.ktoml.KtomlConf import com.akuleshov7.ktoml.writers.AbstractTomlComposer +import com.akuleshov7.ktoml.writers.TomlComposer import okio.BufferedSink import okio.Closeable +/** + * A [TomlComposer] implementation that writes to a [BufferedSink]. + */ internal class TomlSinkComposer( private val sink: BufferedSink, ktomlConf: KtomlConf @@ -17,18 +23,29 @@ internal class TomlSinkComposer( sink.writeUtf8CodePoint(fragment.code) } - override fun emit(fragment1: String, fragment2: String) { + override fun emit( + fragment1: String, + fragment2: String + ) { sink.writeUtf8(fragment1) .writeUtf8(fragment2) } - override fun emit(fragment1: String, fragment2: String, fragment3: String) { + override fun emit( + fragment1: String, + fragment2: String, + fragment3: String + ) { sink.writeUtf8(fragment1) .writeUtf8(fragment2) .writeUtf8(fragment3) } - override fun emit(fragment1: Char, fragment2: String, fragment3: Char) { + override fun emit( + fragment1: Char, + fragment2: String, + fragment3: Char + ) { sink.writeUtf8CodePoint(fragment1.code) .writeUtf8(fragment2) .writeUtf8CodePoint(fragment3.code) @@ -37,4 +54,4 @@ internal class TomlSinkComposer( // Closeable override fun close(): Unit = sink.close() -} \ No newline at end of file +} From d795753994dd45e429ea508e1e95762c4785a14d Mon Sep 17 00:00:00 2001 From: NightEule5 <24661563+NightEule5@users.noreply.github.com> Date: Mon, 10 Jan 2022 04:13:23 -0700 Subject: [PATCH 07/12] Fix unresolved reference missed by diktatFix --- .../src/commonMain/kotlin/com/akuleshov7/ktoml/KtomlConf.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/KtomlConf.kt b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/KtomlConf.kt index e1fc7122..3eb142e3 100644 --- a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/KtomlConf.kt +++ b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/KtomlConf.kt @@ -1,5 +1,7 @@ package com.akuleshov7.ktoml +import com.akuleshov7.ktoml.KtomlConf.Indentation.FOUR_SPACES + /** * @property ignoreUnknownNames - a control to allow/prohibit unknown names during the deserialization * @property emptyValuesAllowed - a control to allow/prohibit empty values: a = # comment @@ -10,7 +12,7 @@ public data class KtomlConf( val ignoreUnknownNames: Boolean = false, val emptyValuesAllowed: Boolean = true, val escapedQuotesInLiteralStringsAllowed: Boolean = true, - val indentation: Indentation = Indentation.FourSpaces + val indentation: Indentation = FOUR_SPACES ) { /** * @property string From 63300a5f87e9e1ad9159ec0d37e062e799520676 Mon Sep 17 00:00:00 2001 From: NightEule5 <24661563+NightEule5@users.noreply.github.com> Date: Mon, 10 Jan 2022 04:21:42 -0700 Subject: [PATCH 08/12] Remove reference to uncommitted code --- .../kotlin/com/akuleshov7/ktoml/file/TomlFileWriter.kt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/ktoml-file/src/commonMain/kotlin/com/akuleshov7/ktoml/file/TomlFileWriter.kt b/ktoml-file/src/commonMain/kotlin/com/akuleshov7/ktoml/file/TomlFileWriter.kt index e12aaa98..4e591098 100644 --- a/ktoml-file/src/commonMain/kotlin/com/akuleshov7/ktoml/file/TomlFileWriter.kt +++ b/ktoml-file/src/commonMain/kotlin/com/akuleshov7/ktoml/file/TomlFileWriter.kt @@ -2,7 +2,6 @@ package com.akuleshov7.ktoml.file import com.akuleshov7.ktoml.KtomlConf import com.akuleshov7.ktoml.Toml -import com.akuleshov7.ktoml.encoders.TomlMainEncoder import com.akuleshov7.ktoml.tree.TomlFile import okio.use @@ -27,9 +26,8 @@ public open class TomlFileWriter( tomlFilePath: String ) { val fileTree = TomlFile(config) - val encoder = TomlMainEncoder(fileTree, config) - serializer.serialize(encoder, value) + // Todo: Write an encoder implementation. TomlSinkComposer( openFileForWrite(tomlFilePath), From be4928385ce3e7d3fbf599a85f0dd534bcab6a83 Mon Sep 17 00:00:00 2001 From: NightEule5 <24661563+NightEule5@users.noreply.github.com> Date: Tue, 11 Jan 2022 20:27:49 -0700 Subject: [PATCH 09/12] Implement PR suggestions and corrections --- .../kotlin/com/akuleshov7/ktoml/KtomlConf.kt | 6 +- .../kotlin/com/akuleshov7/ktoml/Toml.kt | 6 +- .../akuleshov7/ktoml/exceptions/Exceptions.kt | 2 + .../ktoml/writers/AbstractTomlComposer.kt | 134 ------------------ .../akuleshov7/ktoml/writers/TomlComposer.kt | 54 ------- .../akuleshov7/ktoml/writers/TomlEmitter.kt | 132 +++++++++++++++++ .../ktoml/writers/TomlStringComposer.kt | 42 ------ .../ktoml/writers/TomlStringEmitter.kt | 19 +++ .../akuleshov7/ktoml/writers/TomlWriter.kt | 13 +- .../com/akuleshov7/ktoml/file/FileUtils.kt | 2 +- .../akuleshov7/ktoml/file/TomlFileWriter.kt | 6 +- .../akuleshov7/ktoml/file/TomlSinkComposer.kt | 57 -------- .../akuleshov7/ktoml/file/TomlSinkEmitter.kt | 26 ++++ 13 files changed, 195 insertions(+), 304 deletions(-) delete mode 100644 ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/writers/AbstractTomlComposer.kt delete mode 100644 ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/writers/TomlComposer.kt create mode 100644 ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/writers/TomlEmitter.kt delete mode 100644 ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/writers/TomlStringComposer.kt create mode 100644 ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/writers/TomlStringEmitter.kt delete mode 100644 ktoml-file/src/commonMain/kotlin/com/akuleshov7/ktoml/file/TomlSinkComposer.kt create mode 100644 ktoml-file/src/commonMain/kotlin/com/akuleshov7/ktoml/file/TomlSinkEmitter.kt diff --git a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/KtomlConf.kt b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/KtomlConf.kt index 3eb142e3..69621755 100644 --- a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/KtomlConf.kt +++ b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/KtomlConf.kt @@ -12,12 +12,12 @@ public data class KtomlConf( val ignoreUnknownNames: Boolean = false, val emptyValuesAllowed: Boolean = true, val escapedQuotesInLiteralStringsAllowed: Boolean = true, - val indentation: Indentation = FOUR_SPACES + val indentation: Indentation = FOUR_SPACES, ) { /** - * @property string + * @property value */ - public enum class Indentation(public val string: String) { + public enum class Indentation(public val value: String) { FOUR_SPACES(" "), NONE(""), TAB("\t"), diff --git a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/Toml.kt b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/Toml.kt index 45cd173d..ddf70252 100644 --- a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/Toml.kt +++ b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/Toml.kt @@ -5,14 +5,13 @@ import com.akuleshov7.ktoml.exceptions.MissingRequiredFieldException import com.akuleshov7.ktoml.parsers.TomlParser import com.akuleshov7.ktoml.tree.TomlFile import com.akuleshov7.ktoml.writers.TomlWriter - -import kotlin.native.concurrent.ThreadLocal import kotlinx.serialization.DeserializationStrategy import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.SerializationStrategy import kotlinx.serialization.StringFormat import kotlinx.serialization.modules.EmptySerializersModule import kotlinx.serialization.modules.SerializersModule +import kotlin.native.concurrent.ThreadLocal /** * Toml class - is a general entry point in the core, @@ -26,7 +25,8 @@ public open class Toml( private val config: KtomlConf = KtomlConf(), override val serializersModule: SerializersModule = EmptySerializersModule ) : StringFormat { - // parser is created once after the creation of the class, to reduce the number of created parsers for each toml + // parser and writer are created once after the creation of the class, to reduce + // the number of created parsers and writers for each toml public val tomlParser: TomlParser = TomlParser(config) public val tomlWriter: TomlWriter = TomlWriter(config) diff --git a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/exceptions/Exceptions.kt b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/exceptions/Exceptions.kt index 006c23e4..d3b04735 100644 --- a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/exceptions/Exceptions.kt +++ b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/exceptions/Exceptions.kt @@ -10,6 +10,8 @@ public sealed class KtomlException(message: String) : Exception(message) internal class TomlParsingException(message: String, lineNo: Int) : KtomlException("Line $lineNo: $message") +internal class TomlWritingException(message: String) : KtomlException(message) + internal class InternalDecodingException(message: String) : KtomlException(message) internal class InternalAstException(message: String) : KtomlException(message) diff --git a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/writers/AbstractTomlComposer.kt b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/writers/AbstractTomlComposer.kt deleted file mode 100644 index 148c3c59..00000000 --- a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/writers/AbstractTomlComposer.kt +++ /dev/null @@ -1,134 +0,0 @@ -package com.akuleshov7.ktoml.writers - -import com.akuleshov7.ktoml.KtomlConf -import com.akuleshov7.ktoml.writers.IntegerRepresentation.BINARY -import com.akuleshov7.ktoml.writers.IntegerRepresentation.DECIMAL -import com.akuleshov7.ktoml.writers.IntegerRepresentation.GROUPED -import com.akuleshov7.ktoml.writers.IntegerRepresentation.HEX -import com.akuleshov7.ktoml.writers.IntegerRepresentation.OCTAL -import kotlin.jvm.JvmStatic - -/** - * The based implementation of [TomlComposer]. - */ -public abstract class AbstractTomlComposer(ktomlConf: KtomlConf) : TomlComposer { - private val indentation = ktomlConf.indentation.string - - @Suppress("CUSTOM_GETTERS_SETTERS") - final override var indentDepth: Int = 0 - protected set - - final override fun indent(): Int = ++indentDepth - final override fun dedent(): Int = --indentDepth - - protected abstract fun emit(fragment: String) - protected abstract fun emit(fragment: Char) - - protected abstract fun emit(fragment1: String, fragment2: String) - protected abstract fun emit( - fragment1: String, - fragment2: String, - fragment3: String) - protected abstract fun emit( - fragment1: Char, - fragment2: String, - fragment3: Char) - - protected open fun emit(vararg fragments: String): Unit = fragments.forEach(::emit) - - protected open fun emit(fragment: String, count: Int): Unit = repeat(count) { emit(fragment) } - protected open fun emit(fragment: Char, count: Int): Unit = repeat(count) { emit(fragment) } - - final override fun emitNewLine(): Unit = emit('\n') - - final override fun emitIndent(): Unit = emit(indentation, indentDepth) - - final override fun emitWhitespace(count: Int): Unit = emit(' ', count) - - final override fun emitComment(comment: String, endOfLine: Boolean): Unit = - if (endOfLine) { - emit(" # ", comment) - } else { - emit("# ", comment) - } - - final override fun emitKey(key: String) { - if (key matches bareKeyRegex) { - emitBareKey(key) - } else { - emitQuotedKey(key, isLiteral = key matches literalKeyCandidateRegex) - } - } - - final override fun emitBareKey(key: String): Unit = emit(key) - - final override fun emitQuotedKey(key: String, isLiteral: Boolean): Unit = - emitValue(string = key, isLiteral) - - final override fun emitKeyDot(): Unit = emit('.') - - final override fun startTableHeader(): Unit = emit('[') - - final override fun endTableHeader(): Unit = emit(']') - - final override fun startTableArrayHeaderStart(): Unit = emit("[[") - - final override fun emitTableArrayHeaderEnd(): Unit = emit("]]") - - final override fun emitValue( - string: String, - isLiteral: Boolean, - isMultiline: Boolean): Unit = - if (isMultiline) { - val quotes = if (isLiteral) "'''" else "\"\"\"" - - emit("$quotes\n", string, quotes) - } else { - val quote = if (isLiteral) '\'' else '"' - - emit(quote, string, quote) - } - - final override fun emitValue(integer: Long, representation: IntegerRepresentation): Unit = - when (representation) { - DECIMAL -> emit(integer.toString()) - HEX -> emit("0x", integer.toString(16)) - BINARY -> emit("0b", integer.toString(2)) - OCTAL -> emit("0o", integer.toString(8)) - GROUPED -> TODO() - } - - final override fun emitValue(float: Double): Unit = - emit(when (float) { - Double.NaN -> "nan" - Double.POSITIVE_INFINITY -> "inf" - Double.NEGATIVE_INFINITY -> "-inf" - else -> float.toString() - }) - - final override fun emitValue(boolean: Boolean): Unit = emit(boolean.toString()) - - final override fun startArray(): Unit = emit('[') - - final override fun endArray(): Unit = emit(']') - - final override fun startInlineTable(): Unit = emit('{') - - final override fun endInlineTable(): Unit = emit('}') - - final override fun emitElementDelimiter(): Unit = emit(", ") - - final override fun emitPairDelimiter(): Unit = emit(" = ") - - public companion object { - @JvmStatic - private val bareKeyRegex = Regex("[A-Za-z0-9_-]+") - - /** - * Matches a key with at least one unescaped double quote and no single - * quotes. - */ - @JvmStatic - private val literalKeyCandidateRegex = Regex("""[^'"]*((? emit(integer.toString()) + HEX -> { + emit("0x") + emit(integer.toString(16)) + } + BINARY -> { + emit("0b") + emit(integer.toString(2)) + } + OCTAL -> { + emit("0o") + emit(integer.toString(8)) + } + GROUPED -> TODO() + } + + public fun emitValue(float: Double): Unit = + emit(when (float) { + Double.NaN -> "nan" + Double.POSITIVE_INFINITY -> "inf" + Double.NEGATIVE_INFINITY -> "-inf" + else -> float.toString() + }) + + public fun emitValue(boolean: Boolean): Unit = emit(boolean.toString()) + + public fun startArray(): Unit = emit('[') + + public fun endArray(): Unit = emit(']') + + public fun startInlineTable(): Unit = emit('{') + + public fun endInlineTable(): Unit = emit('}') + + public fun emitElementDelimiter(): Unit = emit(", ") + + public fun emitPairDelimiter(): Unit = emit(" = ") + + public companion object { + @JvmStatic + private val bareKeyRegex = Regex("[A-Za-z0-9_-]+") + + /** + * Matches a key with at least one unescaped double quote and no single + * quotes. + */ + @JvmStatic + private val literalKeyCandidateRegex = Regex("""[^'"]*((? - error( + throw TomlWritingException( "A file node is not allowed as a child of another file node." ) is TomlKeyValueArray -> { @@ -47,7 +48,7 @@ public value class TomlWriter(private val ktomlConf: KtomlConf) { } } - private fun TomlComposer.writeKey(key: TomlKey) { + private fun TomlEmitter.writeKey(key: TomlKey) { val keys = key.keyParts if (keys.isEmpty()) { diff --git a/ktoml-file/src/commonMain/kotlin/com/akuleshov7/ktoml/file/FileUtils.kt b/ktoml-file/src/commonMain/kotlin/com/akuleshov7/ktoml/file/FileUtils.kt index 2e46de53..1e35e4dc 100644 --- a/ktoml-file/src/commonMain/kotlin/com/akuleshov7/ktoml/file/FileUtils.kt +++ b/ktoml-file/src/commonMain/kotlin/com/akuleshov7/ktoml/file/FileUtils.kt @@ -42,7 +42,7 @@ internal fun openFileForWrite(tomlFile: String): BufferedSink { val ktomlPath = tomlFile.toPath() return getOsSpecificFileSystem().sink(ktomlPath).buffer() } catch (e: FileNotFoundException) { - println("Not able to fine toml-file in the following path: $tomlFile") + println("Not able to find toml-file in the following path: $tomlFile") throw e } } diff --git a/ktoml-file/src/commonMain/kotlin/com/akuleshov7/ktoml/file/TomlFileWriter.kt b/ktoml-file/src/commonMain/kotlin/com/akuleshov7/ktoml/file/TomlFileWriter.kt index 4e591098..d29201ff 100644 --- a/ktoml-file/src/commonMain/kotlin/com/akuleshov7/ktoml/file/TomlFileWriter.kt +++ b/ktoml-file/src/commonMain/kotlin/com/akuleshov7/ktoml/file/TomlFileWriter.kt @@ -3,13 +3,11 @@ package com.akuleshov7.ktoml.file import com.akuleshov7.ktoml.KtomlConf import com.akuleshov7.ktoml.Toml import com.akuleshov7.ktoml.tree.TomlFile - -import okio.use - import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.SerializationStrategy import kotlinx.serialization.modules.EmptySerializersModule import kotlinx.serialization.modules.SerializersModule +import okio.use /** * Writes to a file in the TOML format. @@ -29,7 +27,7 @@ public open class TomlFileWriter( // Todo: Write an encoder implementation. - TomlSinkComposer( + TomlSinkEmitter( openFileForWrite(tomlFilePath), config ).use { diff --git a/ktoml-file/src/commonMain/kotlin/com/akuleshov7/ktoml/file/TomlSinkComposer.kt b/ktoml-file/src/commonMain/kotlin/com/akuleshov7/ktoml/file/TomlSinkComposer.kt deleted file mode 100644 index cd0ba053..00000000 --- a/ktoml-file/src/commonMain/kotlin/com/akuleshov7/ktoml/file/TomlSinkComposer.kt +++ /dev/null @@ -1,57 +0,0 @@ -@file:Suppress("UNUSED_IMPORT")// TomlComposer used for documentation only - -package com.akuleshov7.ktoml.file - -import com.akuleshov7.ktoml.KtomlConf -import com.akuleshov7.ktoml.writers.AbstractTomlComposer -import com.akuleshov7.ktoml.writers.TomlComposer -import okio.BufferedSink -import okio.Closeable - -/** - * A [TomlComposer] implementation that writes to a [BufferedSink]. - */ -internal class TomlSinkComposer( - private val sink: BufferedSink, - ktomlConf: KtomlConf -) : AbstractTomlComposer(ktomlConf), Closeable { - override fun emit(fragment: String) { - sink.writeUtf8(fragment) - } - - override fun emit(fragment: Char) { - sink.writeUtf8CodePoint(fragment.code) - } - - override fun emit( - fragment1: String, - fragment2: String - ) { - sink.writeUtf8(fragment1) - .writeUtf8(fragment2) - } - - override fun emit( - fragment1: String, - fragment2: String, - fragment3: String - ) { - sink.writeUtf8(fragment1) - .writeUtf8(fragment2) - .writeUtf8(fragment3) - } - - override fun emit( - fragment1: Char, - fragment2: String, - fragment3: Char - ) { - sink.writeUtf8CodePoint(fragment1.code) - .writeUtf8(fragment2) - .writeUtf8CodePoint(fragment3.code) - } - - // Closeable - - override fun close(): Unit = sink.close() -} diff --git a/ktoml-file/src/commonMain/kotlin/com/akuleshov7/ktoml/file/TomlSinkEmitter.kt b/ktoml-file/src/commonMain/kotlin/com/akuleshov7/ktoml/file/TomlSinkEmitter.kt new file mode 100644 index 00000000..b3494ec2 --- /dev/null +++ b/ktoml-file/src/commonMain/kotlin/com/akuleshov7/ktoml/file/TomlSinkEmitter.kt @@ -0,0 +1,26 @@ +@file:Suppress("UNUSED_IMPORT")// TomlComposer used for documentation only + +package com.akuleshov7.ktoml.file + +import com.akuleshov7.ktoml.KtomlConf +import com.akuleshov7.ktoml.writers.TomlEmitter +import okio.BufferedSink +import okio.Closeable + +/** + * A [TomlEmitter] implementation that writes to a [BufferedSink]. + */ +internal class TomlSinkEmitter( + private val sink: BufferedSink, + ktomlConf: KtomlConf +) : TomlEmitter(ktomlConf), Closeable { + override fun emit(fragment: String) { + sink.writeUtf8(fragment) + } + + override fun emit(fragment: Char) { + sink.writeUtf8CodePoint(fragment.code) + } + + override fun close(): Unit = sink.close() +} From 8202750dd43ff645a7f0de6b4678ef5b07ccd155 Mon Sep 17 00:00:00 2001 From: NightEule5 <24661563+NightEule5@users.noreply.github.com> Date: Tue, 11 Jan 2022 20:32:11 -0700 Subject: [PATCH 10/12] Add TODO to unimplemented branches --- .../com/akuleshov7/ktoml/writers/TomlWriter.kt | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/writers/TomlWriter.kt b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/writers/TomlWriter.kt index 57ce9324..a377ebb6 100644 --- a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/writers/TomlWriter.kt +++ b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/writers/TomlWriter.kt @@ -31,21 +31,15 @@ public value class TomlWriter(private val ktomlConf: KtomlConf) { public fun write(file: TomlFile, emitter: TomlEmitter): Unit = file.children.forEach { emitter.writeChild(it) } - private fun TomlEmitter.writeChild(node: TomlNode) = when (node) { + private fun TomlEmitter.writeChild(node: TomlNode): Unit = when (node) { is TomlFile -> throw TomlWritingException( "A file node is not allowed as a child of another file node." ) - is TomlKeyValueArray -> { - - } - is TomlKeyValuePrimitive -> { - - } - is TomlStubEmptyNode -> { } - is TomlTable -> { - - } + is TomlKeyValueArray -> TODO() + is TomlKeyValuePrimitive -> TODO() + is TomlStubEmptyNode -> TODO() + is TomlTable -> TODO() } private fun TomlEmitter.writeKey(key: TomlKey) { From b68537830dc75f95f1629447de2b45be1fa6b930 Mon Sep 17 00:00:00 2001 From: NightEule5 <24661563+NightEule5@users.noreply.github.com> Date: Tue, 11 Jan 2022 20:42:11 -0700 Subject: [PATCH 11/12] Fix import ordering --- .../src/commonMain/kotlin/com/akuleshov7/ktoml/Toml.kt | 5 ++++- .../kotlin/com/akuleshov7/ktoml/file/TomlFileWriter.kt | 4 +++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/Toml.kt b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/Toml.kt index ddf70252..fcd619ad 100644 --- a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/Toml.kt +++ b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/Toml.kt @@ -5,13 +5,16 @@ import com.akuleshov7.ktoml.exceptions.MissingRequiredFieldException import com.akuleshov7.ktoml.parsers.TomlParser import com.akuleshov7.ktoml.tree.TomlFile import com.akuleshov7.ktoml.writers.TomlWriter + +import kotlin.native.concurrent.ThreadLocal + import kotlinx.serialization.DeserializationStrategy import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.SerializationStrategy import kotlinx.serialization.StringFormat + import kotlinx.serialization.modules.EmptySerializersModule import kotlinx.serialization.modules.SerializersModule -import kotlin.native.concurrent.ThreadLocal /** * Toml class - is a general entry point in the core, diff --git a/ktoml-file/src/commonMain/kotlin/com/akuleshov7/ktoml/file/TomlFileWriter.kt b/ktoml-file/src/commonMain/kotlin/com/akuleshov7/ktoml/file/TomlFileWriter.kt index d29201ff..f583c71d 100644 --- a/ktoml-file/src/commonMain/kotlin/com/akuleshov7/ktoml/file/TomlFileWriter.kt +++ b/ktoml-file/src/commonMain/kotlin/com/akuleshov7/ktoml/file/TomlFileWriter.kt @@ -3,11 +3,13 @@ package com.akuleshov7.ktoml.file import com.akuleshov7.ktoml.KtomlConf import com.akuleshov7.ktoml.Toml import com.akuleshov7.ktoml.tree.TomlFile + +import okio.use + import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.SerializationStrategy import kotlinx.serialization.modules.EmptySerializersModule import kotlinx.serialization.modules.SerializersModule -import okio.use /** * Writes to a file in the TOML format. From 0664aa072c7fe8b9d5ac89b0afddd3c6089f5b84 Mon Sep 17 00:00:00 2001 From: NightEule5 <24661563+NightEule5@users.noreply.github.com> Date: Thu, 13 Jan 2022 02:42:23 -0700 Subject: [PATCH 12/12] Document TomlEmitter --- .../akuleshov7/ktoml/writers/TomlEmitter.kt | 118 ++++++++++++++++++ 1 file changed, 118 insertions(+) diff --git a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/writers/TomlEmitter.kt b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/writers/TomlEmitter.kt index e33779d3..e48e1564 100644 --- a/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/writers/TomlEmitter.kt +++ b/ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/writers/TomlEmitter.kt @@ -14,28 +14,78 @@ import kotlin.jvm.JvmStatic public abstract class TomlEmitter(ktomlConf: KtomlConf) { private val indentation = ktomlConf.indentation.value + /** + * The current indent depth, set by [indent] and [dedent]. + */ @Suppress("CUSTOM_GETTERS_SETTERS") public var indentDepth: Int = 0 protected set + /** + * Increments [indentDepth], returning its value after incrementing. + * + * @return The new indent depth. + */ public fun indent(): Int = ++indentDepth + + /** + * Decrements [indentDepth], returning its value after decrementing. + * + * @return The new indent depth. + */ public fun dedent(): Int = --indentDepth + /** + * Emits [fragment] as a raw [String] to the output. + * + * @param fragment The raw text to write to the output. + */ protected abstract fun emit(fragment: String) + + /** + * Emits [fragment] as a raw [Char] to the output. + * + * @param fragment The raw text to write to the output. + */ protected abstract fun emit(fragment: Char) + /** + * Emits a newline character. + */ public fun emitNewLine(): Unit = emit('\n') + /** + * Emits indentation up to the current [indentDepth]. + */ public fun emitIndent(): Unit = repeat(indentDepth) { emit(indentation) } + /** + * Emits [count] whitespace characters. + * + * @param count The number of whitespace characters to write. + */ public fun emitWhitespace(count: Int = 1): Unit = repeat(count) { emit(' ') } + /** + * Emits a [comment], optionally making it end-of-line. + * + * @param comment + * @param endOfLine Whether the comment is at the end of a line, e.g. after a + * table header. + */ public fun emitComment(comment: String, endOfLine: Boolean = false) { emit(if (endOfLine) " # " else "# ") emit(comment) } + /** + * Emits a [key]. Its type is inferred by its content, with bare keys being + * preferred. [emitBareKey] is called for simple keys, [emitQuotedKey] for + * non-simple keys. + * + * @param key + */ public fun emitKey(key: String) { if (key matches bareKeyRegex) { emitBareKey(key) @@ -44,21 +94,55 @@ public abstract class TomlEmitter(ktomlConf: KtomlConf) { } } + /** + * Emits a [key] as a bare key. + * + * @param key + */ public fun emitBareKey(key: String): Unit = emit(key) + /** + * Emits a [key] as a quoted key, optionally making it literal (single-quotes). + * + * @param key + * @param isLiteral Whether the key should be emitted as a literal string + * (single-quotes). + */ public fun emitQuotedKey(key: String, isLiteral: Boolean = false): Unit = emitValue(string = key, isLiteral) + /** + * Emits a key separator. + */ public fun emitKeyDot(): Unit = emit('.') + /** + * Emits the table header start character. + */ public fun startTableHeader(): Unit = emit('[') + /** + * Emits the table header end character. + */ public fun endTableHeader(): Unit = emit(']') + /** + * Emits the table array header start characters. + */ public fun emitTableArrayHeaderStart(): Unit = emit("[[") + /** + * Emits the table array header end characters. + */ public fun emitTableArrayHeaderEnd(): Unit = emit("]]") + /** + * Emit a string value, optionally making it literal and/or multiline. + * + * @param string + * @param isLiteral Whether the string is literal (single-quotes). + * @param isMultiline Whether the string is multiline. + */ public fun emitValue( string: String, isLiteral: Boolean = false, @@ -78,6 +162,12 @@ public abstract class TomlEmitter(ktomlConf: KtomlConf) { emit(quote) } + /** + * Emits an integer value, optionally changing its representation from decimal. + * + * @param integer + * @param representation How the integer will be represented in TOML. + */ public fun emitValue(integer: Long, representation: IntegerRepresentation = DECIMAL): Unit = when (representation) { DECIMAL -> emit(integer.toString()) @@ -96,6 +186,11 @@ public abstract class TomlEmitter(ktomlConf: KtomlConf) { GROUPED -> TODO() } + /** + * Emits a floating-point value. + * + * @param float + */ public fun emitValue(float: Double): Unit = emit(when (float) { Double.NaN -> "nan" @@ -104,18 +199,41 @@ public abstract class TomlEmitter(ktomlConf: KtomlConf) { else -> float.toString() }) + /** + * Emits a boolean value. + * + * @param boolean + */ public fun emitValue(boolean: Boolean): Unit = emit(boolean.toString()) + /** + * Emits the array start character. + */ public fun startArray(): Unit = emit('[') + /** + * Emits the array end character. + */ public fun endArray(): Unit = emit(']') + /** + * Emits the inline table start character. + */ public fun startInlineTable(): Unit = emit('{') + /** + * Emits the inline table end character. + */ public fun endInlineTable(): Unit = emit('}') + /** + * Emits an array/inline table element delimiter. + */ public fun emitElementDelimiter(): Unit = emit(", ") + /** + * Emits a key-value delimiter. + */ public fun emitPairDelimiter(): Unit = emit(" = ") public companion object {